吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 626|回复: 0
收起左侧

[Web逆向] 浅谈OB混淆及其变体特征

[复制链接]
xiaoxiao0329 发表于 2026-3-24 18:28

前言

普通的ob混淆,可以 根据这些网址来进行还原:
https://webcrack.netlify.app/
https://obf-io.deobfuscate.io/

当请求返回的JS代码无法通过这些网址进行反混淆只能解一部分混淆时,就要通过AST,解析代码,然后进行还原。
AST基础可以参考:
视频学习AST基础:
https://www.bilibili.com/video/BV1FQ7pzZEmc?vd_source=61bd07e56393e4c482b0f4e60b0a66c6

这篇博客,总结的很全面:
https://blog.51cto.com/u_11866025/6047324

这篇文章,简单的动态js还原:
https://www.52pojie.cn/thread-2085320-1-1.html

一. OB混淆的基本特征

1.一般由一个大数组或者包含大数组的函数,一个自执行函数,一个解密函数构成;
2.大数组中的列表元素一般为字符串,可能为明文,也可能是经过base64等编码,还可能是经过RC4算法加密的密文;
3.自执行函数,自执行函数的参数,通常为包括 大数组/包含大数组的函数[如果大数组或者大数组函数不在参数上,那就可能在函数体内],偏移量或者计算偏移量的相关参数[数值,十六进制显示];自执行函数会根据偏移量的要求对大数组列表的元素进行位移,因此函数体应该会包含 push,shift关键字;
4.解密函数,如果解密函数中包含类似 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=' 的变量(可能字符顺序不一样),大数组的列表的元素很可能是经过了 base64编码,需要解码;
5.函数名和变量名通常以_0x或者0x开头,后接1~6位数字或字母组合;

简单样例,数组位移:
// 列表元素
var _0x30bc = ['log', 'Hello\x20World!'];

// 自执行函数,加密函数,实现列表元素位移
(function (_0x36d89d, _0x30bcb2) {
    var _0xae0a32 = function (_0x2e4e9d) {
        while (--_0x2e4e9d) {
            _0x36d89d['push'](_0x36d89d['shift']());
        }
    };
    _0xae0a32(++_0x30bcb2);
}(_0x30bc, 0x133));

// 解密函数
var _0xae0a = function (_0x38d89d) {
    _0x38d89d = _0x38d89d - 0x0;
    var _0xae0a32 = _0x30bc[_0x38d89d];
    return _0xae0a32;
};
// ob混淆的js代码
function hi() {
    console[_0xae0a('0x1')](_0xae0a('0x0'));
}

hi();
加上 base64 编码的样例:
// 列表元素
var _0x30bc = [ 'Bg9N','AgvSBg8GD29YBgq'];

// 自执行函数,加密函数,实现列表元素位移
(function (_0x36d89d, _0x30bcb2) {
    var _0xae0a32 = function (_0x2e4e9d) {
        while (--_0x2e4e9d) {
            _0x36d89d['push'](_0x36d89d['shift']());
        }
    };
    _0xae0a32(++_0x30bcb2);
}(_0x30bc, 0x101));

// 解密函数
var _0xae0a = function (_0x38d89d) {
    function base64_decode(t) {
        // base64解码
        for (var e, n, r = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=", o = "", a = "", c = 0, i = 0;
             n = t.charAt(i++);
             ~n && (e = c % 4 ? e * 64 + n : n, c++ % 4) ? o += String.fromCharCode(e >> (c * -2 & 6) & 255) : 0) {
            n = r.indexOf(n);
        }
        for (var u = 0, s = o.length; u < s; u++) {
            a += "%" + ("00" + o.charCodeAt(u).toString(16)).slice(-2);
        }
        return decodeURIComponent(a);
    }

    _0x38d89d = _0x38d89d - 0x0;
    var _0xae0a32 = _0x30bc[_0x38d89d];
    return base64_decode(_0xae0a32);
};

// ob混淆的js代码
function hi() {
    console[_0xae0a('0x1')](_0xae0a('0x0'));
}

hi();
还可以解密函数上
1. 添加  RC4算法解密;
2. 添加  格式化检查语句;
动态识别OB混淆

动态识别OB数组混淆都是根据其基本特征来进行识别还原, 如

  1. 识别 大数组或者大数组函数,可以通过 找到 数组 或者 包含数组的函数,并且数组里面的元素都必须是 字符串
  2. 识别 解密函数,可以根据函数是否包含 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='这样子的字符串来识别

二. OB混淆变体情况一

想要让动态识别OB混淆失效,就可以打破OB混淆基本特征的约束,比如:

  1. 数组列表的元素要求必须是字符串,那就给某个列表元素的数据类型改为变量,或者三元表达式之类的
  2. 数组/数组函数,自执行函数,解密函数 是ob数组混淆的三要素,可以去掉自执行函数,在数组和解密函数中下功夫,数组的列表元素经过 base64编码或者RC4算法加密
  3. 函数名和变量名通常以_0x或者0x开头,那就把 _0x或者0x替换成其他字符
  4. 解密函数中包含 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=' 的字符串,那就把 base64码表的顺序打乱
