编程杂谈

例子//还原前eval(atob("c3RyID0gc3RyWyJyZXBsYWNlIl0oL2RkfERELywgdGhpc1siZ2V0RGF0ZSJdKCkgPiA5ID8gdGhpc1siZ2V0RGF0ZSJdKClbInRvU3RyaW5nIl0oKSA6ICcwJyArIHRoaXNbImdldERhdGUiXSgpKTs="));eval(String.fromCharCode(115,116,114,32,61,32,115,116,114,91,34,114,101,112,108,97,99,101,34,93,40,47,121,121,121,121,124,89,89,89,89,47,44,32,116,104,105,115,91,34,103,101,116,70,117,108,108,89,101,97,114,34,93,40,41,41,59));//还原后str=str["replace"](/dd|DD/,this["getDate"]()>9?this["getDate"]()["toString"]():'0'+this["getDate"]());;str=str["replace"](/yyyy|YYYY/,this["getFullYear"]());;还原这两种可以同时处理,因为都是eval执行,所以遍历调用表达式,判断eval,是就取出eval的参数,判断节点类型,如果是StringLiteral说明就是一串代码字符串,直接替换,如果是其他类型,直接使用node的eval执行,然后替换节点。是否通用:是特别说明:如果遇到解密字符串的是自写的函数,需要先找到解密函数//nodeatobvars=newBuffer.from("待解码的字符","base64").toString("binary")//nodebtoavars=newBuffer.from("待编码的字符","binary").toString("base64")//还原代码functionatob(code){returnnewBuffer.from(code,"base64").toString("binary")}//eval还原traverse(ast,{CallExpression(path){//判断特征if(path.node.callee.name!=='eval')return;letarguments=path.node.arguments//eval的参数letcode=generator(arguments[0]).codeif(type.isStringLiteral(arguments)){path.replaceWith(type.identifier(code))}else{path.replaceWith(type.identifier(eval(code)))//node的eval}}})

编程杂谈

