cookie生成分析

用js直接 hook cookie的生成,增加一些随机指纹信息,通过京东的接口校验即可,其实都没什么校验……核心代码如下:

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
ts10 = int(time.time())
ts13 = int(time.time() * 1000)
rn = random.randint(1000000000000, 9999999999999)
_uid = str(uuid.uuid4())
ts11 = ts10 + 1
cookies = {
"__jd_ref_cls": "LoginDisposition_Go",
"__jda": f"122270672.{ts10}{rn}.{ts10}.{ts10}.{ts10}.1",
"__jdb": f"122270672.1.{ts10}{rn}|1.{ts10}",
"__jdv": f"122270672%7Cdirect%7C-%7Cnone%7C-%7C{ts10}",
"__jdc": "122270672",
"mba_muid": f"{ts10}{rn}",
"shshshfpa": f"{_uid}-{ts11}",
"shshshfpx": f"{_uid}-{ts11}",
"pin": "",
# 关键是下面这个
# "shshshfpb": "BApXSZ_wMz_dAb3D5rFFJhhxKj14hLXYnBmvYkbxo9xJ1MvKIAIG2",
}

url = "https://api.m.jd.com/"
fp_info = {
"appname": "jdwebm_hf",
"jdkey": "",
"isJdApp": False,
"jmafinger": "",
"whwswswws": "",
"businness": "riskhandle",
"body": {
"browser_info": str(uuid.uuid4()).replace("-", ""),
"client_time": ts13,
"period": 24,
"shshshfpa": cookies["shshshfpa"],
"ecflag": "n",
"whwswswws": "",
"jdkey": "",
"isJdApp": False,
"jmafinger": "",
"cookie_pin": "",
"jdu": cookies["mba_muid"],
"mba_muid": cookies["mba_muid"],
"visitkey": "",
"msdk_version": "4.6.0",
"wid": "",
"lan": "zh-CN",
"scrh": 1067,
"scrah": 1019,
"scrw": 1707,
"scaw": 1707,
"oscpu": "",
"platf": "Win32",
"pros": "20030107",
"temp": 33,
"hll": False,
"hlr": False,
"hlo": False,
"hlb": False,
"ll": "Zx231wgAgA",
"lm": 0,
"an": 0,
"al": "",
"il": "",
"sn": 6,
"cn": 8,
"is": "",
"bil": "",
"ol": "",
"fa": "",
"sl": "",
"as": "",
"pe": "00",
"clog": "",
"incognito": "Chrome_1",
"asa": "44100_2_1_0_2_explicit_speakers",
"domBlockers": "",
"timezone": "Asia/Shanghai",
"vendorFlavors": ["chrome"],
"monochrome": 0,
"contrast": 0,
"reducedMotion": False,
"reducedTransparency": False,
"hdr": False,
"math": {
"acos": 1.4473588658278522,
"acosh": 709.889355822726,
"acoshPf": 355.291251501643,
"asin": random.uniform(0, 1),
"asinh": random.uniform(0, 1),
"asinhPf": random.uniform(0, 1),
"atanh": random.uniform(0, 1),
"atanhPf": random.uniform(0, 1),
"atan": random.uniform(0, 1),
"sin": random.uniform(0, 1),
"sinh": 1.1752011936438014,
"sinhPf": 2.534342107873324,
"cos": -0.8390715290095377,
"cosh": 1.5430806348152437,
"coshPf": 1.5430806348152437,
"tan": -1.4214488238747245,
"tanh": random.uniform(0, 1),
"tanhPf": random.uniform(0, 1),
"exp": 2.718281828459045,
"expm1": 1.718281828459045,
"expm1Pf": 1.718281828459045,
"log1p": 2.3978952727983707,
"log1pPf": 2.3978952727983707,
"powPI": 1.9275814160560204e-50,
},
"pdfViewerEnabled": True,
"architecture": 255,
"applePay": -1,
"privateClickMeasurement": "",
"tz": "-8",
"ssl": str(uuid.uuid4()).replace("-", ""),
"cbfp": str(uuid.uuid4()).replace("-", ""),
"color_depth": 24,
"pixel_ratio": 1.5,
"resolution": "1707%;1067",
"available_resolution": "1707%;1019",
"session_storage": 1,
"local_storage": 1,
"indexed_db": 1,
"open_database": 0,
"cpu_class": "unknown",
"navigator_platform": "Win32",
# 其它一些指纹信息就不放了
},
}
data = {
"appid": "risk_h5",
"functionId": "wsgw_getinfo",
"body": json.dumps(fp_info),
"t": ts13,
}
response = requests.post(url, headers=headers, cookies=cookies, data=data)
fpb = response.json()["whwswswws"]
cookies["shshshfpb"] = fpb

获取sid

这里有个enbody参数,不知道是什么生成的,需要hook一下
image.png

image.png

