AST案例

文章 ---

逆向进阶,利用 AST 技术还原 JavaScript 混淆代码

AST-web端javascript逆向杀器之常用API

 

安装

  1. @babel/core:Babel 编译器本身,提供了 babel 的编译 API;

  2. @babel/parser:将 JavaScript 代码解析成 AST 语法树;

  3. @babel/traverse:遍历、修改 AST 语法树的各个节点;

  4. @babel/generator:将 AST 还原成 JavaScript 代码;

  5. @babel/types:判断、验证节点的类型、构建新 AST 节点等。

引入

// parse 导包
const parse = require("@babel/parser").parse;
const parse = require("@babel/core").parse;

// traverse导包
const traverse = require("@babel/traverse").default
const traverse = require("@babel/core").traverse

// generate 导包
const generate = require("@babel/generator").default
// esm 导包
import {parse} from '@babel/parser'
import {traverse} from "@babel/core"
import types from "@babel/types"
import generateModul from "@babel/generator"

let generate = generateModul.default

基本流程

源代码 -> AST -> AST变换 -> AST -> 目标代码

/*
ESM 导包
*/
import {parse} from "@babel/parser"
import traverseModul from "@babel/traverse"
import generateModul from "@babel/generator"
import { types } from "@babel/core"
let traverse = traverseModul.default
let generate = generateModul.default

const code = `
源代码....
`

// 词法分析+ 语法分析 转换为
const ast = parse(code)

const visitor = {
   // 插件信息
}

// AST 转换
traverse(ast, visitor)

// 生成目标代码
const result = generate(ast, {
     "jsescOption": { // 配置中文输出
       "minimal": true
  }
})
console.log(result.code)

 

箭头函数变换

一、变换规则

  • 源代码

const sum=(a,b)=>a+b;
  • 目标代码

const sum = function(a,b){ return a + b }

二 代码演示

//babel 核心库,用来实现核心转换引擎, 类型判断,生成AST零部件
import { transform, types } from 'babel-core';

//源代码
const code = `const sum=(a,b)=>a+b;` //目标代码 const sum = function(a,b){ return a + b }

//插件对象,可以对特定类型的节点进行处理
let visitor = {
   //代表处理 ArrowFunctionExpression 节点
   ArrowFunctionExpression(path){
       let params = path.node.params;
       //创建一个blockStatement
       // let blockStatement = types.blockStatement([
       //     types.returnStatement(types.binaryExpression(
       //         '+',
       //         types.identifier('a'),
       //         types.identifier('b')
       //     ))
       // ]);
       let blockStatement = types.blockStatement([
           types.returnStatement(path.node.body)
      ]);
       //创建一个函数
       let func = types.functionExpression(null, params, blockStatement, false, false);
       //替换
       path.replaceWith(func);
  }
}

//transform方法转换code
//babel先将代码转换成ast,然后进行遍历,最后输出code
let result = transform(code,{
   plugins:[
      {
           visitor
      }
  ]
})

console.log(result.code);

变量申明分离

变换规则

  • 源代码

var a = 123,b = 456;
let c = 789,d = 120;
  • 目标代码

var a = 123;
var b = 456;
var c = 789;
var d = 120;

代码实现

//babel 核心库,用来实现核心转换引擎, 类型判断,生成AST零部件
import { transform, types } from 'babel-core';

//源代码
const code = `
var a = 123,b = 456;
let c = 789,d = 120;
`
//目标代码 const sum = function(a,b){ return a + b }

let visitor = {
VariableDeclaration(path){
path.node.kind = "var"
let {node} = path;
if (node.declarations.length == 1){
return // 只有一个变量申明的跳过
}
let {kind, declarations} = node;
let newNodes = []
for (const varNode of declarations){
let varDecNode = types.VariableDeclaration(kind, [varNode]);
newNodes.push(varDecNode)
}
// 替换多个字段
path.replaceWithMultiple(newNodes)
}
}

let result = transform(code,{
plugins:[
{
visitor
}
]
})

console.log(result.code);

unicode 转 中文

一、变换规则

  • 源码