说明第二种平坦流相比于第一种难度都差不多,只是分发器结构不同,难点也是在于没有break的case流程分支构造分析结构可以通过观察,发现类似于一下的结构vari=0,arr=[...];while(!![]){switch(syZ[i++]){case1:...varmP=arr.push//获取pushbreak;case2:...varr8V=[...]//部分分发器break;case3:...mP.apply(arr,r8V);//更新分发器break;case4:...//没有breakcase5:...break还原思路1、通过特征判断,确定平坦流代码2、获取分发器初始数组(shufferVar)3、遍历while-case映射出case值和case代码块({‘case值’:代码块}),并获取出相应的变量名4、通过初始分发器数组,遍历映射的case,组建完整的分发器5、还原1、判断特征平坦流都在函数节点里初始分发器数组+whilwtraverse(ast,{'FunctionDeclaration|FunctionExpression'(path){//第一裂平坦流都在函数里path.traverse({VariableDeclaration(p){//通过特征定位定位分发器位置varrbD=0,BFH=[]letdeclarations=p.node.declarations;if(declarations.length!==2||!type.isNumericLiteral(declarations[0].init,{value:0})||!type.isArrayExpression(declarations[1].init))return;letwhilePath=p.getSibling(p.key+1)if(!type.isWhileStatement(whilePath))return;//声明语句后不是while节点}})}})2、获取分发器初始数组先通过定位while节点,获取前兄弟节点,判断是否是初始分发器数组//获取同级的初始分发器数组letshufferVar=declarations[1].id.name;letshufferArr={value:declarations[1].init.elements};//初始分发器数组console.info('初始分发器:',{name:shufferVar,lenght:shufferArr.value.length})3、映射映射为了方面获取case代码块,映射过程,可以通过apply的特征,记录其他分发器数组的变量名//映射case代码块,并记录更新分发器变量名let_case={},shufferVarName={arrName:{}};whilePath.node.body.body[0].cases.map((v)=>{//映射case和代码块letswitchCase=v.consequent;//获取cases里的代码块数组if(type.isExpressionStatement(switchCase.slice(-2)[0])){letn=switchCase.slice(-2)[0]if(n.expression.callee//形式:mP.apply(syZ,r8V);&&n.expression.callee.property.name==='apply'&&n.expression.arguments[0].name===shufferVar){shufferVarName.arrName[n.expression.arguments[1].name]=true//记录另一部分分发器变量名switchCase.splice(-2,1)//删除apply}}_case[v.test.value]=switchCase})4、组建完整分发器每次取从tmpShufferArr删除头部值并获得,来进行过一遍流程分支,过的过程中动态添加tmpShufferArr值,来获得完整的分发器lettmpShufferArr=shufferArr.value.concat()//深复制shufferArr=[]//置空while(tmpShufferArr.length!==0){//这样写每次更新临时分发器时,能增加循环letindex=tmpShufferArr.shift().value//每次从临时分发器头部获取流程值letconsequent=_case[index];shufferArr.push(index)//从临时分发器获得的流程值才是真实的letn=consequent.slice(-1)[0]//获取最后一个代码块if(!type.isBreakStatement(n)&&!type.isReturnStatement(n)){//为了处理:结尾不是break和return的case,下一次直接进入下一个case代码块tmpShufferArr.unshift({value:index+1})//更新临时分发器}letb=consequent.slice(-2)[0]//再获取倒数第二个,判断是否是分发器if(type.isVariableDeclaration(b)&&b.declarations[0]&&shufferVarName.arrName[b.declarations[0].id.name]){//更新临时分发器tmpShufferArr.push(...b.declarations[0].init.elements)_case[index].splice(-2,1)//删除分发器}if(type.isVariableDeclaration(b)&&b.declarations[0]&&b.declarations[0].init&&b.declarations[0].init.object&&b.declarations[0].init.object.name===shufferVar//形式:varxoL=KDW.p;&&b.declarations[0].init.property.name==='p'){_case[index].splice(-2,1)//删除push声明}}console.info('完整分发器:',JSON.stringify(shufferArr))还原遍历分发器,获取case代码块,添加到父节点的body里,并删除breakp.remove()//删除分发器whilePath.remove()//删除while节点letparentPath=whilePath.parentshufferArr.map((v)=>{if(type.isBreakStatement(_case[v].slice(-1)[0])){_case[v].pop()//删除break}parentPath.body.push(..._case[v])//添加到path节点})验证

编程杂谈

流程平坦化它使用了两种平坦流,这里还原第一种分析结构每次执行case时,才更新控制器的值,这种平坦流还是挺常见的,但该Js做了一点处理varsCS=16;//分发器初始值while(!![]){switch(sCS){case1:...sCS+=22;break;case2:...sCS+=3;//该分支没有break,直接进入下一个casecase3:...sCS+=12;break;case4:...sCS+=2;break;case5:...sCS+=12;break;...}还原思路1、通过特征判断,确定平坦流代码2、获取分发器初始值(shufferVar)3、遍历while-case映射出case值和case代码块({'case值':代码块})4、再次遍历while-case,构造分支流程(自己说的词),表示流程的前后分支关系,当然没有break的需要特殊处理一下,形如:[[2,3],[1,2],[3,4]]5、通过分支流程的前后关系,构造出分发器数组6、遍历分发器数组(shufferArr),根据数组元素顺序获取映射的代码块,同时判断break,并删除break和更新流程节点,添加到父节点的body里面,添加前删除分发器节点和while节点1、判断特征平坦流都在函数节点里面,声明节点+whilw节点traverse(ast,{'FunctionDeclaration|FunctionExpression'(path){path.traverse({//只处理分发器是单个声明的VariableDeclaration(p){//通过特征定位定位分发器位置varrbD=0;letdeclarations=p.node.declarations;if(!(declarations.length===1&&type.isNumericLiteral(declarations[0].init)))return;letwhilePath=p.getSibling(p.key+1)if(!type.isWhileStatement(whilePath))return;//声明语句后不是while节点}})}})2、获取分发器判断完成,获取分发器变量名和初始值,变量名用来更新分发器值时判断//获取分发器letshufferVar={name:declarations[0].id.name,value:declarations[0].init.value}//分发器起始值console.info('分发器其起始值:',shufferVar)3、映射case映射为了方面获取case代码块//映射case和代码块let_case={}whilePath.node.body.body[0].cases.map((v)=>{letswitchCase=v.consequent;//获取cases里的代码块数组_case[v.test.value]=switchCase})4、构造分支流程构造分支流程的思路是,既然进入到该case,那么分发器现在的值就是该case的值,那么下一步的流程更新代码,就在该case代码块里面,也就能构造出前后分支的关系数组了//构造分支流程letshufferFlow=[],//分支流程数组flag=false;//没有break标识whilePath.node.body.body[0].cases.map((v)=>{letn=v.consequent.slice(-1)[0]//获取最后一个代码块大多数是break,不是break的话,该cases执行完就跳到下一个caseif(type.isBreakStatement(n)){letcaseNum=v.test.valueletnextNode=v.consequent.slice(-2)[0]if(nextNode.expression.left.name===shufferVar.name){//变量名与分发器起始值一样更新letnextNum=caseNum;eval(`nextNum//下一次流程${nextNode.expression.operator}${nextNode.expression.right.value}`)flag&&(nextNum-=1)&&(flag=false)//特殊处理就是下一次节点-1shufferFlow.push([caseNum,nextNum])}}else{if(!type.isReturnStatement(n)){//不是return执行完直接到下一个cases,但下一个case需要单独处理letcaseNum=v.test.valueletnextNode=caseNum+1flag=true//标识记录shufferFlow.push([caseNum,nextNode])}}})5、还原分发器上一步构造出来的前后分支关系,顺序是乱的,没有还原出代码的前后执行关系,所以在还原分发器之前,还需要通过分发器初始值,还原出代码的前后执行关系。得到代码的前后执行流程后,还原出分发器时,这里取巧的使用了函数,它原本是用来数组聚合的//构造前后分支关系letshufferFlow=[],//前后分支关系flag=false;//没有break标识whilePath.node.body.body[0].cases.map((v)=>{letn=v.consequent.slice(-1)[0]//获取最后一个代码块大多数是break,不是break的话,该cases执行完就跳到下一个caseif(type.isBreakStatement(n)){letcaseNum=v.test.valueletnextNode=v.consequent.slice(-2)[0]if(nextNode.expression.left.name===shufferVar.name){//变量名与分发器起始值一样更新letnextNum=caseNum;eval(`nextNum//下一次流程${nextNode.expression.operator}${nextNode.expression.right.value}`)flag&&(nextNum-=1)&&(flag=false)//特殊处理就是下一次节点-1shufferFlow.push([caseNum,nextNum])}}else{if(!type.isReturnStatement(n)){//不是return执行完直接到下一个cases,但下一个case需要单独处理letcaseNum=v.test.valueletnextNode=caseNum+1flag=true//标识记录shufferFlow.push([caseNum,nextNode])}}})console.log('前后分支关系:',JSON.stringify(shufferFlow),shufferFlow.length)//通过起始节点流程还原letshufferFlow_=[]shufferFlow.map((v1,i1)=>{if(shufferVar.value===v1[0]){shufferFlow_.push(v1)//寻找起头for(leti=0;i<shufferFlow.length;i++){for(letj=0;j<shufferFlow.length;j++){if(shufferFlow_[i][1]===shufferFlow[j][0]){shufferFlow_.push(shufferFlow[j]);break}}}}})console.log('分支执行流程:',JSON.stringify(shufferFlow_))//构造分发器letshufferArr=[];shufferFlow_.reduce((k1,k2)=>{k1&&shufferArr.push(...k1)k2[1]>0&&shufferArr.push(k2[1])})console.log('分发器数组:',JSON.stringify(shufferArr),shufferArr.length)6、整体还原获取完整分发器后,只需要把每个case里面的代码,按照分发器提取,添加到父节点的body里面就好,这个过程使用判断删除一点代码就好了p.remove()//删除分发器初始值whilePath.remove()//删除while节点letparentPath=whilePath.parentshufferArr.map((v)=>{if(type.isBreakStatement(_case[v].slice(-1)[0])){_case[v].pop()//删除break_case[v].pop()//删除流程更新节点}parentPath.body.push(..._case[v])//添加到path节点})console.log('父节点body数:',parentPath.body.length)验证可以看到已经还原了)