网页逆向-某pinxx webpack网页逆向

https://www.pinrenwu.cn/pc/#/

登录请求

nonce_strsign字段需要分析

在js文件中,一般有app.xxx.js或者vendor.xxx.js的文件名,都是webpack打包之后的js,webpack是什么?平时在开发的时候,不可能把代码都写在一个文件中,这样不利于维护,开发的时候肯定是分文件去写的,在发布的时候,webpack可以把不同文件的代码打包,打包后的文件会少很多

webpack分为两个部分,一个是模块,可以理解为模块在原始的源代码里,就是一个单独的文件,一个文件对应一个模块

另一个是模块加载器,也就是下面的n("Ds5P") ,n就是加载器,对于webpack代码的扣法,需要把加载器和对应的模块都给扣下来

1
2
3
4
5
6
7
8
9
"7Jvp": function(t, e, n) {
var r = n("Ds5P")
, i = Math.asinh;
r(r.S + r.F * !(i && 1 / i(0) > 0), "Math", {
asinh: function t(e) {
return isFinite(e = +e) && 0 != e ? e < 0 ? -t(-e) : Math.log(e + Math.sqrt(e * e + 1)) : e
}
})
},

稍微跟一下,找到关键函数,可以看出来这里就是对sign参数赋值的

1
2
3
i.sign = h.exchangeMD5(o()(i, {
nonce_str: h.nonce_str()
}));

