初级
初级的控制流,以switch-case呈现,能够直接看出语句的执行顺序,都是顺序语句,大概有两种形式,
一种是直接把语句的执行顺序以数组形式呈现:
function _0x31ad27() {
var _0x28aafb = "3|1|2|0|4|5".split("|"),
_0x2577c8 = 0;
while (true) {
switch (_0x28aafb[_0x2577c8++]) {
case "0":
console.log('case 0:statement...');
continue;
case "1":
console.log('case 1:statement...');
continue;
case "2":
console.log('case 2:statement...');
continue;
case "3":
console.log('case 3:statement...');
continue;
case "4":
console.log('case 4:statement...');
continue;
case "5":
console.log('case 5:statement...');
}
break;
}
}
_0x31ad27();
还原这种控制流,可以参考
某红书4.3.2 ob混淆还原(字符串数组/字典/控制流平坦化)
https://www.52pojie.cn/thread-2100076-1-1.html
(出处: 吾爱破解论坛)
一种是只给出初始值,然后需要跳转对应的case,在case里面的语句得到下一跳的case
function _0x31ad28(e) {
var i = e || 3; // 初始值是 3
while (true) {
switch (i) {
case 0: // 要改成 number类型的
console.log('case 0:statement...');
i = 4;
continue;
case 1:
console.log('case 1:statement...');
i = 2;
continue;
case 2:
console.log('case 2:statement...');
i = 0;
continue;
case 3:
console.log('case 3:statement...');
i = 1;
continue;
case 4:
console.log('case 4:statement...');
i = 5;
continue;
case 5:
console.log('case 5:statement...');
break;
}
break;
}
}
_0x31ad28();
中级
中级的控制流,case不直接呈现出来,而是以 if-else嵌套的形式或者多层 switch-case 嵌套的形式呈现,下一跳的信息有时没有直接给出来,形式灵活多样,各有各的特点
最里层的if-else, if(i< num1){statement1;}else{statement2;}, 可以转化为 case num1-1:statement1;case num1:statement2;
function _0x31ad29(e) {
var i = e || 1;
for (; ;)
if (i < 2) {
if (i < 1) { // i = 0
console.log('case 0...');
break;
} else { // i = 1
console.log('case 1...');
i += 1; // 下一跳的信息还得计算得出
}
} else {
if (i < 3) { // i = 2
console.log('case 2...')
i += 1
} else { //i = 3
console.log('case 3...')
i -= 3
}
}
}
_0x31ad29();
还原这种控制流,可以参考
[新手向]复杂化平坦流还原的小案例
https://www.52pojie.cn/thread-1904992-1-1.html
(出处: 吾爱破解论坛)
里面的案例是md5加密,把案例里面的代码 console.log(n(String(Date.now()))); 转成 console.log(n(String(1))); 就可以看出来,
这个帖子只给出了外层的case还原,可以扩展
如果条件是 === num,取 右边的值 num 为case值,如果是else {},即 path.node.alternate.type === 'BlockStatement',找到 if 条件 === num,num+1 为 case的值
(function(){
try{
var l,c,a,_,e,p,t,n,o,v,i,d,y,r,f,s,u,h,g;
var G = 7;
while(G !== undefined){
if(G < 4){
if(G === 0){
console.log('case 0...');
G =2;
}else if(G === 1){
console.log('case 1...');
G = 5;
}else if(G === 2){
console.log('case 2...');
G = undefined;
}else{ //G == 3
console.log('case 3...');
G = 4;
}
} else if (G<8){
if(G === 4){
console.log('case 4...');
G = 0;
}else if(G === 5){
console.log('case 5...');
G = 3;
}else if(G ===6){
console.log('case 6...');
G = 1;
}else{ // G ==7
console.log('case 7...');
G = 6;
}
}
}
}catch (e) {
}
})()
可以参考
web控制流平坦化解混淆
https://www.52pojie.cn/thread-1901741-1-1.html
(出处: 吾爱破解论坛)
多层switch-case嵌套,最里层的case是嵌套的三元表达式,下面的代码是 某boss的代码片段
for (var p = 93,Al,Ml,Tl; p !== void 0; ) {
var ip = 3 & p
, sp = 3 & p >> 2
, dp = 3 & p >> 4
, hp = 3 & p >> 6;
// p 有初始值,p被分解成了 ip,sp,dp,hp
// console.log('p:',p,'ip:',ip,'sp:', sp,'dp:', dp,'hp:', hp)
switch (ip) {
case 0:
var up = function () {
switch (sp) {
case 0:
var a = function () {
switch (dp) {
case 0:
var a = function () {
0 === hp ? p = void 0 : 1 === hp ? p = 148 : 2 === hp ? (Al = Ml + Tl,
p = 152) : 3 === hp ? (al = function p() {
return l.apply(this, [19906].concat(Array.prototype.slice.call(arguments)))
}
,
p = 212) : void 0
}
.apply(this, arguments);
if (a)
return a;
break;
}
}
}
}
}
}
想要还原为单层switch-case,需要知道每个case p的值,有的解法是 每个状态变量都会 & 3,证明了只取两位(bit),对应的范围就是0-3,有4个状态变量,8bit的范围就是 2^8-1=255
我的思路是,p被分解成了 ip,sp,dp,hp,那么根据 ip,sp,dp,hp 就可以推出 p 值,例如:0 === hp ? p = void 0 :alternate_statement 得到 hp值为0,对于的上层的dp,sp,ip值都为0,
再根据 var ip = 3 & p, sp = 3 & p >> 2, dp = 3 & p >> 4, hp = 3 & p >> 6; 进行反向推理,得到 p = ip + (sp << 2) + (dp << 4) + (hp << 6),代入数值,p值为0,
还原case: switch (p){case 0:p = void 0;}
给出语句顺序的数组,可以根据数组得到case值,然后把if-else嵌套还原为case,某数片段
demo1: 比较运算符不固定,有 <,===
function _$$E(_$aS, _$jA) {
var _$_Q, _$_8, _$fZ = _$aS, _$gh = [0, 0, 0, 1, 2, 3, 0];
while (1) {
_$_8 = _$gh[_$fZ++];
if (_$_8 < 4) { // 比较运算符不固定,有 <,===
if (_$_8 === 0) {
return;
} else if (_$_8 === 1) {
_$_Q = _$d8[7][_$h0[25]](_$jA, _$gY[81]) === 0;
} else if (_$_8 === 2) {
!_$_Q ? _$fZ += 1 : 0;
} else {
_$_6(44, _$jA);
}
} else
;
}
}
_$$E(0);
demo2: 需要四则运算再比较,如:(-432 < -108 * _$a1)
var _$v$, _$a1, _$5R = _$3Z, _$Ls = _$GF[0]; // _$GF[0] 是数组
while (1) {
_$a1 = _$Ls[_$5R++];
if (-432 < -108 * _$a1) {
if (62 === _$a1 + 60) {
_$jD = _$aK[_$M$[5]] = {};
} else if (15 - _$a1 < 15 && -109 > _$a1 - 111) {
_$5R += -5;
} else if (90 - _$a1 > 89) {
_$jD = _$aK[_$M$[5]];
} else {
_$akK = window,
_$cP = String,
_$qo = Array,
_$Cw = document,
_$uW = Date;
}
} else if (-111 > -37 * _$a1 && _$a1 < 8) {
}
}
这种控制流的还原,可以参考
某数控制流改写
https://www.52pojie.cn/thread-1783820-1-1.html
(出处: 吾爱破解论坛)
文中的第二种还原方式是 一边收集 statement_i 存储到数组中,一边插入 index_obj[_$_o] = i;,其中的 i 是指 statement_i 在数组中的位置/下标(index)
还有终极大法,那就是直接浏览器调试,收集case值了
高级
初级控制流说到的都是顺序语句,当下一跳信息是条件判断,分别指向两个case,这时候就要了解一下程序的基本控制结构,通常分为三类:顺序结构、分支结构(也叫选择结构)和循环结构
p = v ? 12 : 13 可能是条件语句的入口,也可能是循环语句的入口,如果只出现一次,那肯定是条件语句,
这种控制流的还原,网上找到两种方法
一种是收集 case值,case里面的语句信息,下一跳的信息,case被指向的次数等信息,根据 case被指向次数等来判断
可以参考
还原某里226控制流混淆的思路
https://www.52pojie.cn/thread-1818486-1-1.html
(出处: 吾爱破解论坛)
还有
227还原实战(六)控制流专题
https://blog.csdn.net/weixin_52001594/article/details/141129435?spm=1001.2101.3001.10796
这一篇 227还原实战 是根据 还原某里226控制流混淆的思路 进行的编写的,使用babel来进行还原,有分支和循环的控制流样例,讲解很详细,有些方法需要补全
可以根据这个把中级中的第一样例(md5)语句顺序还原出来
第二种也收集 case的信息,但不管case被指向的次数,只要知道初始值指向哪个case
可以参考
400行代码解混淆猿人学第二届第三题然后进行完美反编译
https://alanhays.github.io/posts/6066.html
虽然还原了控制流,但还有jsvmp需要处理
可以根据这个把中级中的第一样例(md5)语句顺序还原出来
两种方法,各有优缺点,
第二种方法,不关心case被指向次数,可以无视掉 语句被复用的情况,但因为是根据 初始值 一步一步找case,
如果本身就存在多个初始值(多个函数放在一起),就像一团毛线,有多个线头,只找了一个线头,理出一条线
第一种方法,关心case被指向的次数,就不能出现语句被复用,虚假分支的情况,但可以处理多而函数并存的情况,
就像一团毛线,多个线头,都能够一一找出来
其他问题
控制流中,往往还有语句复用,虚假分支的情况,控制流还原,还要先解决这些问题
虚假分支
判断条件,恒真或恒假的情况,
例如:p = a = 0 ? 1 : 2 得到的结果永远是 p = 2
例如:p = a = 1 ? 1 : 2 得到的结果永远是 p = 1
更多虚假分支的例子,请参考
阿里云虚假分支剖析
https://blog.csdn.net/qq_36780279/article/details/141721767
不过,花样太多了,形式太多变,还是通过浏览器调试,收集 下一跳的条件判断语句和真实的下一跳信息,解决掉虚假分支问题(包括不是虚假分支的,但是也只指向了一个分支的)
ps:收集case值和虚假分支信息是两个方向的,虚假分支中另外一个不被指向的分支的case,会存在其他case的调用,虚假分支不被指向的分支是可以跳转到代码的任意位置;控制流还原前处理;
语句复用
例如:p = void 0,return;,aa.push(bb) 等语句,在多个完整的条件代码块和循环代码块出现,都指向同一个case,
无法分别是属于哪个代码块中的语句了;控制流还原前处理;
常量串联
switch(p){
case 0:
d = "ge";
f = "tTi";
g = "me";
c = d + f;
h = c + g;
p = void 0;
case 1:
d = "wi";
f = "nd";
g = "ow";
c = d + f;
h = c + g;
p = void o;
}
这个属于控制流还原之后的问题了,可能不能直接binding进行替换,
我的解决思路是每个case转成code,然后再转成ast,这样就可以去掉和其他case的关联,然后进行常量替换