quickjs程序逆向
字数 1554 2025-08-06 18:07:40
QuickJS程序逆向分析教学文档
1. QuickJS简介
QuickJS是一个轻量级的JavaScript引擎,具有以下特点:
- 体积小且速度快
- 支持ES2020规范
- 可以将JS编译为字节码
- 包含解释器(qjs)和编译器(qjsc)
主要组件:
qjs: JavaScript解释器,直接执行JS代码qjsc: 编译器,将JS文件编译为C文件(包含字节码)qjscalc: JS计算器
2. QuickJS编译流程分析
2.1 编译过程
-
从QuickJS官网下载并编译选定版本
-
使用qjsc将JS文件编译为C文件:
./qjsc -e -o output.c input.js-e选项输出C文件,否则直接编译为可执行文件
-
编译生成的C文件包含:
- 字节码数组(如
qjsc_hello) - 运行时初始化代码
- 主函数调用链:
main -> eval_file -> eval_buf -> JS_EvalFunction -> JS_EvalFunctionInternal -> JS_CallFree -> JS_CallInternal
- 字节码数组(如
2.2 字节码结构
字节码存储在C文件的数组中,如:
const uint8_t qjsc_hello[78] = {
0x02, 0x04, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x6f,
0x6c, 0x65, 0x06, 0x6c, 0x6f, 0x67, 0x16, 0x4c,
// ... 更多字节码
};
3. QuickJS逆向技术
3.1 字节码提取与反编译
-
修改QuickJS源码以启用字节码dump功能:
- 取消
quickjs.c中DUMP_BYTECODE宏的注释 - 在
JS_ReadFunctionTag函数的return前插入js_dump_function_bytecode调用
- 取消
-
关键patch点:
// patch1: 启用字节码dump
#define DUMP_BYTECODE (1)
// patch2: 在适当位置插入dump调用
#if DUMP_BYTECODE
js_dump_function_bytecode(ctx, b);
#endif
- 重新编译QuickJS后,执行时会输出易读的字节码
3.2 字节码分析
QuickJS虚拟机特点:
- 基于栈的虚拟机
- 操作码定义在
quickjs-opcode.h中 - 常见指令:
call: 调用函数,后跟参数个数call_method: 调用对象方法get: 获取参数入栈push: 直接压入栈put: 将栈中数据存放至变量
4. 实战案例:2022YCB-easyre逆向
4.1 题目分析
-
识别QuickJS编译的可执行文件特征:
- 字符串中包含"quickjs.c"
- 存在大量
js_开头的函数
-
提取字节码:
- 从二进制中提取4513字节的机器码
- 替换自定义C文件中的字节码数组
-
编译并运行修改后的程序,获取反编译输出
4.2 加密流程分析
程序主要逻辑:
data3 = [5911837666743816200, 5133585975960501272, ...] # 15个元素的数组
data4 = [2101524238053948931, 9154814254531429383, 8941618984500083987]
data5 = [0]*12
# 加密流程
data2 = myenc.encode64(enc1(input, k)) # 魔改XXTEA + 换表base64
for i in range(len(data2)): # 异或处理
data2[i] ^= data2[(i+2)%len(data2)]
for i in range(0, len(data2), 8): # 数据转换
if i%32 != 8:
arg = [data2[i+j] for j in range(8)]
data5[index] = func5(arg)
else:
data5[index] = data4[index1]
index1 += 1
index += 1
# 校验
z = 0
for i in range(3):
if func6(i):
z += 1
if z == 3:
print('suc')
else:
print('nono')
4.3 关键函数分析
4.3.1 encode64函数
- 换表base64编码
- 使用自定义字母表:
keystr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/="
4.3.2 enc1函数
- 魔改XXTEA加密
- 修改参数:
- DELTA = 289739796
- 位运算参数:6、3、4、5
- 密钥:
k = "welcometoycb2022"
4.3.3 func6校验函数
- 将输入分为4个8字节一组(x,y,z,w)
- 进行复杂逻辑运算后与data5比对
- 约束条件:每组第二个数据(y)必须等于data4中的数据
4.4 解题步骤
-
约束求解:
- 使用Z3求解器解func6的方程
- 确保y值等于data4中的对应值
from z3 import * data3 = [...] # 原始数据 data4 = [...] # 约束数据 for i in range(3): x = BitVec('x',65) y = BitVec('y',65) z = BitVec('z',65) w = BitVec('w',65) s = Solver() s.add((~x)&z == data3[5*i]) s.add(w ^ (((~y)&x) | ((~y)&z) | (x&y)| ((~x)&z)) == data3[5*i+1]) # 添加更多约束... s.add(y == data4[i]) print(s.check()) m = s.model() # 获取解... -
数据转换:
- 将求解结果转换为字节序列
- 逆向处理异或操作:
for i in range(len(ms)): ms[i] ^= ms[(i+2)%len(ms)]
-
Base64解码:
- 使用自定义字母表逆向换表base64
-
XXTEA解密:
- 实现魔改版XXTEA解密算法:
DELTA = 289739796 def decrypt(v, n, k): rounds = 6 + int(52 / n) sum = c_uint32(rounds * DELTA) y = v[0].value while rounds > 0: e = (sum.value >> 2) & 3 p = n - 1 while p > 0: z = v[p - 1].value v[p].value -= (((z >> 6 ^ y << 3) + (y >> 4 ^ z << 5)) ^ ((sum.value ^ y) + (k[(p & 3) ^ e] ^ z))) y = v[p].value p -= 1 z = v[n - 1].value v[0].value -= (((z >> 6 ^ y << 3) + (y >> 4 ^ z << 5)) ^ ((sum.value ^ y) + (k[(p & 3) ^ e] ^ z))) y = v[0].value sum.value -= DELTA rounds -= 1
- 实现魔改版XXTEA解密算法:
5. 总结与防御建议
5.1 逆向技巧总结
- 识别QuickJS特征:字符串、函数名等
- 修改源码启用字节码dump功能
- 理解基于栈的虚拟机工作原理
- 分析关键加密函数(如魔改XXTEA)
- 使用约束求解器处理复杂逻辑
5.2 防御建议
- 对关键代码进行混淆
- 使用更复杂的加密算法组合
- 增加反调试机制
- 使用原生代码实现核心算法
6. 参考资源
- QuickJS官方源码
- QuickJS内部实现分析文章
- XXTEA算法标准实现
- Z3求解器官方文档