AES 加密逆向实战——从定位到还原
AES 加密逆向实战——从定位到还原
前言
在 JS 逆向中,AES 加密是最常见的对称加密算法之一。无论你是爬虫工程师还是安全研究员,掌握 AES 的逆向方法都是基本功。
本文将从实际角度出发,讲解如何在混淆的 JS 代码中定位 AES 加密、提取关键参数,并完整还原加密过程。
1. AES 基础回顾
AES(Advanced Encryption Standard)是一种对称加密算法,加解密使用同一个密钥。几个关键参数:
| 参数 | 说明 | 常见值 |
|---|---|---|
| 密钥长度 | 128 / 192 / 256 bit | AES-128 最常见 |
| 加密模式 | 块密码工作模式 | CBC(最常见)、ECB、CTR、GCM |
| 填充方式 | 数据不足时的补齐方式 | PKCS7(最常见)、ZeroPadding |
| IV(初始向量) | CBC 等模式需要 | 16 字节随机值 |
在逆向中,最重要的就是找到:密钥(Key)、IV(若使用 CBC 模式)和加密模式(Mode)。
2. 如何定位 AES 加密
2.1 搜索关键词法
在浏览器开发者工具中搜索以下关键词:
// 关键词搜索
AES
CryptoJS
encrypt
decrypt
wordArray
parse
createCipheriv
createDecipheriv
实战技巧:
- 全局搜索
CryptoJS→ 几乎可以确定使用了 AES - 搜索
encrypt→ 配合上下文判断 - 搜索
\"iv\"或\"mode\"→ 定位加密配置对象
2.2 特征定位法
AES 加密在 JS 中有一些固定特征模式:
CryptoJS 模式(最常见):
// 特征:CryptoJS.AES.encrypt / decrypt
const encrypted = CryptoJS.AES.encrypt(plaintext, key, {
iv: CryptoJS.enc.Utf8.parse('1234567890123456'),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
Web Crypto API 模式(现代浏览器):
// 特征:window.crypto.subtle.encrypt
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-CBC", iv: iv },
key,
data
);
Node.js crypto 模式:
// 特征:createCipheriv
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
2.3 Hook 断点法
当你找到可疑的加密函数后,在控制台 Hook 住它:
// Hook CryptoJS AES.encrypt
const originalEncrypt = CryptoJS.AES.encrypt;
CryptoJS.AES.encrypt = function() {
console.log('AES.encrypt 被调用:', arguments);
debugger; // 触发断点
return originalEncrypt.apply(this, arguments);
};
或者 Hook 更底层的 WordArray:
// Hook WordArray 的创建
const originalParse = CryptoJS.enc.Utf8.parse;
CryptoJS.enc.Utf8.parse = function() {
console.log('Utf8.parse 被调用:', arguments);
debugger;
return originalParse.apply(this, arguments);
};
3. 实战案例:定位加密参数
场景
一个登录接口有 encryptData 参数,我们需要逆向出它的生成逻辑。
Step 1: 搜索 encrypt 关键词
在 Sources 面板中搜索 encrypt,找到可疑函数:
function getEncryptData(t) {
var e = CryptoJS.enc.Utf8.parse("a1b2c3d4e5f6g7h8"),
n = CryptoJS.enc.Utf8.parse("1234567890123456"),
i = CryptoJS.AES.encrypt(t, e, {
iv: n,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return i.toString();
}
Step 2: 提取参数
一眼就能看出:
- 密钥(Key):
a1b2c3d4e5f6g7h8(16 字节,AES-128) - IV:
1234567890123456(16 字节) - 模式:CBC
- 填充:PKCS7
Step 3: 还原验证
在控制台直接调用验证:
// 验证加密结果
var key = CryptoJS.enc.Utf8.parse("a1b2c3d4e5f6g7h8");
var iv = CryptoJS.enc.Utf8.parse("1234567890123456");
var encrypted = CryptoJS.AES.encrypt("password123", key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log(encrypted.toString()); // 与抓包结果对比
4. 处理更复杂的情况
4.1 密钥被混淆
有时密钥不是明文,而是经过运算生成的:
// 混淆后的密钥生成
var _0x3f2e = function() {
var key = '';
var chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < 16; i++) {
key += chars.charAt(Math.floor(Math.random() * chars.length));
}
return key;
};
对策:在控制台执行这段函数,拿到实际生成的密钥。
4.2 自定义加密配置
有些网站会自定义 mode 或 padding:
// 自定义配置
var cfg = {
mode: CryptoJS.mode.ECB, // ECB 模式不需要 IV
padding: CryptoJS.pad.ZeroPadding // ZeroPadding
};
注意:ECB 模式不需要 IV,如果漏掉了这个会导致解密失败。
4.3 Base64 和 Hex 编码
密钥或密文可能经过额外编码:
// Base64 编码的密钥
var key = CryptoJS.enc.Base64.parse("YWJjZGVmZzEyMzQ1Njc4OTA=");
// Hex 编码的密钥
var key = CryptoJS.enc.Hex.parse("6162636465666731323334353637383930");
识别方法:
Base64.parse→ 密钥是 Base64 编码Hex.parse→ 密钥是 Hex 编码Utf8.parse→ 密钥是明文字符串
5. 完整还原模板
当你定位到 AES 加密后,可以用以下模板来还原整个加密过程:
Python 还原
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
def aes_encrypt(plaintext, key, iv, mode=AES.MODE_CBC):
"""
还原 JS AES 加密
:param plaintext: 明文字符串
:param key: 密钥(bytes,16/24/32 字节)
:param iv: 初始向量(bytes,16 字节,CBC 模式需要)
:param mode: 加密模式
:return: base64 编码的密文
"""
key = key.encode('utf-8')
iv = iv.encode('utf-8')
cipher = AES.new(key, mode, iv)
padded_data = pad(plaintext.encode('utf-8'), AES.block_size)
encrypted = cipher.encrypt(padded_data)
return base64.b64encode(encrypted).decode()
def aes_decrypt(ciphertext_b64, key, iv, mode=AES.MODE_CBC):
"""
还原 JS AES 解密
"""
key = key.encode('utf-8')
iv = iv.encode('utf-8')
cipher = AES.new(key, mode, iv)
encrypted = base64.b64decode(ciphertext_b64)
decrypted = cipher.decrypt(encrypted)
return unpad(decrypted, AES.block_size).decode()
# 使用示例
key = "a1b2c3d4e5f6g7h8"
iv = "1234567890123456"
result = aes_encrypt("password123", key, iv)
print(f"加密结果: {result}")
JavaScript(Node.js)还原
const CryptoJS = require("crypto-js");
function aesEncrypt(plaintext, key, iv) {
const keyWord = CryptoJS.enc.Utf8.parse(key);
const ivWord = CryptoJS.enc.Utf8.parse(iv);
const encrypted = CryptoJS.AES.encrypt(plaintext, keyWord, {
iv: ivWord,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
function aesDecrypt(ciphertext, key, iv) {
const keyWord = CryptoJS.enc.Utf8.parse(key);
const ivWord = CryptoJS.enc.Utf8.parse(iv);
const decrypted = CryptoJS.AES.decrypt(ciphertext, keyWord, {
iv: ivWord,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return CryptoJS.enc.Utf8.stringify(decrypted);
}
// 使用
console.log(aesEncrypt("password123", "a1b2c3d4e5f6g7h8", "1234567890123456"));
6. 调试技巧汇总
| 技巧 | 用法 |
|---|---|
| 断点定位 | 在 Sources 面板中给 CryptoJS.AES.encrypt 调用行打断点 |
| 堆栈追踪 | 断点触发后看 Call Stack,找到调用来源 |
| 控制台测试 | 在断点处直接在 Console 中执行表达式验证变量值 |
| 搜索侧信道 | 搜 iv、key、mode、padding 等配置关键词 |
| 重放验证 | 用 Python/Node 还原加密后对比抓包结果 |
7. 结语
AES 逆向的核心就三步:
- 定位 → 找到加密函数(搜索关键词或 Hook)
- 提取 → 拿到 key、iv、mode、padding
- 还原 → 用 Python/Node 重放验证
掌握了这个流程,绝大多数 JS 加密都能轻松应对。
声明:本文仅供学习交流,请勿用于非法用途。逆向分析应仅限于自己拥有权限的系统或 CTF 比赛环境。
本文由作者按照 CC BY 4.0 进行授权