[另类⽅式破解]⽀付宝的⼩程序sign验签参数算法
前段时间想到⽀付宝的⼀个做任务领集分宝的⼩程序…
挺好的,想要做⼀个获取到所有任务,然后全⾃动做任务的⼯具,抓包发现有sign验签,于是有了本帖
抓完才发现,整个⼩程序是⽤https传输数据的。
另类⽅式破解的⽬的
⼩⽩就要有⼩⽩的办法,咱们要让⼩程序投降,让他⾃⼰乖乖的把sign⽤到的所有参数都告诉我。
0x0⼿机环境
安卓11
K40稳定版/miui12/解锁BL
已刷⾯具(刷了Move Certificates)
LSPosed框架
0x1⽤到的⼯具
⼿机端
1. Drony 1.3.154 (作⽤:应该搭建梯⼦转发请求到指定ip:端⼝。下载地址:论坛内帖⼦:www.52pojie/thread-
1340770-1-1.html)
2. ⽀付宝
3. ⼩黄鸟抓包
4. MT管理器(搜索⽂件等⼯作)
电脑端
1. fiddler(以下部分地⽅会简称fd)
2. Notepad++(JSTool插件,⼀键美化/缩放JS代码)【可有可⽆】
3. 解压缩软件
张萌图片0x2实现解密的过程
1.⽀付宝⼩程序抓包
⼩黄鸟抓包⿇烦,所以想换成fiddler+drony抓包
⼀开始⽤的是⼩黄鸟APP抓包,抓的挺全的,但是⽤注⼊器⽐较⿇烦,所以想着⽤电脑FD抓包,之前⽤过Drony,
纸衣服但是换了安卓11之后旧版本获取不到wifi列表,所以更新了⼀下Drony,Drony怎么⽤之前已经讲过了,需要的话可以⾃⼰跳转:fiddler+drony抓包
配置好drony之后开启转发,电脑打开fd,⼿机打开⽀付宝APP,并进⼊⼩程序,fd抓到包
sign为6e2edd36a36df5609e71e22a6fb41987
任务列表get,就是在headers⾥⾯有sign/token/等信息。直接打开Task/list?会提⽰{"code":-2001,"msg":"签名错误"},就更加验证了sign是验签的想法,所以接下来我们要想办法得到sign值的算法。
2.从源码⼊⼿分析sign
拿到源码
按照上贴中的说法.tar ⽂件⾥⾯包括了⼩程序的源码,可恶的是MT竟然打不开(改ZIP后缀也打开失败),所以传到电脑⽤解压软件打开。果然是⼩程序的源码,有html和2个js⽂件
解压分析
全部解压到⽂件夹,搜索Task/list 发现2个JS中都包含了。但是我们依次打开搜索发现index.js中并不是Task,⽽是task。。未区分⼤⼩写字母,所以排除掉。
所以index.worker.js就成为了我们的⽬标,打开⽂件,再次搜索发现可以看得到调⽤了 ⽅法,由url看出2个参数的值全都是0,所以getApp().globalData.filter=0
然后搜索get: ⽅法
搜索get:,发现其中有⼀处疑似⽹络请求的代码再看看quest
搜索request:到request函数(其实⼀开始我是搜索sign,到的request函数,不过都差不多啦),upTaskInfo: function () {    ({        url: "Task/list",        data: {            is_filter: 0,            filter: getApp().globalData.filter        },        login: !1,        loading: !0    }).then((function (e) {            var t            //此处省略N 多代码        })).catch((function (e) {}))}
带手字的成语
1
2
3
4
圆通快递价格表5
6
7
89
10
11
12
桃之夭夭灼灼其华
13
14
15HOST: s,API_ROOT: s + "/",API_VERSION: u,DEVICE_TYPE: r,APP_ID: c,get: function (e) {    hod = "GET",quest(e)},post: function (e) {    hod = "POST",quest(e)}
1
2
3
4
5
6
7
8
9
10
11
12
稍微分析⼀下,可得出request: function (e) {    var n,u,r,c,l,d,m,p,h,g,    v = this;    if (1 == a.default.state.foo ? (this.HOST = "a.b", this.API_ROOT = "a.b/") :        (this.HOST = s, this.API_ROOT = s + "/"), e = Object.assign({            data: {}        },e), n = Date.parse(new Date) / 1e3 + "", u = il(1e4 * Math.random()) + "", r = f, -1 !== e.url.indexOf("Ad/record") && (e.data.filter = getApp().gl
for (d in l = l[1].split("&"))            c[(m = l[d].split("="))[0]] = m[1];        e.url = l[0]    }    return Object.assign(e.data, c),    Object.keys(e.data).forEach((function (t) {            void 0 === e.data[t] && (e.data[t] = "")        })),    p = {},    Object.assign(p, e.data),    p.appid = this.APP_ID,    p.nonce = u,    p.timestamp = n,    p.os = this.DEVICE_TYPE,    p.v = this.API_VERSION,    p.token = r,    p._url = this.API_ROOT + e.url.split("?")[0],    h = "",    Object.keys(p).sort().forEach((function (e) {            h += e + p[e]        })),    g = (0, o.hexMD5)(de(h)),    new Promise((function (a, o) {            t.request({                url: v.API_ROOT + e.url,                data: e.data,                method: e.method ? e.method : "POST",                header: {                    "Cache-Control": "no-cache",                    "Content-Type": "application/x-www-form-urlencoded",                    os: v.DEVICE_TYPE,                    appid: v.APP_ID,                    nonce: u,                    v: v.API_VERSION,                    timestamp: n,                    token: r,                    sign: g,                    "Adzone-Id": getApp().globalData.adzoneId             
  },                success: function (n) {                    -2e3 == de ? (v.login((function () {                                quest(e))                            })), t.showToast({                            content: "需要授权登录"                        })) : a(n)                },                fail: function (e) {                    o(e)                }            })        }))}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
我要开网店53
54
55
56
57
58
59
60
修改JS 后放回⼿机原⽬录
PS:其实⼀开始我没想到这么多,我还以为sign就只是简单的字符串拼接然后加⼀个key,md5⼀下。。。以上均为⼩程序投降之后的分析我⼀开始不是分析JS来着,index.worker.js 修改了JS的代码,在request函数return之前,把所有的参数全都提⽰了⼀个遍,上代码:⼩程序⽂件校验失败,重新请求了index.worker.js 但是保存index.worker.js
放回.tar 压缩包,传到⼩程序⽬录后,重新打开⼩程序。⼩程序竟然重新加载了。(后来⼀看才发现,⽬录⾥⾯的cert.json
和sign.json 可能是验证⽂件md5之类的⽂件,发现修改后会重新请求)
但是fd中出现了index.worker.js 的请求… 那不⼀个样道理吗,所以⽤FD的⾃动响应(AutoResponder),返回修改后的JS。
那,这不就缴械了吗,sign为6e2edd36a36df5609e71e22a6fb41987
0x3结案⼩程序已经交出了钥匙
PS:每个接⼝的h值拼接的字符串不⼀样,但是直接⽤js的⽅法来说。
sign加密的步骤⼤约就是:每个接⼝的参数加上参数p的总和按照ascii码值⼤⼩顺序,然后value+key拼接,最后base64编码后,md5即为sign值Object.assign(e.data, c), Object.keys(e.data).forEach((fu
nction (t) {        void 0 === e.data[t] && (e.data[t] = "")    })), p = {}, Object.assign(p, e.data), p.appid = this.APP_ID, p.nonce = u, p.timestamp = n, p.os = this.DEVICE_TYPE, p.v = this.API_VERSION, p.token = r
h += e + p[e]    }));g = (0, o.hexMD5)(de(h));//sign 的值g t.showModal({    title: "参数信息提⽰",    content: 'request 参数e:' + JSON.stringify(data) + '\n\n 参数p:' + JSON.stringify(p) + '\n\np._url:' + p._url + '\n\nh:' + h + '\n\nbase64后sign 值:' + g,    showCancel: !1,//变量data 已经在request 函数⼀开始就⽤e 赋值了    confirmText: "已阅,退下吧"})1
2
3
4
5
6
7
8
9
10
11
12
13
14