可以看到请求是xhr的类型,所以直接hook XMLHttpRequest

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
(function() {
'use strict';

// 保存原始的XMLHttpRequest.open和.send方法
var originalOpen = XMLHttpRequest.prototype.open;
var originalSend = XMLHttpRequest.prototype.send;

// 重写open方法
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
this.addEventListener('readystatechange', function() {
if (this.readyState === 4 && this.responseURL.includes('functionId=createSid')) {
console.log('Hook捕获到请求->', this.responseURL);
debugger;
}
});
originalOpen.apply(this, arguments);
};

// 重写send方法
XMLHttpRequest.prototype.send = function(body) {
if (body && body.includes('functionId=createSid')) {
console.log('Hook捕获到请求体->', body);
debugger;
}
originalSend.apply(this, arguments);
};
})();

断住了,然后开始追栈,追到了 _0x53ff96 函数,继续下断点来看看 _0x2fdfbb 的 body 是怎么生成的

image.png

可以看到 enbody 是在这里生成的,来看看调了什么加密,传了什么参数

image.png

简单还原后,其实就是,除了 requestIdshshshfpx 其他参数都是空

1
2
3
4
5
6
7
8
Object(_0x3cb3ac['encodeBody'])({
'requestId': _0x451b4b,
'evApi': encodeURIComponent(_0x348e8),
'evType': _0x3b73ff,
'shshshfpx': Object(_0x3cb3ac['getValue'])('shshshfpx'),
'eid': Object(_0x3cb3ac['getEid'])(),
'evSid': _0x5d0fce
})

