吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 246|回复: 0
上一主题 下一主题
收起左侧

[Web逆向] 控制流的多种变化特征

[复制链接]
跳转到指定楼层
楼主
xiaoxiao0329 发表于 2026-10-15 15:40 回帖奖励
本帖最后由 xiaoxiao0329 于 2026-10-15 15:43 编辑

初级

初级的控制流,以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的关联,然后进行常量替换

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
Eric00 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-10-16 09:51

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表