console['\u006c\u006f\u0067']('\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u006f\u0072\u006c\u0064\u0021')
  • 目标代码

console["log"]("Hello world!")

二、 插件代码

import {parse} from "@babel/parser"
import traverseModul from "@babel/traverse"
import generateModul from "@babel/generator"
let traverse = traverseModul.default
let generate = generateModul.default


let code = `
console['\u006c\u006f\u0067']('\u0048\u0065\u006c\u006c\u006f\u0020\u0077\u006f\u0072\u006c\u0064\u0021');
console.log("中文")
`
let visitor = {
StringLiteral(path){
path.node.extra.raw = path.node.extra.value
}
}

let ast = parse(code)
traverse(ast, visitor)
let result = generate(ast, {
"jsescOption": { // 配置中文输出
"minimal": true
}
}).code
console.log(result);

执行表达式

一 变换规则

let {confident, value} = path.evaluate(): 执行代码

  • 源代码

const a = !![]+!![]+!![];
const b = Math.floor(12.34 * 2.12)
const c = 10 >> 3 << 1
const d = String(21.3 + 14 * 1.32)
const e = parseInt("1.893" + "45.9088")
const f = parseFloat("23.2334" + "21.89112")
const g = 20 < 18 ? '未成年' : '成年'
const a = 3;
const b = 26;
const c = 2;
const d = "39.78";
const e = parseInt("1.89345.9088");
const f = parseFloat("23.233421.89112");
const g = "\u6210\u5E74";

二、变换代码

import {parse} from "@babel/parser"
import traverseModul from "@babel/traverse"
import generateModul from "@babel/generator"
import { types } from "@babel/core"
let traverse = traverseModul.default
let generate = generateModul.default


let code =`
const a = !![]+!![]+!![];
const b = Math.floor(12.34 * 2.12)
const c = 10 >> 3 << 1
const d = String(21.3 + 14 * 1.32)
const e = parseInt("1.893" + "45.9088")
const f = parseFloat("23.2334" + "21.89112")
const g = 20 < 18 ? '未成年' : '成年'
`

let ast = parse(code);

let visitor = {
"BinaryExpression|CallExpression|ConditionalExpression"(path){
// 执行表达式
let {confident, value} = path.evaluate(); // 执行语句
if (confident ){
path.replaceInline(types.valueToNode(value))
}
}
}
traverse(ast, visitor)

let result = generate(ast).code

删除未使用的变量

const binding = path.scope.getBinding(path.node.id.name) -> 在作用域中获取变量; binding.referenced -> 变量的引用次数

一、变换规则

const a = 1;
const b = a * 2;
const c = 2;
const d = b + 1;
const e = 3;
console.log(d)
  • 目标代码

const a = 1;
const b = a * 2;
const d = b + 1;
console.log(d);

二、代码实现

import {parse} from "@babel/parser"
import traverseModul from "@babel/traverse"
import generateModul from "@babel/generator"
import { types } from "@babel/core"
let traverse = traverseModul.default
let generate = generateModul.default

const code = `
const a = 1;
const b = a * 2;
const c = 2;
const d = b + 1;
const e = 3;
console.log(d)
`
const ast = parse(code)