看看用了什么加密,Object(_0x3cb3ac['\x65\x6e\x63\x6f\x64\x65\x42\x6f\x64\x79']) 进入内部,还原一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
_0x787ac = function(_0x34e61c) {
var _0x23af52 = '';
try {
if (_0x34e61c)
_0x23af52 = function(_0x46545c, _0x153a9e, _0x51d034) {
var _0x662d53 = _0x1174c0['default']['enc']['Utf8']['parse'](_0x153a9e)
, _0x40bd96 = _0x1174c0['default']['enc']['Utf8']['parse'](_0x51d034);
return _0x1174c0['default']['AES']['encrypt'](_0x46545c, _0x662d53, {
'iv': _0x40bd96,
'mode': _0x1174c0['default']['mode']['CBC'],
'padding': _0x1174c0['default']['pad']['Pkcs7']
})['toString']()['replace'](/\+/g, '-')['replace'](/\//g, '_')['replace'](/=+$/, '');
}('string' == typeof _0x34e61c ? _0x34e61c : JSON['stringify'](_0x34e61c), 'rhiasnkdhand' + 'risk', 'r-s-h-n_r_is' + 'nkdk');
} catch (_0x301e52) {
_0x23af52 = '',
_0x292d16['default']['jsagentRepor' + 't']('code', 0x2ee, 'encodeBody\u5931\u8d25', {
'param': _0x34e61c,
'error': _0x301e52 && _0x301e52['message']
});
}
return _0x23af52;
}

嗯。一看就是 AES 的 CBC 加密,那就只要找到 key 和 iv 就好了,继续下断点

image.png

这里就很明显了,key 是 rhiasnkdhandrisk,iv 是 r-s-h-n_r_isnkdk,找个网站简单验证一下

image.png

结果一致,接下来就是用python还原了

1
2
3
4
5
6
7
key_bytes = "rhiasnkdhandrisk".encode("utf-8")
iv_bytes = "r-s-h-n_r_isnkdk".encode("utf-8")
message_bytes = origin_data.encode("utf-8")
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
encrypted_bytes = cipher.encrypt(pad(message_bytes, AES.block_size))
encrypted_b64 = b64encode(encrypted_bytes).decode("utf-8")
safe_b64_str = encrypted_b64.replace("+", "-").replace("/", "_").rstrip("=")

获取fp

拿验证码图片之前,看到cookie中多了一个jcap_dvzw_fp,应该是上一个请求生成的

image.png

就是这个请求来的,其中si已经从上面一步拿到了,所以来看看ct是怎么生成的

image.png

这里直接hook一下链接 https://jcap.m.jd.com/cgi-bin/api/fp,然后进去 promise 内部下断点,继续调就行了

image.png

然后定位到这里,ct的值是在最后一行生成的,套娃了好几层函数,分别从内到外拆出来看看吧

1
2
3
4
5
6
7
8
p = {};
p.si = a,
p.ct = "",
p.version = 2,
p.lang = f,
p[n("0x95") + "ent"] = h;
var v = p;
v.ct = pt(r.kshuP(r.MSflZ(vt(o) + gt(v.si[e("0x81") + n("0x7d")], 4) + v.si, u), i), s, d),
  1. 首先是gt函数,就是把st的长度用0补足4位

image.png

  1. 接着是vt函数,核心代码就这几行
1
2
3
4
5
6
7
8
i = Date.parse(new Date + "")
o = r.XBwqB(i, 19)
function vt(t) {
for (var e = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"], n = "", r = 0; r < t; r++) {
n += e[Math.floor(35 * Math.random())]
}
return n
}
  1. u是一些设备信息,直接写死好了,i是13位的时间戳,没什么好说的
1
{"capfp":"wak0el70W208CUJs9DEBUeLQBkDOglUJG2BC4QqegBDu-HNJZ1h--MkStpPhPglvuDDf4hBcSo8cybWfMUJhuA==","cvs":"d8990301e212da3ff35c33e784d8e820","wgl":"6e0b2a20c73f88d4feffb7d4b6d457fc","pr":"1.5","cd":"24","fv":"","fts":"Arial,BookAntiqua,BookmanOldStyle,Calibri,Cambria,Century,CenturyGothic,CenturySchoolbook,Consolas,Courier,CourierNew,Garamond,Georgia,Helvetica,Impact,LucidaBright,LucidaConsole,LucidaHandwriting,LucidaSans,LucidaSansTypewriter,LucidaSansUnicode,MonotypeCorsiva,MSGothic,MSPGothic,PalatinoLinotype,SegoePrint,SegoeScript,SegoeUI,Tahoma,Times,TimesNewRoman,TrebuchetMS,Verdana,Wingdings,Symbol,BookshelfSymbol7,Candara,Constantia,Corbel,Ebrima,FangSong,FrenchScriptMT,Gabriola,MicrosoftYaHei,MicrosoftYiBaiti,MingLiUExtB,PMingLiUExtB,SimHei,SimSun,SimSunExtB","scr":"1707x1067,1707x1019","cpu":"24","pt":"Win32","tzo":"Asia/Shanghai","lan":"zh-CN","wvr":"Google Inc. (Intel)~ANGLE (Intel, Intel(R) UHD Graphics (0x00004688) Direct3D11 vs_5_0 ps_5_0, D3D11)","wdr":"0","mem":"8","sdv":"2.0","jsv":"0dugxe","sdf":"{\\"swf11n\\":\\"lS3luZ\\",\\"8EC4N0\\":\\"Z3XTMw\\"}","lns":"zh-CN","tsp":"0","pdf":"1","cke":"1","bid":"","gpu":"4","uat":"11","ol":"1","ets":"33","wch":"0"}
  1. r.MSflZr.kshuP 都是一个拼接函数
1
(t,e){return t+e}
  1. 最后就是 pt 函数了,参数s的值是 99992,d是空,直接写死好了

前面几个函数可以用py还原,最后的pt函数太复杂,只能补js了,建议是在清空桌面,新建一个js文件,从源头调用开始,缺啥补啥,最后还原出来的pt函数如下,打印的结果和浏览器一致即可

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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
function f(t, e) {
return l(d(t, e, 99992))
}

var i = [
"C2vX",
"twfY",
# 这个数组太长就不列举了
]

var o = function (t, e) {
var n = i[t -= 0];
if (void 0 === o.OcTlit) {
o.cmwhiL = function (t) {
for (var e = function (t) {
for (var e, n, r = String(t).replace(/=+$/, ""), i = "", o = 0, a = 0; n = r.charAt(a++); ~
n && (e = o % 4 ? 64 * e + n : n,
o++ % 4) ? i += String.fromCharCode(255 & e >> (-2 * o & 6)) : 0)
n = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=".indexOf(n);
return i
}(t), n = [], r = 0, i = e.length; r < i; r++)
n += "%" + ("00" + e.charCodeAt(r).toString(16)).slice(-2);
return decodeURIComponent(n)
},
o.IidJoH = {},
o.OcTlit = !0
}
var r = o.IidJoH[t];
return void 0 === r ? (n = o.cmwhiL(n),
o.IidJoH[t] = n) : n = r,
n
}

function h(t) {
var e = function (t, e) {
return o(t - -778, e)
},
n = function (t, e) {
return o(t - -778, e)
},
r = {
RNcsD: function (t, e) {
return t < e
},
WPEgW: function (t, e) {
return t >> e
},
kcHQJ: function (t, e) {
return t | e
}
};
r[e(-405) + "ve"] = function (t, e) {
return t & e
},
r[n(-375) + "aH"] = function (t, e) {
return t <= e
},
r.uaVpl = function (t, e) {
return t << e
},
r[e(-443) + "RH"] = function (t, e) {
return t & e
},
r.izwEa = function (t, e) {
return t & e
},
r[e(-496) + "aU"] = "Mal" + n(-619) + e(-713) + " string";
var i = r;
if (/^[\x00-\x7f]*$captcha/.test(t))
return t;
for (var a = [], c = t.length, s = 0, u = 0; i.RNcsD(s, c); ++s,
++u) {
var f = t[n(-475) + e(-465) + "deAt"](s);
if (f < 128)
a[u] = t.charAt(s);
else if (f < 2048)
a[u] = String[e(-466) + e(-594) + "arCode"](192 | i.WPEgW(f, 6), 128 | 63 & f);
else {
if (!(f < 55296 || f > 57343)) {
if (s + 1 < c) {
var l = t.charCodeAt(s + 1);
if (i.RNcsD(f, 56320) && l >= 56320 && i[n(-375) + "aH"](l, 57343)) {
var d = 65536 + (i.uaVpl(1023 & f, 10) | i.JmMRH(l, 1023));
a[u] = String.fromCharCode(240 | 63 & i[n(-477) + "gW"](d, 18), 128 | 63 & i.WPEgW(d, 12), 128 |
d >> 6 & 63, 128 | i[n(-599) + "Ea"](d, 63)),
++s;
continue
}
}
throw new Error(i.vAlaU)
}
a[u] = String["fro" + e(-594) + e(-516) + "ode"](i.kcHQJ(224, f >> 12), 128 | f >> 6 & 63, 128 | i.fExve(f,
63))
}
}
return a.join("")
}

function p(t, e) {
var n = function (t, e) {
return o(t - "0x323", e)
},
r = function (t, e) {
return o(t - "0x323", e)
},
i = {
mDjJH: function (t, e) {
return t << e
},
ZdUuO: function (t, e) {
return t < e
},
cFWgd: function (t, e) {
return t - e
},
ANEuq: function (t, e) {
return t > e
}
};
i[n("0x3b3") + "GQ"] = function (t, e) {
return t < e
},
i[n("0x4bb") + "gU"] = function (t, e) {
return t & e
},
i.pdIJp = function (t, e) {
return t >>> e
};
var a = i,
c = t["len" + r("0x4ae")],
s = a[r("0x3be") + "JH"](c, 2);
if (e) {
var u = t[c - 1];
if (s -= 4,
a.ZdUuO(u, a.cFWgd(s, 3)) || a.ANEuq(u, s))
return null;
s = u
}
for (var f = 0; a.NvTGQ(f, c); f++)
t[f] = String["fro" + n("0x3db") + "arCode"](a[r("0x4bb") + "gU"](t[f], 255), 255 & a.pdIJp(t[f], 8), t[f] >>>
16 & 255, t[f] >>> 24 & 255);
var l = t.join("");
return e ? l.substring(0, s) : l
}

function x() {
var t = {};
return t.QsOVK = "MTU0Mjk2N" + function (t, e) {
return o(t - "0x248", e)
}("0x3b6") + "OQ",
1111471640 + parseInt(y(t.QsOVK))
}

function y(t) {
var e = function (t, e) {
return o(t - "0x2c", e)
},
n = function (t, e) {
return o(t - "0x2c", e)
},
r = {};
r[e("0x133") + "Xi"] = function (t, e) {
return t - e
},
r[n("0x130") + "sH"] = function (t, e) {
return t + e
},
r.XgTDg = function (t, e) {
return t >> e
},
r.drWLi = function (t, e) {
return t < e
},
r.pTvwW = function (t, e) {
return t & e
},
r.kRnVo = function (t, e) {
return t >> e
},
r[n("0x1a4") + "Ol"] = function (t, e) {
return t & e
};
var i, a, c, s, u, f, l, d, h, p, v = r,
g = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57,
58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 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, -1, -1, -1, -1, -1, -1, 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, -1, -1, -1, -1, -1];
if (l = t[n("0x184") + "gth"],
t = (t += Array(5 - l % 4)[e("0x104") + "n"]("=")).replace(/\-/g, "+")[n("0x84") + e("0x111") + "e"](/\_/g, "/"),
/[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+\/\=]/.test(t))
return "";
for (h = l,
(d = "=" == t["cha" + e("0x6f")](l - 2) ? 1 : "=" == t[n("0x15b") + "rAt"](v.kPQXi(l, 1)) ? 2 : 0) > 0 && (h -=
4),
h = v[e("0x130") + "sH"](3 * v.XgTDg(h, 2), d),
p = new Array(h),
u = f = 0; v[e("0x18f") + "Li"](u, l) && -1 != (i = g[t.charCodeAt(u++)]) && -1 != (a = g[t["charCo" + n("0x91") +
"t"](u++)]) && (p[f++] = String.fromCharCode(i << 2 | (48 & a) >> 4),
-1 != (c = g[t.charCodeAt(u++)])) && (p[f++] = String.fromCharCode(v[n("0x139") + "wW"](a, 15) << 4 | v.kRnVo(
60 & c, 2)),
-1 != (s = g[t[e("0x15b") + "rCodeAt"](u++)]));)
p[f++] = String[n("0x164") + "mCh" + n("0x132") + "ode"](v.AXEOl(c, 3) << 6 | s);
return p.join("")
}

