某东h5st全流程
免责声明
本文仅供学习交流使用,严禁用于商业用途及非法用途!
- 本文所涉及的技术、代码、思路均来源于公开的网络资源及个人学习研究,仅用于技术交流与知识分享。
- 本文中的爬虫示例代码、逆向分析方法,仅作为学习爬虫技术与反爬虫技术的参考,读者应在遵守相关法律法规及网站服务条款的前提下使用。
- 严禁使用本文中的技术手段进行任何形式的非法数据获取、商业竞争、侵犯隐私等行为。 任何因不当使用本文内容而产生的法律纠纷及后果,均由使用者自行承担,与本文作者无关。
- 如果相关网站或平台认为本文内容侵犯了您的权益,请联系作者,作者将在第一时间删除相关内容。
- 学技术是为了保护自己,而不是去伤害别人。 请尊重网站的数据所有权,合理合法地使用网络资源。
- 如有问题,请第一时间练习本人进行删除。
h5st采用控制流混淆,以及大量指纹与环境检测,颇为麻烦,这篇文章我们用补环境的方式来拿到h5st。
本篇针对最新版h5st,也就是5.3版本😭
由于我的账号被风控了,为了演示清楚,我在网上找了一些类似的浏览器截图✊
流程

通过抓包我们可以发现,表单里有个h5st参数,这个参数就是我们的目标。
我们直接利用搜索,在所有和h5st相关的地方打上断点。
最后会断在这里

