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

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

一.实战网址

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

二.加密参数分析

抓包,分析接口数据:

AST还原实战|使用AST还原猿人学第16题js混淆代码_第1张图片

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

AST还原实战|使用AST还原猿人学第16题js混淆代码_第2张图片

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

AST还原实战|使用AST还原猿人学第16题js混淆代码_第3张图片

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

三.解混淆分析

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

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

AST还原实战|使用AST还原猿人学第16题js混淆代码_第4张图片

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

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

AST还原实战|使用AST还原猿人学第16题js混淆代码_第5张图片

可以看到,函数定义 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();     
  },
   
}

还原后效果如下:

AST还原实战|使用AST还原猿人学第16题js混淆代码_第6张图片

这样,就把之前的那些不同的函数名全部变成 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

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

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();


  },
}

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

AST还原实战|使用AST还原猿人学第16题js混淆代码_第7张图片

字面量全部还原了。

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

AST还原实战|使用AST还原猿人学第16题js混淆代码_第8张图片

而一般的 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();
    
  }
}

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

AST还原实战|使用AST还原猿人学第16题js混淆代码_第9张图片

可以看到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

感谢阅读,再见!

你可能感兴趣的:(js,javascript,java,python,web)