function m(t, e, n, r, i, a, c) {
var s = function (t, e) {
return o(t - "0xd8", e)
},
u = {
qNPpO: function (t, e) {
return t - e
}
};
u[s("0x1e8") + "nj"] = function (t, e) {
return t >>> e
},
u.FKQrE = function (t, e) {
return t & e
},
u.PGTAt = function (t, e) {
return t & e
},
u.lcuPp = function (t, e) {
return t ^ e
},
u.vUugl = function (t, e) {
return t >>> e
},
u.dtMtv = function (t, e) {
return t + e
};
var f = u,
l = f[s("0x186") + "pO"](c, 25700);
if (1 == f.QmXnj(l, 16)) {
var d = 7 & f.QmXnj(l, 12),
h = f.FKQrE(l >>> 8, 7),
p = l >>> 4 & 7,
v = f.PGTAt(l, 7);
return f.lcuPp(b(t, e, n, r, i, a), (f.vUugl(e, d) ^ n << h) + f[s("0x288") + "Pp"](t >>> p & 63, f.dtMtv(n, e) >>>
(f.qNPpO(7, v) >>> 1) & 63))
}
return b(t, e, n, r, i, a)
}

function b(t, e, n, r, i, o) {
var a = {
aa: function (t, e) {
return t ^ e
},
bb: function (t, e) {
return t + e
},
cc: function (t, e) {
return t << e
},
dd: function (t, e) {
return t >>> e
}
};
return a.aa(a.bb(a.aa(n >>> 5, a.cc(e, 2)), a.aa(a.dd(e, 3), a.cc(n, 4))), (t ^ e) + (o[a.aa(3 & r, i)] ^ n))
}

