简介:这是一份面向前端安全与JS逆向学习者的实战项目代码包,聚焦盼之decode__1174版本的补环境方案,适合具备一定JavaScript基础、希望深入理解反调试与加密混淆机制的中高级开发者。资源围绕无限debugger绕过、后缀动态添加方式、加密环境数组明文还原等核心检测点展开,并深入涉及localStorage处理、Math.random.constructor构造检测、debugger冲突Hook方法以及Object.defineProperty描述符重写等补环境关键技术,帮助读者建立可控运行环境、掌握逆向排错思路。压缩包共5个文件,以3个html页面为主,辅以inscode配置与gitignore文件,整体约17KB,结构精简便于快速定位核心逻辑。目前已有221人学习,适合作为JS逆向补环境练习与前端安全研究的参考素材。
1. 从一次登录请求说起:JS逆向里的 decode__1174 到底是什么
某天抓一个站点的登录接口,请求体里躺着一串看着像 Base64 又不太像的字符串,末尾还挂着decode__1174这样的标记。直接 Base64 解出来是乱码,丢进控制台报错,翻遍网络面板也找不到明文。这就是很多人第一次撞上decode__1174的场景——它不是标准算法,而是某个前端混淆方案给自定义解码函数起的名字,1174往往是构建工具或混淆器生成的编号。你要做的不是去背这个编号,而是搞懂它背后那套「字符串加密 + 运行时还原」的套路,把混淆后的 JS 还原成能读、能复现的逻辑。这篇笔记面向的是需要对接这类接口、做数据采集或安全测试的工程师,从定位函数一路讲到把解码逻辑抠出来独立跑通,中间会踩的坑我都标出来。
2. 定位 decode__1174:从调用栈反推混淆入口
2.1 先判断它是不是「运行时解密」
拿到一个带decode__1174的站点,第一步不是急着格式化代码,而是判断这个函数是编译期就解好了,还是运行时才解密。判断方法很直接:在 Sources 面板全局搜索decode__1174,如果只在某个巨大的 IIFE 里出现一次定义、多处调用,而且调用点附近有while(true)、switch这类结构,基本可以确定是运行时解密。这类方案的典型特征是:字符串以密文形式存在数组里,启动时通过一个自执行函数把数组顺序打乱,再用decode__1174按下标取出并解密。
我一般会先在控制台做一次快速验证:
// 在 Console 里直接调用,看它接什么参数、返回什么 // 注意:函数可能挂在 window 或某个闭包内,先确认可见性 typeof decode__1174; // 若为 "undefined",说明在闭包内,需要断点如果控制台拿不到,就在调用decode__1174的那一行下断点,刷新页面,断下来后看 Scope 面板里的 Closure,通常能找到密文数组和解密函数本体。这一步的关键是别被1174这个编号带偏——编号本身没有语义,真正有用的是它引用的那个数组和移位逻辑。
2.2 用断点把密文数组和移位逻辑抓出来
断下来之后,Scope 里会看到一个类似_0xabc1的数组,里面全是十六进制或 Base64 样式的字符串。旁边还有一个数字变量,常见命名是_0xabc2或直接叫offset。这个数字就是解密时的移位量。很多混淆器会把数组整体旋转若干位,再配合一个固定的密钥做异或或查表。
操作步骤:
- 在断点处把密文数组完整复制出来,存成
cipherArray.json。 - 记录当前
offset的值,以及decode__1174内部对下标做的运算,常见是index - offset再取模。 - 找到真正的解密核心,通常是一个几行的循环,对每个字符做
charCodeAt后异或一个常量,再fromCharCode。
// 还原后的解密核心,参数说明见下方 function decode__1174(index) { // offset 来自闭包,实际使用时需替换为断点抓到的真实值 const realIndex = (index - offset + cipherArray.length) % cipherArray.length; const cipher = cipherArray[realIndex]; let result = ''; for (let i = 0; i < cipher.length; i++) { // key 是异或密钥,常见为单个字符的 charCode result += String.fromCharCode(cipher.charCodeAt(i) ^ key); } return result; }逻辑说明:realIndex那行处理的是数组旋转,+ length再取模是为了避免负数下标。异或部分是最常见的对称解密,key可能是固定值,也可能由某个环境变量推导。参数上,offset和key必须从实际断点里取,不能猜。我见过有人直接抄网上的offset=0x10,结果解出来全是乱码,就是因为每个站点的构建配置不同。
2.3 把解密函数抽成独立脚本
抓到逻辑后,别急着在浏览器里反复断点。把密文数组、offset、key和解密函数一起抠出来,写成一个独立的 Node 脚本,这样后续批量处理请求参数会快很多。
// decode_standalone.js const cipherArray = require('./cipherArray.json'); const offset = 0x1a; // 替换为断点抓到的真实值 const key = 0x3f; // 替换为断点抓到的真实密钥 function decode(index) { const realIndex = (index - offset + cipherArray.length) % cipherArray.length; const cipher = cipherArray[realIndex]; let out = ''; for (let i = 0; i < cipher.length; i++) { out += String.fromCharCode(cipher.charCodeAt(i) ^ key); } return out; } // 验证:打印前 10 个解密结果,人工确认是否为可读字符串 for (let i = 0; i < 10; i++) { console.log(i, decode(i)); }跑通这个脚本的标志是:输出的字符串里能看到接口路径、参数名、错误提示这类可读内容。如果全是控制字符,回头检查offset和key是否取错,或者密文数组是否在复制时漏了元素。这一步稳了,后面才有得谈。
3. 还原调用链:把 decode__1174 嵌回请求参数生成流程
3.1 找到参数拼接的入口函数
解密只是第一步,真正要复现的是「明文参数怎么变成请求体里那串密文」。回到断点,往上翻调用栈,通常会看到一个负责组装params或data的函数。这个函数里会先构造一个对象,然后对某个字段调用decode__1174的反向操作——也就是加密。注意,很多混淆方案里加密和解密共用同一个函数,只是传入的index不同。
我一般会在调用栈里找第一个出现JSON.stringify或&拼接的地方,那里就是参数成型的节点。把那个函数的逻辑抄下来,重点看三件事:哪些字段被加密、加密前有没有排序、有没有时间戳或随机数参与。
3.2 处理时间戳与随机数带来的「每次不一样」
这是最容易翻车的地方。你明明把解密逻辑跑通了,自己构造的请求却始终返回签名错误,十有八九是时间戳或随机数没对齐。常见做法是:请求体里有一个ts字段,参与签名计算,但服务端允许一定时间窗口。你复现时如果直接写死一个旧时间戳,必然失败。
// 构造请求参数的片段,重点看 ts 和 nonce 的处理 function buildParams(payload) { const ts = Date.now(); // 毫秒级时间戳,部分站点用秒级需 /1000 const nonce = Math.random().toString(36).slice(2, 10); // 8 位随机串 const raw = { ...payload, ts, nonce }; // 按 key 字典序排序后再拼接,顺序错了签名必错 const sortedKeys = Object.keys(raw).sort(); const signStr = sortedKeys.map(k => `${k}=${raw[k]}`).join('&'); // 这里的 encrypt 就是 decode__1174 的反向调用 raw.sign = encrypt(signStr); return raw; }参数说明:ts的精度要和站点一致,用毫秒还是秒,断点里看一眼就知道。nonce的长度和字符集也要对齐,有的站点要求纯数字,有的允许字母。signStr的拼接顺序如果和前端不一致,服务端算出来的签名就对不上。我习惯在断点里把signStr打印出来,和自己脚本里生成的做逐字符对比,差异往往就藏在排序或编码上。
3.3 用 Node 复现一次完整请求
把解密、参数构造、签名三块拼起来,用axios或原生https发一次请求,看返回是否和浏览器一致。
const axios = require('axios'); const crypto = require('crypto'); async function main() { const payload = { page: 1, size: 20 }; const params = buildParams(payload); const res = await axios.post('https://目标站点/api/list', params, { headers: { 'Content-Type': 'application/json', // 部分站点需要携带特定的 Referer 或 UA,按断点里的请求头补齐 'User-Agent': 'Mozilla/5.0 ...' } }); console.log(res.data); } main().catch(console.error);如果返回 200 但数据为空,先检查payload里的字段名是否和前端一致;如果返回签名错误,回到 3.2 对比signStr。这一步跑通,说明整个 decode__1174 的链路你已经吃透了。
4. 避坑与排查:decode__1174 最常见的 5 个翻车点
4.1 密文数组复制不全导致下标越界
现象:解密脚本跑起来报undefined或解出空字符串。原因:混淆器把数组拆成多个片段,或者在运行时动态 push 新元素,你只复制了初始部分。解决:在断点处用JSON.stringify(cipherArray)完整导出,并在脚本里打印数组长度,和断点里的length对比。如果长度对不上,检查是否有push或concat操作在初始化之后执行。
4.2 offset 取错导致整体错位
现象:解出来的字符串大部分可读,但个别字符是乱码,或者接口路径差一个字符。原因:offset在运行时可能被修改,你抓的是修改前的值。解决:在decode__1174函数内部第一行下断点,看实际参与运算的offset是多少。有些混淆器会在每次调用时对offset做自增,这种情况需要把自增逻辑也复现出来。
4.3 异或密钥不是单字节
现象:按单字节异或解出来全是乱码。原因:密钥可能是多字节字符串,或者用的是查表替换而非异或。解决:在解密循环里看key的类型,如果是字符串,取charCodeAt(i % key.length);如果是数组,说明是查表,把表也复制出来。
4.4 请求头缺失导致服务端拒绝
现象:参数完全正确,但返回 403 或空数据。原因:站点校验了Referer、Origin或自定义头。解决:在浏览器网络面板里右键请求,选择「Copy as cURL」,把里面的头逐个搬到脚本里,再逐个删减,定位到必需的那几个。
4.5 时间戳窗口过窄
现象:脚本刚跑通,过几分钟再跑就失败。原因:服务端签名有效期很短,比如 30 秒。解决:确保每次请求都重新生成ts,不要缓存。如果站点用的是服务器时间而非本地时间,还需要先请求一个时间接口做校准。
5. 进阶:把 decode__1174 的处理做成可复用的解码器
走到这里,单个站点的 decode__1174 已经不再是黑匣子。但实际工作中你会遇到同一套混淆方案换个编号出现在别的站点上,这时候把逻辑抽象成可复用的解码器就很有价值。我的习惯是建一个decoder目录,每个站点一个配置文件,把cipherArray、offset、key、encrypt入口这些变量抽成 JSON,核心解码逻辑只写一份。
// decoder/factory.js function createDecoder(config) { const { cipherArray, offset, key } = config; return function decode(index) { const realIndex = (index - offset + cipherArray.length) % cipherArray.length; const cipher = cipherArray[realIndex]; let out = ''; for (let i = 0; i < cipher.length; i++) { out += String.fromCharCode(cipher.charCodeAt(i) ^ key); } return out; }; } module.exports = { createDecoder };配置文件长这样:
{ "cipherArray": ["0x1a2b", "0x3c4d"], "offset": 26, "key": 63 }这样下次遇到新站点,只需要在断点里抓这三个值,填进配置就能跑。验证方法也很直接:用新配置解出来的前 20 个字符串,人工扫一眼有没有可读的接口路径或参数名。如果有,基本就对了;如果全是乱码,回头检查offset和key。
还有一个技巧:把解密后的字符串和原始密文做一个映射表缓存起来,避免每次请求都重新计算。对于高频调用的场景,这个缓存能省不少 CPU。但要注意,如果offset在运行时会变,缓存就得带上版本号,否则会拿到过期结果。
最后说个我自己的教训:早期做这类逆向时,总想着一步到位把整个混淆文件还原成可读代码,结果在 AST 还原上耗了好几天,收益却很低。后来想通了,decode__1174 这类方案的核心就是「运行时解密」,你只需要把运行时那几行逻辑抠出来,剩下的混淆代码完全可以不管。把精力花在参数构造和请求复现上,比死磕代码美化划算得多。希望帮到你。
本文还有配套的精品资源,点击获取