编辑
2025-06-02
jsVmp反编译
00
请注意,本文编写于 33 天前,最后修改于 12 天前,其中某些信息可能已经过时。

目录

一、CFG的构建
1. createGraphNode - 图节点创建核心
2. getBlockAst - 构建入口及整体流程
3. traverseLinkNode - 递归构建
3.1 创建空节点并连边
3.2 结束条件
3.3 代理astList自动建图节点
3.4 代理执行栈自动生成变量声明
3.5 避免重复递归
4. excuteInstruction - 指令执行模拟,每个网站的vmp不同的部分
5. 构建结构化代码块
6. 结果图实例
二、SSA的转化与代码结构还原

一、CFG的构建

1. createGraphNode - 图节点创建核心

javascript
function createGraphNode(nodeGraph, astNode, fromPc, type) { const node = { astNode: astNode, // 关联的AST语法节点 fromPc, // 对应的程序计数器位置 postNodes: [], // 后继节点索引数组 index: nodeGraph.length, // 当前节点在nodeGraph中的索引 type // 节点类型("direct"顺序/"jump"跳转等) }; nodeGraph.push(node); return node; }

目的:为每条语句或表达式创建图节点,承载控制流信息

2. getBlockAst - 构建入口及整体流程

javascript
function getBlockAst(startPc, noProxystack, blockEndPc, stackTop) { const nodeGraph = []; // 存储语句节点 const pcNodeMap = new Map(); // 记录PC对应的图节点 const enterNode = createGraphNode(nodeGraph, t.emptyStatement(), startPc); // 递归构建控制流图 traverseLinkNode(startPc, noProxystack, enterNode, stackTop); //后续进行代码结构的还原 const blockAst = convertBlockAst(nodeGraph, enterNode); return blockAst; }

参数说明

  • startPc:字节码起始地址
  • noProxystack:操作栈
  • blockEndPc:代码块结束地址
  • stackTop:当前栈顶指针位置

3. traverseLinkNode - 递归构建

代码:

javascript
function traverseLinkNode(pc, noProxystack, preNode, stackTop) { //每个pc的访问都需要创建一个空节点,用做节点的连接。 const firstNode = createGraphNode(nodeGraph, t.emptyStatement(), pc); //把空语句节点连接前节点的后驱,并让前节点引用指向当前节点 preNode.postNodes.push(firstNode.index); preNode = firstNode; if (pc >= blockEndPc) { return; } var startPc = pc; var breakFlag = false; function handlerForAstList() { } function createProxyStack(noProxystack) { return new Proxy(noProxystack, handlerForStack()); } function handlerForStack() { } const proxyStack = createProxyStack(noProxystack); var astList = []; const proxyAstList = new Proxy(astList, handlerForAstList()); const visNode = pcNodeMap.get(pc); if (visNode) { preNode.postNodes.push(visNode.index); return; } pc = excuteInstruction(pc, proxyStack, proxyAstList); pcNodeMap.set(startPc, firstNode); if (breakFlag || pc >= blockEndPc) { return; } traverseLinkNode(pc, noProxystack, preNode, blockEndPc, stackTop); function excuteInstruction(pc, valueStack, astList) { } const blockAst = convertBlockAst(nodeGraph, enterNode, fucCount);//后续进行控制流还原 return blockAst; }

3.1 创建空节点并连边

javascript
const firstNode = createGraphNode(nodeGraph, t.emptyStatement(), pc); preNode.postNodes.push(firstNode.index); preNode = firstNode;

作用:每次递归入口创建空节点

3.2 结束条件

javascript
if (pc >= blockEndPc) { return; }

作用:超过块范围时停止递归

3.3 代理astList自动建图节点

javascript
const proxyAstList = new Proxy(astList, { get(target, prop, receiver) { if (prop === 'push') { return (astNode, type) => { const cloneAstNode = t.cloneNode(astNode); const node = createGraphNode(nodeGraph, cloneAstNode, startPc, type || "direct"); preNode.postNodes.push(node.index); preNode = node; if (t.isReturnStatement(astNode) || t.isThrowStatement(astNode)) { breakFlag = true; // 遇到终止语句停止递归 } return node; }; } return Reflect.get(target, prop, receiver); } });

功能

  • 所有推入astList的AST节点自动生成图节点
  • 遇到return/throw语句设置breakFlag停止后续递归遍历

3.4 代理执行栈自动生成变量声明

javascript
const proxyStack = new Proxy(noProxystack, { set(target, prop, value, receiver) { if (!isNaN(prop)) { if (t.isNode(value)) { const id = t.identifier(`temp${prop}`); const assignStmt = t.variableDeclaration("let", [ t.variableDeclarator(id, value) ]); const node = proxyAstList.push(assignStmt); value = id; } return Reflect.set(target, prop, value, receiver); } } });

功能:执行栈写入AST节点时自动生成临时变量声明语句

3.5 避免重复递归

javascript
const visNode = pcNodeMap.get(pc); if (visNode) { preNode.postNodes.push(visNode.index); return; }

作用:访问已处理节点时直接连接,防止重复生成和死循环

4. excuteInstruction - 指令执行模拟,每个网站的vmp不同的部分

javascript
function excuteInstruction(pc, valueStack, astList) { const instructionCode = instruction[pc++]; switch (instructionCode) { // 算术指令示例 case 0: // 原: // valueStack[stackTop] = valueStack[stackTop] + valueStack[stackTop - 1]; // 实际AST构造 valueStack[stackTop] = t.binaryExpression( "+", valueStack[stackTop], valueStack[stackTop - 1] ); break; // 条件跳转指令示例 case 1: //原: // const gotoValue = instruction[pc++]; // valueStack[stackTop] && pc + gotoValue; // break; //实际处理: const gotoValue = instruction[pc++]; const test = valueStack[stackTop]; proxyAstList.push(test); // 自动创建条件节点 // 克隆当前栈状态 const ifTrueStack = shallowCloneArray(noProxystack); const ifFalseStack = shallowCloneArray(noProxystack); // 递归处理两个分支 traverseLinkNode(pc + gotoValue, ifTrueStack, preNode, stackTop); traverseLinkNode(pc, ifFalseStack, preNode, stackTop); breakFlag = true; break; } return pc; }

功能

  • 普通指令:构建一个ast表达式模拟指令的操作
  • 跳转指令:递归遍历分支路径构建完整语句图节点

5. 构建结构化代码块

javascript
const blockAst = convertBlockAst(nodeGraph, enterNode);

作用:将语句图交给后续的还原模块,生成结构化代码块。

6. 结果图实例

image.png

可以看到每一个代码语句或表达式都是图中的一个节点,并且正确构建了连接关系

二、SSA的转化与代码结构还原

详细见http://dtt666.com/post/3

本文作者:韦峰

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!