AST 技巧:模版(template)的高级用法

大家好,今天分享一下 AST 语法树中模版(template)的高级用法。

模版的作用

模版,就是 template,一般用来快速构造节点。例如要反混淆逻辑表达式,看一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const template = require("@babel/template").default;
const generator = require("@babel/generator").default;
const types = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;

ast = parser.parse('a + b && (c = d) ;');

const buildRequire = template(`
if(A){B;};
console.log(111)
`);

traverse(ast, {
LogicalExpression(path) {
let {node} = path;
path.replaceWithMultiple(buildRequire({'A': node.left, 'B': node.right}));
}
})

console.log(generator(ast).code);

运行结果:

image-20250525211058953

如果要自己构造节点的话,图中的 IfStatement 节点以及它的多个属性都要正确的构造,不仅繁琐而且很容易出错,代码可读性也很差,使用模版就很方便了。

image-20250525211332408

模版的使用

标识符占位

模版的使用有两种方法,一般情况下使用上文中的方法就足够了,使用一个标识符来占位,例如文中的 AB,标识符会在构造节点的时候被替换成具体的代码。

例如:

1
2
3
4
5
6
7
8
9
const buildRequire = template(`
var IMPORT_NAME = require(SOURCE);
`);

const ast = buildRequire({
IMPORT_NAME: t.identifier("myModule"),
SOURCE: t.stringLiteral("my-module"),
});
//const myModule = require("my-module");

但是最近在做猿人学 2024 新春题目的时候,发现如果使用标识符占位的话,有的时候需要打印某个大写的变量,模版也会尝试替换这个变量,这样的话会报错,这个时候可以使用模版的另一种不太常见的方法。

报错例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ast = parser.parse('a + b && (c = d) ;');

let ddd='ddd';
const buildRequire = template(`
console.log(new URL(),A)
`);

traverse(ast, {
LogicalExpression(path) {
let {node} = path;
path.replaceWithMultiple(buildRequire({'A': node.left, 'B': node.right}));
}
})

console.log(generator(ast).code);

image-20250525230100256

语法占位

语法占位符就是使用 %% 将需要占位的字符标识出来,可以防止与其他的大写字符冲突,可以在标识符可能出现问题的地方使用。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
const buildRequire = template(`
var %%importName%% = require(%%source%%+new URL());
`);

const ast = buildRequire({
importName: t.identifier("myModule"),
source: t.stringLiteral("my-module"),
});

console.log(generator(ast).code);
//var myModule = require("my-module" + new URL());

可以看到即使有大写字符,但是还是可以正常解析并且完成相应的功能。上面的代码仅作为示例,实际可能无法运行。

注:请注意,语法占位符是在 Babel 7.4.0 中引入的,如果使用低于此版本的 babel 可能会导致错误。

其他用法

模版还可以将组装好的代码直接解析为 AST 语法树,方便在反混淆的时候进行替换或者覆盖操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const name = "my-module";
const mod = "myModule";

const ast = template.ast`
var ${mod} = require("${name}");
`;
console.log(ast)
// {
// type: 'VariableDeclaration',
// kind: 'var',
// declarations: [
// {
// type: 'VariableDeclarator',
// id: [Object],
// init: [Object],
// loc: undefined
// }
// ],
// loc: undefined
// }

注:在使用 template 的时候别忘了 template 的返回值是一个函数,在进行节点替换的时候要执行函数传入可选参数才可以正常使用。

1
2
3
4
5
6
7
8
9
10
const source = "my-module";

const fn = template`
var IMPORT_NAME = require('${source}');
`;

console.log(fn)
console.log(typeof fn)
//[Function (anonymous)]
//function

template

默认情况下,根据解析的结果返回单个语句或语句数组。

template.statement

返回单个节点,如果传入的参数不是单个节点,则抛出异常。

template.statements

返回节点数组。

template.expression

返回表达式节点。

template.program

返回 Program 节点。

更多用法大家可以参考 babel 官网

总结

模版在 AST 反混淆中用的还算是比较多的,一般在可能遇到报错的地方使用语法占位符解决即可,但是切记两种方法不能混合使用,在使用的时候要注意自己使用的 babel 的版本,防止语法占位符不被支持。

本文章首发于个人博客 LLLibra146’s blog

本文作者:LLLibra146

更多文章请关注公众号 (LLLibra146):LLLibra146

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

本文链接
https://blog.d77.xyz/archives/a976747e.html