前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一处JS反调试引发的思考

一处JS反调试引发的思考

作者头像
亿人安全
发布2022-06-30 15:37:57
3310
发布2022-06-30 15:37:57
举报
文章被收录于专栏:红蓝对抗

起因

白帽子们挖Web漏洞时,JavaScript信息是至关重要的一环

从JS中可以得到隐藏接口等信息,然后尝试挖掘越权,SQL注入和上传等洞

笔者刚入门时候曾用这种办法挖到了一些CNVD,算是收获颇丰

回到主题,审计JS能够获得重要信息,然而并不是所有JS都能直接拿来看

很多情况下白帽子们将会面临混淆后的JS,这时候就需要尝试逆向调试分析了

最近在研究JS逆向相关的事情,遇到了一处比较有趣的代码

也许对于大佬来说很简单,不过我不太懂JS,第一次遇到感觉挺有趣的

起因是发现调试该JS时候会发现卡死,但目标网站在正常使用该JS脚本

分析

做全局JavaScript做了一定的分析后,最终跟踪到代码如下

注意到其中有类似正则的地方,所有第一印象这里可能是业务逻辑代码,分析后发现并不是

代码语言:javascript
复制
var _0x578a10 = _0x2ba9['nKWcry'][_0x101b8f];
if (_0x578a10 === undefined) {
    var _0x4b1809 = function (_0x3b1d14) {
        this['YlKlnG'] = _0x3b1d14;
        this['NsTJKl'] = [0x1, 0x0, 0x0];
        this['HILIkx'] = function () {
            return 'newState';
        };
        this['GGmyeM'] = '\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*';
        this['VUtdVO'] = '[\x27|\x22].+[\x27|\x22];?\x20*}';
    };
    _0x4b1809['prototype']['OsLPar'] = function () {
        var _0x1403ab = new RegExp(this['GGmyeM'] + this['VUtdVO']);
        var _0x3fadf0 = _0x1403ab['test'](this['HILIkx']['toString']()) ? --this['NsTJKl'][0x1] : --this['NsTJKl'][0x0];
        return this['anWLTR'](_0x3fadf0);
    };
    _0x4b1809['prototype']['anWLTR'] = function (_0x26db32) {
        if (!Boolean(~_0x26db32)) {
            return _0x26db32;
        }
        return this['xTDWoN'](this['YlKlnG']);
    };
    _0x4b1809['prototype']['xTDWoN'] = function (_0x597ca7) {
        for (var _0x3e27c4 = 0x0, _0x192434 = this['NsTJKl']['length']; _0x3e27c4 < _0x192434; _0x3e27c4++) {
            this['NsTJKl']['push'](Math['round'](Math['random']()));
            _0x192434 = this['NsTJKl']['length'];
        }
        return _0x597ca7(this['NsTJKl'][0x0]);
    };
    new _0x4b1809(_0x2ba9)['OsLPar']();
    _0x27941a = _0x2ba9['dzoqWA'](_0x27941a);
    _0x2ba9['nKWcry'][_0x101b8f] = _0x27941a;
} else {
    _0x27941a = _0x578a10;
}
return _0x27941a;

在一开始定义了大函数_0x4b1809

最终这样调用:new _0x4b1809(_0x2ba9)['OsLPar']();

跟入OsLPar函数

代码语言:javascript
复制
function () {
        var _0x1403ab = new RegExp(this['GGmyeM'] + this['VUtdVO']);
        var _0x3fadf0 = _0x1403ab['test'](this['HILIkx']['toString']()) ? --this['NsTJKl'][0x1] : --this['NsTJKl'][0x0];
        return this['anWLTR'](_0x3fadf0);
};

_0x1403ab拼出了一个正则:\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}