const visitor = {
VariableDeclarator(path){
const binding = path.scope.getBinding(path.node.id.name);
// 如标识符被修改过,则不能进行删除动作。
if (!binding || binding.constantViolations.length > 0) {
return;
}

// 未被引用
if (!binding.referenced) {
path.remove();
}

// 被引用次数为0
// if (binding.references === 0) {
// path.remove();
// }

// 长度为0,变量没有被引用过
// if (binding.referencePaths.length === 0) {
// path.remove();
// }
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)

 

删除冗余逻辑代码

一 、变换规则

  • 源代码

const example = function () {
let a;
if (false) {
a = 1;
} else {
if (1) {
a = 2;
}
else {
a = 3;
}
}
return a;
};
  • 目标代码

const example = function () {
let a;
a = 2;
return a;
};

二、代码实现

import {parse} from "@babel/parser"
import traverseModul from "@babel/traverse"
import generateModul from "@babel/generator"
import { types } from "@babel/core"
let traverse = traverseModul.default
let generate = generateModul.default

const code = `
const example = function () {
let a;
if (false) {
a = 1;
} else {
if (1) {
a = 2;
}
else {
a = 3;
}
}
return a;
};
`
const ast = parse(code)

const visitor = {
IfStatement(path){
let {node} = path;
if (node.test.value){
path.replaceInline(node.consequent.body)
} else{
if (node.alternate){
path.replaceInline(node.alternate.body)

}else {
path.remove()
}
}
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)

switch平坦化

变换规则

  • 源代码

const _0x34e16a = '3,4,0,5,1,2'['split'](',');
let _0x2eff02 = 0x0;
while (!![]) {
switch (_0x34e16a[_0x2eff02++]) {
case'0':
let _0x38cb15 = _0x4588f1 + _0x470e97;
continue;
case'1':
let _0x1e0e5e = _0x37b9f3[_0x50cee0(0x2e0, 0x2e8, 0x2e1, 0x2e4)];
continue;
case'2':
let _0x35d732 = [_0x388d4b(-0x134, -0x134, -0x139, -0x138)](_0x38cb15 >> _0x4588f1);
continue;
case'3':
let _0x4588f1 = 0x1;
continue;
case'4':
let _0x470e97 = 0x2;
continue;
case'5':
let _0x37b9f3 = 0x5 || _0x38cb15;
continue;
}
break;
}
  • 目标代码

let _0x4588f1 = 0x1;
let _0x470e97 = 0x2;
let _0x38cb15 = _0x4588f1 + _0x470e97;
let _0x37b9f3 = 0x5 || _0x38cb15;
let _0x1e0e5e = _0x37b9f3[_0x50cee0(0x2e0, 0x2e8, 0x2e1, 0x2e4)];
let _0x35d732 = [_0x388d4b(-0x134, -0x134, -0x139, -0x138)](_0x38cb15 >> _0x4588f1);

二、实现

方式一

import {parse} from "@babel/parser"
import traverseModul from "@babel/traverse"
import generateModul from "@babel/generator"
import { types } from "@babel/core"
let traverse = traverseModul.default
let generate = generateModul.default

const code = `
const _0x34e16a = '3,4,0,5,1,2'['split'](',');
let _0x2eff02 = 0x0;
while (!![]) {
switch (_0x34e16a[_0x2eff02++]) {
case'0':
let _0x38cb15 = _0x4588f1 + _0x470e97;
continue;
case'1':
let _0x1e0e5e = _0x37b9f3[_0x50cee0(0x2e0, 0x2e8, 0x2e1, 0x2e4)];
continue;
case'2':
let _0x35d732 = [_0x388d4b(-0x134, -0x134, -0x139, -0x138)](_0x38cb15 >> _0x4588f1);
continue;
case'3':
let _0x4588f1 = 0x1;
continue;
case'4':
let _0x470e97 = 0x2;
continue;
case'5':
let _0x37b9f3 = 0x5 || _0x38cb15;
continue;
}
break;
}
`
const ast = parse(code)
const visitor = {
WhileStatement(path) {
// switch 节点
let switchNode = path.node.body.body[0];
// switch 语句内的控制流数组名,本例中是 _0x34e16a
let arrayName = switchNode.discriminant.object.name;
// 获得所有 while 前面的兄弟节点,本例中获取到的是声明两个变量的节点,即 const _0x34e16a 和 let _0x2eff02
let prevSiblings = path.getAllPrevSiblings();
// 定义缓存控制流数组
let array = []
// forEach 方法遍历所有节点
prevSiblings.forEach(pervNode => {
let {id, init} = pervNode.node.declarations[0];
// 如果节点 id.name 与 switch 语句内的控制流数组名相同
if (arrayName === id.name) {
// 获取节点整个表达式的参数、分割方法、分隔符
let object = init.callee.object.value;
let property = init.callee.property.value;
let argument = init.arguments[0].value;
// 模拟执行 '3,4,0,5,1,2'['split'](',') 语句
array = object[property](argument)
// 也可以直接取参数进行分割,方法不通用,比如分隔符换成 | 就不行了
// array = init.callee.object.value.split(',');
}
// 前面的兄弟节点就可以删除了
pervNode.remove();
});

// 储存正确顺序的控制流语句
let replace = [];
// 遍历控制流数组,按正确顺序取 case 内容
array.forEach(index => {
let consequent = switchNode.cases[index].consequent;
// 如果最后一个节点是 continue 语句,则删除 ContinueStatement 节点
if (types.isContinueStatement(consequent[consequent.length - 1])) {
consequent.pop();
}
// concat 方法拼接多个数组,即正确顺序的 case 内容
replace = replace.concat(consequent);
}
);
// 替换整个 while 节点,两种方法都可以
path.replaceWithMultiple(replace);
// path.replaceInline(replace);
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)
  • 方式二

import {parse} from "@babel/parser"
import traverseModul from "@babel/traverse"
import generateModul from "@babel/generator"
import { types } from "@babel/core"
let traverse = traverseModul.default
let generate = generateModul.default

const code = `
const _0x34e16a = '3,4,0,5,1,2'['split'](',');
let _0x2eff02 = 0x0;
while (!![]) {
switch (_0x34e16a[_0x2eff02++]) {
case'0':
let _0x38cb15 = _0x4588f1 + _0x470e97;
continue;
case'1':
let _0x1e0e5e = _0x37b9f3[_0x50cee0(0x2e0, 0x2e8, 0x2e1, 0x2e4)];
continue;
case'2':
let _0x35d732 = [_0x388d4b(-0x134, -0x134, -0x139, -0x138)](_0x38cb15 >> _0x4588f1);
continue;
case'3':
let _0x4588f1 = 0x1;
continue;
case'4':
let _0x470e97 = 0x2;
continue;
case'5':
let _0x37b9f3 = 0x5 || _0x38cb15;
continue;
}
break;
}
`
const ast = parse(code)

const visitor = {
WhileStatement(path) {
// switch 节点
let switchNode = path.node.body.body[0];
// switch 语句内的控制流数组名,本例中是 _0x34e16a
let arrayName = switchNode.discriminant.object.name;
// 获取控制流数组绑定的节点
let bindingArray = path.scope.getBinding(arrayName);
// 获取节点整个表达式的参数、分割方法、分隔符
let init = bindingArray.path.node.init;
let object = init.callee.object.value;
let property = init.callee.property.value;
let argument = init.arguments[0].value;
// 模拟执行 '3,4,0,5,1,2'['split'](',') 语句
let array = object[property](argument)
// 也可以直接取参数进行分割,方法不通用,比如分隔符换成 | 就不行了
// let array = init.callee.object.value.split(',');

// switch 语句内的控制流自增变量名,本例中是 _0x2eff02
let autoIncrementName = switchNode.discriminant.property.argument.name;
// 获取控制流自增变量名绑定的节点
let bindingAutoIncrement = path.scope.getBinding(autoIncrementName);
// 可选择的操作:删除控制流数组绑定的节点、自增变量名绑定的节点
bindingArray.path.remove();
bindingAutoIncrement.path.remove();

// 储存正确顺序的控制流语句
let replace = [];
// 遍历控制流数组,按正确顺序取 case 内容
array.forEach(index => {
let consequent = switchNode.cases[index].consequent;
// 如果最后一个节点是 continue 语句,则删除 ContinueStatement 节点
if (types.isContinueStatement(consequent[consequent.length - 1])) {
consequent.pop();
}
// concat 方法拼接多个数组,即正确顺序的 case 内容
replace = replace.concat(consequent);
}
);
// 替换整个 while 节点,两种方法都可以
path.replaceWithMultiple(replace);
// path.replaceInline(replace);
}
}

traverse(ast, visitor)
const result = generate(ast)
console.log(result.code)
 
posted @ 2022-12-19 19:27  柳帅  阅读(126)  评论(0编辑  收藏  举报
//替换成自己路径的js文件