随后可以定位到生成nonce_str的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
, p = i("fZjL")
, g = i.n(p)
, v = i("NC6I")
, u = i.n(v)
, h = {
exchangeMD5: function(t) {
for (var e = g()(t).sort(), i = {}, a = 0; a < e.length; a++)
i[e[a]] = t[e[a]];
var n = "";
for (var o in i)
n = n + o + "=" + i[o] + "&";
var s = (n = n.substr(0, n.length - 1)) + "&key=951d4c42326611e8a17f6c92bf3bb67f";
return u()(s).toUpperCase()
},

这里u函数来自于vv是通过模块加载器i加载的,在p = i("fZjL")处下断点,模块加载器是在网页加载的时候初始化的,刷新网页才能断下来,然后步入函数

扣的时候先把所有代码扣下来

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
61
62
63
64
65
!function(r) {
var n = window.webpackJsonp;
window.webpackJsonp = function(e, u, c) {
for (var f, i, p, a = 0, l = []; a < e.length; a++)
i = e[a],
o[i] && l.push(o[i][0]),
o[i] = 0;
for (f in u)
Object.prototype.hasOwnProperty.call(u, f) && (r[f] = u[f]);
for (n && n(e, u, c); l.length; )
l.shift()();
if (c)
for (a = 0; a < c.length; a++)
p = t(t.s = c[a]);
return p
}
;
var e = {}
, o = {
2: 0
};
function t(n) {
if (e[n])
return e[n].exports;
var o = e[n] = {
i: n,
l: !1,
exports: {}
};
return r[n].call(o.exports, o, o.exports, t),
o.l = !0,
o.exports
}
t.m = r,
t.c = e,
t.d = function(r, n, e) {
t.o(r, n) || Object.defineProperty(r, n, {
configurable: !1,
enumerable: !0,
get: e
})
}
,
t.n = function(r) {
var n = r && r.__esModule ? function() {
return r.default
}
: function() {
return r
}
;
return t.d(n, "a", n),
n
}
,
t.o = function(r, n) {
return Object.prototype.hasOwnProperty.call(r, n)
}
,
t.p = "./",
t.oe = function(r) {
throw console.error(r),
r
}
}([]);

function t(n) 是加载模块,n是模块名,整个代码是一个匿名自执行函数,现在要做的是让这个匿名自执行函数能够根据模块名加载需要的模块,这里只定义了t函数,而不一定会调用它,所以需要把t函数给赋值给全局变量,在匿名函数外部去调用t函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let moduleImport;
!function(r) {
//...
function t(n) {
if (e[n])
return e[n].exports;
var o = e[n] = {
i: n,
l: !1,
exports: {}
};
return r[n].call(o.exports, o, o.exports, t),
o.l = !0,
o.exports
}
moduleImport = t;
//...
}([]);

t函数中,是通过 return r[n].call(o.exports, o, o.exports, t) 来调用的模块函数,r是匿名函数的参数,n可以理解为r参数的属性名,r[n]就是取属性的值进行调用,所以这里传入的参数r可以不是数组,改成对象就行,而且属性名就是模块名,属性值就是函数体,而且前面加密的函数模块名是NHnr,这个模块定义了很多函数,在扣的时候可以先扣核心加密代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
NHnr: function(t, e, i) {
// ...上面还有很多
, p = i("fZjL")
, g = i.n(p)
, v = i("NC6I")
, u = i.n(v)
, h = {
exchangeMD5: function(t) {
for (var e = g()(t).sort(), i = {}, a = 0; a < e.length; a++)
i[e[a]] = t[e[a]];
var n = "";
for (var o in i)
n = n + o + "=" + i[o] + "&";
var s = (n = n.substr(0, n.length - 1)) + "&key=951d4c42326611e8a17f6c92bf3bb67f";
return u()(s).toUpperCase()
},
nonce_str: function(t) {
t = t || 32;
for (var e = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678", i = e.length, a = "", n = 0; n < t; n++)
a += e.charAt(Math.floor(Math.random() * i));
return a
}
// ...下面还有很多
}

修改一下代码,使代码符合语法,现在抠出来的加密代码如下

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
let moduleImport;
!function(r) {
var n = window.webpackJsonp;
window.webpackJsonp = function(e, u, c) {
for (var f, i, p, a = 0, l = []; a < e.length; a++)
i = e[a],
o[i] && l.push(o[i][0]),
o[i] = 0;
for (f in u)
Object.prototype.hasOwnProperty.call(u, f) && (r[f] = u[f]);
for (n && n(e, u, c); l.length; )
l.shift()();
if (c)
for (a = 0; a < c.length; a++)
p = t(t.s = c[a]);
return p
}
;
var e = {}
, o = {
2: 0
};
function t(n) {
if (e[n])
return e[n].exports;
var o = e[n] = {
i: n,
l: !1,
exports: {}
};
return r[n].call(o.exports, o, o.exports, t),
o.l = !0,
o.exports
}
moduleImport = t;
t.m = r,
t.c = e,
t.d = function(r, n, e) {
t.o(r, n) || Object.defineProperty(r, n, {
configurable: !1,
enumerable: !0,
get: e
})
}
,
t.n = function(r) {
var n = r && r.__esModule ? function() {
return r.default
}
: function() {
return r
}
;
return t.d(n, "a", n),
n
}
,
t.o = function(r, n) {
return Object.prototype.hasOwnProperty.call(r, n)
}
,
t.p = "./",
t.oe = function(r) {
throw console.error(r),
r
}
}({
NHnr: function (t, e, i) {
let p = i("fZjL")
, g = i.n(p)
, v = i("NC6I")
, u = i.n(v)
, h = {
exchangeMD5: function (t) {
for (var e = g()(t).sort(), i = {}, a = 0; a < e.length; a++)
i[e[a]] = t[e[a]];
var n = "";
for (var o in i)
n = n + o + "=" + i[o] + "&";
var s = (n = n.substr(0, n.length - 1)) + "&key=951d4c42326611e8a17f6c92bf3bb67f";
return u()(s).toUpperCase()
},
nonce_str: function (t) {
t = t || 32;
for (var e = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678", i = e.length, a = "", n = 0; n < t; n++)
a += e.charAt(Math.floor(Math.random() * i));
return a
}
}
},});
moduleImport('NHnr');

至此,模块导入已经全部实现了,现在还需要调用加密函数,加密函数在h函数处,还需要把这个函数赋值给全局变量,在外部调用

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
let moduleImport,encParamObj;
!function(r) {
var n = window.webpackJsonp;
window.webpackJsonp = function(e, u, c) {
for (var f, i, p, a = 0, l = []; a < e.length; a++)
i = e[a],
o[i] && l.push(o[i][0]),
o[i] = 0;
for (f in u)
Object.prototype.hasOwnProperty.call(u, f) && (r[f] = u[f]);
for (n && n(e, u, c); l.length; )
l.shift()();
if (c)
for (a = 0; a < c.length; a++)
p = t(t.s = c[a]);
return p
}
;
var e = {}
, o = {
2: 0
};
function t(n) {
if (e[n])
return e[n].exports;
var o = e[n] = {
i: n,
l: !1,
exports: {}
};
return r[n].call(o.exports, o, o.exports, t),
o.l = !0,
o.exports
}
moduleImport = t;
t.m = r,
t.c = e,
t.d = function(r, n, e) {
t.o(r, n) || Object.defineProperty(r, n, {
configurable: !1,
enumerable: !0,
get: e
})
}
,
t.n = function(r) {
var n = r && r.__esModule ? function() {
return r.default
}
: function() {
return r
}
;
return t.d(n, "a", n),
n
}
,
t.o = function(r, n) {
return Object.prototype.hasOwnProperty.call(r, n)
}
,
t.p = "./",
t.oe = function(r) {
throw console.error(r),
r
}
}({
NHnr: function (t, e, i) {
let p = i("fZjL")
, g = i.n(p)
, v = i("NC6I")
, u = i.n(v)
, h = {
exchangeMD5: function (t) {
for (var e = g()(t).sort(), i = {}, a = 0; a < e.length; a++)
i[e[a]] = t[e[a]];
var n = "";
for (var o in i)
n = n + o + "=" + i[o] + "&";
var s = (n = n.substr(0, n.length - 1)) + "&key=951d4c42326611e8a17f6c92bf3bb67f";
return u()(s).toUpperCase()
},
nonce_str: function (t) {
t = t || 32;
for (var e = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678", i = e.length, a = "", n = 0; n < t; n++)
a += e.charAt(Math.floor(Math.random() * i));
return a
}
}
// 全局变量接受加密函数
encParamObj = h;
},
});
moduleImport('NHnr');
// 参数可以打断点看到
let t = {
"mobile": "15233331111",
"smsCode": "123123",
"terminalCode": "3",
"nonce_str": "Wksn35CArKncJmZCD4MRmz6nbPrkBWXE"
}
encParamObj.exchangeMD5(t);

当然跑起来的时候,会报错少很多模块,需要手动一个个导入,导入的地方就在匿名函数的参数里去加属性名和值

自动化抠需要用到AST混淆还原,这个后面再说

抠webpack的流程就是,加载模块,调用加载模块函数去加载模块,然后执行加密代码