2023年6月29日发(作者:)
JS逆向:AST还原极验混淆JS实战本⽂仅供学习交流使⽤,请勿⽤于商业⽤途或不正当⾏为如果侵犯到贵公司的隐私或权益,请联系我⽴即删除AST是抽象语法树,名字感觉很⾼级,其实也不⽤怕,可以简单理解为是将JS代码归类后的JSON,并且提供了很多⽅法给你对这个JSON进⾏增删改查。学习⼀个新东西,⾸先⼀定要搞清楚它有什么⽤,都不知道有啥⽤,学习它⼲啥,对吧作为⼀个爬⾍攻城狮,JS逆向也是家常便饭了,JS逆向经常会遇到各种混淆过后的代码,极其难以阅读,这时候就可以使⽤AST对这些混淆代码进⾏⼀定的还原处理,得到⼀个相对容易阅读的JS代码,⽅便我们进⾏JS分析。阅读本⽂需要有⼀点AST的基础,不要害怕,只需要稍微学习⼀点AST知识就⾏了,并不需要掌握AST的全部知识才能看得懂,不过最起码要对AST定位节点有⼀点认识,关于AST的学习⼤家肯定找蔡⽼板啊相信只要能认真阅读完本⽂内容,理解其中的概念,就能对AST还原混淆代码有⼀点点感觉了,并会发现AST其实也不是那么难。本次反混淆的是下⾯这个⽂件/static/js/复制代码保存到本地,收缩⼀下代码看看整体结构简单看下这⼏个function嗯, 啥也看不懂…别慌,静下⼼来,⼀点⼀点分析,⼀点⼀点解决!观察代码发现⾥有很多地⽅调⽤了,搜索出515个匹配结果,如下图在匹配结果中有两类调⽤,如上图红框标⽰的第⼀个:带括号的(79); 暂时不知道是在⼲啥;第⼆个,不带括号的,可以理解为mZtVWz = ['qhicV'].concat()复制代码concat()是合并数组的作⽤也就是说应该是⼀个数组控制台打印看看果然类似数组,通过索引就能取出值来,返回值都是字符串类型。那么问题来了:如何将代码⾥的()还原成对应的字符串?很快就能想到,在JS代码⾥将字符串"()“逐个匹配出来,拿到括号⾥的索引数字,然后将数字值传⼊函数()计算得到字符串结果,然后将结果替换代码⾥的字符串"()”,就实现了还原的⽬的了。其实AST操作的话也差不多是这个思路,我们把代码复制到在线AST解析⽹站,看看在AST⾥是什么样⼦的/复制代码能看到所有的都在CallExpression节点⾥,所以我们⾸先遍历CallExpression节点,然后定位到name属性得到值并判断是否为DAi,是的话就定位到value属性取出值,再将拿到的value传⼊到函数()进⾏计算,得到结果再使⽤AST操作替换。刚才说到要将value传⼊到()⾥执⾏,所以⾸先需要将的代码从JS⾥扣过来放到Node⾥⽤于计算。代码实现:替换后的效果:可以看到带括号的()原来有8个,处理后已经都被还原掉了不带括号的还有519个接下来我们来处理这些不带括号的在处理之前,先分析⼀下观察上图:nxWF = ;复制代码concat是合并数组,所以mZtVWz = ['qhicV'].concat(nxWF)复制代码其实等于mZtVWz = ['qhicV',]复制代码⽽oeXg = mZtVWz[1];复制代码就等于oeXg = ;复制代码图中所有红框的nxWF和绿框的oeXg,实际上都等于这⾥我们的处理思路是:将所有的这两个位置的name全部遍历出来,放⼊⼀个数组,然后遍历判断所有类似oeXg(12)的name是否在数组⾥,是的话就替换代码实现:看下效果能看到在数组nameArray⾥存在的函数已经被还原了,但是发现还存在⼀些冗余代码,我们看看如何将它们删除继续分析AST结构能看到左边这三组对应的是右边的三个节点,我们只需要定位到右边的这三个节点,然后删除就⾏⾸先定位第⼀个节点,定位的⽅法就是判断VariableDeclaraction下⾯property下⾯的name是否为DAi代码实现:还原效果:看起来清晰点了继续分析,观察到代码⾥有很多unicode代码在AST结构⾥,能看到unicode代码是在axtra⾥的,所以我们只需要将axtra给删掉,就能还原掉unicode了代码实现:效果:处理完你会发现仍然有⼤量的unicode编码的字符串这些是中⽂的unicode,解决办法是在generator的第⼆个参数添加opts = {jsescOption:{"minimal":true}}复制代码添加之后,重新还原⼀下能看到中⽂unicode编码也很轻松就被还原了剩下的37个匹配结果,都是正则⾥的,不影响,就不需要做处理了另外在代码⾥还发现⼀些eval的代码还原后,eval⾥⾯是字符串形式的JS代码所以在反混淆之前⼤家可以先去浏览器执⾏⼀下eval⾥的这些函数,拿到值后格式化⼀下替换掉这些eval,这样可以在还原时⼀起将那些字符串形式的JS代码也⼀并还原掉,这⾥就不多说了。继续分析,细⼼的你⼀定能看到极验的JS代码中有很多这种结构的代码,⾮常影响代码的阅读这种结构的代码俗称控制流平坦化,简单来讲就是将代码块之间的关系打断,由⼀个分发器来控制代码块的跳转,找了个图如下先引⽤⼀个简单的例⼦,这样清晰点switch就是流程控制器,它会遍历arr数组,因为每⼀句case⾥⾯都有continue,也就是说switch每次从arr中取出值,就会把所有的case语句都执⾏⼀遍,这样去扰乱程序的执⾏流程,以增加我们分析代码的难度,等case都执⾏完后再从arr数组中取下⼀个值。直到遍历完arr,才退出循环。所以arr数组就是程序的主要执⾏顺序,我们只需遍历它,然后执⾏对应case语句块的代码,就能达到简化流程的⽬的了。极验跟上⾯这个例⼦还有点不同,它在每个case⾥会重新赋值改变控制流的判断条件值,分发器根据这个判断条件值来执⾏流程控制,所以重点是需要能知道下⼀个执⾏的是哪个case语句。极验的控制流还原思路是先拿到流程控制的初始值和for循环的条件判断值,接着提取出所有的case,然后遍历每⼀个case,计算出case的条件判断值,对⽐这个条件判断值是否与初始值相同,相同的话,可以删除⼀下⽆⽤语句,将当前case语句块存放到⼀个专门⽤于存放case语句块的数组,更新修改下条件判断值,最后将case语句块的数组替换ForStatement,具体看代码代码实现参考公众号的全⽂吧:看看代码实现对应的JS和AST结构// 获取上⼀个节点,也就是VariableDeclaration
var PrevSibling = vSibling();复制代码当前的节点是ForStatement,它的上⼀个节点就是VariableDeclaration,也就是for语句上⾯的⼀个语句,它是控制的初始判断值;// 获取控制流的初始值var argNode = ner[0].declarations[0].init;var init_arg_f = ;var init_arg_s = ;var init_arg = ()[init_arg_f][init_arg_s];复制代码// 提取for节点中的if判断参数的value作为判断参数var break_arg_f = ;var break_arg_s = ;var break_arg = ()[break_arg_f][break_arg_s];复制代码// 提取并计算case后的条件判断的值var case_arg_f = case_list[i].;var case_arg_s = case_list[i].;var case_init = ()[case_arg_f][case_arg_s];复制代码//当前case下的所有节点var targetBody = case_list[i].consequent;复制代码// 提取break节点的上⼀个节点()后⾯的两个索引值var change_arg_f = targetBody[ - 2].;var change_arg_s = targetBody[ - 2].;// 修改控制流的初始值init_arg = ()[change_arg_f][change_arg_s];复制代码(); // 删除(); // 删除break节点的上⼀个节点复制代码resultBody = (targetBody);复制代码对照着上⾯的代码和AST结构⾃⾏研究哈,注释也⽐较详细了经过以上的处理之后,就能得到⼀个相对容易阅读的JS代码了,这样分析逆向的时候也会更容易⼀点还原后的效果今天的AST还原极验混淆代码实战到这⾥就全部完成了,⼤家如果有更好的思路,欢迎分享给我呀。以上内容仅为个⼈学习总结记录,思路主要参考公众号:搞点逆向778,分享出来希望能帮到⼤家,如有错误或遗漏之处请见谅公众号:⼀⽣向风
发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1687987202a64135.html
评论列表(0条)