数组列表元素包含非字符串的样例:
var _0x30bb = 'log';
// 列表元素中包含变量
var _0x30bc = [_0x30bb, 'Hello\x20World!'];

// 解密函数,自执行函数,js混淆的代码 和 第一个简单样例一样
不包含自执行函数
// 列表元素
var _0x30bc = [ 'Bg9N','AgvSBg8GD29YBgq'];

// 去掉自执行函数,不做位移

// 解密函数 和 加上 base64 编码的样例 中的解密函数一样

// ob混淆的js代码,解密函数参数顺序和数组类别顺序一致
function hi() {
    console[_0xae0a('0x0')](_0xae0a('0x1'));
}

hi();
解密函数中base64码表的顺序打乱的样例:
// 列表元素,base64码表顺序打乱得到的列表元素
var _0x30bc = [ 'Ov9W','NvyQOv8VU29BOve'];

// 自执行函数,加密函数,实现列表元素位移
(function (_0x36d89d, _0x30bcb2) {
    var _0xae0a32 = function (_0x2e4e9d) {
        while (--_0x2e4e9d) {
            _0x36d89d['push'](_0x36d89d['shift']());
        }
    };
    _0xae0a32(++_0x30bcb2);
}(_0x30bc, 0x101));

// 解密函数,base64码表顺序打乱
var _0xae0a = function (_0x38d89d) {
    function base64_decode(t) {
        // base64解码
        for (var e, n, r = "nozufdvtxsrgawjkelqcpyihbmNOZUFDVTXSRGAWJKELQCPYIHBM0123456789+/=", o = "", a = "", c = 0, i = 0;
             n = t.charAt(i++);
             ~n && (e = c % 4 ? e * 64 + n : n, c++ % 4) ? o += String.fromCharCode(e >> (c * -2 & 6) & 255) : 0) {
            n = r.indexOf(n);
        }
        for (var u = 0, s = o.length; u < s; u++) {
            a += "%" + ("00" + o.charCodeAt(u).toString(16)).slice(-2);
        }
        return decodeURIComponent(a);
    }

    _0x38d89d = _0x38d89d - 0x0;
    var _0xae0a32 = _0x30bc[_0x38d89d];
    return base64_decode(_0xae0a32);
};

// ob混淆的js代码
function hi() {
    console[_0xae0a('0x1')](_0xae0a('0x0'));
}

hi();
解决思路

以上以打破OB混淆基本特征的约束使动态识别失效的样例,解决办法就是通过手动将 数组/数组函数,自执行函数,解密函数,变量等相关的代码都加载到内存中,
如果只是 数组列表元素包含 一两个非字符串的元素,直接根据其变量或者三元表达式返回的字符串替换掉原来的元素,也可以通过反混淆网址实现反混淆;
如果存在 格式化检查代码,就把代码压缩后加载到内存中;
然后通过 AST识别  解密函数的调用的path,将其替换为 解密函数调用的结果 即可

三.OB混淆变体情况二

既然能够通过手动加载OB混淆的相关代码到内存中来解决上面的解混淆问题,那么也可以从其他方面去使其反混淆失效,比如:

  1. 解密函数分身,把解密函数赋值给另外一个变量,由这个变量实现函数调用
  2. 还可以进一步实现分身,把解密函数赋值给一个变量a,这个变量a又赋值给另外一个变量b,由这个变量b实现函数调用,还有可能实现嵌套赋值的情况
解密函数赋值给一个变量的样例:
// 数组,加密函数,解密函数 和 第一个简单样例一样

// 解密函数赋值给另外一个变量
var dd = _0xae0a;
// ob混淆的js代码
function hi() {
    console[dd('0x1')](dd('0x0'));
}

hi();
解密函数多重赋值的样例:
// 数组,加密函数,解密函数 和 第一个简单样例一样

// 解密函数赋值给另外一个变量
var dd = _0xae0a;
// ob混淆的js代码
function hi() {
    var uu = dd;
    console[uu('0x1')](uu('0x0'));
}

hi();
解决思路

解密函数被多重赋值的情况,可以通过 AST定位到解密函数的path,通过 path.scope.getbinding(name) 获取变量赋值的path,获取其节点赋值的变量名,将其变量名调用的path中的函数名称改为解密函数的名称;
多重赋值,要用到递归;将相关的变量赋值的函数调用名称都改成解密函数名称后,在将解密函数调用的path 改为 解密函数调用的结果
可以参考这个视频来学习如何解决这种解密函数被多重赋值的问题:
https://www.bilibili.com/video/BV1SfTjzaEBG?p=5&vd_source=61bd07e56393e4c482b0f4e60b0a66c6

