本帖最后由 szh12123 于 2026-6-25 21:41 编辑
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!若有侵权,请联系作者删除。
目标网址:aHR0cHM6Ly9maW5hbmNlLmJhaWR1LmNvbS8=
获取最新股价信息需要两个关键参数一个是headers里面的acs-token,一个是cookies里面的ab_sr,acs-token以前的帖子讲过了,本次介绍ab_sr参数详细解析。
ab_sr需要通过这个网址请求https://xxxx.com/abdr?_o=https%3A%2F%2Fxxxxx.com,并且传入三个参数,主要就在于分析data是如何加密的,
当然如果只是为了获取信息的话,直接用密文请求ab_sr就行了,感觉没有时间戳验证,同一个密文可以一直用。
常规搜索data、abdr等关键词都搜不到,最后通过encodeURIComponent搜到,虽然搜索结果很多,但仔细一看不难发现abclite_2108的嫌疑最大,因为就他是混淆过后的,打几个断点,发现了密文位置,
但这里的混淆太多不好分析,于是简单写几个替换,先把b函数代码抠出来,保存到b.js,在python中调用b(),将原js文件中所有使用b()的正则匹配替换为运行结果,
[Python] 纯文本查看 复制代码 def func2str(text):
rs = re.findall(r"(b\('0x[0-9a-fA-F]+'\))", text)
rs = set(rs)
with open('./b.js', 'r', encoding='utf-8') as f:
js_code = f.read()
ctx = execjs.compile(js_code)
for i in rs:
result = ctx.call('b', i[3:-2])
if "'" in result:
result = '"' + result + '"'
else:
result = "'" + result + "'"
text = text.replace(i, result)
return text
再将\x75\x72等转为字符串,
[Python] 纯文本查看 复制代码 def ascii2str(text):
rs2 = re.findall(r"('\\x[0-9a-fA-F]+(?:\\x[0-9a-fA-F]+)*')", text)
rs2 = set(rs2)
rs2 = list(rs2)
rs2.sort(key=len, reverse=True)
for i in rs2:
if i == r"'\x0a'":
continue
m = i.replace('\\x', '%')
m = unquote(m)
if "'" in m[1:-1]:
m = '"' + m[1:-1] + '"'
text = text.replace(i, m)
return text
替换指定的字典常量,
[Python] 纯文本查看 复制代码
def dict2str(ph,text):
for k, v in ph.items():
text = text.replace(f"ph['{k}']", str(v))
return text
这样就有了一个可读性良好的文件,
这样直接就搜索'data',很容易就找到了,
分析得知bM是AES-CBC加密,4个参数是明文,key,iv,bool控制输出hex字符还是base64字符。明文里面有好几十个参数,
每个参数值的设置位置直接搜索key值就可以直接找到,
分析不难但是有点繁琐,然后他是异步获取,懂一点递归的话理解起来很容易,就不一一分析了,直接放结果,下面是所有参数的含义:
[JavaScript] 纯文本查看 复制代码 var plaintext = {
//运行document['createElement']('canvas')['getContext']('2d'),成功返回1,失败返回0
"1": 1,
//创建canvas添加各种参数,最后转str再用sha1加密,是固定值
"3": "ea990cbXXXXX7dba9c",
//screen['colorDepth']
"4": 24,
//屏幕总尺寸 screen['width'] + 'x' + screen['height']
"5": "2560x1440",
//屏幕可用尺寸 screen['availWidth'] + 'x' + screen['availHeight'])
"6": "2560x1400",
//屏幕X分辨率 [screen['deviceXDPI'] || '', screen['j'] || '']['join']()
"7": ",",
//浏览器所有插件navigator['plugins']的name值
"8": "AdobXXXXXPlugin",
//媒体类型标识符 navigator['mimeTypes']的description值
"9": "npaXXXXXefox,,,,,,,,",
//判断浏览器永久本地存储window['localStorage']是否存在
"11": 1,
//判断浏览器当前页本地存储window['sessionStorage']是否存在
"12": 1,
//获取值navigator['cookieEnabled']
"13": true,
//获取本地时间与 UTC 时间相差多少分钟 new Date()['getTimezoneOffset']()
"14": -480,
//获取值navigator['language']
"15": "zh-CN",
//获取值navigator['Va']
"16": "",
//判断媒体 MIME 类型是否支持,[存在document['createElement']('video')['canPlayType'],支持mp4,支持webm-vp8,支持webm-vp9,支持x-mpegURL]['join']()
"17": "1,0,1,1,1,0",
//获取设备物理像素与 CSS 像素的比率值window['devicePixelRatio']
"18": 1,
//获取CPU线程数navigator['hardwareConcurrency']
"19": 12,
//判断document['getElementsByClassName']('adsbox')[0]['offsetHeight']的值是否为0
"20": 0,
//不知道什么值,返回空
"21": "",
//获取值 [navigator['product'], navigator['productSub'], navigator['vendor'], navigator['vendorSub'], navigator['appCodeName'], navigator['appName'], navigator['platform']]['join']()
"22": "GecXXXXXn32",
//获取值 [支持几个手指同时触屏, document['createEvent']('TouchEvent')是否存在, 手指是否触碰屏幕]['join']()
"23": "0,0,0",
//判断浏览器内置的本地数据库window['indexedDB']是否存在
"24": 1,
//获取 GPU 的真实供应商和渲染器型号字符串 [获取供应商,获取渲染器]['join']()
"25": "GooglXXXXX3D11)",
//获取值navigator['userAgent']
"27": "MozillXXXXXafari/537.36",
//获取值 [判断document['getBoxObjectFor']是否存在, 判断window['opera']是否存在]['join']()
"28": "false,false",
//获取值 ['undefined' == typeof window['eval']['prototype'], 'eval' === window['eval']['name'], -1 != window['eval']['toString']()['indexOf']('native')]['join']()
"29": "true,true,true",
//判断document['body']['addBehavior']是否存在
"30": 0,
//获取内存大小值navigator['deviceMemory']
"31": 8,
//当前时间减去设置103时的时间
"32": 122,
//获取值navigator['platform']
"34": "Win32",
//获取值 [!!(navigator['D'] || navigator['I'] || navigator['va']),获取电量navigator['getBattery']是否存在]['join']()
"35": "false,true",
//获取值电池是否充电 navigator['getBattery']()['charging'],超时返回-1
"41": true,
//获取值电池完全充满还需的时间 navigator['getBattery']()['chargingTime'],超时返回-1
"42": 0,
//获取值电池耗尽当前电量的时间 navigator['getBattery']()['dischargingTime'],超时返回-1
"43": Infinity,
//获取值电池电量 navigator['getBattery']()['level'],超时返回-1
"44": 1,
//返回空
"58": "",
//返回空
"60": "",
//返回空
"61": "",
//返回空
"62": "",
//判断 'webdriver' in navigator
"63": true,
//判断navigator['ta']是否存在
"64": false,
//判断window['H']是否存在
"65": false,
//获取值 存在Object['getOwnPropertyDescriptor'](navigator,'webdriver'),则返回字符串长度,否则返回0
"69": 0,
//返回0
"70": 0,
//返回空
"71": "",
//获取值navigator['language']
"72": "zh-CN,zh",
//返回空
"73": "",
//返回空
"74": "",
//判断window中是否存在其中之一['cc11001100_hook', 'vpn_set_obj', 'vpn_get_obj', 'vpn_fn_call']
"76": 0,
//获取值 "[3, 8, 19, 25, 34] + [5, 6, 9, 14, 15, 27]中随机取两个作为key,取该字典对应的value值组成的列表,随机打乱顺序再sha1加密" + "_" + "前面value值对应的key值组成的列表合并再AES-CBC加密结果取hex"
"78": "30e6XXXXX7ba7c_1a65XXXXX46dc",
//判断这些属性是否是函数,是则返回获取的字符串长度,不是返回0,func = Object['getOwnPropertyDescriptor']; [func(navigator['plugins']['name'])所有插件最后相加,func(screen['availWidth'])+func(screen['availHeight']),func(screen['width'])+func(screen['height']),func(navigator['hardwareConcurrency']),func(navigator['platform'])]['join']()
"79": "0,0,0,0,0",
//获取值 [0,0,0,判断navigator['maxTouchPoints']或navigator['msMaxTouchPoints']是否为函数,0]['join']()
"80": "0,0,0,0,0",
//判断cookie是否包含指定的字符串,不包含返回1 window['RegExp']('function\s?\(\)\{return\sdocument')['test'](document['cookie'])
"81": 1,
//通过_00la的所有代码判断_00la是否被篡改的sha1加密,是固定值
"82": "54c7XXXXXe3b93",
//该字典的key为[78, 101, 103, 82]的对应的值用'_'合并再sha1加密
"85": "b97XXXXXd160c6",
//'anti-bot-df' + Math['random']() + document['URL'] + document['referrer'] + navigator['userAgent'] + 该字典[103],再整体sha1加密
"101": "62b1cXXXXX2892ac",
//获取当前时间 new Date().getTime()
"103": 1780000000000,
//固定值
"106": 2108,
//固定值
"107": "3.11.4",
//获取值 document['URL']
"108": "https://xxxxx.com/",
//获取值 document['referrer']
"109": "",
//获取值 window['__abbaidu_2108_zidgetf']
"112": "",
//获取值 window['__abbaidu_2108_bidgetf']
"113": "",
//获取值 window['__abbaidu_2108_subidgetf']或window['__abbaidu_20180315_subidgetf']
"114": "",
//获取值 window['__abbaidu_2108_extra_datagetf']
"115": "",
//'lid' + Math['random']() + document['URL'] + document['referrer'] + navigator['userAgent'] + new Date()['getTime'](),再sha1加密
"116": "2904a8xxxxx155a9e",
//固定值
"130": "[]",
//判断document['getElementsByTagName']('iframe')是否存在属性值,应返回"[]"
"136": "[]",
//获取值 window['eval']['toString']()['length']
"198": 33,
//获取值 navigator['H'] || ''
"199": "",
//固定值
"200": 1,
//固定值,一个包含很多数学运算的列表用sha1加密,再取前8位
"300": "d47288f3",
//固定值,navigator的所有属性名称排序再合并,用sha1加密,再取前16位
"301": "7ad0xxxxx4e20",
//固定值,window的所有属性名称
"302": "$BAxxxxxngth",
//固定值
"402": ""
};
他获取了很多信息,但最后发现有用的并不多,初始固定值只需要这些,很多信息还可以为空,
然后加上103、78、101、85、116的获取,就可以获取到股价信息,但这里如果只保留106和200两个参数也可以获取到ab_sr的值,但是在后续获取股价信息时,会请求失败。
抠代码分析参数加密过程,还有补环境,总共几大百行,简化成python后加上请求信息也就100来行。整体来说不算难,参数较多比较繁琐,当然也可能是这个网站没有太多的验证机制,很多参数并没有用上。
最后放一张请求成功图。 |