反爬(六)

反爬六

此次练习的网站,其实这个网站无需破解,奈何当时脑子懵了,怎么请求都无法请求到数据,然后观察该网站的请求方式,在请求头中发现了奇怪的pqm参数,理所当然认为这是关键所在,所以开始了破解之路。悲催的是,破解之后还是请求不到数据,才发现问题所在。(不过讲道理,如果这个网站真的利用pqm反爬是很有可能的,我们的破解思路也是没什么问题)。

因为这个网站的破解不是必须的,下面就简单看下破解过程吧。

发现异常参数

首先通过观察,我们发现请求头里有个pqm的参数,那么当我们向服务器进行请求时,服务器端就很有可能利用该参数进行检测,如果我们发送过去的参数值不对,就不给我们返回正确数据。

定位该参数在js中的位置

一般情况,这种参数值的生成方法都是写在js代码中的,当在浏览器中加载页面时就会自动执行。

我们直接搜索pqm,可以直接找到其所在的js文件:

点进去这个文件,继续搜索pqm,很直观看到以下代码:

扣关键代码

很显然,pqmSignature.getSignatureByObj方法生成的,我们只需要把该方法拿出来自己执行就ok了,但是该方法难免中间调用了一系列方法,我们要把这些依赖都扣出来。

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
Signature.getSignatureByObj = function(paramObj) {
var str = JSON.stringify(paramObj);
var secret = Signature.getSignature(str);
return secret;
}

// 获取签名值
Signature.getSignature = function(strMw, ntpTime) {
var str = Signature_getMd5(strMw, ntpTime);
var getByte = stringToByte(str);
var numberStr = getNumberStr(getByte);
var getByteAgain = stringToByte(numberStr);
var signs = getSepcialSignStr(getByteAgain);
return md5_default()(signs);
};

// 获取Md5值(指定时间戳)
var Signature_getMd5 = function getMd5(userName, ntpTime) {
var time = ntpTime ? moment_default()(ntpTime).format("YYYYMMDDHHmm") : "";
var secretStr = userName + time;
return md5_default()(secretStr);
};

// 将字符串转化为Byte数组
/* eslint-disable */
var stringToByte = function stringToByte(str) {
var bytes = [];
var len = str.length;
var c;
for (var i = 0; i < len; i++) {
c = str.charCodeAt(i);
if (c >= 0x010000 && c <= 0x10FFFF) {
bytes.push(c >> 18 & 0x07 | 0xF0);
bytes.push(c >> 12 & 0x3F | 0x80);
bytes.push(c >> 6 & 0x3F | 0x80);
bytes.push(c & 0x3F | 0x80);
} else if (c >= 0x000800 && c <= 0x00FFFF) {
bytes.push(c >> 12 & 0x0F | 0xE0);
bytes.push(c >> 6 & 0x3F | 0x80);
bytes.push(c & 0x3F | 0x80);
} else if (c >= 0x000080 && c <= 0x0007FF) {
bytes.push(c >> 6 & 0x1F | 0xC0);
bytes.push(c & 0x3F | 0x80);
} else {
bytes.push(c & 0xFF);
}
}
return bytes;
};

// 获取Byte字符串
var getNumberStr = function getNumberStr(bytes) {
return bytes.join("");
};

// 字符串处理
var getSepcialSignStr = function getSepcialSignStr(bytes) {
var signs = bytes.map(function(item) {
return SignObjects[item];
});
signs[14] = "";
signs[11] = "";
signs[7] = "";
signs[2] = "";
return signs.join("");
};

代码先贴这么多,我们来简单看下,其实代码思路很清晰,把传递过来的参数先转变为字符串,然后调用

Signature.getSignature,在这个方法中又调用了一系列方法,但是这里面有两个未解决的问题,分别是moment_default()、md5_default(),任我们在这个js文件里如何查找也找不到关于这俩方法的有用信息。那先不急,我们来看下他们有啥用,“打断点调试”:

代码执行到这里,我们用鼠标选中time就可以看到它的值了,或者在console中输入time然后按回车也可以,多试几次,我们会发现其始终为空字符串,那既然这样我们就不用管moment_default()了,这里直接写成time=””即可。继续执行我们发现md5_default()把字符串加密为了一串固定长度的字符,与我们平时用的md5`函数很像,但是很容易就可以知道这个是人家自定义的方法,我们需要将其挖出来。

如果缺乏经验这里可能有点难以下手,有经验的话我们很容易想到,当程序执行到这里,md5_default()已经被加载到当前环境中了(我这种说法可能不准确,反正意思就是已定义可以直接调用),那我们可以在console中输入md5_default()按回车,看到其函数体。

注意,是输入md5_default()而不是md5_default,通过分析他的调用方式return md5_default()(secretStr);,执行完md5_default()返回一个函数,所以这里是函数嵌套调用;可以采用同样的方式看moment_default()

我们直接点这个返回的函数体可以查看详细的函数体内容。

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
var crypt = {
// Bit-wise rotation left
rotl: function rotl(n, b) {
return n << b | n >>> 32 - b;
},
// Bit-wise rotation right
rotr: function rotr(n, b) {
return n << 32 - b | n >>> b;
},
// Swap big-endian to little-endian and vice versa
endian: function endian(n) {
// If number given, swap endian
if (n.constructor == Number) {
return crypt.rotl(n, 8) & 0x00FF00FF | crypt.rotl(n, 24) & 0xFF00FF00;
}
// Else, assume array and swap all items

for (var i = 0; i < n.length; i++) {
n[i] = crypt.endian(n[i]);
}

return n;
},
// Generate an array of any length of random bytes
randomBytes: function randomBytes(n) {
for (var bytes = []; n > 0; n--) {
bytes.push(Math.floor(Math.random() * 256));
}

return bytes;
},
// Convert a byte array to big-endian 32-bit words
bytesToWords: function bytesToWords(bytes) {
for (var words = [], i = 0, b = 0; i < bytes.length; i++,
b += 8) {
words[b >>> 5] |= bytes[i] << 24 - b % 32;
}

return words;
},
// Convert big-endian 32-bit words to a byte array
wordsToBytes: function wordsToBytes(words) {
for (var bytes = [], b = 0; b < words.length * 32; b += 8) {
bytes.push(words[b >>> 5] >>> 24 - b % 32 & 0xFF);
}

return bytes;
},
// Convert a byte array to a hex string
bytesToHex: function bytesToHex(bytes) {
for (var hex = [], i = 0; i < bytes.length; i++) {
hex.push((bytes[i] >>> 4).toString(16));
hex.push((bytes[i] & 0xF).toString(16));
}

return hex.join('');
},
// Convert a hex string to a byte array
hexToBytes: function hexToBytes(hex) {
for (var bytes = [], c = 0; c < hex.length; c += 2) {
bytes.push(parseInt(hex.substr(c, 2), 16));
}

return bytes;
},
// Convert a byte array to a base-64 string
bytesToBase64: function bytesToBase64(bytes) {
for (var base64 = [], i = 0; i < bytes.length; i += 3) {
var triplet = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];

for (var j = 0; j < 4; j++) {
if (i * 8 + j * 6 <= bytes.length * 8)
base64.push(base64map.charAt(triplet >>> 6 * (3 - j) & 0x3F));
else
base64.push('=');
}
}

return base64.join('');
},
// Convert a base-64 string to a byte array
base64ToBytes: function base64ToBytes(base64) {
// Remove non-base-64 characters
base64 = base64.replace(/[^A-Z0-9+\/]/ig, '');

for (var bytes = [], i = 0, imod4 = 0; i < base64.length; imod4 = ++i % 4) {
if (imod4 == 0)
continue;
bytes.push((base64map.indexOf(base64.charAt(i - 1)) & Math.pow(2, -2 * imod4 + 8) - 1) << imod4 * 2 | base64map.indexOf(base64.charAt(i)) >>> 6 - imod4 * 2);
}

return bytes;
}
};

// Binary encoding
var bin = {
// Convert a string to a byte array
stringToBytes: function stringToBytes(str) {
for (var bytes = [], i = 0; i < str.length; i++) {
bytes.push(str.charCodeAt(i) & 0xFF);
}

return bytes;
},
// Convert a byte array to a string
bytesToString: function bytesToString(bytes) {
for (var str = [], i = 0; i < bytes.length; i++) {
str.push(String.fromCharCode(bytes[i]));
}

return str.join('');
}
};

var utf8 = {
// Convert a string to a byte array
stringToBytes: function stringToBytes(str) {
return bin.stringToBytes(unescape(encodeURIComponent(str)));
},
// Convert a byte array to a string
bytesToString: function bytesToString(bytes) {
return decodeURIComponent(escape(charenc.bin.bytesToString(bytes)));
}
};

md5 = function md5(message, options) {
// Convert to byte array
if (message.constructor == String) {
if (options && options.encoding === 'binary')
message = bin.stringToBytes(message);
else
message = utf8.stringToBytes(message);
} else if (isBuffer(message))
message = Array.prototype.slice.call(message, 0);
else if (!Array.isArray(message))
message = message.toString();
// else, assume byte array already

var m = crypt.bytesToWords(message)
, l = message.length * 8
, a = 1732584193
, b = -271733879
, c = -1732584194
, d = 271733878;
// Swap endian

for (var i = 0; i < m.length; i++) {
m[i] = (m[i] << 8 | m[i] >>> 24) & 0x00FF00FF | (m[i] << 24 | m[i] >>> 8) & 0xFF00FF00;
}
// Padding

m[l >>> 5] |= 0x80 << l % 32;
m[(l + 64 >>> 9 << 4) + 14] = l;
// Method shortcuts

var FF = md5._ff
, GG = md5._gg
, HH = md5._hh
, II = md5._ii;

for (var i = 0; i < m.length; i += 16) {
var aa = a
, bb = b
, cc = c
, dd = d;
a = FF(a, b, c, d, m[i + 0], 7, -680876936);
d = FF(d, a, b, c, m[i + 1], 12, -389564586);
c = FF(c, d, a, b, m[i + 2], 17, 606105819);
b = FF(b, c, d, a, m[i + 3], 22, -1044525330);
a = FF(a, b, c, d, m[i + 4], 7, -176418897);
d = FF(d, a, b, c, m[i + 5], 12, 1200080426);
c = FF(c, d, a, b, m[i + 6], 17, -1473231341);
b = FF(b, c, d, a, m[i + 7], 22, -45705983);
a = FF(a, b, c, d, m[i + 8], 7, 1770035416);
d = FF(d, a, b, c, m[i + 9], 12, -1958414417);
c = FF(c, d, a, b, m[i + 10], 17, -42063);
b = FF(b, c, d, a, m[i + 11], 22, -1990404162);
a = FF(a, b, c, d, m[i + 12], 7, 1804603682);
d = FF(d, a, b, c, m[i + 13], 12, -40341101);
c = FF(c, d, a, b, m[i + 14], 17, -1502002290);
b = FF(b, c, d, a, m[i + 15], 22, 1236535329);
a = GG(a, b, c, d, m[i + 1], 5, -165796510);
d = GG(d, a, b, c, m[i + 6], 9, -1069501632);
c = GG(c, d, a, b, m[i + 11], 14, 643717713);
b = GG(b, c, d, a, m[i + 0], 20, -373897302);
a = GG(a, b, c, d, m[i + 5], 5, -701558691);
d = GG(d, a, b, c, m[i + 10], 9, 38016083);
c = GG(c, d, a, b, m[i + 15], 14, -660478335);
b = GG(b, c, d, a, m[i + 4], 20, -405537848);
a = GG(a, b, c, d, m[i + 9], 5, 568446438);
d = GG(d, a, b, c, m[i + 14], 9, -1019803690);
c = GG(c, d, a, b, m[i + 3], 14, -187363961);
b = GG(b, c, d, a, m[i + 8], 20, 1163531501);
a = GG(a, b, c, d, m[i + 13], 5, -1444681467);
d = GG(d, a, b, c, m[i + 2], 9, -51403784);
c = GG(c, d, a, b, m[i + 7], 14, 1735328473);
b = GG(b, c, d, a, m[i + 12], 20, -1926607734);
a = HH(a, b, c, d, m[i + 5], 4, -378558);
d = HH(d, a, b, c, m[i + 8], 11, -2022574463);
c = HH(c, d, a, b, m[i + 11], 16, 1839030562);
b = HH(b, c, d, a, m[i + 14], 23, -35309556);
a = HH(a, b, c, d, m[i + 1], 4, -1530992060);
d = HH(d, a, b, c, m[i + 4], 11, 1272893353);
c = HH(c, d, a, b, m[i + 7], 16, -155497632);
b = HH(b, c, d, a, m[i + 10], 23, -1094730640);
a = HH(a, b, c, d, m[i + 13], 4, 681279174);
d = HH(d, a, b, c, m[i + 0], 11, -358537222);
c = HH(c, d, a, b, m[i + 3], 16, -722521979);
b = HH(b, c, d, a, m[i + 6], 23, 76029189);
a = HH(a, b, c, d, m[i + 9], 4, -640364487);
d = HH(d, a, b, c, m[i + 12], 11, -421815835);
c = HH(c, d, a, b, m[i + 15], 16, 530742520);
b = HH(b, c, d, a, m[i + 2], 23, -995338651);
a = II(a, b, c, d, m[i + 0], 6, -198630844);
d = II(d, a, b, c, m[i + 7], 10, 1126891415);
c = II(c, d, a, b, m[i + 14], 15, -1416354905);
b = II(b, c, d, a, m[i + 5], 21, -57434055);
a = II(a, b, c, d, m[i + 12], 6, 1700485571);
d = II(d, a, b, c, m[i + 3], 10, -1894986606);
c = II(c, d, a, b, m[i + 10], 15, -1051523);
b = II(b, c, d, a, m[i + 1], 21, -2054922799);
a = II(a, b, c, d, m[i + 8], 6, 1873313359);
d = II(d, a, b, c, m[i + 15], 10, -30611744);
c = II(c, d, a, b, m[i + 6], 15, -1560198380);
b = II(b, c, d, a, m[i + 13], 21, 1309151649);
a = II(a, b, c, d, m[i + 4], 6, -145523070);
d = II(d, a, b, c, m[i + 11], 10, -1120210379);
c = II(c, d, a, b, m[i + 2], 15, 718787259);
b = II(b, c, d, a, m[i + 9], 21, -343485551);
a = a + aa >>> 0;
b = b + bb >>> 0;
c = c + cc >>> 0;
d = d + dd >>> 0;
}

return crypt.endian([a, b, c, d]);
};

md5._ff = function(a, b, c, d, x, s, t) {
var n = a + (b & c | ~b & d) + (x >>> 0) + t;
return (n << s | n >>> 32 - s) + b;
}
;

md5._gg = function(a, b, c, d, x, s, t) {
var n = a + (b & d | c & ~d) + (x >>> 0) + t;
return (n << s | n >>> 32 - s) + b;
}
;

md5._hh = function(a, b, c, d, x, s, t) {
var n = a + (b ^ c ^ d) + (x >>> 0) + t;
return (n << s | n >>> 32 - s) + b;
}
;

md5._ii = function(a, b, c, d, x, s, t) {
var n = a + (c ^ (b | ~d)) + (x >>> 0) + t;
return (n << s | n >>> 32 - s) + b;
};


// 这是md5_default()
module.exports = function (message, options) {
if (message === undefined || message === null) throw new Error('Illegal argument ' + message);
var digestbytes = crypt.wordsToBytes(md5(message, options));
return options && options.asBytes ? digestbytes : options && options.asString ? bin.bytesToString(digestbytes) : crypt.bytesToHex(digestbytes);
};

以上就是全部的核心代码了,只需要对代码做部分调整就能正常执行了。

后续

其实本网站无需破解,一开始我post请求的数据格式没有选对,所以导致一直请求不到数据,该网站使用python的requests库的Post方法要使用json参数(post_man中为body为raw)。