四.OB混淆变体情况三

上面的情况,都是围绕OB混淆的特征和解密函数多重赋值来解混淆,还可以引入字典,和OB混淆相互作用,使其只能解决部分混淆的情况,
字典和数组有点相似,字典是通过调用key来返回value,数组是通过调用index来返回value,隐藏真实值
字典的key值,可以是数值,也可以是字符串
字典的value值,可以是数值,是字符串,还可以是函数等等,尤其是函数,这和 数组混淆 最大不同的,函数要根据实参替换掉形参,执行函数后,然后返回结果

字典结合数组混淆的样例1,字典的value是字面量:
// 数组,加密函数,解密函数 和 第一个简单样例一样

// 加入字典
var dict ={
    'aaa':'0x1',
    'bbb':'0x0',
}

// ob混淆的js代码
function hi() {
    console[_0xae0a(dict['aaa'])](_0xae0a(dict['bbb']));
}

hi();
字典结合数组混淆的样例2,字典的value可以是函数,函数的参数类型还可能是函数:

// 列表元素
var _0x30bc = ['sXyej', 'Hello\x20World!'];

// 加密函数,解密函数 和 第一个简单样例 一样

// 加入字典,字典的value的类型可以是字面量,也可以是函数
var ix = {
    'MLMYr': function (Pi, PD) {
    return Pi == PD; // 返回值为 二进制表达式如:算术运算,关系运算,位运算 等等
  },
   'KWXXu': function (Pi, PD) {
    return Pi(PD); //Pi 是函数
  },
  'sXyej': "log",  
}

// ob混淆的js代码
function hi(name){
    if(ix['MLMYr']('lili','lili')){
        console[ix[_0xae0a('0x1')]](name);
    }
}

ix['KWXXu'](hi, _0xae0a('0x0'))
解决思路

字典结合数组混淆,

  1. 要判断是先执行字典的混淆还是先执行数组的混淆,是字典的value作为数组解密函数的参数,还是字典的value函数参数是数组解密函数,会存在多种情况,字典的个数不止一个;
  2. 字典的value是函数,实参 替换 形参时,函数中 params 和 body 中的 arguments 要配对替换
  3. 如果字典的value是函数,实参有可能是字典另外一个key值的调用,字典嵌套问题,所以还要考虑执行顺序问题等等问题,可以考虑利用堆栈来解决[参数中存在函数调用就先入栈]
  4. 字典和解密函数一样,也存在多重嵌套赋值问题,解决思路一样

五.OB混淆变体情况四

除了引入字典,还可以引入函数,函数 a 调用 数组解密函数,函数b 调用 函数a,就是在解密函数外面套多层外衣,目的就是把人绕晕。

引入函数的样例:

// 数组,加密函数,解密函数 和 第一个简单样例一样

// 引入两个函数,给解密函数套外衣
function uu(a,b){
    return _0xae0a(a+b)

}
function nn(a,b,c){
    return uu(a+b,c)
}
// ob混淆的js代码
function hi() {
    console[nn(0,'x',1)](nn(0,'x',0));
}

hi();
解决思路
  1. 定位解密函数最外层的外衣,函数的特点是body只有一个return语句,并且函数参数的实参是常量(数值或者字符串或者一元表达式[操作符加常量]);
  2. 也可以定位解密函数,一层层往上查找,直到函数调用的参数为常量;
  3. 复杂两种方式应该联合使用
  4. 还有,要先考虑函数名称和函数参数名称相同的问题,如 function a(a,b,c),函数名称和参数名称都为a,AST识别的时候会识别错,要先把 函数中 params 和 body 中的 arguments 和函数名相同的参数名称改为其他名称
  5. 函数嵌套的层级和作用域错综复杂,可以考虑引入沙箱进行隔离

六.碎碎念

一开始,我以为不懂OB混淆,不懂AST也没关系,有反混淆的网站;
数组混淆的特征也不清楚,只拿了数组和解密函数,发现无法解混淆。。。只能通过调试的时候把解混淆的结果拿下来;
后来看到一个函数,有好多if-else分支,全是混淆的代码,想要把调试的结果拿下来是异想天开;
后来才了解OB混淆的特征,但 AST 了解的很模糊,基础没学会,跟着视频学习,t.isIdentifier()是什么东西,t是怎么来的都不知道;
到了后面看了 jsvmp正向 ,对AST了解也还是模模糊糊的,直到学到了 jsvmp 逆向的视频,才算是入了门,终于知道 types.isIdentifier(),types.identifier() 怎么用;
然后慢慢了解到,path.scope 的重要性,在每个visitor 最后加上 path.crawl() 解决节点被替换后AST结构发生变化导致影响下一个visitor无法查找到节点问题;

免费评分

参与人数 2威望 +1 吾爱币 +21 热心值 +2 收起 理由
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
1346 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-4-12 04:25

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表