AST还原实战|使用AST还原猿人学第16题js混淆代码

上周直播讲解了第16题混淆js的还原,本文再做个补充,方便星友们学习AST插件的编写技巧。

一.实战网址

https://match.yuanrenxue.com/match/16

二.加密参数分析

抓包,分析接口数据:

562ffe279e427b90395a47e23a265c6a.png

点击去,即可发现加密参数 m 及 t 赋值的地方:

d8526e9123ec8aeb0d417966146d0c67.png

在 r.m = n[e(528)](btoa, p_s) 这行代码打上断点,再次请求,断住后,控制台输入 btoa并回车,双击下面的代码,来到这里:

dc32dc2132d6e7cf0eaf6ba3434f98c4.png

代码往上翻,把整个 732 相关的代码抠下来,并保存到js文件中。

三.解混淆分析

代码扣下来格式化,看着有点像 ob 混淆,但与常规的还是有点区别,用我那个一键还原无法还原,不过没关系,见招拆招就好。

分析混淆文件,看到了这些函数调用:

439e6ac8d0e3fcd944fd49a04a9b2f1e.png

之前的文章也讲过,像这种 函数调用,其实参为字面量,大概率是可以进行还原的,因此我们先进行还原?

不慌,我们看看它的函数定义在哪里:

47e759156526e71a737c971436143bd6.png

可以看到,函数定义 l ,然后 l 又赋值给了e,看到这种情况,我们可以先将e替换成l,即还原重复定义的变量。

AST源代码如下:

//删除重复定义且未被改变初始值的变量
const deleteRepeatDefine = {
  "VariableDeclarator|FunctionDeclaration"(path)
  {
    let {node,scope,parentPath} = path;
    
    if (path.isFunctionDeclaration())
    {
      scope = parentPath.scope;
    }
    let name = node.id.name;
    const binding = scope.getBinding(name);
    if (path.isFunctionDeclaration())
    {
      if(!binding || binding.constantViolations.length > 1)
      {
        return;
      }
    }
    else
    {
      if(!binding || !binding.constant) return;
    }
    
    scope.traverse(scope.block,{
      VariableDeclarator(path)
      {
        let {node,scope} = path;
        let {id,init} = node;
        if (!types.isIdentifier(init,{name:name})) return;
        
        const binding = scope.getBinding(id.name);
        
        if (!binding || !binding.constant) return;
      
        scope.rename(id.name,name);
        path.remove();
      },
    })
    
    scope.crawl();     
  },
   
}

还原后效果如下:

6ddd3cfffcec2d6dc581880b13dd3cdf.png

这样,就把之前的那些不同的函数名全部变成 l了,方便我们进一步分析。

这个时候,我们把l的函数声明 及其环境依赖复制到还原的ast代码中,注意,这里需要进行代码压缩,因为进行格式化后运行,容易导致内存溢出。

PS:不用管ob混淆检测了什么,直接对其检测的代码进行格式万事大吉

压缩后的代码如下:

var r,o,a,s;_0x34e7=["AqLWq","0zyxwvutsr","TKgNw","eMnqD","thjIz","btoa","MNPQRSTWXY","oPsqh","niIlq","evetF","LVZVH","fYWEX","kmnprstwxy","aYkvo","tsrqpomnlk","HfLqY","aQCDK","lGBLj","test","3210zyxwvu","QWK2Fi",'return /" ',"hsJtK","jdwcO","SlFsj","OWUOc","LCaAn","[^ ]+)+)+[","FAVYf","2Fi+987654","floor","join","EuwBW","OXYrZ","charCodeAt","SkkHG","iYuJr","GwoYF","kPdGe","cVCcp","INQRH","INVALID_CH","charAt","push","apply","lalCJ","kTcRS",'+ this + "',"ykpOn","gLnjm","gmBaq","kukBH","dvEWE","SFKLi","^([^ ]+( +","qpomnlkjih","^ ]}","pHtmC","length","split","ABHICESQWK","FKByN","U987654321","lmHcG","dICfr","Szksx","Bgrij","iwnNJ","jihgfdecba","GfTek","gfdecbaZXY","constructo","QIoXW","jLRMs"],a=_0x34e7,s=function(e){for(;--e;)a.push(a.shift())},(o=(r={data:{key:"cookie",value:"timeout"},setCookie:function(e,o,t,r){r=r||{};for(var n=o+"="+t,i=0,u=e.length;i<u;i++){var a=e[i];n+="; "+a;var c=e[a];e.push(c),u=e.length,!0!==c&&(n+="="+c)}r.cookie=n},removeCookie:function(){return"dev"},getCookie:function(e,o){var t,r=(e=e||function(e){return e})(new RegExp("(?:^|; )"+o.replace(/([.$?*|{}()[]\/+^])/g,"$1")+"=([^;]*)"));return t=133,s(++t),r?decodeURIComponent(r[1]):void 0},updateCookie:function(){return new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+['|\"];? *}").test(r.removeCookie.toString())}}).updateCookie())?o?r.getCookie(null,"counter"):r.removeCookie():r.setCookie(["*"],"counter",1);var l=function(e,o){return _0x34e7[e-=188]};

现在就可以愉快的进行 函数表达式的还原了,代码如下:

const callToLiteral = 
{
  CallExpression(path)
  {
    let {scope,node} = path;
    
    let {callee,arguments} = node;
    
    if (!types.isIdentifier(callee,{name:"l"}) ||
       arguments.length != 1 ||  !types.isNumericLiteral(arguments[0]))
    {
      return;
    }
    
    let arg = arguments[0].value;
    
    let value = l(arg);
    
    path.replaceWith(types.valueToNode(value));
    
    scope.crawl();


  },
}

运行上面的插件后,混淆代码变成了这样:

4686ed4948c16cab040a0f90b864757e.png

字面量全部还原了。

熟悉ob混淆的朋友都知道,在还原函数调用后,接下来就是还原object对象与控制流,这里发现控制流是for循环:

34894a4dfc89662a77bddb5e2448812a.png

而一般的 ob混淆都是while 混淆,因此想用我的一键还原,还需要把这里的控制流改一下,即for循环改为 符合ob混淆的while循环。

AST代码如下:

const obForToObWhile = 
{
  ForStatement(path)
  {
    let {scope,node} = path;
   
    let {init,test,update,body} = node;
    if (!types.isVariableDeclaration(init) ||
       test != null || update != null)
    {
      return;
    }
    path.insertBefore(init);
    
    let whileNode = types.WhileStatement(types.BooleanLiteral(true),body);
    
    path.replaceWith(whileNode);
    
    scope.crawl();
    
  }
}

这个插件运行后,就可以愉快的使用星球里的一键还原脚本了,还原结果如下:

93769878a3a4b03abfff562ec867d305.png

可以看到btoa函数定义在这里,在删除一些垃圾代码后,723 这个代码最终变成了这样:

var f = "U9876543210zyxwvutsrqpomnlkjihgfdecbaZXYWVUTSRQPONABHICESQWK2Fi+9876543210zyxwvutsrqpomnlkjihgfdecbaZXYWVUTSRQPONABHICESQWK2Fi";


function d(e) {
    e = e || 32;
    var l = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
    var s = l["length"];
    var c = "";


    for (i = 0; i < e; i++) {
        c += l["charAt"](Math["floor"](Math["random"]() * s));
    }


    return c;
}


function btoa(e) {
    if (/([^\u0000-\u00ff])/["test"](e)) {
        throw new Error("INVALID_CHARACTER_ERR");
    }


    for (var o, a, s, l = 0, c = []; l < e["length"]; ) {
        switch (a = e["charCodeAt"](l),
        s = l % 6) {
        case 0:
            delete window;
            delete document;
            c["push"](f["charAt"](a >> 2));
            break;


        case 1:


            c["push"](f["charAt"]((2 & o) << 3 | a >> 4));


            break;


        case 2:
            c["push"](f["charAt"]((15 & o) << 2 | a >> 6));
            c["push"](f["charAt"](a & 63));
            break;


        case 3:
            c["push"](f["charAt"](a >> 3));
            break;


        case 4:
            c["push"](f["charAt"]((o & 4) << 6 | a >> 6));
            break;


        case 5:
            c["push"](f["charAt"]((o & 15) << 4 | a >> 8));
            c["push"](f["charAt"](a & 63));
        }


        o = a;
        l++;
    }


    0 == s ? false || (c["push"](f["charAt"]((o & 3) << 4)),
    c["push"]("FM")) : s == 1 && (c["push"](f["charAt"]((15 & o) << 2)),
    c["push"]("K"));
    return d(15) + window["md5"](c["join"]("")) + d(10);
}
;

整个代码,就剩md5这个函数未定义了,因此在网站上把md5所在的代码也扣下来,按照上面的方式进行还原,也可以得到一个十分清爽的代码,再次不作重复讲解。

好了,文章就讲解到这里,不懂的随时群里问,反混淆后的代码我放在星球里了,需要的自取:

https://t.zsxq.com/BIEQbqZ

感谢阅读,再见!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AST混淆还原是指通过对JavaScript代码的抽象语法树(AST)进行还原,来反混淆经过混淆处理的代码AST混淆还原入门可以通过以下几个步骤实现: 1. 了解抽象语法树(AST):抽象语法树是用于表示代码结构的一种数据结构。它将代码转换为树状结构,每个节点代表代码的不同部分。了解AST的基本概念和节点类型对于进行混淆还原非常重要。 2. 学习JavaScript语法:要进行AST混淆还原,需要对JavaScript的语法有一定的了解。熟悉JavaScript的语法规则和常见的代码结构将有助于理解和还原混淆代码。 3. 使用AST还原工具:在进行AST混淆还原时,可以使用一些开源的AST还原工具,如丁仔大佬的AST还原工具。这些工具可以将混淆后的代码转换为AST,然后通过对AST进行分析和还原,最终得到可读性较高的代码。 4. 学习AST还原技术:了解AST还原的原理和技术对于深入理解和应用AST还原工具非常重要。可以学习一些AST还原的基本技术,如遍历AST、修改AST节点等,以及一些高级的AST还原技术,如模式匹配、符号执行等。 5. 实践与练习:通过实践与练习,逐渐提升对AST混淆还原的理解和技巧。可以选择一些混淆代码进行还原,尝试使用AST还原工具进行还原,并对还原结果进行分析和验证。 需要注意的是,AST混淆还原是一个复杂的过程,对于不同的混淆代码可能会有不同的还原策略和技术。因此,除了入门的基础知识外,还需要不断学习和积累经验,才能在实际应用中取得更好的效果。<span class="em">1</span> #### 引用[.reference_title] - *1* [AST混淆js还原工具2.2(20230203)](https://download.csdn.net/download/jia666666/87413335)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值