function v(t, e, n) {
var r = function (t, e) {
return o(t - "0x79", e)
},
i = {};
i[function (t, e) {
return o(t - "0x79", e)
}("0x121") + "GJ"] = function (t) {
return t()
},
i.gTVbn = function (t, e) {
return t & e
},
i.cTHvA = function (t, e, n, r, i, o, a, c) {
return t(e, n, r, i, o, a, c)
},
i.YAzCo = function (t, e, n, r, i, o, a, c) {
return t(e, n, r, i, o, a, c)
};
var a, c, s, u, f, l, d = i,
h = t[r("0x1d1") + r("0x204")],
p = h - 1;
for (c = t[p],
s = 0,
l = 0 | Math.floor(6 + 52 / h); l > 0; --l) {
for (s = g(s + d.duBGJ(x)),
u = d[r("0x139") + "bn"](s >>> 2, 3),
f = 0; f < p; ++f)
a = t[f + 1],
c = t[f] = g(t[f] + d[r("0x96") + "vA"](m, s, a, c, f, u, e, n));
a = t[0],
c = t[p] = g(t[p] + d.YAzCo(m, s, a, c, p, u, e, n))
}
return t
}

function g(t) {
return 4294967295 & t
}

function w(t, e) {
var n = function (t, e) {
return o(t - -194, e)
},
r = {
lCviv: function (t, e) {
return t >> e
}
};
r[n("0x71") + "Km"] = function (t, e) {
return t << e
};
var i, a = r,
c = t.length,
s = a.lCviv(c, 2);
0 != (3 & c) && ++s,
e ? (i = new Array(s + 1))[s] = c : i = new Array(s);
for (var u = 0; u < c; ++u)
i[u >> 2] |= a.ubZKm(t.charCodeAt(u), a[n("0x71") + "Km"](3 & u, 3));
return i
}

function _(t) {
var e = function (t, e) {
return o(t - "0x211", e)
};
return t.length < 4 && (t[e("0x369") + e("0x39c")] = 4),
t
}

function d(t, e, n) {
var r = function (t, e) {
return o(t - -524, e)
},
i = {
Wrcde: function (t, e) {
return t === e
},
gOvIv: function (t, e) {
return t === e
}
};
i[r(-101) + "Dw"] = function (t, e) {
return t(e)
};
var a = i;
return void 0 === t || a[r(-190) + "de"](t, null) || a.gOvIv(t["len" + function (t, e) {
return o(t - -524, e)
}(-129)], 0) ? t : (t = a[r(-101) + "Dw"](h, t),
e = h(e),
p(v(w(t, !0), _(w(e, !1)), n), !1))
}

function l(t) {
var e = function (t, e) {
return o(t - "0x26d", e)
},
n = function (t, e) {
return o(t - "0x26d", e)
},
r = {};
r[e("0x38f") + "MD"] = function (t, e) {
return t << e
},
r.HDFem = function (t, e) {
return t + e
},
r.xMlmP = function (t, e) {
return t >> e
},
r.BCXAB = function (t, e) {
return t & e
},
r[e("0x28d") + "AT"] = function (t, e) {
return t | e
},
r[n("0x2a3") + "fP"] = function (t, e) {
return t / e
},
r[e("0x3ce") + "hb"] = function (t, e) {
return t % e
};
for (var i = r, a = ("0|1|4|8|6|9|5|10|7" + e("0x2cc") + "2|11").split("|"), c = 0;;) {
switch (a[c++]) {
case "0":
var s = ("ABC" + e("0x335") + n("0x312") + "JKLMNOPQRSTU" + n("0x364") + "YZa" + n("0x27c") + e("0x289") +
"hijklmnop" + e("0x3a2") + "tuv" + n("0x3eb") + "z01" + n("0x2c1") + "567" + e("0x321") + "_")[
n("0x406") + "it"]("");
continue;
case "1":
var u, f, l, d, h, p, v;
continue;
case "2":
1 == h ? (v = t[e("0x39c") + "rCodeAt"](f++),
u[l++] = s[v >> 2] + s[i[e("0x38f") + "MD"](3 & v, 4)]) : 2 == h && (v = t["cha" + e("0x3a6") +
"deAt"](f++) << 8 | t["cha" + e("0x3a6") + "deAt"](f++),
u[l++] = i.HDFem(s[i.xMlmP(v, 10)], s[v >> 4 & 63]) + s[i.BCXAB(v, 15) << 2]);
continue;
case "3":
for (; f < d;)
v = i[n("0x28d") + "AT"](t.charCodeAt(f++) << 16 | t["cha" + n("0x3a6") + "deAt"](f++) << 8, t[n(
"0x39c") + e("0x3a6") + "deAt"](f++)),
u[l++] = i.HDFem(i.HDFem(s[v >> 18], s[i.BCXAB(v >> 12, 63)]) + s[v >> 6 & 63], s[63 & v]);
continue;
case "4":
f = l = 0;
continue;
case "5":
p = i[e("0x2a3") + "fP"](d, 3) << 2;
continue;
case "6":
h = i.XEThb(d, 3);
continue;
case "7":
u = new Array(p);
continue;
case "8":
d = t[n("0x3c5") + "gth"];
continue;
case "9":
d -= h;
continue;
case "10":
h > 0 && (p += 4);
continue;
case "11":
return u.join("")
}
break
}
}

