2026/4/18 14:48:55
网站建设
项目流程
株洲网站建设联系方式,商城网站建设运营合同,wordpress插件用户,沙井建网站Python爬虫破解JS混淆数据加密实战
在当今的Web应用中#xff0c;AI服务接口越来越普遍地采用前端JavaScript动态处理与加密技术来保护核心能力。像OCR、语音识别、翻译这类高价值功能#xff0c;往往不会直接暴露明文API#xff0c;而是通过复杂的JS混淆 数据加密 环境检…Python爬虫破解JS混淆数据加密实战在当今的Web应用中AI服务接口越来越普遍地采用前端JavaScript动态处理与加密技术来保护核心能力。像OCR、语音识别、翻译这类高价值功能往往不会直接暴露明文API而是通过复杂的JS混淆 数据加密 环境检测三重反爬机制进行防护。本文将带你深入一个真实案例从腾讯混元OCR网页版中提取并还原其被加密的识别接口最终实现用Python脚本自动调用该服务。整个过程不依赖Selenium模拟浏览器而是精准逆向前端逻辑在本地高效执行解密流程。我们面对的目标是这样一个场景用户上传图片后页面通过JavaScript发送请求到后端收到一串看似乱码的十六进制或Base64编码数据再经由一段高度混淆的JS函数解密成可读JSON最后渲染出文字识别结果。原始HTML中没有任何文本内容Network面板里的响应体也是密文——这意味着传统的静态抓取完全失效。打开开发者工具F12切换至Network → XHR/Fetch面板上传一张测试图很快就能捕获到类似如下的请求POST https://api.hunyuan.tencent.com/ocr/web/infer Content-Type: application/json {image: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIA...}而响应体则是一长串无意义字符7A3B5C8D9E1F2A4B6C8D0E1F3A5B7C9D1E2F4A6B8C0D2E4F6...显然这并非真正的识别结果而是经过AES或其他算法加密后的密文。要拿到真实数据必须还原前端的解密逻辑。接下来的关键步骤就是定位这个“解密入口”。在 Sources 面板使用CtrlShiftF全局搜索fetch或/infer找到发起请求的位置。顺着代码向上追溯重点关注对响应体的处理方式。理想情况下会看到类似这样的结构fetch(url, options).then(res res.text()).then(data { let decrypted someDecryptFunction(data); let result JSON.parse(decrypted); render(result); });此时someDecryptFunction就是我们要找的核心。但由于变量名全部被混淆为_0x4da59e、__0x3b9e这类形式无法直接阅读因此需要借助断点调试辅助分析。更聪明的做法是搜索JSON.parse——几乎所有前端展示的数据最终都会经历这一步。设置断点后刷新页面当程序执行到此处时查看调用栈Call Stack逐层回溯即可发现真正负责解密的函数。经过调试追踪我们最终锁定一个名为webInstace.decrypt()的方法。它属于一个构造函数webDES的实例内部包含一系列混淆逻辑和条件跳转核心结构如下var webDES function() { this[decrypt] function(_0xa0c834) { var _0x51eedc { pKENi: function(a, b) { return a b; }, VMmle: 7|1|8|9|5|2|3|6|0|4, // ...其他混淆函数 }; if (_0x51eedc[pKENi](ZGz, _0x51eedc[wnfPa])) { return _0xa0c834; } else { var _0x492a62 _0x51eedc[VMmle].split(|); var _0x356b01 0x0; while (!![]) { switch (_0x492a62[_0x356b01]) { case 0: _0x554c90 _grsa_JS[AES][decrypt](...); continue; case 1: if (!_0xa0c834 || _0xa0c834.length 16) return _0xa0c834; continue; case 7: if (!navigator || !navigator.userAgent) return ; continue; // ...其余case省略 } break; } return _0x554c90.toString(); } }; }; var webInstace new webDES();这段代码虽然难以直读但已经暴露出几个关键信息点使用了_grsa_JS.AES.decrypt进行解密暗示底层依赖的是 CryptoJS 库存在一个字符串7|1|8|9|5|2|3|6|0|4控制执行顺序这是典型的“控制流平坦化”混淆手段对navigator.userAgent的检查说明存在环境校验防止Node.js等非浏览器环境运行。为了能在本地成功还原解密能力我们必须完整补全以下几部分1. 提取主解密类将上述webDES函数保存为独立文件hunyuan_decrypt.js作为后续调用的基础模块。2. 补全_0x2246混淆字符串解码器在原始页面中搜索类似定义var _0x2246 function(_0x5c2ba4, _0x76e2e) { _0x5c2ba4 - 0x0; var _0x32e905 __0x2fb9f[_0x5c2ba4]; // ... };连同其对应的字符串池__0x2fb9f数组一起复制过来。这些数组通常非常大但只有一小部分会被实际访问可以保留完整以确保兼容性。更重要的是该函数内部实现了RC4解密逻辑并内嵌了一个简易的atobpolyfill用于处理Base64解码。这部分必须完整保留否则某些字符串无法正确还原。3. 注入_grsa_JS依赖库根据命名和调用方式判断_grsa_JS实际上是对 CryptoJS 的别名封装。我们可以手动引入该库在 Node.js 环境中安装npm install crypto-js然后在脚本顶部添加const CryptoJS require(crypto-js); var _grsa_JS CryptoJS;这样就能让_grsa_JS.AES.decrypt正常工作。4. 模拟浏览器全局对象由于代码中存在if (!navigator || !navigator.userAgent)判断若不在浏览器环境中运行函数会直接返回空字符串。因此需在 Node.js 中模拟必要的全局变量global.navigator { userAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 };此外还需确保window或global可用避免引用错误。完成以上准备后整合出完整的decryptor.js文件const CryptoJS require(crypto-js); var _grsa_JS CryptoJS; global.navigator { userAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 }; // 混淆字符串解析函数含RC4解密 var _0x2246 function(_0x5c2ba4, _0x76e2e) { _0x5c2ba4 _0x5c2ba4 - 0x0; var _0x32e905 __0x2fb9f[_0x5c2ba4]; if (_0x2246[initialized] undefined) { (function() { var _0x6dc9dd typeof window ! undefined ? window : global; var _0x4dc154 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/; if (!_0x6dc9dd.atob) { _0x6dc9dd.atob function(input) { input String(input).replace(/$/, ); let str ; for (let i 0; i input.length; i 4) { let block (_0x4dc154.indexOf(input[i]) 18) | (_0x4dc154.indexOf(input[i 1]) 12) | (_0x4dc154.indexOf(input[i 2]) 6) | _0x4dc154.indexOf(input[i 3]); str String.fromCharCode((block 16) 0xff, (block 8) 0xff, block 0xff); } return str.length % 3 ? str.substring(0, str.length - (str.length % 3)) : str; }; } }()); var _0x9bf5c5 function(_0x29e5a4, _0x4e0418) { var _0x317a0c [], _0x58cb6f 0, _0x1ef9fa, _0x2b84ff , _0x406a41 ; _0x29e5a4 atob(_0x29e5a4); for (let i 0; i _0x29e5a4.length; i) { _0x406a41 % (00 _0x29e5a4.charCodeAt(i).toString(16)).slice(-2); } _0x29e5a4 decodeURIComponent(_0x406a41); for (let i 0; i 256; i) _0x317a0c[i] i; for (let i 0; i 256; i) { _0x58cb6f (_0x58cb6f _0x317a0c[i] _0x4e0418.charCodeAt(i % _0x4e0418.length)) % 256; _0x1ef9fa _0x317a0c[i]; _0x317a0c[i] _0x317a0c[_0x58cb6f]; _0x317a0c[_0x58cb6f] _0x1ef9fa; } let i 0, j 0; for (let k 0; k _0x29e5a4.length; k) { i (i 1) % 256; j (j _0x317a0c[i]) % 256; _0x1ef9fa _0x317a0c[i]; _0x317a0c[i] _0x317a0c[j]; _0x317a0c[j] _0x1ef9fa; _0x2b84ff String.fromCharCode(_0x29e5a4.charCodeAt(k) ^ _0x317a0c[(_0x317a0c[i] _0x317a0c[j]) % 256]); } return _0x2b84ff; }; _0x2246[rc4] _0x9bf5c5; _0x2246[data] {}; _0x2246[initialized] true; } var _0x4b1179 _0x2246[data][_0x5c2ba4]; if (_0x4b1179 undefined) { _0x32e905 _0x2246[rc4](_0x32e905, _0x76e2e); _0x2246[data][_0x5c2ba4] _0x32e905; } else { _0x32e905 _0x4b1179; } return _0x32e905; }; // 字符串池示例截取 var __0x2fb9f [ w61yEQYNMcKN, UcK/IcOnwpLDkMO5, wpbCnjvCvwIaPcOxw7E, /* ... */ ]; // 主解密类 var webDES function() { var _0x9843d3 function(_0x29d556, _0xcc6df, _0x3d7020) { if (0x0 _0xcc6df) return _0x29d556.substr(_0x3d7020); var _0x48914b _0x29d556.substr(0x0, _0xcc6df); return _0x48914b _0x29d556.substr(_0x2246(0x256, DK[)(_0xcc6df, _0x3d7020)); }; this[decrypt] function(_0xa0c834) { var _0x51eedc { pKENi: function(_0x5b6f5a, _0x440924) { return _0x5b6f5a _0x440924; }, wnfPa: ZGz, VMmle: 7|1|8|9|5|2|3|6|0|4, GKWFf: function(_0x40cfde, _0x16f3c2) { return _0x40cfde _0x16f3c2; }, MUPgQ: function(_0x19038b, _0x4004d6) { return _0x19038b _0x4004d6; }, qrTpg: function(_0x1198fb, _0x22e1db, _0x1b091a) { return _0x1198fb(_0x22e1db, _0x1b091a); }, pdmMk: function(_0xe2b022, _0x4af286, _0x4c2fd4) { return _0x4af286 - _0x4c2fd4; }, xVKWW: function(_0x1094a3, _0x5f3627, _0x2a0ac5, _0x3ad2e5) { return _0x5f3627(_0x2a0ac5, _0x3ad2e5); } }; if (_0x51eedc[pKENi](_0x2246(0x259, EPI), _0x51eedc[wnfPa])) { return _0xa0c834; } else { var _0x492a62 _0x51eedc[VMmle].split(|), _0x356b01 0x0; while (!![]) { switch (_0x492a62[_0x356b01]) { case 0: _0x554c90 _grsa_JS[AES][decrypt]({ ciphertext: _grsa_JS[enc][Hex][parse](_0xa0c834) }, _0x2cf8ae, { iv: _0x554c90, mode: _grsa_JS[mode][CBC], padding: _grsa_JS[pad][Pkcs7] }).toString(_grsa_JS[enc][Utf8]); continue; case 1: if (_0x51eedc[GKWFf](null, _0xa0c834) || _0x51eedc[MUPgQ](0x10, _0xa0c834.length)) return _0xa0c834; continue; case 2: _0xa0c834 _0x9843d3(_0xa0c834, _0x2cf8ae, 0x8); continue; case 3: _0x2cf8ae _grsa_JS.enc.Hex.parse(_0x554c90); continue; case 4: return _0x554c90.substring(0, _0x51eedc[pdmMk](_0x554c90.indexOf(}), -1) 1); case 5: _0x554c90 _0x9843d3(_0xa0c834, _0x554c90, 0x8); continue; case 6: _0x554c90 _grsa_JS.enc.Utf8.parse(_0x554c90); continue; case 7: if (!navigator || !navigator.userAgent) return ; continue; case 8: var _0x554c90 _0x51eedc[qrTpg](parseInt, _0xa0c834[_0x51eedc[xVKWW](_0xa0c834.length, 0x1)], 0x10), _0x2cf8ae parseInt(_0xa0c834[_0x554c90], 0x10); continue; case 9: _0xa0c834 _0x9843d3(_0xa0c834, _0x554c90, 0x1); continue; } break; } } }; }; // 导出供Python调用 if (typeof module ! undefined module.exports) { const instance new webDES(); module.exports instance.decrypt.bind(instance); }现在我们就可以在 Python 中利用PyExecJS来加载并调用这个JS解密函数。安装所需依赖pip install PyExecJS注意建议系统已安装 Node.js否则execjs可能默认使用较慢的 JScript 引擎。编写主调用脚本import execjs import base64 import requests # 加载外部JS文件 with open(decryptor.js, r, encodingutf-8) as f: js_code f.read() # 编译上下文 ctx execjs.compile(js_code) def decrypt_response(encrypted_data): try: result ctx.call(decrypt, encrypted_data) return result except Exception as e: print(解密失败:, str(e)) return None def ocr_infer(image_path): # 图片转base64 with open(image_path, rb) as f: img_b64 base64.b64encode(f.read()).decode() payload {image: fdata:image/jpeg;base64,{img_b64}} headers { User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36, Referer: https://hunyuanocr.tencent.com/, Origin: https://hunyuanocr.tencent.com, Content-Type: application/json } resp requests.post( https://api.hunyuan.tencent.com/ocr/web/infer, jsonpayload, headersheaders ) if resp.status_code 200: encrypted resp.text.strip() print(收到加密数据:, encrypted[:64] ...) decrypted decrypt_response(encrypted) if decrypted: print(✅ 解密成功) return decrypted else: print(❌ 解密失败) return None else: print(请求失败:, resp.status_code, resp.text) return None # 示例调用 result ocr_infer(test.jpg) print(\n识别结果:\n, result)运行后如果一切正常你将看到类似输出收到加密数据: 7A3B5C8D9E1F2A4B6C8D0E1F3A5B7C9D1E2F4A6B8C0D2E4F6... ✅ 解密成功 识别结果: {code:0,msg:Success,data:{text:欢迎使用腾讯混元OCR,blocks:[...]}}至此整个自动化流程打通从图像输入到接口调用再到JS解密全部在纯Python环境中完成无需启动任何浏览器实例效率极高。这种基于JS逆向的爬虫策略特别适用于那些“表面开放、实则设防”的Web AI服务。它的优势在于轻量高效相比 Puppeteer/Selenium资源消耗极低可批量处理适合集成进后台任务系统灵活性强一旦掌握解密逻辑可自由构造请求。但也存在几点风险与挑战稳定性差前端代码一旦更新混淆结构改变原有JS脚本可能立即失效维护成本高每次变更都需要重新调试定位新解密函数法律边界模糊虽用于学习研究但仍可能违反服务条款。因此建议- 定期自动化比对线上JS版本差异- 结合 Selenium 动态抽取最新混淆代码实现自适应更新- 仅限个人用途避免大规模高频调用。这场与前端混淆的“猫鼠游戏”本质上是对工程逆向能力的一次综合考验。它不仅要求你懂网络协议、熟悉浏览器调试还要能读懂压缩过的JS逻辑甚至理解加密模式与运行环境之间的耦合关系。而对于开发者而言这也提醒我们纯粹的前端加密终究只是障眼法。真正安全的设计应结合服务端鉴权、频率限制与行为分析才能有效抵御自动化攻击。不过话说回来正是这些层层设防又不断被破解的过程推动着前后端对抗技术持续演进——而这也正是 Web 安全最迷人的地方。