还原控制流(重头戏!)
-
这里面 5 个方法对应了下面的五个标题
-
在处理之前我们先将 scDic 这个对象初始化一下,因为设置的全局变量,所以每次还原新的控制流时都要初始化,避免冲突
-
存储所有的 if 语句 + if 转 case + 虚假分支处理
-
这里可以看到我们传入两个参数,一个是 path 节点,一个是 if_name,path 节点是为了确保我们还原的时候不会还原错,搞混, if_name 是用来判断当前的 if 语句是否正确
判断是否到达存储 if 的位置
-
判断是否到达我们需要存储 if else 的地方,有两个判断方式
-
一先判断当前节点下第一个是不是 if 语句二
-
判断是否为表达式语句,如果是表达式语句就接着判断这个表达式语句是不是包裹着一元表达式
-
以上都不符合就代表未到达我们需要存储 if 语句的位置
-
获取 if 语句里判断的标识符 并判断标识符是否正确
-
在获取之前,要分两种情况判断,这两种就是上面的两种
-
直接 if 的 直接那就行
-
一元表达式的就需要进行细分的判断,原因如图
-
最后就判断 if 里的标识符(mi)是不是和我们一开始拿的标识符一致,是的话就代表来对地方了,可以准备存储了
if 语句预处理
-
这一块预处理的方法是学习了 52破解里的 sergiojune 大佬还原某里226控制流混淆的思路 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn],这里我也详细讲一下
-
先讲一下预处理的思路,大致就是遍历当前 if 语句下的所有代码块==if (){代码块}else{代码块}==,然后将代码块拿出来,做一个编号存储在对象里面,最后将代码修改成这个编号就好,如图
-
这样处理过后,我们就可以方便准确的拿到对应 if 下的 代码块,==不用判断 if 来拿是因为不够准确==,在一些情况下是不知道当前 if 下的代码块是属于哪个数字的,比如 if (a > 2){code1} 它是 3?4?5?还是什么,这就需要结合它的上一个 if 来判断,那要是 if 多了,怎么判断,所以 sergiojune 大佬的思路就很好,==直接通过它本身的 if 来判断,判断完之后,就可以拿到当前 if 下代码块的编号,然后直接去 state_table 对象中取就可以==
-
先判断当前代码块的第一个语句是不是 if 语句,是的话就判断它里面的判断标识符是不是和 if_name,是的话就代表他是 if 嵌套,这下面的代码块就不存储,直接返回走下一个
虚假分支的处理
-
什么是虚假分支,227 的控制流里,有一些判断会让你的整个代码流程走的方式不正确,这种就是虚假分支,227的虚假分支挺垃圾的,==没有用到环境检测什么的==,就比较好弄
-
它这个==虚假分支是无法通过合并节点然后取判断它是否正确的==,因为有些虚假分支是指向一个==错误的分支==,有些虚假分支是指向一个==正确的分支(但流程不正确)==,你==无法判断它指向的分支是错误的还是正确的或者流程是否正确==,无法有效的进行一个区分
-
这个时候就只能去它的源代码上面看,看他虚假分支的判断,最后你就会发现,它虚假分支防的不是环境防的是还原的人(我踩过坑,就是想通过判断来进行还原),所以就只能去看它的虚假分支有多少个,然后记下来,还原就好
-
它==虚假分支判断的标识符都是相互调用的==,所以就导致它始==终是 true==,在三元的判断里面就直接拿 true 的分支就可以了
-
==当然你如果有什么想法也可以来和我进行探讨==
-
先将当前的代码块存储下来,并给他一个编号
-
然后将当前代码块的最后一句取出来,然后对其进行判断,也就是判断是不是赋值类型的三元表达式
-
然后再判断这个三元表达式的判断标识符是否存在于我们记录的虚假分支对象中,存在的话,就将这个三元换成这个三元判断正确指向的分支
-
将处理好的最后一个语句添加回去,然后再在这个代码块的屁股添加一个 break(转 swicth 要用)
-
之后将这个代码块修改成对应的编号,标识符就为 if_name,然后将编号+1,防止冲突
获取当前 if 语句所在 case 的位置
-
code new_node 就是构建一个 switch 语句
-
index 为等会还原成 swicth 开始时,模仿控制流的数字
-
if_str 就是将上面预处理完后的 if 语句转换成字符串
-
getCase 传入当前节点获取当前节点往上的所有 case 节点
-
然后就是在 scDic 中取出来,有就直接取,没有就创建,最后就会得到当前节点所在位置(高亮) scDic {0 : {0: =={}==}}
if 转 swicth + 存储 if
-
init_state 用来存储已经获取过的编号
-
先构建一个 eval_code 用来获取对应控制流下的代码块编号,然后按照对应的进行存储,if 预处理里的第二张图,比如当前控制流是 14 我就获取到编号为 2 的代码块,然后下一次循环这个 2 就是控制流,依次循环,直到遇到重复的数字,就代表当前 if 中的代码块我们拿完了
-
执行 eval_code 获取编号
-
判断编号是否存在,存在就代表已经拿完,不存在就将编号存储在 init_state 中
-
用 .slice 获取当前代码块,防止变量污染
-
将代码块添加到 swicth 里的 cases 数组中,判断数字为 index
-
取出 res 的最后一个,判断如果不是 break 就添加回去
-
最后通过 map 将用 expressionStatement 包裹的语句取出(方便后续处理),按照控制流数字,存储在 scDic 中
-
控制流 + 1
-
最后根据不同情况修改对应位置的代码后, if语句 就成功转换为 swicth语句了
存储外层的 case 语句
-
这里比较简单,说一下大致原因就过了,不详细讲,因为上面已经将所有的 if 语句存储完毕了,但是还是有一些 case 语句没有存储,毕竟它不是所有的 case 下都套 if 的控制流,所以需要额外进行存储处理
将代码块与控制流数字进行对应
-
code 就是把当前控制流节点转换为字符串,用来匹配控制流数字
-
正则匹配控制流数字,两种情况
-
li = num;
-
li = xx ? num1 : num2;
-
-
Ee 是因为有些数字用了科学计数法,\d 匹配不到
-
num_cont 用来存储对应好的代码块
-
先说明一下 num_cont 中 key 会存在的属性
-
num 代表这个控制流数字出现了几次
-
red 代表代码块
-
next_num 代表改控制流执行完毕后会走的下一个控制流数字
-
side 代表这个控制流存在在三元表达式中
-
-
用 + 将匹配的字符串转换为数字,然后判断是否存在,如果存在就将对应的 num +1
-
不存在就进行存储,初始化数值 num,red
-
get_code 方法用于获取控制流数字对应的代码块,里面就是模仿控制流的运算,不详细说,上面的参数前面基本都有介绍
-
取出当前代码块的最后一个,判断它的右边是数字类型还是三元表达式
-
数字类型就将 next_num 赋值为这个数字
-
三元表达式就将这最后一个语句重新添加回去,并将 next_num 赋值为 -1
-
最后一句话是 return 就赋值为 re
-
是一元表达式(void 0)就赋值为 un
-
第二个的处理方式是一样的,只不过是多了一个 side ,存在于三元表达式中
-
最后返回对应好控制流数字的对象
合并单一节点
-
new_num_cont 合并完后的新对象
-
num_key 获取传入对象的所有 key
-
num_key 里没有了就结束,代表合并完毕
-
从 num_key 中取出一个要进行合并的 控制流对象
-
获取相应控制流数字对象中的参数 red, next_num, num
-
判断是否含有下一个 next_num,next_num 中的 num 是否为 1,为 1 就代表只调用过一次,可以直接合并
-
存在于 num_cont 里
-
获取下一个控制流的对象
-
取出这个对象的 代码块数组,进行合并
-
之后将这个数字在 num_key 中删除掉
-
最后将当前控制流对象的 next_num 替换成 next_num 对象里的 next_num
-
-
存在于 new_num_cont 里
-
从 new_num_cont 里取出对象后,只需要删除 new_num_cont 里对应的对象就好,因为只要存储在 new_num_cont 里,num_key 就不会含有存在 new_num_cont 里的 key
-
操作和上面一样
-
-
最后跳到下一个循环
-
最后没跳出,就在 new_num_cont 里添加相应的对象,然后把 num_cont 里的删除就好
-
然后返回处理完毕的对象
下一章争取把控制流讲完~
本期还原后代码已放在星球中,有需要自行取用
有想交流或者交个朋友的可以加我
let v = Died_in2021