本文将会逆向京东的 JS
,分析京东请求内的 h5st 加密参数生成规则及部分 Cookie
生成
1 h5st
1.1 加密定位
进入网址:手机 - 商品搜索 - 京东 (jd.com) 后打开浏览器 F12 进行抓包
可以看到请求都有一个 h5st
的加密参数,通过全文搜索,关键字可以是 .h5st
可以查看调用的地方,定位到加密的地方
1.2 算法分析
打上断点开始逐步分析发现 window.PSign.sign(e)
是一个 Promise
,也就是一个异步的处理
而 e
是 body
进行加密后的结果,加密方式为 sha256
加密原文是之前的 body
所以可以还原 genBody
函数
复制 def gen_body ( functionid , ts , body ):
"""
:param body: 请求参数内的 body
:param functionid: 函数 id
:param ts: 时间戳, 秒级
:return:
"""
return {
"appid" : "search-pc-java" ,
"functionId" : functionid ,
"client" : "pc" ,
"clientVersion" : "1.0.0" ,
"t" : ts ,
"body" : hashlib . sha256 (body. encode ()). hexdigest ()
}
根据断点步进查看 window.PSign.sign
函数
查看 window.PSign
这个对象,因为加密都是调用的这个对象的方法,看看有没有可以的方法!
点开 Prototype
查看发现里面有几个疑似函数
分析一下 window.PSign.__genSign
发现 t
是一串加密的字符串,r
是上一步加密好的 body
让我们来看一下代码是啥
代码很简单,可以看出来:t
就是一个加密的 key
, 而 u
是之前的 body
进行的格式化,加密方式为 hamc
这边等于清楚了第一步,那么接下来要分析 t
是怎么来的,我们需要在堆栈这里找到上一步,然后打断点继续分析
断点下来我们可以发现是在这里调用的 __genSign
方法,主要分析 C
是怎么来的, 断点调试发现:
复制 this [ u ( 0 , Qe , 0 , $e)] ? C = this [ u ( 0 , to , 0 , ro)](m , w , s , A , eI) .toString () || "" : b ? C = this [ a ( 0 , 0 , - no , - eo) + a ( 0 , 0 , 30 , oo)](b , w , s , A ) : ( this ._defaultToken = kf[ u ( 0 , io , 0 , ao)]( II , this [ u ( 0 , co , 0 , uo) + "nt" ]) ,
C = this [ "__genDefau" + u ( 0 , fo , 0 , so)]( this [ u ( 0 , ee , 0 , ho) + a ( 0 , 0 , - lo , - vo)] , w , s , A ));
浏览器自助解一下混淆,然后看解完混淆之后的代码:
复制 this [ '_isNormal' ] ? C = this [ '__genKey' ](m , w , s , A , eI) .toString () || "" : b ? C = this [ '__genDefaultKey' ](b , w , s , A ) : ( this ._defaultToken = II ( this [ '_fingerprint' ]) , C = this [ '__genDefaultKey' ]( this [ '_defaultToken' ] , w , s , A ));
因为 this['_isNormal']
为 true
,所以 C = this['__genKey'](m, w, s, A, eI).toString() || ""
而 this['__genKey']
这个函数又是来源于:
因此接下来要分析 m, w, s, A, eI
都是哪里来的
s
:s = XC(h, 'yyyyMMddhhmmssSSS')
,跟时间相关的,后面的是他的格式
eI
:是一个加密工具,分析 this['__genKey']
的代码发现并没有使用
所以可以还原出 genKey
的加密算法了:
复制 def gen_key ( ts ):
"""
生成hmac256 盐值
"""
rd = '6vRABalFEPcI'
str_ = f ' { T [ "env" ] [ "tk" ] }{ T [ "env" ] [ "fingerprint" ] }{ ts }{ appid }{ rd } '
return hashlib . md5 (str_. encode ()). hexdigest ()
接着还原 genSign
的代码:
复制 def gen_sign ( data : dict , key ):
"""
生成加密参数
:param data: hash 之后的body
:param key: 加密的 key
:return:
"""
message = ""
for k in sorted (data. keys ()):
message += f ' { k } : { data [ k ] } '
return hmac . new (key. encode (), message. encode ( 'utf-8' ), digestmod = sha256). hexdigest ()
这个 data
是需要加密的 body
,而那个 key
是调用 genKey
生成的 key
最后分析完了这三个关键函数,我们就要分析 h5st
了,继续分析刚刚的代码:
发现又调用了 __genSignParams
方法
这不就是我们需要的 h5st
吗?那我们追进去看看
先分析一下 __genSignParams
方法,发现就是进行拼接
解一下混淆并去掉没用的代码:
复制 [n , this [ '_fingerprint' ] , this [ '_appId' ] , this [ '_token' ] , t , this [ '_version' ] , r , e][ 'join' ]( ";" )
用 python
还原一下
复制 def gen_h5st ( sign , ts , tstr ):
return ";" . join ([tstr, T[ "env" ][ "fingerprint" ], appid, T[ "env" ][ "tk" ], sign, version, ts, T[ "env" ][ "e" ]])
通过上述分析,我们还有几个参数不知道怎么来的:
接下来我们来分析这个 e
是怎么来的,首先在刚刚那个加密的地方打上断点,然后刷新页面
可以看到 r
就是我们需要分析的参数
点到上一层堆栈查看可以知道 r
来源与这个 q
, 而 q = t[i(m, 0, 718)]
解开混淆发现:q = t["sent"]
,所以我们需要去查看这个的来源,那么这个 t
又是怎么来的呢?网上查看代码发现 t
也是传入进来的,所以我们要分析一下,传入来的 t
是不是就包含了这个 sent
,还是说一开始没有这个,后面走了一段代码后就有了呢?所以我们可以使用 Chrome
自带的 js
文件替换进行插桩输出看看!
插桩之后查看日志可以知道,有时候有,有时候没有
我们继续在上层堆栈中插桩,输出所有的 n
刷新页面后查看日志
发现这个加密参数出现的时候有一个这样的日志
也就是当 sent
为 {sua: 'Macintosh; Intel Mac OS X 10_15_7', pp: {…}, random: 'GFfcGJW9IV', referer: 'https://www.jd.com/', v: 'v3.2.1', …}
的时候,他的 rval
就是我们需要的值,其实熟知 JavaScript
的应该知道,这是 js
中常见的异步手段,通过 promise
实现的,sent
就是参数,rval
就是返回值!所以这个参数肯定和这条日志是有关系的!
我们接着插桩,当 sent
为 {sua: 'Macintosh; Intel Mac OS X 10_15_7', pp: {…}, random: 'GFfcGJW9IV', referer: 'https://www.jd.com/', v: 'v3.2.1', …}
的时候,我们就断下来,然后逐步分析
断下来了,这一步还没有生成我们需要的加密参数,所以我们先在下一行打一个断点,放过这个断点后,再分析下一步的执行流程
此时放过去之后即将调用 t
函数,那么这个 t
函数是哪个呢?我们可以在 console
上输出一下
发现他调用的是这个函数,所以我们可以进入到这个函数里面,分析一下,进去发现是一段分支语句
复制 for (; ; ){
switch ( j .prev = j[ N ( 0 , t , 0 , r)]) {
case 0 :
return j[ N ( 0 , 460 , 0 , 444 )] = 2 ,
I [ N ( 0 , 662 , 0 , n)]( KI , 1 );
case 2 :
return ( M = j .sent).fp = this [ N ( 0 , e , 0 , o) + "nt" ] ,
O = JSON .stringify ( M , null , 2 ) ,
this [ N ( 0 , 478 , 0 , i)]( I .aDGtR[ N ( 0 , a , 0 , c)]( O )) ,
L = eI[ P ( - 19 , 0 , 0 , - u)] .encrypt ( O , eI .enc[ P (f , 0 , 0 , s)][ P (h , 0 , 0 , 184 )]([ "wm" , I [ N ( 0 , l , 0 , 326 )] , "w_" , I [ N ( 0 , v , 0 , p)] , I [ P (d , 0 , 0 , 46 )] , "o(" ][ P ( - y , 0 , 0 , - d)]( "" )) , {
iv : eI[ N ( 0 , 379 , 0 , g)][ P (f , 0 , 0 , - x)][ N ( 0 , 526 , 0 , _)]([ "01" , "02" , "03" , "04" , "05" , "06" , "07" , "08" ] .join ( "" )) ,
mode : eI[ P ( - m , 0 , 0 , b)][ N ( 0 , w , 0 , A )] ,
padding : eI[ N ( 0 , C , 0 , S )][ P ( - 10 , 0 , 0 , - B )]
}) ,
j[ N ( 0 , 339 , 0 , z)]( I .KWfsh , L .ciphertext[ P ( D , 0 , 0 , k)]());
case 8 :
case I [ N ( 0 , E , 0 , 425 )]:
return j[ P ( - 99 , 0 , 0 , - T )]()
}
}
我们先解一下混淆
复制 for (; ; ){
switch ( j .prev = j[ 'next' ]) {
case 0 :
return j[ 'next' ] = 2 ,
I [ 'fMHMe' ]( KI , 1 );
case 2 :
return ( M = j .sent).fp = this [ '_fingerprint' ] ,
O = JSON .stringify ( M , null , 2 ) ,
this [ '_log' ]( '__collect envCollect=' [ 'concat' ]( O )) ,
L = eI[ 'AES' ] .encrypt ( O , eI .enc[ 'Utf8' ][ 'parse' ]( 'wm0!@w_s#ll1flo(' ) , {
iv : eI[ 'enc' ][ 'Utf8' ][ 'parse' ]( '0102030405060708' ) ,
mode : eI[ 'mode' ][ 'CBC' ] ,
padding : eI[ 'pad' ][ 'Pkcs7' ]
}) ,
j[ 'abrupt' ]( I .KWfsh , L .ciphertext[ 'toString' ]());
case 8 :
case 'end' :
return j[ 'stop' ]()
}
}
就可以很清晰的看到一个 AES
加密了,我们可以手动再 console
上调用一下,看看是不是我们需要的结果,发现断点不好打,就直接插桩输出看看吧,插桩代码如下:
复制 ( M = j .sent).fp = this [ N ( 0 , e , 0 , o) + "nt" ];
O = JSON .stringify ( M , null , 2 )
L = eI[ P ( - 19 , 0 , 0 , - u)] .encrypt ( O , eI .enc[ P (f , 0 , 0 , s)][ P (h , 0 , 0 , 184 )]([ "wm" , I [ N ( 0 , l , 0 , 326 )] , "w_" , I [ N ( 0 , v , 0 , p)] , I [ P (d , 0 , 0 , 46 )] , "o(" ][ P ( - y , 0 , 0 , - d)]( "" )) , {
iv : eI[ N ( 0 , 379 , 0 , g)][ P (f , 0 , 0 , - x)][ N ( 0 , 526 , 0 , _)]([ "01" , "02" , "03" , "04" , "05" , "06" , "07" , "08" ] .join ( "" )) ,
mode : eI[ P ( - m , 0 , 0 , b)][ N ( 0 , w , 0 , A )] ,
padding : eI[ N ( 0 , C , 0 , S )][ P ( - 10 , 0 , 0 , - B )]
})
console .log ( "key: " , [ "wm" , I [ N ( 0 , l , 0 , 326 )] , "w_" , I [ N ( 0 , v , 0 , p)] , I [ P (d , 0 , 0 , 46 )] , "o(" ][ P ( - y , 0 , 0 , - d)]( "" ))
console .log ( "iv: " , [ "01" , "02" , "03" , "04" , "05" , "06" , "07" , "08" ] .join ( "" ))
console .log ( "mode: " , eI[ P ( - m , 0 , 0 , b)][ N ( 0 , w , 0 , A )])
console .log ( "padding: " , eI[ N ( 0 , C , 0 , S )][ P ( - 10 , 0 , 0 , - B )])
console .log ( "msg: " , O )
console .log ( "result: " , L .ciphertext[ P ( D , 0 , 0 , k)]())
return j[ N ( 0 , 339 , 0 , z)]( I .KWfsh , L .ciphertext[ P ( D , 0 , 0 , k)]())
因此就可以得到我们需要的参数 e
了,从上述分析步骤我们可以大概看出来加密方式是 aes CBC Pkcs7
,我们使用现成的工具验证一下
没有问题,所以这个加密参数已经还原了!接下来还有两个参数:_fingerprint
和 token
,这两个参数我们通过分析请求可以看到其实是来源于请求里的:
但是这个请求却又有一段加密参数
因此我们分析完 expandParams
之后基本所有的参数就全部分析完毕了,我们直接全局搜索 expandParams
,找到了这么一个地方
所以继续查看代码分析一下这个参数的来源,发现其来源于:$ = t[K(0, 970, 0, B)]
,打上断点继续分析
解混淆发现其来自于 t['env']
,那么这个 t
又是怎么来的呢 ?根据堆栈往上层找
t
就是 this['_onRequestTokenRemotely']
,而 t['env']
来源于 h
,h
又来源于 s.ciphertext.toString()
而 s
来源于下面这一行代码
复制 s = eI[ e ( M , 366 )][ Xt ( 484 , 370 )](f , eI[ Xt ( 594 , O )].Utf8[ Xt ( L , R )]([ "wm" , n[ Xt ( P , N )] , "w-" , n[ Xt ( 504 , W )] , n[ e ( H , q)] , "o(" ] .join ( "" )) , {
iv : eI[ Xt ( F , U )][ e ( G , K )][ e ( V , Y )]([ "01" , "02" , "03" , "04" , "05" , "06" , "07" , "08" ][ e ( A , J )]( "" )) ,
mode : eI[ Xt ( X , Z )][ e ( 567 , $)] ,
padding : eI[ e ( 285 , tt)][ Xt (rt , nt)]
})
解一下混淆
复制 s = eI[ 'AES' ][ 'encrypt' ](f , eI[ 'enc' ].Utf8[ 'parse' ]( 'wm0!@w-s#ll1flo(' ) , {
iv : eI[ 'enc' ][ 'Utf8' ][ 'parse' ]( '0102030405060708' ) ,
mode : eI[ 'mode' ][ 'CBC' ] ,
padding : eI[ 'pad' ][ 'Pkcs7' ]
})
很简单就可以看出来又是一个 AES
加密,我们可以看看加密的 f
是什么
发现是一些浏览器的信息!使用通用的工具验证一下,发现也没有问题
到目前为止,我们需要的所有参数全部分析完毕,可以愉快的生成 h5st
了,读者可以总结归纳一下加密流程,这边就不做太多的介绍了,附上完整的 Python
还原代码
复制 # -*- coding: utf-8 -*-
# @Time : 2023-05-31 10:38
# @Author : Kem
# @Desc : 生成加密参数 h5st
# -*- encoding: utf-8 -*-
import datetime
import hashlib
import hmac
import json
import time
from hashlib import sha256
import requests
from lego . state import T
from lego . utils import fake
from lego . utils . crypto import AES
from loguru import logger
appid = 'f06cc'
version = '3.1'
key1 = "wm0!@w-s#ll1flo("
key2 = "wm0!@w_s#ll1flo("
iv = "0102030405060708"
def gen_key ( ts ):
"""
生成hmac256 盐值
"""
rd = '6vRABalFEPcI'
str_ = f ' { T [ "env" ] [ "tk" ] }{ T [ "env" ] [ "fingerprint" ] }{ ts }{ appid }{ rd } '
return hashlib . md5 (str_. encode ()). hexdigest ()
def gen_sign ( data : dict , key ):
"""
生成加密参数
:param data: hash 之后的body
:param key: 加密的 key
:return:
"""
message = ""
for k in sorted (data. keys ()):
message += f ' { k } : { data [ k ] } '
return hmac . new (key. encode (), message. encode ( 'utf-8' ), digestmod = sha256). hexdigest ()
def gen_body ( functionid , ts , body ):
"""
:param body: 请求参数内的 body
:param functionid: 函数 id
:param ts: 时间戳, 秒级
:return:
"""
return {
"appid" : "search-pc-java" ,
"functionId" : functionid ,
"client" : "pc" ,
"clientVersion" : "1.0.0" ,
"t" : ts ,
"body" : hashlib . sha256 (body. encode ()). hexdigest ()
}
def gen_h5st ( sign , ts , tstr ):
return ";" . join ([tstr, T[ "env" ][ "fingerprint" ], appid, T[ "env" ][ "tk" ], sign, version, ts, T[ "env" ][ "e" ]])
def init_env ():
while True :
# 0102030405060708
fake_fp = fake . gen_random_str ( 16 , "0123456789" )
local_env = {
"wc" : 0 ,
"wd" : 0 ,
"l" : "zh-CN" ,
"ls" : "zh-CN" ,
"ml" : 2 ,
"pl" : 5 ,
"av" : "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35" ,
"ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35" ,
"sua" : "Macintosh; Intel Mac OS X 10_15_7" ,
"pp" : {},
"pp1": "jsavif=0; jsavif=0; __jda=122270672.1685523235636810059383.1685523236.1685523236.1685523236.1; __jdc=122270672; __jdv=122270672|direct|-|none|-|1685523235637; shshshfpa=1fba7094-cf40-400d-972a-11138fdc6ca9-1685523235; shshshfpx=1fba7094-cf40-400d-972a-11138fdc6ca9-1685523235; shshshfpb=pAiDkfHR84jPVp-gmv-FY8g; areaId=18; ipLoc-djd=18-1482-0-0; rkv=1.0; qrsc=3; __jdb=122270672.11.1685523235636810059383|1.1685523236",
"pm" : {
"ps" : "prompt" ,
"np" : "default"
},
"w" : 1920 ,
"h" : 1080 ,
"ow" : 1920 ,
"oh" : 1055 ,
"url" : "https://search.jd.com/Search?keyword= %E 6 %89% 8B %E 6%9C%BA&wq= %E 6 %89% 8B %E 6%9C%BA&pvid=8858151673f941e9b1a4d2c7214b2b52&page=5&s=116&click=0" ,
"og" : "https://search.jd.com" ,
"pr" : 1 ,
"re" : "" ,
"random" : "bf35MB70a6" ,
"referer" : "" ,
"v" : "v3.2.1" ,
"ai" : appid ,
"fp" : fake_fp
}
expand_params = AES . CBC . encrypt (
msg = json. dumps (
local_env,
indent = 2 ,
ensure_ascii = False
),
key = key1,
iv = iv,
_output = "hex"
)
url = "https://cactus.jd.com/request_algo?g_ty=ajax"
payload = json . dumps ({
"version" : "3.1" ,
"fp" : fake_fp,
"appId" : appid,
"timestamp" : int (time. time () * 1000 ),
"platform" : "web" ,
"expandParams" : expand_params.ciphertext,
"fv" : "v3.2.1"
})
headers = {
'Accept-Language' : 'zh-CN,zh;q=0.9' ,
'Cache-Control' : 'no-cache' ,
'Connection' : 'keep-alive' ,
'DNT' : '1' ,
'Origin' : 'https://search.jd.com' ,
'Pragma' : 'no-cache' ,
'Referer' : 'https://search.jd.com/' ,
'Sec-Fetch-Dest' : 'empty' ,
'Sec-Fetch-Mode' : 'cors' ,
'Sec-Fetch-Site' : 'same-site' ,
'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35' ,
'accept' : 'application/json' ,
'content-type' : 'application/json' ,
'sec-ch-ua' : '"Microsoft Edge";v="113", "Chromium";v="113", "Not-A.Brand";v="24"' ,
'sec-ch-ua-mobile' : '?0' ,
'sec-ch-ua-platform' : '"macOS"' ,
'Cookie' : 'ipLoc-djd=18-1482-48937-0'
}
response = requests . request ( "POST" , url, headers = headers, data = payload)
data = response . json ()
if data . get ( 'status' ) == 200 :
logger . info ( f "初始化环境成功, 新的 fingerprint 为: { data[ 'data' ][ 'result' ][ 'fp' ] } " )
env = {
"sua" : "Macintosh; Intel Mac OS X 10_15_7" ,
"pp" : {},
"random" : fake . gen_random_str ( 10 ),
"referer" : "https://www.jd.com/" ,
"v" : "v3.2.1" ,
"fp" : data [ 'data' ] [ 'result' ][ 'fp' ]
}
e = AES . CBC . encrypt (
msg = json. dumps (
local_env,
indent = 2 ,
ensure_ascii = False
),
key = key2,
iv = iv,
_output = "hex"
)
# e = crypto.call("AES.encrypt", json.dumps(env, ensure_ascii=False, indent=2), key, iv)
T [ "env" ] = {
"fingerprint" : data [ 'data' ] [ "result" ][ 'fp' ] ,
"tk" : data [ 'data' ] [ "result" ][ 'tk' ] ,
"e" : e . ciphertext ,
}
return
else :
logger . warning ( f "初始化环境失败, 等待重试" )
time . sleep ( 60 )
def encrypt ( functionid , body , ts = None ):
"""
获取 h5st
:param functionid:
:param body:
:param ts:
:return:
"""
if not T [ "env" ]: init_env ()
ts = ts or time . time ()
if len ( str (ts)) == 13 : ts = int (ts) / 1000
# ts = 1685500202335 / 1000
tstr = datetime . datetime . fromtimestamp (ts). strftime ( '%Y%m %d %H%M%S %f ' ) [ : - 3 ]
# tstr = "20230531092616337"
body = gen_body (functionid, int (ts * 1000 ), body)
key = gen_key (tstr)
sign = gen_sign (body, key)
return {
"h5st" : gen_h5st (sign, str ( int (ts * 1000 )), tstr),
"ts" : str ( int (ts * 1000 ))
}
2 Cookie
分析完 h5st
的算法,下面我们继续分析一下京东的 Cookie
生成规则,因为京东接口请求成功率还是和 Cookie
有一点点关系的,分析的 Cookie
都选取的算法生成的,至于请求生成的,那就自己抓包分析吧,本文分析的 Cookie
如下:
分析 Cookie
生成规则可以借助一个油猴脚本,可以很快速的定位位置,地址如下:JS Cookie Monitor/Debugger Hook (greasyfork.org)
装完这个脚本之后,你可以明确的在控制台看到所有 Cookie
操作了,真的很 nice
!
下面我们拿 ___jda
举例,看看是如何分析的, 首先我们先把 Cookie
全部清空,然后刷新浏览器,就可以看到新增了的(绿色的就是新增的)
这边还能指向是在哪里执行的,我们点进去后打上断点,清除 Cookie
了重来一次
找前面的堆栈
看到了设置的地方,可以看到 __jda
来源于下面这一行代码
复制 [(e = ce) .get (f) , e .get (h) || "-" , e .get ( A ) || "-" , e .get ( L ) || "-" , e .get ( D ) || "-" , e .get ( W ) || 1 ] .join ( "." )
好像是从一个对象里面取的值,我们看看是哪个对象
发现其实就是 e
这个对象,因此我们需要知道 e
是怎么设置的值
复制 [(e = ce) .get ( '2' ) , e .get ( '1' ) || "-" , e .get ( '22' ) || "-" , e .get ( '23' ) || "-" , e .get ( '24' ) || "-" , e .get ( '25' ) || 1 ] .join ( "." )
我们可以在他的 set
方法插桩,看一下设置流程
清除 Cookie
, 刷新页面,查看日志
运行打上断点,开始查看
0
: this.set(g, "UA-J2011-1");
, 所以 0
一直都是 "UA-J2011-1"
3
:this.set(p, t)
,t
来源于 window.document.domain.indexOf("360buy") >= 0 ? "360buy.com" : "jd.com";
2
:this.set(f, Q())
,来源与 Q()
复制 re = function (e) {
var t , r = 1 , n = 0 ;
if ( ! te (e))
for (r = 0 ,
t = e[a] - 1 ; t >= 0 ; t -- )
n = e .charCodeAt (t) ,
r = (r << 6 & 268435455 ) + n + (n << 14 ) ,
n = 266338304 & r ,
r = 0 != n ? r ^ n >> 21 : r;
return r
}
function () {
return re ( r .domain)
}
4
:this.set(m, Math.round((new Date).getTime() / 1e3))
, 时间戳
5
:this.set(v, 15552e6)
, 常量,15552000000
6
:this.set(w, 1296e6)
,常量,1296000000
7
:this.set(_, 18e5)
,常量,1800000
13
:this.set(C, ee());
,代码一堆,但是实际就是返回 '-'
14
:this.set(x, r.name)
,浏览器名称
15
:this.set(N, r.version)
,浏览器版本
16
:this.set(O, ie());
,系统名称
17
:this.set(R, n.D)
,分辨率
19
:this.set(T, n.language)
, 语言
20
:this.set(I, n.javaEnabled)
, 是否运行 JavaScript
21
:his.set(J, n.characterSet)
,characterSet
27
:this.set(H, X)
,X
是浏览器中定义的一个数组
32
:this.set(B, (new Date).getTime())
, 时间戳
35
:this.set(Y, i[a] ? i[0] : "-")
, 其实就是 -
36
:this.set(F, s || "-")
,其实就是 -
8
:e.set(b, r.location.hostname)
,当前网站的 hostname
9
: e.set(j, r.title.replace(/\$/g, ""))
,当前网址标题
10
:e.set(y, r.location.pathname)
,网址 path
11
: e.set(k, r.referrer.replace(/\$/g, ""))
,referrer
地址
12
:e.set(S, r.location.href)
,当前网址
分析完这个对象怎么生成的就很容易分析出我们需要的 Cookie
是怎么生成的了!这边不分析了,附上 Python
版本算法
复制 # -*- coding: utf-8 -*-
# @Time : 2023-06-05 17:54
# @Author : Kem
# @Desc : 生成京东一些根据 js 生成的 Cookie
import json
import random
import time
from urllib . parse import urlparse
import requests
from lego . utils import fake , crypto
def ct ( t ):
return t is None or t == "-" or t == ""
def ut ( t ):
n = 1
if not ct (t):
for e in range ( len (t) - 1 , - 1 , - 1 ):
char_code = ord (t[e])
n = (n << 6 & 268435455 ) + char_code + (char_code << 14 )
r = 266338304 & n
n = n ^ r >> 21 if r != 0 else n
return n
def parse_domain ( url : str ):
# 解析URL,获取域名和子域名
parsed_url = urlparse (url)
domain_parts = parsed_url . hostname . split ( '.' )
if len (domain_parts) >= 2 :
return '.' . join (domain_parts[ - 2 :])
else :
return parsed_url . hostname
def gen_cookie ( url : str ):
domain = parse_domain (url)
timestamp = int (time. time () * 1000 )
t1 , t2 , t3 = ut (domain), f ' { timestamp }{ fake . gen_random_str ( 10 , base_str = "0123456789" ) } ' , str (timestamp // 1000 )
jda = "." . join ([ str (t1), t2, t3, t3, t3, "1" ])
jdb = "." . join ([ str (t1), "1" , f ' { t2 } |1' , t3])
jdc = "." . join ([ str (t1)])
jdv = "|" . join ([ str (t1), "direct" , "-" , "none" , "-" , str (timestamp)])
shshshfpx = generate_uuid ()
shshshfpa = generate_uuid ()
mba_muid = f ' { timestamp }{ fake . gen_random_str ( 10 , base_str = "0123456789" ) } '
mba_sid = f ' { timestamp }{ fake . gen_random_str ( 16 , base_str = "0123456789" ) } .0'
# ; shshshfpb={get_shshshfpb()}
return "; " . join ([
f '__jda= { jda } ' ,
f '__jdb= { jdb } ' ,
f '__jdc= { jdc } ' ,
f '__jdv= { jdv } ' ,
f 'shshshfpx= { shshshfpx } ' ,
f 'shshshfpa= { shshshfpa } ' ,
f 'mba_muid= { mba_muid } ' ,
f 'mba_sid= { mba_sid } ' ,
])
def generate_uuid ():
uuid = ''
for i in range ( 1 , 33 ):
random_num = random . randint ( 0 , 15 )
uuid += hex (random_num) [ 2 : ]
if i in [ 8 , 12 , 16 , 20 ] :
uuid += '-'
now = time . time ()
uuid += '-' + str ( int (now * 1000 / 1000 ))
return uuid
def get_shshshfpb ( shshshfpa = None ):
"""
这个获取 Cookie 的暂时用不上
:param shshshfpa:
:return:
"""
shshshfpa = shshshfpa or generate_uuid ()
body = {
"appname" : "jdwebm_hf" ,
"jdkey" : "" ,
"isJdApp" : False ,
"jmafinger" : "" ,
"whwswswws" : "" ,
"businness" : "jshopx" ,
"body" : {
"browser_info" : crypto . Hash . md5 (shshshfpa),
"client_time" : int (time. time () * 1000 ),
"period" : 24 ,
"shshshfpa" : shshshfpa ,
"ecflag" : "n" ,
"whwswswws" : "" ,
"jdkey" : "" ,
"isJdApp" : False ,
"jmafinger" : "" ,
"cookie_pin" : "" ,
"jdu" : "" ,
"mba_muid" : "" ,
"visitkey" : "" ,
"msdk_version" : "2.5.0" ,
"wid" : "" ,
"lan" : "zh-CN" ,
"scrh" : 1080 ,
"scrah" : 1055 ,
"scrw" : 1920 ,
"scaw" : 1920 ,
"oscpu" : "" ,
"platf" : "MacIntel" ,
"pros" : "20030107" ,
"temp" : 33 ,
"hll" : False ,
"hlr" : False ,
"hlo" : False ,
"hlb" : False ,
"color_depth" : 24 ,
"pixel_ratio" : 1 ,
"resolution" : "1920;1080" ,
"available_resolution" : "1920;1055" ,
"session_storage" : 1 ,
"local_storage" : 1 ,
"indexed_db" : 1 ,
"open_database" : 1 ,
"cpu_class" : "unknown" ,
"navigator_platform" : "MacIntel" ,
"regular_plugins": "PDF Viewer::Portable Document Format::application/pdf~pdf,text/pdf~pdf;Chrome PDF Viewer::Portable Document Format::application/pdf~pdf,text/pdf~pdf;Chromium PDF Viewer::Portable Document Format::application/pdf~pdf,text/pdf~pdf;Microsoft Edge PDF Viewer::Portable Document Format::application/pdf~pdf,text/pdf~pdf;WebKit built-in PDF::Portable Document Format::application/pdf~pdf,text/pdf~pdf",
"adblock" : False ,
"touch_support" : 0 ,
"app_code_name" : "Mozilla" ,
"app_name" : "Netscape" ,
"app_version" : "5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.35" ,
"cookie_enabled" : True ,
"regular_mimetypes" : "::Portable Document Format::;::Portable Document Format::" ,
"online" : "unknown" ,
"hardwareConcurrency" : 8 ,
"product" : "Gecko" ,
"vendor" : "Google Inc." ,
"vendorSub" : "unknown" ,
"devicePixelRatio" : 1 ,
"updateInterval" : "unknown" ,
"orientationType" : "landscape-primary" ,
"doNotTrack" : "1" ,
"canvas" : "canvas winding:yes~canvas fp:518a43b5a0f974add394ce72abb7b18b" ,
"webgl": "fp:02ffb1efaaefd7e35acf063246d0e907~extensions:ANGLE_instanced_arrays;EXT_blend_minmax;EXT_color_buffer_half_float;EXT_disjoint_timer_query;EXT_float_blend;EXT_frag_depth;EXT_shader_texture_lod;EXT_texture_compression_rgtc;EXT_texture_filter_anisotropic;EXT_sRGB;KHR_parallel_shader_compile;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_color_buffer_float;WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBGL_multi_draw~aliased line width range:[1, 1]~aliased point size range:[1, 64]~alpha bits:8~antialiasing:yes~blue bits:8~depth bits:24~green bits:8~max anisotropy:16~max combined texture image units:32~max cube map texture size:16384~max fragment uniform vectors:1024~max render buffer size:16384~max texture image units:16~max texture size:16384~max varying vectors:31~max vertex attribs:16~max vertex texture image units:16~max vertex uniform vectors:1024~max viewport dims:[16384, 16384]~red bits:8~renderer:WebKit WebGL~shading language version:WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)~stencil bits:0~vendor:WebKit~version:WebGL 1.0 (OpenGL ES 2.0 Chromium)~unmasked vendor:Google Inc. (Apple)~unmasked renderer:ANGLE (Apple, Apple M1, OpenGL 4.1)~vertex high float:23(127,127)~vertex medium float:23(127,127)~vertex low float:23(127,127)~fragment high float:23(127,127)~fragment medium float:23(127,127)~fragment low float:23(127,127)~vertex high int:0(31,30)~vertex medium int:0(31,30)~vertex low int:0(31,30)~fragment high int:0(31,30)~fragment medium int:0(31,30)~fragment low int:0(31,30)",
"device_memory" : 8 ,
"is_headless_browser" : 0
}
}
url = "https://blackhole-m.m.jd.com/getinfo"
with requests . post (url = url, data = { "body" : json. dumps (body, ensure_ascii = False )}) as res :
return "; " . join ([ f ' { k } = { v } ' for k, v in res.cookies. items ()])
def get_shshshs_id ( shshshfpa ):
return f ' { crypto . Hash . md5 (shshshfpa) } _1_ {int (time. time () * 1000 ) } '
if __name__ == '__main__' :
print ( gen_cookie ( "https://mall.jd.com/view_search-472889-0-0-1-24-3.html" ))
# print(get_shshshs_id(generate_uuid()))