Js逆向入门:AST技术解混淆(下篇)

admin 2024年10月13日17:40:03评论28 views字数 3954阅读13分10秒阅读模式

在上篇中给师傅们介绍了AST技术以及AST技术中常用模块的使用和代码示例,那么今天我们就主要讲解AST技术在Js反混淆中的利用,也就是如何使用AST技术还原混淆代码,并给出了两种最常见的反混淆示例

01

表达式还原

Js逆向入门:AST技术解混淆(下篇)

有时候,我们会看到有一些混淆的JavaScript代码其实就是把简单的东西复杂化,比如说

一个布尔常量true,被写成!![];

一个数字,被转化为parselnt加一些字符串的拼接

Js逆向入门:AST技术解混淆(下篇)

通过这些方式,一些简单又直观的表达式就被复杂化了。

这里我们给出一个简单的混淆代码作为示例

const a = !![];const b = "abc" == "bcd";const c = (1 << 3) | 2;const d = parseInt("5" + "0");

对于这种情况,有没有还原的方法呢?当然有,借助于AST,我们可以轻松实现。

Js逆向入门:AST技术解混淆(下篇)

首先,在=的右侧,其实都是一些表达式的类型,比如"abc"="bcd"就是一个BinaryExpression(二元表达式),他代表的是一个布尔类型的结果。

Js逆向入门:AST技术解混淆(下篇)

下面是Js中几种表达式类型和介绍

UnaryExpression(一元表达式):一元表达式指的是只涉及一个操作数的表达式。例如,取负值(-x)、递增(++x)、递减(--x)、逻辑非(!x)、按位非(~x)、类型转换(typeof x)等。在AST中,这种表达式通常包含一个操作符(如 -、++ 等)和一个操作数(即被操作的值)。

BinaryExpression(二元表达式):

二元表达式涉及两个操作数和一个操作符,用于执行算术运算(如加法 +、减法 -、乘法 *、除法 /)、比较(如等于 ==、不等于 !=、大于 >、小于 <)、逻辑运算(如与 &&、或 ||)等。

例如,a + b 是一个二元表达式,其中 + 是操作符,a 和 b 是操作数。

在AST中,这种表达式通常包含左操作数、操作符和右操作数。

ConditionalExpression(条件表达式):

条件表达式,也称为三元运算符,是一种特殊的表达式,它允许在单个语句中根据条件来返回不同的值。

语法为 condition ? exprIfTrue : exprIfFalse。

例如,x > 0 ? "positive" : "negative" 是一个条件表达式,如果 x 大于 0,则表达式的结果是 "positive",否则是 "negative"。

在AST中,这种表达式通常包含测试条件、当条件为真时的表达式和当条件为假时的表达式。

CallExpression(函数调用表达式):

函数调用表达式指的是对函数的调用,可以是一个简单的函数调用(如 foo()),也可以带有参数(如 foo(a, b))。

例如,Math.max(a, b) 是一个函数调用表达式,其中 Math.max 是被调用的函数,a 和 b 是传递给函数的参数。

在AST中,这种表达式通常包含被调用的函数(可以是一个标识符、成员表达式等)和传递给函数的参数列表。

Js逆向入门:AST技术解混淆(下篇)

怎么处理呢?我们将上述代码保存为cod2js,根据上篇中我们介绍到的知识,可以编写如下还原代码:

import traverse from "@babel/traverse";import { parse } from "@babel/parser";import generate from "@babel/generator";import * as types from "@babel/types";import fs from "fs";// 解析为ASTconst code = fs.readFileSync("../code_demo/code2.js", "utf-8");let ast = parse(code);// 遍历AST,遇到表达式时执行内部代码traverse(ast, {  // 匹配所有的表达式类型  "UnaryExpression|BinaryExpression|ConditionalExpression|CallExpression": (    path  ) => {    // path.evaluate()返回的是表达式的结果对象,包含confident和value属性    // confident为true时表示该表达式的计算可靠,没有隐患    // value为计算结果    const { confident, value } = path.evaluate;    // 判断value是否为正无穷或负无穷,是就直接退出    if (value == Infinity || value == -Infinity) return;    // 判断confident是否为true,是则替换表达式为value    confident && path.replaceWith(types.valueToNode(value));  },});const { code: output } = generate(ast);console.log(output);