console.log(f(
'damuvrflk8wnc4q0091bJ5ZLAACAAAAADnIUR0AMH-8tZkrX09X7su3Sqx9RF6Zc0QErPaCJTQbzigk7hW7KrbDXs6hfzvcemRrLnHe9AAAAAA{"capfp":"wak0el70W208CUJs9DEBUeLQBkDOglUJG2BC4QqegBDu-HNJZ1h--MkStpPhPglvuDDf4hBcSo8cybWfMUJhuA==","cvs":"d8990301e212da3ff35c33e784d8e820","wgl":"6e0b2a20c73f88d4feffb7d4b6d457fc","pr":"1.5","cd":"24","fv":"","fts":"Arial,BookAntiqua,BookmanOldStyle,Calibri,Cambria,Century,CenturyGothic,CenturySchoolbook,Consolas,Courier,CourierNew,Garamond,Georgia,Helvetica,Impact,LucidaBright,LucidaConsole,LucidaHandwriting,LucidaSans,LucidaSansTypewriter,LucidaSansUnicode,MonotypeCorsiva,MSGothic,MSPGothic,PalatinoLinotype,SegoePrint,SegoeScript,SegoeUI,Tahoma,Times,TimesNewRoman,TrebuchetMS,Verdana,Wingdings,Symbol,BookshelfSymbol7,Candara,Constantia,Corbel,Ebrima,FangSong,FrenchScriptMT,Gabriola,MicrosoftYaHei,MicrosoftYiBaiti,MingLiUExtB,PMingLiUExtB,SimHei,SimSun,SimSunExtB","scr":"1707x1067,1707x1019","cpu":"24","pt":"Win32","tzo":"Asia/Shanghai","lan":"zh-CN","wvr":"Google Inc. (Intel)~ANGLE (Intel, Intel(R) UHD Graphics (0x00004688) Direct3D11 vs_5_0 ps_5_0, D3D11)","wdr":"0","mem":"8","sdv":"2.0","jsv":"0dugxe","sdf":"{\\"swf11n\\":\\"lS3luZ\\",\\"8EC4N0\\":\\"Z3XTMw\\"}","lns":"zh-CN","tsp":"0","pdf":"1","cke":"1","bid":"","gpu":"4","uat":"11","ol":"1","ets":"33","wch":"0"}1732124708000',
'E736B80A35290F193C2034A8021CC63B'))

获取验证码缺口图和滑块图

有两个加密参数,ct猜测加密和上一个差不多,先来看看入参是什么吧

image.png

定位到关键两行代码

image.png

1
2
w.tk = pt(a[r("0x2b") + "cO"](a.uLccO(a[i("0x2b") + "cO"](g + gt(c["len" + r("0x2c")], 4) + c, gt(l[r("0x30") + "gth"], 4)) + l, a.rBNYA(gt, t["len" + i("0x2c")], 6)), t) + JSON.stringify(m) + vt(x), u, v),
w.ct = a.WHtDX(pt, vt(a[i("0x3a") + "uL"](g, 19)) + gt(c["len" + i("0x2c")], 4) + c + b + g, u, v),

一番调试过后,发现ct的调用函数和入参和上面的一毛一样,直接来看tk吧,tk的参数过多,从后往前来看看,因为最后还是调pt函数,所以丝毫不慌

  1. vt(x),其中 x = g % 41,也是时间戳取余
  2. JSON.stringify(m){"touchList":{}}
  3. a.rBNYA(gt, t["len" + i("0x2c")], 6) 固定成 000000
  4. l 是上一步获取的st,c是sid
  5. gt(l[r("0x30") + "gth"], 4) 就是 l 的长度补0,即 0016
  6. gt(c["len" + r("0x2c")], 4) 又是 0091
  7. 其他几个函数都是拼接作用,就不赘述了

识别距离,滑动轨迹,获取vt

拿着缺口图和滑块图,用opencv可以很简单的识别出距离,这里不赘述,重点来讲一下轨迹的校验

随便滑一下,还是上一个的check接口,ct还是一样的没有变化,但是tk参数变得巨长无比,应该是把轨迹藏在里面了

