1. 初识JS混淆与AES加密
当你打开一个网页,发现所有数据都是加密传输的时候,是不是感觉无从下手?这就是前端加密的典型场景。现在越来越多的网站采用JS混淆+AES加密的组合拳来保护数据安全,给爬虫工程师和安全研究人员带来了不小的挑战。
我最近逆向一个电商网站时就遇到了这种情况。打开Chrome开发者工具,Network面板里看到的全是乱码一样的加密数据。这种时候千万别慌,我们只需要找到三个关键要素:密钥(Key)、初始向量(IV)和加密模式(如CBC)。就像开保险箱需要密码一样,有了这些参数,我们就能在Python中复现解密过程。
JS混淆其实就像把代码打碎重组,让人难以阅读。常见的混淆手段包括变量名替换、控制流扁平化、字符串加密等。比如原本清晰的getUserInfo()可能被混淆成_0x12ab3c()。不过别担心,现代浏览器都自带调试工具,配合一些技巧我们总能找到突破口。
2. 动态调试定位加密函数
实战中我最喜欢用Chrome的全局搜索功能(Ctrl+Shift+F)。比如遇到加密的API响应,可以先搜索encrypt、AES等关键词。如果找不到,那就试试搜索JSON.parse——因为前端通常需要先解密数据再解析为JSON。
最近一个项目中,我在一个3万行的混淆JS文件中找到了这样的代码片段:
function _0x45ab2(t) { var e = CryptoJS.enc.Hex.parse(t) , n = CryptoJS.enc.Base64.stringify(e) , a = CryptoJS.AES.decrypt(n, _0x12f3d, { iv: _0x23ac1, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }) , r = a.toString(CryptoJS.enc.Utf8); return r.toString() }这就是典型的AES解密函数!通过下断点调试,我发现_0x12f3d是密钥,_0x23ac1是IV,加密模式是CBC,填充方式是Pkcs7。这四个参数就是我们需要的"钥匙"。
3. 追踪关键参数来源
找到解密函数只是第一步,更重要的是追踪这些参数是怎么生成的。有些网站会硬编码在JS里,有些则会通过API动态获取。在我分析的案例中,密钥是通过以下方式生成的:
function _0x12f3d() { var t = "5f8g2#jKlMnBvXyZ" , e = new Date().getHours(); return t + e.toString() }这意味着密钥每小时都会变化!如果不追踪这个逻辑,直接硬编码密钥,解密代码很快就会失效。通过调试,我确认IV是固定值"0123456789ABCDEF",这让我们后续处理简单了不少。
4. Python复现解密流程
拿到所有参数后,就可以用Python的pycryptodome库复现解密过程了。下面是我实际使用的代码:
from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import base64 def decrypt_data(encrypted_hex, key, iv): # 将16进制字符串转换为bytes encrypted_bytes = bytes.fromhex(encrypted_hex) # 使用CBC模式解密 cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) # 解密并去除填充 decrypted = unpad(cipher.decrypt(encrypted_bytes), AES.block_size) return decrypted.decode('utf-8') # 实际调用示例 key = "5f8g2#jKlMnBvXyZ" + str(datetime.now().hour) iv = "0123456789ABCDEF" encrypted_data = "a3b5c7...(从网络抓包获取的加密数据)" print(decrypt_data(encrypted_data, key, iv))注意几个关键点:
- 加密数据通常是Base64或Hex编码的,需要先解码
- CBC模式需要提供IV参数
- 解密后要用unpad去除填充字节
- 密钥生成逻辑要与前端保持一致
5. 处理反调试技巧
有些网站会使用反调试技术阻止开发者工具分析。常见的手法包括:
- 检测console打开状态
- 检测代码执行时间
- 禁用断点调试
遇到这种情况,可以尝试以下方法:
- 使用Fiddler或Charles等抓包工具直接拦截请求
- 在Node.js环境中加载JS文件进行调试
- 使用
debugger;语句强制暂停执行
我常用的一个技巧是重写console.log,让网站检测不到调试行为:
console.log = function() {}; window.console = console;6. 实战案例分析
让我们看一个完整的电商网站价格加密案例。通过抓包发现商品价格显示为"U2FsdGVkX1+..."这样的Base64字符串。分析流程如下:
- 全局搜索
U2Fsd发现是CryptoJS的加密特征 - 定位到加密函数使用了AES加密
- 调试发现密钥是通过用户Token+固定盐值生成的
- 最终Python解密代码:
import hashlib from Crypto.Cipher import AES def generate_key(token): salt = "fixed_salt_value" return hashlib.md5((token + salt).encode()).hexdigest() def decrypt_price(encrypted_b64, token): encrypted_bytes = base64.b64decode(encrypted_b64) key = generate_key(token) iv = encrypted_bytes[:16] # 前16字节是IV cipher = AES.new(key.encode(), AES.MODE_CBC, iv) return unpad(cipher.decrypt(encrypted_bytes[16:]), 16).decode()这个案例的特殊之处在于使用了动态IV(每次加密随机生成,放在密文开头),增加了逆向难度。
7. 进阶技巧与工具推荐
对于更复杂的场景,我推荐以下工具链:
- Chrome开发者工具:基础调试
- Fiddler/Charles:抓包分析
- WebStorm:JS代码格式化与静态分析
- pycryptodome:Python加密库
- Node.js:在服务端运行前端JS代码
一个实用技巧是使用astor库将混淆的JS代码转换为AST语法树,再进行分析。比如:
import astor import ast with open('obfuscated.js') as f: tree = ast.parse(f.read()) print(astor.to_source(tree)) # 输出格式化后的代码对于WebAssembly加密的极端情况,可能需要使用wasm逆向工具,但这已经超出本文范围。