'UnaryExpression|BinaryExpression|ConditionalExpression|CallExpression"作为对象的键名,分别用于处理一元表达式、布尔表达式、条件表达式、调用表达式如果AST对应的path对象符合这几种表达式,就会执行我们定义的回调方法。在回调方法里面

我们调用了path的evaluate方法,该方法会对path对象进行执行,计算所得到的结果。其内部实现会返回一个confident字段表示置信度,value字段表示计算结果

Js逆向入门:AST技术解混淆(下篇)

我这里将四个表达式的path.evaluate()打印如下,这样师傅们看着是不是就一目了然了

Js逆向入门:AST技术解混淆(下篇)

如果认定结果是可信的,那么confident就是true,我们就调用path的replaceWith方法把表达式的内容替换为执行的结果value,否侧不替换。

现在我们运行一下这个代码,结果如下

Js逆向入门:AST技术解混淆(下篇)

可以看到已经成功将code2.js代码中的表达式进行了还原

那么AST技术对表达式进行还原到这里就演示完了,其实原理很简单,就是利用AST去执行这个表达式并获取表达式的结果,再将代码中的表达式替换为执行结果即可

02

字符串还原

Js逆向入门:AST技术解混淆(下篇)

之前我们了解到,JavaScripti被混淆后,有些字符串会被转化为Unicode或者UTF-8编码的数据,比如说这个样子:

const string=["x68x65x6cx6cx6f","x77x6fx72x6cx64"];

其实这原本就是一个简单的字符串,被转换成UTF-8编码之后,其可读性大大降低了,如果这样的字符串被隐藏在JavaScript代码里面,我们想通过搜索字符串的方式寻找关键突破口,就搜不到了。

其实AST处理这种字符串的还原,就更简单了,因为将Js解析为AST时,其实字符串的真实值已经被解析出来了,这是什么意思呢,我们可以将这段Js代码解析为AST看看

Js逆向入门:AST技术解混淆(下篇)

代码如下

import traverse from "@babel/traverse";import { parse } from "@babel/parser";import generate from "@babel/generator";import * as types from "@babel/types";import fs from "fs";// 解析为ASTconst code = fs.readFileSync("../code_demo/code3.js", "utf-8");let ast = parse(code);traverse(ast, {  enter(path) {    console.log(path.node);  },});

运行结果如下

Js逆向入门:AST技术解混淆(下篇)

可以看到在node的extra中,虽然raw中仍然是Js代码的初始内容,但rawValue却是Js代码初始内容解析过后的真实值

也就是说将Js代码还原为AST时,会自动对字符串进行解析,并将解析结果存放到rawValue,所以我们如果想实现字符串的还原,只需要将raw的值替换为rawValue的值即可

Js逆向入门:AST技术解混淆(下篇)

代码如下

import traverse from "@babel/traverse";import { parse } from "@babel/parser";import generate from "@babel/generator";import fs from "fs";const code = fs.readFileSync("../code_demo/code3.js", "utf-8");let ast = parse(code);traverse(ast, {  StringLiteral({ node }) {    // extra.raw字符串中是否包含Unicode转义序列(u或x)。这是通过正则表达式/\[ux]/gi来实现的,    // 该正则表达式匹配u或x(由于在字符串中是转义字符,所以需要用\来表示它本身)。    if (node.extra && /\[ux]/gi.test(node.extra.raw)) {      node.extra.raw = node.extra.rawValue;    }  },});const { code: output } = generate(ast);console.log(output);

运行结果如下

Js逆向入门:AST技术解混淆(下篇)

可以看到我们已经成功利用AST技术还原了Js代码中混淆过的字符串,主要的原理就是Js代码解析为AST时,AST会自动解析字符串的真实内容并存储在AST对象,所以我们只需要将AST中Js混淆字符串替换为AST中存储的真实内容,理解起来也非常的简单

03

小结

Js逆向入门:AST技术解混淆(下篇)

AST在Js反混淆中还有许多利用方法,列如变量名/函数名还原,控制流平坦化等等,但都大差不差,主要就是利用AST的特性,师傅有有兴趣的可以自己再下去研究一下

有任何的疑惑和见解欢迎私信或留言哈,看到了都会回

Js逆向入门:AST技术解混淆(下篇)

原文始发于微信公众号(Daylight庆尘):Js逆向入门:AST技术解混淆(下篇)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月13日17:40:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Js逆向入门:AST技术解混淆(下篇)https://cn-sec.com/archives/3262339.html

发表评论

匿名网友 填写信息