image.png

直接下断点调试,先来看tk参数变化的地方

  1. JSON.stringify(m) 变成了记录鼠标点击坐标点的数组,如果一直点击会累加,这里加一些简单的随机数,同时注意time值(或者直接传控股)
1
{"touchList":[{"eid":"click","did":"","cn":"move-img","sx":502,"sy":667,"px":502,"py":546,"time":1732165092961}]}
  1. t 是鼠标轨迹的还原和一些盐值
1
{"ht":179,"wt":290,"bw":50,"sw":290,"mw":69,"list":[[0,0,0],[1,0,94],[2,0,7],[3,-2,8],[5,-2,8],[8,-3,8],[10,-4,8],[12,-4,8],[14,-5,8],[17,-6,8],[19,-6,8],[20,-7,8],[21,-7,8],[23,-7,8],[24,-8,8],[25,-8,8],[26,-8,8],[27,-8,8],[28,-8,8],[29,-8,16],[30,-9,8],[31,-9,8],[32,-10,8],[33,-10,8],[34,-10,8],[35,-10,8],[36,-10,8],[37,-10,8],[38,-11,8],[39,-11,8],[40,-11,8],[41,-12,8],[42,-12,8],[42,-12,8],[43,-12,8],[44,-13,8],[45,-13,8],[45,-13,8],[46,-13,8],[47,-13,8],[47,-13,24],[48,-14,16],[49,-14,31]],"1btRY&":"^r0Z#F","GuLf0&":"*1cYhu"}

其他的参数没有变化,这里可以先滑到底,录制一条完整的轨迹,再用缺口识别出的距离,等比例缩放,简单说明一下,就不放代码了

校验vt,上报cookie,否则cookie不能用

这个和第一个creatSid是一样的AES加密,不赘述……

image.png

部分核心代码

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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348

with open("gen_ct.js", "r", encoding="utf-8") as f:
js = f.read()
ctx = execjs.compile(js)


def base64_to_img(base64_str):
base64_str = base64_str.split(",")[-1]
img_data = base64.b64decode(base64_str)
np_array = np.frombuffer(img_data, np.uint8)
img = cv2.imdecode(np_array, cv2.COLOR_RGB2BGR)
return img


def b64_img_save(b64_img, filename):
b64_img = b64_img.split(",")[-1]
img = base64.b64decode(b64_img)
with open(filename, "wb") as f:
f.write(img)


def get_enbody(origin_data):
key_bytes = "rhiasnkdhandrisk".encode("utf-8")
iv_bytes = "r-s-h-n_r_isnkdk".encode("utf-8")
message_bytes = origin_data.encode("utf-8")
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
encrypted_bytes = cipher.encrypt(pad(message_bytes, AES.block_size))
encrypted_b64 = b64encode(encrypted_bytes).decode("utf-8")
safe_b64_str = encrypted_b64.replace("+", "-").replace("/", "_").rstrip("=")
return safe_b64_str


def createSid(rprid):
url = "https://api.m.jd.com/api"
origin_data = {
"requestId": rprid,
"evApi": "",
"evType": "",
"shshshfpx": cookies["shshshfpx"],
"eid": "",
"evSid": "",
}
origin_data = json.dumps(origin_data)
enbody = get_enbody(origin_data)
body = {"sdkClient": "pc", "sdkVersion": "pc_2.1.0", "enbody": enbody}
data = {"appid": "risk_h5", "functionId": "createSid", "body": json.dumps(body)}
response = session.post(url, headers=headers, cookies=cookies, data=data)
sid = response.json()["data"]
print("获取sid成功", sid)
return sid, enbody


def get_fp(sid):
a = str(len(sid)).rjust(4, "0")
i = int(time.time()) * 1000
b = i % 19
l = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
]
c = ""
for _ in range(b):
c += random.choice(l)
u = '指纹信息'
cxs = c + a + sid + u + str(i)
ct = ctx.call("gen_ct", cxs)
url = "https://jcap.m.jd.com/cgi-bin/api/fp"
data = {"si": sid, "ct": ct, "version": "2", "lang": "1", "client": "pc"}
response = session.post(url, headers=headers, cookies=cookies, data=data)
fp = response.json()["fp"]
st = response.json()["st"]
cookies["jcap_dvzw_fp"] = fp
print("获取fp成功", fp)
print("获取st成功", st)
print("设置cookie成功", response.cookies)
return fp, st


def get_img(sid, st):
url = "https://jcap.m.jd.com/cgi-bin/api/check"
a = str(len(sid)).rjust(4, "0")
i = int(time.time()) * 1000
b = i % 19
l = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
]
c = ""
for _ in range(b):
c += random.choice(l)
u = '指纹信息'
cxs_ct = c + a + sid + u + str(i)
ct = ctx.call("gen_ct", cxs_ct)
d = str(len(st)).rjust(4, "0")
f = i % 41
e = ""
for _ in range(f):
e += random.choice(l)
cxs_tk = str(i) + a + sid + d + st + "000000" + '{"touchList":{}}' + e
tk = ctx.call("gen_ct", cxs_tk)
data = {"si": sid, "lang": "1", "tk": tk, "ct": ct, "version": "2", "client": "pc"}
response = session.post(url, headers=headers, cookies=cookies, data=data)
data = response.json()
img_data = json.loads(data["img"])
img1, img2 = img_data["b1"], img_data["b2"]
print("验证码图片获取成功", img1)
return img1, img2, data["tp"]


