网站Logo 小李的博客

某航空同盾AST解混淆和初步分析

xiaoli
17
2025-07-13

某航空同盾AST解混淆和初步分析

提示:仅供学习交流,不得用做商业交易,如有侵权请及时联系作者删除

混淆分析

首先将目标代码下载下来,在本地进行格式化(方便分析),可以看到整体代码格式很工整,一个自执行方法,传入了一个大串字符串

image-20250713223618858

将代码展开,可以看到首先就是一些花指令,包含了简单的运算(+,-,*,/)等等,然后就是一个自执行方法,传入一个函数,接着运行这个函数。

image-20250713224505348

那么就可以知道传入的这个函数就是正儿八经的整体逻辑了,展开后发现有一层经典的OB混淆,字符串都调用了Oooo0这个数组,获取其中的Index位置的字符串。image-20250713224726841

解密函数定位

从上分析得知,字符串混淆根据Oooo0这个数组得出,那么只需要拿到这个数组就可以反解成正常的字符串,根据代码可以看出 Oooo0 是由 ooQGOO(Oooo0)返回生成的,那么传入的参数就是第一个自执行方法传入的长串字符串了

image-20250713225040527

AST解混淆

根据上诉可得,只需要拿到ooQGOO函数,以及长串字符串即可成功解密混淆

image-20250713225154039

直接编写AST代码尝试进行解混淆

const fs = require('fs');
const babel = require('@babel/core');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const targetFilePath = './test/xxx.js';   // 代码源文件
const code = fs.readFileSync(targetFilePath, 'utf-8');

var Oooo0 = "decodeStr";  // 解密用到的长串字符串

// 解密用到的解密函数
function ooQGOO(QQ00O, QOoo0){
    return xxx;
}
// 解密后的数组
var decodeArray = ooQGOO(Oooo0);

// AST代码,匹配  MemberExpression 节点,对其进行替换
traverse(ast,
    {
        MemberExpression(path) {
            try {
                if (path.node.object.name == "Oooo0" && path.node.property.type == 'NumericLiteral') {
                    path.replaceWith(babel.types.StringLiteral(decodeArray[path.node.property.value]))
                }
            } catch (error) {
                // 处理可能的错误,例如访问不存在的属性
                console.error('处理CallExpression时出错:', error);
            }
        }
    }
);

const output = generate(ast, {}, code);
const outputFilePath = './test/output.js';
fs.writeFileSync(outputFilePath, output.code, 'utf-8');
console.log('解混淆完成,结果已保存到:', outputFilePath);



尝试第一次运行,发现会报错,某个函数不存在,秉承着缺什么补什么的原则,去源码里面找,发现是花指令中的一个,查看全部的花指令发现并不多,这里可以直接全部复制

image-20250713225701898

image-20250713225823830

那么此时代码就变成了下面的样式

const fs = require('fs');
const babel = require('@babel/core');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const targetFilePath = './test/xxx.js';   // 代码源文件
const code = fs.readFileSync(targetFilePath, 'utf-8');

// 花指令
function oQQOO(QQ00O, QOoo0) {
    return QQ00O <= QOoo0;
}

function QOQoQ(QQ00O, QOoo0) {
    return QQ00O != QOoo0;
}
//  xxxxx  记得全部复制,省事

var Oooo0 = "decodeStr";  // 解密用到的长串字符串

// 解密用到的解密函数
function ooQGOO(QQ00O, QOoo0){
    return xxx;
}
// 解密后的数组
var decodeArray = ooQGOO(Oooo0);

// AST代码,匹配  MemberExpression 节点,对其进行替换
traverse(ast,
    {
        MemberExpression(path) {
            try {
                if (path.node.object.name == "Oooo0" && path.node.property.type == 'NumericLiteral') {
                    path.replaceWith(babel.types.StringLiteral(decodeArray[path.node.property.value]))
                }
            } catch (error) {
                // 处理可能的错误,例如访问不存在的属性
                console.error('处理CallExpression时出错:', error);
            }
        }
    }
);

const output = generate(ast, {}, code);
const outputFilePath = './test/output.js';
fs.writeFileSync(outputFilePath, output.code, 'utf-8');
console.log('解混淆完成,结果已保存到:', outputFilePath);

继续尝试运行,发现报错window不存在,将window对象补上

image-20250713230030383

那么最终代码如下:

const fs = require('fs');
const babel = require('@babel/core');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const targetFilePath = './test/xxx.js';   // 代码源文件
const code = fs.readFileSync(targetFilePath, 'utf-8');

window = globalThis;

// 花指令
function oQQOO(QQ00O, QOoo0) {
    return QQ00O <= QOoo0;
}

function QOQoQ(QQ00O, QOoo0) {
    return QQ00O != QOoo0;
}
//  xxxxx  记得全部复制,省事

var Oooo0 = "decodeStr";  // 解密用到的长串字符串

// 解密用到的解密函数
function ooQGOO(QQ00O, QOoo0){
    return xxx;
}
// 解密后的数组
var decodeArray = ooQGOO(Oooo0);

// AST代码,匹配  MemberExpression 节点,对其进行替换
traverse(ast,
    {
        MemberExpression(path) {
            try {
                if (path.node.object.name == "Oooo0" && path.node.property.type == 'NumericLiteral') {
                    path.replaceWith(babel.types.StringLiteral(decodeArray[path.node.property.value]))
                }
            } catch (error) {
                // 处理可能的错误,例如访问不存在的属性
                console.error('处理CallExpression时出错:', error);
            }
        }
    }
);

const output = generate(ast, {}, code);
const outputFilePath = './test/output.js';
fs.writeFileSync(outputFilePath, output.code, 'utf-8');
console.log('解混淆完成,结果已保存到:', outputFilePath);

此时已经可以解除字符串混淆了,效果如下:

image-20250713230159886

image-20250713230207572

结语

解完混淆后发现,和上个版本的相比,关键加密代码差距挺大的,上个版本的3DES,hash128,RSA的明显特征这个版本基本搜不到,纯算的话还是需要点时间的,补环境话应该简单点。这里提供部分上个版本的指纹值,可以做下参考

image-20250713230746561

image-20250713230759819

image-20250713230811172