尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JS逆向实战:从定位到复现decode__1174混淆解密函数

JS逆向实战:从定位到复现decode__1174混淆解密函数 简介面向JS逆向学习者针对盼之decode__1174版本提供补环境项目代码系统覆盖无限debugger绕过、动态后缀添加分析、加密环境数组明文还原等逆向基础流程适用于分析加密JS逻辑与调试反调试机制的场景。压缩包内共5个文件含3个HTML页面、1个inscode配置及1个gitignore文件整体仅17KB轻量而完整目录结构简洁。已有201人学习下载适合掌握一定JavaScript基础、希望进阶补环境技术的开发者研读配合浏览器开发者工具可逐步跟踪代码执行过程。代码紧扣实际检测点实现localStorage触发后缀、Math.random.constructor构造检测、debugger冲突Hook以及Object.defineProperty描述符重写等关键手段逐段对照可理清程序运行时的环境校验逻辑掌握从环境伪造、断点绕过到数据还原的完整思路。HTML页面可在浏览器中直接运行便于结合具体项目逐行验证补环境效果是实践JS逆向的轻量参考。 JS逆向里最磨人的往往不是算法本身而是像 decode__1174 这种编号随机、逻辑绕来绕去的混淆函数。这周我和朋友处理一个游戏交易平台的展示数据采集目标站就叫盼之吧。接口返回的订单名、角色名全是密文页面却能正常显示说明前端必然有一个 decode 函数在做解密。全局搜了一轮锁定的就是 decode__1174。整个排查过程走下来从定位、断点、Hook 到用 Python 复现思路其实完全通用。本文就按这个过程拆开讲适合正在啃混淆 JS、做参数逆向或者被动态解密接口折磨的朋友参考核心不是让你背代码而是教你抓住输入输出用动态调试反推静态逻辑。1. 项目背景与总体逆向思路1.1 decode__1174 在链路中到底干了什么先交代背景。盼之这个站是比较典型的 SPA 结构列表数据通过 XHR 拉取返回 JSON 里的关键字段不是明文而是类似kY8...的长字符串。页面展示走的是自研组件你在 Network 面板里看到的响应是密文屏幕上显示的却是明文。这中间隔着的就是一层 JS 解密。全局搜“decode”能捞出一堆候选最后定位到的是 decode__1174。1174 这种编号说明它来自构建工具级别的混淆函数名被统一打平不是业务手写的命名。也就是说这类函数大概率不是独立设计的高强度算法而是一个把 A 转成 B 的工具函数我们只要能拿到输入和输出就能把它从黑盒变成白盒。为什么说先弄清楚角色很重要因为方向一错时间全浪费。比如你把焦点放在分析某个自研加密算法上结果发现它只是对服务端返回做了一层 base64 转码反过来你以为只是一个简单解码里面却嵌套了 AES 和动态 key。所以第一步不是写代码而是先确认这个函数被谁调用、传进去什么、吐出来什么。1.2 动态调试优先静态分析为辅遇到 decode__1174 这种函数名已经能说明它是被自动化混淆过的。你去静态读那一大坨_0x3f2a、_0x5b9c人脑容易被绕晕。我的习惯是优先动态调试让浏览器自己把答案算出来。具体做法就是下断点、打印调用栈、Hook 返回值。这就好比你想知道自动售货机怎么把硬币换成饮料与其拆开机箱研究电路不如投一次币在取货口接住结果再在投币口装一个记录仪。动态调试的最大优势是不用理解每一行代码。只要能在函数入口和出口拿到 input/output就能确认算法效果。然后你再用静态阅读去还原其中关键几步效率会高很多。这套流程适用于 decode__1174也适用于大多数 sign、token、param 类的混淆函数。1.3 手上要有的工具清单环境上不需要特别重型的东西我实际用到的就这些Chrome DevTools核心工具。Sources 里可以格式化压缩 JSNetwork 里能看请求发起者 InitiatorConsole 里能随时执行表达式Overrides 功能可以直接覆写线上 JS 文件。Charles 或 Fiddler用来做请求断点、重写返回必要时直接替换某个 JS 文件为本地版本。没有的话用 DevTools 本地替换也能实现类似效果。VS Code Prettier / js-beautify把大段混淆代码格式化便于搜索和阅读。注意格式化后的文件要另存不要直接在线上改。Node.js npm后面用 Python 复刻算法之前至少要能本地运行一段 JS 验证。推荐装 pyexecjs 或 jsdom用来在 Python 里临时调用 JS。Python pycryptodome / requests批量解密验证用。先别急着写代码把这几样准备好。2. 定位目标函数从搜索到断点2.1 在几百个 JS 文件里捞关键字正式开始。打开 DevTools 的 Sources找到项目主域名的文件目录按 CtrlShiftF 全局搜索。搜索词不要一上来就搜 decode__1174因为函数名可能动态拼接或者实际压缩后出现在别的分包里。先把范围扩大搜decode、atob、btoa、fromCharCode、charCodeAt、CryptoJS、AES、RSA、base64。这些是解密类函数的常见特征命中概率高。我这里运气不算差搜decode__直接跳到了定义位置一个约 300KB 的主业务 JS 文件里第几万行的位置。压缩后的代码长这样function decode__1174(e){var t{}...格式化之后函数体仍然有一堆_0x...说明字符串被抽到了一个数组里。但不用怕先在函数开头、中间、结尾分别打上断点然后去页面上操作一次触发接口。断点一停右键当前函数看 Scope、Call Stack然后 Console 里打印arguments[0]和decode__1174(arguments[0])输入输出立刻明确。如果全局搜索 decode__1174 搜不到通常意味着函数名是在运行时动态生成的。别慌改用搜索Function(、eval(、new Function或者直接看Function.prototype.constructor相关调用。动态生成函数一般会先拼接字符串再执行在网络请求返回的 JS 里搜1174也常常能捞到蛛丝马迹。2.2 顺着调用栈锁定真正的入口光找到函数定义还不够要搞清楚它是被谁调的、什么时候被调用的。这里有个很实用的功能在 Network 面板里找到返回密文的 XHR 请求往下拉看 Initiator 那一列的调用栈。点击栈帧会自动跳转到发起请求或处理响应的源码位置。在响应处理的回调那句下断点比如JSON.parse(xhr.responseText)然后刷新页面。断点触发后Console 里输入console.trace()或者直接看 Call Stack 面板一路点进调用链直到某一帧出现 decode__1174。我这次就是在某一帧里看到一个命名类似renderList的函数拿到服务端返回后对每个 item 的 name 字段调用了decode__1174(item.name)。此时疑问消失了它不是生成请求参数的加密函数而是响应数据的解包函数。它把服务端给的 code 字符串解成可读文本。这一步决定了后面复刻算法的方向。注意如果调用链非常深、回调很多你可以在 decode__1174 的入口断点处直接看 caller。旧版浏览器可以用arguments.callee.caller新版直接右键 Call Stack 面板里的上层函数跳转。不要试图手动往上翻几万行代码找调用方那是低效的。2.3 静态阅读看懂关键步骤而非全部定位之后就是静态阅读目标不是把整段混淆代码逐行翻译而是挑出影响结果的几个关键动作。我习惯先把函数体复制到本地用 VS Code 格式化再逐块标记。第一块预处理。看入参 e 是否被取子串、替换某些字符。例如e.substr(4)或者e.replace(/^..../,)说明密文有一个固定前缀需要跳过去。第二块核心算法。看有没有调用atob、CryptoJS.AES.decrypt、new Blob、TextDecoder、decodeURIComponent这类高特征 API。有的话基本就能确定算法族。第三块后处理。看解密结果是否有reverse()、replace、JSON.parse、split之类的操作。这一步很容易被忽略但会导致你用 Python 解出来一坨正确 base64 却得不到正常字符串。我这里看到的一个细节是函数最开始用字符串的charCodeAt(0)减去某个偏移量得到前缀长度再截掉前缀中间调用了一个从字符串数组里取出来的函数核心是 atob 加移位最后把结果逆序。整个下来本质就是把服务端返回的字符串做了一层自定义编码再包了一层 base64而不是真正的加密。到这里算法轮廓已经清楚。3. 还原解密逻辑与代码复现3.1 逐行还原出可读的 JS 逻辑按照前面的静态分析我把 decode__1174 还原成了一个可读版本。这里要注意真实版本里第 3 步不是直接用浏览器原生 atob而是从一个字符串数组_0x2f1a[...]里取出来的等价函数底层还是一样。这里直接写成 atob 是为了方便理解// 原混淆函数: decode__1174 function decode__1174(input) { // 1. 计算前缀长度首字符的 charCode - 48 var prefixLen input.charCodeAt(0) - 48; if (prefixLen 0 || prefixLen 8) return ; // 2. 去掉前缀 var body input.slice(prefixLen); // 3. base64 解码 var decoded atob(body); // 4. 逐字符逆序 var result decoded.split().reverse().join(); return result; }如果你遇到的是 AES 这类对称加密会多出 key 和 iv 两个问题。key 和 iv 往往不是写死的字符串而是由其它函数动态算出来的。我的建议是别在 Python 里硬编码 key先用 Hook 把 key 的真实值打出来或者干脆在 Python 里用 subprocess 调用 Node 执行一段 JS把 key 计算出来再喂给解密函数。这样目标站一旦更新 key 生成逻辑你只需要更新 JS不用动解密主流程。3.2 用 Hook 验证你的理解算法还原得对不对最好的验证方式是 Hook。如果 decode__1174 是挂在全局对象上的直接在 Console 执行const originDecode window.decode__1174; window.decode__1174 function (...args) { const result originDecode.apply(this, args); console.log([hook] in:, args[0]); console.log([hook] out:, result); return result; };这样页面再调用的时候Console 会打印真实入参和出参。把输出和你自己的还原脚本结果对比一致就说明算法吃透了。针对不在全局对象上的函数有一个更通用的办法用 DevTools 的 Local Overrides在源码里临时加一行window.__decode1174 decode__1174;刷新后就可以在 Console 操作它。如果你不想改文件也可以右键行号设置 Logpoint只打日志不改逻辑。把 Logpoint 放在函数 return 那一行能直接看到返回值放在第一行能看到入参。这个技巧在压缩代码里尤其好用。3.3 用 Python 实现批量解密算法确认后接下来就是把这套逻辑搬进自己的采集脚本。我这里用 Python 实现一个简化版本import base64 import requests from Crypto.Cipher import AES from Crypto.Util.Padding import unpad # 示例1: 如果是“前缀长度base64逆序” def decode_1174_simple(cipher_text: str) - str: prefix_len ord(cipher_text[0]) - 48 body cipher_text[prefix_len:] decoded base64.b64decode(body).decode(utf-8) return decoded[::-1] # 示例2: 如果是“4字节随机前缀 AES-CBC” def decode_1174_aes(cipher_text: str, key: bytes, iv: bytes) - str: payload cipher_text[4:] enc base64.b64decode(payload) cipher AES.new(key, AES.MODE_CBC, iv) plain unpad(cipher.decrypt(enc), AES.block_size) return plain.decode(utf-8) # 批量验证 resp requests.get(https://target.example.com/api/list, headers{...}).json() for item in resp[data]: name decode_1174_simple(item[name]) print(name)实战中我强烈建议先跑一个包含 10 条左右记录的小样本把脚本输出和页面上显示的内容逐条比对。确认完全一致后再放开全量跑。因为一旦有 1% 的边界情况没覆盖比如空字符串、null、异常前缀全量跑会哗啦啦报错。批量处理的代码要增加异常捕获遇到非法密文直接记录日志而不是中断。另外如果出于某种原因你不想在 Python 里重写算法还可以用 pyexecjs 或者 Node 子进程直接执行原 JS。这是最保真的方式代价是会慢一点。常见的做法是把整个加解密模块剪出来在 Node 环境里挂一个最小化的 window/document mock然后暴露成 module.exports。注意依赖的浏览器 API 越少越容易跑起来。4. 实战中的常见问题与排查技巧4.1 断点失效、无限 debugger 和反调试做这种带混淆的站点必然会碰到反调试。最常见的两个现象第一页面无限进 debugger。点继续之后立刻又停这种多半是代码里有一段setInterval(function(){debugger;}, 100)或者Function(debugger)()。解决办法是 Local Overrides 把包含 debugger 的 JS 文件替换成本地副本搜索debugger关键词批量删掉。第二调试一开就检测。比较粗暴的站点会用 console 内容差异检测、窗口大小检测、时间差检测。遇到这种优先考虑用 Puppeteer/Playwright 这类无头浏览器跑它们天然就是一个真实环境反调试代码一般不会触发。要是反调试太强再考虑在 JS 里 Hookconsole、Date、performance这些会被探测的 API。不要一碰到反调试就退缩。大多数网站的所谓“防护”只是用现成混淆工具开了一层壳核心还是那几类算法把壳去掉之后前面分析的解密流程完全不变。4.2 混淆变量、闭包和调用时机另一个常见坑是找不到函数引用。函数被定义在一个立即执行函数IIFE里外部根本访问不到。你按照我上面的做法去window.decode__1174取返回 undefined这很正常。解决办法有两个改源码导出。在函数定义后面加window.__decode1174 decode__1174;然后用 Overrides 刷新页面试试。在函数内部打断点等断点停住后在 Console 的 Scope 区域找到this、decode__1174手动执行。这也能验证函数。再就是调用时机。有些 key、iv、盐值是页面加载后通过某个接口动态获取的如果你在页面还没加载完就手动调用 decode__1174会得到错误结果。遇到这种情况先重复触发一次正常流程确保依赖的数据已经生成再调用解密函数。4.3 常见问题速查表把这次和以往项目中常遇到的问题整理成表方便你复盘时对照现象常见原因排查手段全局搜不到 decode__1174函数名被动态拼接或写在 eval/Function 中搜Function(、eval(、1174或用断点停到调用后看作用域函数在闭包内无法访问定义在 IIFE 里改源码导出到 window或函数内断点手动调用断点一直被跳过检测调试、使用了 release 模式压缩本地替换 JS 去掉 debugger/检测解密结果乱码key/iv/编码/模式不一致和页面输出比对试 ECB/CBC、Pkcs7/ZeroPadding前缀长度算法看错charCodeAt 取的是首字符而不是 ASCII 数字Console 打印input.charCodeAt(0)验证Python 调用 JS 报 window 未定义原 JS 依赖浏览器全局对象用 jsdom/VM 模拟 window/document或只剪出纯函数解密成功但列表个别字段为空服务端返回了空串或非标准编码增加异常捕获记录并跳过表格里第 4、5 条是真实踩过的。尤其是前缀长度那里我最初以为首字符就是前缀长度结果发现它把字符的 ASCII 码减了 48 得到数字如果用int(input[0])去解析一个字母开头就直接报错。这种细节非常容易让批量脚本崩掉。4.4 别忘了留底和对比最后分享一个习惯。定位到 decode__1174 后我会第一时间在 Console 里执行copy(decode__1174.toString())把函数源码存成文件再记下调用它的入口 URL 和参数示例。这样过几天网站升级函数逻辑变了我拿新旧代码一 diff几秒钟就知道它改了哪一行。这个习惯帮我省掉了大量重复逆向时间也方便把算法变化同步到自己的脚本里。整套流程走下来你会发现 decode__1174 这类函数并没有想象中神秘。它大概率就是混淆版的 base64、AES、自研位移难点不在算法而在定位。定位的核心思路永远是先找到输入输出再用动态调试反推静态逻辑最后用脚本验证。只要你抓到一条真实的入参和出参这个黑盒就已经开了 80%。另一个想提醒的是所有分析和测试我都在自己有权访问的测试环境里做大家动手练习时也尽量用自己搭的靶场或者官方允许的站点别拿线上核心接口硬来。本文还有配套的精品资源点击获取
返回列表