def slider_check(sid, st, mouse_array_data):
url = "https://jcap.m.jd.com/cgi-bin/api/check"
a = str(len(sid)).rjust(4, "0")
i = int(time.time()) * 1000
b = i % 19
l = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
]
c = ""
for _ in range(b):
c += random.choice(l)
u = '指纹信息'
cxs_ct = c + a + sid + u + str(i)
ct = ctx.call("gen_ct", cxs_ct)
d = str(len(st)).rjust(4, "0")
f = i % 41
e = ""
for _ in range(f):
e += random.choice(l)
touch_lst = {
"touchList": [
{
"eid": "click",
"did": "",
"cn": "captcha_footer",
"sx": random.randint(290, 300),
"sy": random.randint(650, 660),
"px": random.randint(290, 300),
"py": random.randint(650, 660),
"time": i - 10,
}
]
}
g = quote_plus(mouse_array_data).replace("%3A", ":").replace("%2C", ",")
h = str(len(g)).rjust(6, "0")
touch_lst = json.dumps(touch_lst, separators=(",", ":"))
cxs_tk = str(i) + a + sid + d + st + h + g + touch_lst + e
tk = ctx.call("gen_ct", cxs_tk)
data = {"si": sid, "lang": "1", "tk": tk, "ct": ct, "version": "2", "client": "pc"}
response = session.post(url, headers=headers, cookies=cookies, data=data)
data = response.json()
vt = data.get("vt")
if vt:
print("滑动成功,获取vt", vt)
return vt


def verify_ocr(bg_img, tp_img):
bg_img = base64_to_img(bg_img)
tp_img = base64_to_img(tp_img)
# 识别图片边缘
bg_edge = cv2.Canny(bg_img, 100, 200)
tp_edge = cv2.Canny(tp_img, 100, 200)
# 转换图片格式
bg_pic = cv2.cvtColor(bg_edge, cv2.COLOR_GRAY2RGB)
tp_pic = cv2.cvtColor(tp_edge, cv2.COLOR_GRAY2RGB)
# 缺口匹配
res = cv2.matchTemplate(bg_pic, tp_pic, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 寻找最优匹配
# 绘制方框
th, tw = tp_pic.shape[:2]
tl = max_loc # 左上角点的坐标
br = (tl[0] + tw, tl[1] + th) # 右下角点的坐标
cv2.rectangle(bg_img, tl, br, (0, 0, 255), 2) # 绘制矩形
cv2.imwrite("big_img.jpg", bg_img) # 保存在本地
# 返回缺口的X坐标
return tl[0]


def get_distance(bg_b64, slider_b64):
dis = verify_ocr(bg_b64, slider_b64)
# 290为网页端背景图片宽度 275为图片实际宽度
return dis / 275 * 290


def get_mouse_array(distance):
# 从网页截取并录入鼠标轨迹
origin_mouse_array = [
...
]
result = []
num = -7
while len(result) < 40:
_max = random.randint(4, 15)
for _ in range(5, _max):
result.append(num)
num -= 1
result = result[:40]
print("生成的随机Y轴序列", result, len(result))
i = -1
for n in result[::-1]:
origin_mouse_array[i][1] = n
i -= 1

mouse_array = []
origin_distance = origin_mouse_array[-1][0]
per = distance / origin_distance
for i in origin_mouse_array:
new_x = round(i[0] * per)
new_y = round(i[1] * per)
new_t = round(i[2] * per)
mouse_array.append([new_x, new_y, new_t])
print("缩放后的滑动轨迹", mouse_array)
mouse_array_data = {
"ht": 179,
"wt": 290,
"bw": 50,
"sw": 290,
"mw": 69,
"list": mouse_array,
"1btRY&": "^r0Z#F",
"GuLf0&": "*1cYhu",
}
print("完整的滑动数据", mouse_array_data)
return json.dumps(mouse_array_data, separators=(",", ":"))


def check_token(vt, sid, rprid):
url = "https://api.m.jd.com/api"
origin_data = {
"sid": sid,
"token": quote_plus(vt),
"requestId": rprid,
"evApi": "",
"evType": "2",
"shshshfpx": cookies["shshshfpx"],
"eid": "",
"pin": "",
"evSid": "",
}
origin_data = json.dumps(origin_data)
enbody = get_enbody(origin_data)
body = {"sdkClient": "pc", "sdkVersion": "pc_2.1.0", "enbody": enbody}
data = {"appid": "risk_h5", "functionId": "checkToken", "body": json.dumps(body)}
response = session.post(url, headers=headers, cookies=cookies, data=data)
print(response.text)

人生第一个纯算法过滑块完成 👍抱着打游戏闯关的心态来逆向,还是挺好玩的