现在就清晰明了了,我们要拿到h5st,就要搞到d这个参数。d就在上面,没截进来。
我们在node里模拟出d,这里我取名为u,参数照着浏览器拿就行。
u = { appid: "search-pc-java", functionId: 'pc_search_searchWare', client: "pc", clientVersion: "1.0.0", t: new Date().getTime(), body: crypto.SHA256(JSON.stringify(params)).toString()};u的body又是一个sha256加密的params,我们接着去拿这个params
params = { "enc": "utf-8", "area": "52993_52994_146660_0", "page": 1, "mode": "", "concise": false, "hoverPictures": false, "newAdvRepeat": false, "mixerParam": false, "new_interval": true, "s": 1}这里只有page和s是动态变化的,这个s生成逻辑如下,pf是个动态数组,需要请求才能拿到。
nction get_s(e) { Pf = new Map([
]) try { let t = 0 , n = 0; return Pf.forEach(r => { r.page < e && (t += +r.adNum, n += +r.repeatNum) } ), 1 + (e - 1) * 30 - t + n } catch (t) { return 1 }}但是我们通过重放,可以发现,对s校验不是很严格!(ps:这个有可能是个风控点)所以我们先不管他。
这样,我们就可以在node构造获取h5st的函数了。
function get_h5st() { params = { "enc": "utf-8", "area": "52993_52994_146660_0", "page": 1, "mode": "", "concise": false, "hoverPictures": false, "newAdvRepeat": false, "mixerParam": false, "new_interval": true, "s": 1} u = { appid: "search-pc-java", functionId: 'pc_search_searchWare', client: "pc", clientVersion: "1.0.0", t: new Date().getTime(), body: crypto.SHA256(JSON.stringify(params)).toString() }; window.PSign = new window.ParamsSign({ appId: 'f06cc', preRequest: false, onSign: (res) => { // 签名可用率监控,业务方自行上报 if (res.code != 0) { try { window.dra && window.dra.sendCustomEvent && window.dra.sendCustomEvent({ name: 'main_search', metrics: { error_code: '751', error_type_txt: '接口加密失败onSign非0', }, context: { error_code: res.code, }, }) } catch (error) { console.log(error) } } }, onRequestTokenRemotely: (res) => { // 算法接口可用率监控,业务方自行上报 if (res.code != 200) { try { window.dra && window.dra.sendCustomEvent && window.dra.sendCustomEvent({ name: 'main_search', metrics: { error_code: '751', error_type_txt: '接口加密失败onRequestTokenRemotely', }, context: { error_msg: res && res.message ? res.message : '接口加密失败', }, }) } catch (error) { console.log(error) } } }, }) h5st = window.PSign._$sdnmd(u) return h5st}window.PSign通过搜索也能拿到,他是在文件开头被初始化,我们直接扣下来放在上面。
通过跟栈,我们可以发现,h5st生成就是在js_security这个文件里,我们把这个文件全部拿到node里并命名为js_code。
const crypto = require('crypto-js')//生成逻辑require('./js_code')
function get_h5st() { params = { "enc": "utf-8", "area": "52993_52994_146660_0", "page": 1, "mode": "", "concise": false, "hoverPictures": false, "newAdvRepeat": false, "mixerParam": false, "new_interval": true, "s": 1} u = { appid: "search-pc-java", functionId: 'pc_search_searchWare', client: "pc", clientVersion: "1.0.0", t: new Date().getTime(), body: crypto.SHA256(JSON.stringify(params)).toString() }; window.PSign = new window.ParamsSign({ appId: 'f06cc', preRequest: false, onSign: (res) => { // 签名可用率监控,业务方自行上报 if (res.code != 0) { try { window.dra && window.dra.sendCustomEvent && window.dra.sendCustomEvent({ name: 'main_search', metrics: { error_code: '751', error_type_txt: '接口加密失败onSign非0', }, context: { error_code: res.code, }, }) } catch (error) { console.log(error) } } }, onRequestTokenRemotely: (res) => { // 算法接口可用率监控,业务方自行上报 if (res.code != 200) { try { window.dra && window.dra.sendCustomEvent && window.dra.sendCustomEvent({ name: 'main_search', metrics: { error_code: '751', error_type_txt: '接口加密失败onRequestTokenRemotely', }, context: { error_msg: res && res.message ? res.message : '接口加密失败', }, }) } catch (error) { console.log(error) } } }, }) h5st = window.PSign._$sdnmd(u) return h5st
}
console.log(get_h5st())现在,我们直接挂上代理,补环境即可!这里耐心补就行,不过它检测了原型链!需要补原型链!
基本大量是document的环境。
接着是canvas指纹和webgl指纹。
这两个取巧可以直接去拿。
function _$RD(_$RO) {_$RI(_$RO, 'pf', function(_$RY) { return window.navigator.platform; }, _$RW), _$RI(_$RO, 'pr', function(_$RY) { return window.devicePixelRatio; }, _$RW), _$RI(_$RO, 're', function(_$RY) { return document.referrer; }, _$RW), _$I.AWTVk(_$RI, _$RO, Zn(0x26b), function(_$RY) { return _$q8(0x2184 * -0x1 + 0x6dd + -0x86 * -0x33); }, _$RW), _$RI(_$RO, Zn(0x27e), function(_$RY) { var Ze = Zn , _$RQ = new RegExp(Ze(0x1f5)) , _$Rb = document.referrer.match(_$RQ); return _$Rb && _$Rb[0xae0 + -0x21b8 + 0x2db * 0x8] ? _$Rb[-0x305 * 0x4 + 0x62 * -0x53 + 0x74f * 0x6] : ''; }, _$RW), _$RI(_$RO, 'v', function(_$RY) { return _$ql; }, _$RW), _$RI(_$RO, Zn(0x290), function(_$RY) { var Za = Zn , _$RQ = new Error(Za(0x2d8)).stack.toString() , _$Rb = _$RQ.split('\x0a') , _$Rw = _$Rb.length; return _$RA.ZaihA(_$Rw, -0x1c4f + -0x228f + -0x22b * -0x1d) ? _$Rb[_$Rw - (-0x1 * -0x265 + 0x120c + -0x1470)] : _$RQ; }, _$RW), _$RI(_$RO, Zn(0x302), function(_$RY) { return Window.toString() + '$' + Window.toString.toString.toString(); }, _$RW), _$I.GxyBI(_$RI, _$RO, Zn(0x1d2), function(_$RY) { return '0'; }, _$RW), _$I.GxyBI(_$RI, _$RO, _$I.ELueU, function(_$RY) { var _$RQ = _$qT.get(_$qk.CANVAS_FP) , _$Rb = _$RA.CGaEl(_$q7, _$RQ) ? _$RQ.v : ''; return _$Rb || (navigator.userAgent && !/Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) && (_$Rb = _$RA.ilAqh(_$qA)), _$Rb && _$qT.set(_$qk.CANVAS_FP, { 'v': _$Rb, 't': Date.now(), 'e': 0x1e13380 })), _$Rb; }, _$RW), _$RI(_$RO, _$I.xhXJd, function(_$RY) { var _$RQ = _$qA(); return _$RQ && _$qT.set(_$qk.CANVAS_FP, { 'v': _$RQ, 't': Date.now(), 'e': 0x1e13380 }), _$RQ; }, _$RW), _$I.AWTVk(_$RI, _$RO, Zn(0x227), function(_$RY) { var _$RQ = _$qT.get(_$qk.WEBGL_FP); return _$q7(_$RQ) && _$RQ.v ? _$RQ.v : ''; }, _$RW), _$RI(_$RO, Zn(0x2ef), function(_$RY) { return navigator.hardwareConcurrency; }, _$RW), _$RW; }}我们可以看到,它检测了大量navigator,直接搜到这里,我们能发现_$RW对象包含了大量指纹信息。
直接把他拿到我们的node环境里赋值,注意里面有两个random随机数,这个我们可以通过追栈或者直接hook,最后能找到这个随机数的生成函数_q8,就能拿到random属性`"random": _q8(10)`。
python模拟请求
由于检测了tls指纹,我们需要利用curl_cffi来模拟指纹,另外execjs这个狗屎编码问题也需要解决。方案如下
import timefrom curl_cffi import requestsimport subprocessfrom functools import partial# 解决 Windows 编码问题subprocess.Popen = partial(subprocess.Popen, encoding='utf-8')import execjsprint(execjs.get().name) # 确保能正常运行with open("狗东h5st.js", "r", encoding="utf-8") as f: js_code = f.read()ctx = execjs.compile(js_code)headers = { "accept": "application/json, text/plain, */*", "accept-language": "zh-CN,zh;q=0.9", "cache-control": "no-cache", "origin": "https://search.jd.com", "pragma": "no-cache", "priority": "u=1, i", "referer": "https://search.jd.com/Search?keyword=jk&enc=utf-8&pvid=cd0e7cded0fd46e19f75c9ea87e29986&themeColor=&from=home&spmTag=YTAyMTkuYjAwMjM1Ni5jMDAwMDcxNjEuMSU0MDE3Nzg4OTI5MTA2MTQlMjMxNzc4NzY0NzE5NTE1MTY5OTk0OTU0MSUyMzE5Mjg0MTIyNzc", "sec-ch-ua": "\"Chromium\";v=\"148\", \"Google Chrome\";v=\"148\", \"Not/A)Brand\";v=\"99\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36", "x-referer-page": "https://search.jd.com/Search", "x-rp-client": "h5_2.1.0"}cookies = { "__jdv": "229668127|cn.bing.com|t_2037222536_0_0|adrealizable|f49c1979fb28d62d-p_0|1778760960019", "jcap_dvzw_fp": "67G6EpotjV1J0eDMn-JsRuIHuU_J5iLXjyGeSV8Tmajx66RJzEiZ4WlUT1sDg04R6Es07eAs84QRfEuCdzxPlW3p1uA=", "PCSYCityID": "CN_320000_320500_0", "user-key": "1063871e-fef9-46fb-90f3-2f11fcc86806", "shshshfpa": "7016242f-a138-659d-7491-5709d4550ab4-1778761184", "shshshfpx": "7016242f-a138-659d-7491-5709d4550ab4-1778761184", "__jdu": "17787647195151699949541", "areaId": "52993", "ipLoc-djd": "52993-52994-146660-0", "TrackID": "1RZcfqjkwy76IJKCAe59oT1JBBKuBGlMrbE4NgcObczVnYG-e0UwFdLh7NeVnDUTohS1B4BCz2FDIzNSd36rexqAPv1GW9ae6HvVCJM56_S4", "light_key": "AASBKE7rOxgWQziEhC_QY6ya2qL1uZOKJ26TLn-7vCoZWxnGUe6STkaVDa2a003PEPXUYde7", "pinId": "OI66V8DdRcDvRP4X3ePfTg", "pin": "jd_KlBTjtKcZjSM", "unick": "2l652aq5b18189", "_tp": "nRRl2Me9wmEDL1EY%2FCqfTg%3D%3D", "_pst": "jd_KlBTjtKcZjSM", "autoOpenApp_downCloseDate_autoOpenApp_handler": "1778839798571_1", "thor": "43756E9B198DF241723079EF4BD85A79B835F3CDCF648F0BEB2B713C4B12EB320A368AA2F11FBD14F679272B9C59349767EBFA088D41BD415A033394A27983D0", "3AB9D23F7A4B3CSS": "jdd034BPKMDD2RHGRUQV5KRSOZKORJMNUXX4XMCMHWQPUJ6S6H4YZVJ2TU4GFI7OULL45RNG7SPUP5HVZB7KFZA45ZHCP7EAAAAM6FZDYL2YAAAAADB6MGM3YYN2XO4X", "mail_times": "4%2C1%2C1778892901084", "umc_count": "1", "__jda": "143920055.17787647195151699949541.1778764720.1778852393.1778892900.6", "__jdc": "143920055", "cid": "9", "3AB9D23F7A4B3C9B": "4BPKMDD2RHGRUQV5KRSOZKORJMNUXX4XMCMHWQPUJ6S6H4YZVJ2TU4GFI7OULL45RNG7SPUP5HVZB7KFZA45ZHCP7E", "flash": "3_3IVwA_AoKgPBjcEwGfJ2h_Q1FrVfx3ICqMi7CSFiFkp04BNsQQrKw538-hT1tH_HQnXHEg5uZN9r520I51HFpxb472wCxV29wpLR_ZtH9qMd2KmfhH4s0NzTv_6Yv2cc9tG7Zp5U1miT5yRbxY6snslW2-25fMjohNusHGdETDoW4TzxBZxp", "sdtoken": "AAbEsBpEIOVjqTAKCQtvQu17ufJOKOnkyrGFeRRK4gqM-z1ZogKzU6BynGrK2MBnxOr6xKr65bBRTa9bDZ-dUEbF9g3zPVLJUutUZEdm3lGX1ZDNafLoH_YeoTDumsyCeqk7Q_NwHm_u1e45Ccsb", "shshshfpb": "BApXWaiWLLftA4Io_U3-LKcZYPn5vk7tuBjo3Fqto9xJ1PdZfQq3asC7hmAD5CYxAXjWF06vnsaxgIblgv64M5Ip_Og3qp_RZiYI", "__jdb": "143920055.40.17787647195151699949541|6.1778892900"}url = "https://api.m.jd.com/api"res = ctx.call("get_h5st")params = { "appid": "search-pc-java", "t": [ str(res['t']), str(int(time.time()*1000)), ], "client": "pc", "clientVersion": "1.0.0", "cthr": "1", "uuid": "17787647195151699949541", "loginType": "3", "keyword": "jk", "functionId": "pc_search_searchWare", "body": "{\"enc\":\"utf-8\",\"pvid\":\"cd0e7cded0fd46e19f75c9ea87e29986\",\"from\":\"home\",\"area\":\"52993_52994_146660_0\",\"page\":1,\"mode\":\"\",\"concise\":false,\"hoverPictures\":false,\"newAdvRepeat\":false,\"mixerParam\":false,\"new_interval\":true,\"s\":1}", "x-api-eid-token": "aldjaodaiodj", "h5st":res["h5st"]}response = requests.get(url, headers=headers, cookies=cookies, params=params,impersonate="chrome110")
print(response.text)print(response)最后能拿到数据,但是注意风控!
裙+白色衬衫+蓝条纹领带","commentFuzzy":"32","d30ItemUvDesc":"100+","deliveryable":1,"disableCart":0,"finalPrice":{"count":1,"estimatedPrice":"461.64","isFinal":1,"subPriceType":null,"title":"到手价","type":1},"finalPriceJson":"{\"count\":1,\"estimatedPrice\":\"461.64\",\"isFinal\":1,\"title\":\"到手价\",\"type\":1}","followed":0,"govSubsidyBenefit":null,"hidePrice":0,"iconList1":null,"iconList2":null,"iconList3":null,"iconList4":null,"imageurl":"jfs/t1/327793/5/9733/112790/68a934a9F52c765ec/441fe048eb1734c9.jpg","isContrast":1,"jdPrice":"481.50","jdPriceDesc":"","jdPriceFuzzy":"","limitCompanyBuy":0,"longImageUrl":"","mockFinalPrice":null,"oriPrice":"","pingou":0,"plusGoodShop":0,"plusLimit":0,"presale":0,"productUrl":null,"promotionDiscount":"","realPrice":"481.50","sdx":"","seckill":0,"selfSupport":0,"showAddCart":1,"showPurchaseList":0,"skuId":null,"stock":1,"subsidyPrice":"","timeOrderType":0,"totalSales":"","wareId":"10175899914465","wholeSales":"","yushouInfo":null,"yuyueInfo":null,"yuyueYushouJson":""},{"addCartUrl":null,"advanceBooking":0,"cashFirst":"","clickUrl":"","color":"粉色百褶裙+白色衬衫+少女心事领","commentFuzzy":"32","d30ItemUvDesc":"1","deliveryable":1,"disableCart":0,"finalPrice":{"count":1,"estimatedPrice":"461.64","isFinal":1,"subPriceType":null,"title":"到手价","type":1},"finalPriceJson":"{\"count\":1,\"estimatedPrice\":\"461.64\",\"isFinal\":1,\"title\":\"到手价\",\"type\":1}","followed":0,"govSubsidyBenefit":null,"hidePrice":0,"iconList1":null,"iconList2":null,"iconList3":null,"iconList4":null,部分信息可能已经过时