化简后为其实就可以看懂了:\w+ *\(\) *{\w+ *['|"].+['|"];? *}

不难猜出,该正则匹配的是:aaa () {aaa'aaa';}这样的字符串,看上去似乎是一个函数调用

照着写一段,验证猜测

代码语言:javascript
复制
var a = new RegExp("\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}");
var b = a.test("function a () {return 'ok';}");
var c = a.test("function a () {return 'ok'; }");
var d = a.test("function a () { return 'ok';}");
console.log(b);// true
console.log(c);// true
console.log(d);// false

得出结论,函数调用的最后分号后可以跟多个空格,但左大括号之后必须跟字符串

这个正则正是导致我卡死的反调试核心代码

回到代码中_0x1403ab['test']等价于上文代码的a.test

代码语言:javascript
复制
var _0x3fadf0 = _0x1403ab['test'](this['HILIkx']['toString']()) ? --this['NsTJKl'][0x1] : --this['NsTJKl'][0x0];
return this['anWLTR'](_0x3fadf0);

函数参数为this['HILIkx']['toString']()

这个参数是正则需要匹配的目标,跟入HILIkx函数发现只是简单的return字符串

代码语言:javascript
复制
this['HILIkx'] = function () {
    return 'newState';
};

注意最后还有一个toString代码,这里的toString不同于Java的方法

这里直接返回的是纯字符串"function () {\n return'newState';\n};"

显然可以得出结论,无法匹配到正则\w+ *\(\) *{\w+ *['|"].+['|"];? *}因此返回false

继续看后面的表达式,如果为true会从NsTJKl数组中移除1否则移除0,暂不分析这里的用途

代码语言:javascript
复制
this['NsTJKl'] = [0x1, 0x0, 0x0];

函数的return会跟入anWLTR函数,传入的_0x26db32一定是false

其中的~是非运算,所以代码最终会进入this['xTDWoN'](this['YlKlnG'])

代码语言:javascript
复制
_0x4b1809['prototype']['anWLTR'] = function (_0x26db32) {
    if (!Boolean(~_0x26db32)) {
        return _0x26db32;
    }
    return this['xTDWoN'](this['YlKlnG']);
};

跟入xTDWoN函数,先不考虑传入的参数,因为参数只会影响到返回值

代码语言:javascript
复制
_0x4b1809['prototype']['xTDWoN'] = function (_0x597ca7) {
    for (var _0x3e27c4 = 0x0, _0x192434 = this['NsTJKl']['length']; _0x3e27c4 < _0x192434; _0x3e27c4++) {
        this['NsTJKl']['push'](Math['round'](Math['random']()));
        _0x192434 = this['NsTJKl']['length'];
    }
    return _0x597ca7(this['NsTJKl'][0x0]);
};

重点关注for循环内容,次数数组this['NsTJKl']['length']长度为2大于0,所以成功进入for循环

将for循环化简如下,发现for循环内会往数组NsTJKl中push一个随机数

然后将数组长度赋值给length,本来进入for循环的条件i<length是满足的,继续加入随机数导致for循环永远满足,也就是死循环

代码语言:javascript
复制
for (var i = 0, length = this['NsTJKl']['length']; i < length; i++) {
    this['NsTJKl']['push'](Math['round'](Math['random']()));
    length = this['NsTJKl']['length'];
}

这里找到了卡死的原因,本质是一处的正则没有匹配到

问题来了,为什么目标网站的正则可以匹配到,但我本地无法匹配到

原因不难,目标网站的JS是压缩后的代码

代码语言:javascript
复制
this['HILIkx'] = function () {return 'newState';};

逆向者在本地尝试做破解的时候,会将代码格式化(无论chrome还是vscode里都会很容易地进行格式化)

格式化后的代码不满足条件,所以会进入死循环

绕过方式其实也简单,还原回压缩格式即可

代码语言:javascript
复制
......
var _0x4b1809 = function (_0x3b1d14) {
    this['YlKlnG'] = _0x3b1d14;
    this['NsTJKl'] = [0x1, 0x0, 0x0];
    this['HILIkx'] = function () {return 'newState';};
    this['GGmyeM'] = '\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*';
    this['VUtdVO'] = '[\x27|\x22].+[\x27|\x22];?\x20*}';
};
......

实现

使用node的babel库

代码语言:javascript
复制
npm install @babel/parser

上手写代码

代码语言:javascript
复制
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
const fs = require("fs");

const jscode = fs.readFileSync("../demo.js", {
    encoding: "utf-8"
});
let ast = parser.parse(jscode);

// 正则初始化
var targetRegex = "\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}";
var initValue = t.newExpression(t.identifier("RegExp"),[
    t.stringLiteral(targetRegex),
]);
var regDec = t.variableDeclaration("var", [t.variableDeclarator(t.identifier("_4ra1n"), initValue)]);

// 用于验证的函数
var tempBlock = t.blockStatement([t.returnStatement(t.stringLiteral("_4ra3n"))]);
var funcExpr = t.functionExpression(t.identifier("_4ra2n"),[],tempBlock);
var funcDec = t.variableDeclaration("var",[t.variableDeclarator(t.identifier("_4ra4n"), funcExpr)]);

// 正则test调用
var tempMem = t.memberExpression(t.identifier("_4ra1n"),t.stringLiteral("test"),true);
var callExpr =  t.callExpression(tempMem,[t.identifier("_4ra4n")]);
var callDec = t.variableDeclaration("var",[t.variableDeclarator(t.identifier("_4ra5n"),callExpr)]);

// 最终if判断和死循环模拟
var tempCallExpr = t.callExpression(t.identifier("Boolean"),[t.identifier("_4ra5n")]);
var tempForBlock = t.blockStatement([t.forStatement(null,null,null,t.blockStatement([]))]);
var unaryExpr = t.unaryExpression("!",tempCallExpr);
var ifState = t.ifStatement(unaryExpr,tempForBlock);

console.log("start");
// 遍历语法树添加
traverse(ast,{
    FunctionDeclaration(path) {
        let blockStatement = path.node.body;
        blockStatement.body.unshift(ifState);
        blockStatement.body.unshift(callDec);
        blockStatement.body.unshift(regDec);
        blockStatement.body.unshift(funcDec);
        path.get("body").replaceWith(blockStatement);
    }
});

let code = generator(ast).code;
fs.writeFile("./demo-new.js", code, (err) => { });

给出一个demo.js文件,简单的一个两数相加函数

代码语言:javascript
复制
function add(a, b) {
    var c = a + b;
    return c;
}

经过笔者自制工具处理后代码如下,给末尾添加调用语句add(1,2);执行该JS文件发现卡死

代码语言:javascript
复制
function add(a, b) {
  var _4ra4n = function _4ra2n() {
    return "_4ra3n";
  };

  var _4ra1n = new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+['|\"];? *}");

  var _4ra5n = _4ra1n["test"](_4ra4n);

  if (!Boolean(_4ra5n)) {
    for (;;) {}
  }

  var c = a + b;
  return c;
}

add(1,2);

使用babel库的压缩代码功能

代码语言:javascript
复制
let code = generator(ast, {
    retainLines: false,
    comments: false,
    compact: true
}).code;

得到如下代码,执行代码后发现没问题

代码语言:javascript
复制
function add(a,b){var _4ra4n=function _4ra2n(){return"_4ra3n";};var _4ra1n=new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+['|\"];? *}");var _4ra5n=_4ra1n["test"](_4ra4n);if(!Boolean(_4ra5n)){for(;;){}}var c=a+b;return c;}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-11-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 亿人安全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 起因
  • 分析
  • 实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档