浅谈iOS上的ollvm与vmp
本帖最后由 lichao 于 2023-7-25 10:06 编辑## Ollvm
 Ollvm和vmp都是源码级混淆技术,用于复杂化编译后的代码,Ollvm(Obfusecated-llvm)即llvm混淆,印象中是一个12年左右某大学毕设论文里首次提出的技术,其核心思想就是执行流扁平化,编译器创造一个伪逻辑:通过每次for循环并设置下一次的tag,实现相同执行逻辑,如下为简单示例:(在实际实现中,tag的值都是随机数,且采用多tag,多层while的方式提高复杂度。)
```c
printf("step 1");
printf("step 2");
printf("step 3");
// 转换为
int tag = 1;
while (true) {
if (tag == 1) {
printf("step 1");
tag = 2;
} else if (tag == 2) {
printf("step 2");
tag = 3;
} else if (tag == 3) {
printf("step 3");
tag = 4;
} else {
break;
}
}
```
 目前开源项目中,Hikari是支持Mac/Linux/Win的ollvm项目,笔者20年初接触到该项目(https://github.com/HikariObfuscator/Hikari),目前已经不再更新,且有其他作者开发出增强的Hiakri-LLVM15项目。这里简单说下其功能以及arm64架构下的逻辑恢复:
* bcf 控制流混淆,即插入绝对不会执行的条件语句,这样做的结果是让IDA F5后的伪代码增加一倍。对于bcf本人懒得处理,因为看一半逻辑就可以了,比如100行的伪代码,前50行和后50行其实是一样的逻辑
* fla 即上述扁平化执行流,加上去看着会很复杂的switch-case,搞不清case执行顺序。对于fla,可以写IDA脚本,将switch的各个tag识别出来,并识别出对应tag的下一个case,然后在tag赋值之处(一般在case结尾处)直接改指令跳转到该case即可。如果在case结尾处指令空间不够做条件跳转或者逻辑过于复杂,需要开发microcode脚本,这种技术需要ida7.3+,能实现“同一个地址存放多条指令”。不过这种情况较少,基本上改跳转就能搞定大部分ollvm。
* split 拆分代码块
* sub 指令替换为相同功能的指令,貌似对IDA F5没啥影响
* acd 防止classdump,商业版的有用,破坏了底层objc结构,导致frida如果带通配符匹配objc类及函数会进程崩溃。对acd的处理本人是动态去做的,如果不能hook objc则hook相对地址就好了
* indibran 跳转位置(bne/beq/...)变跳转寄存器(br),使用后IDA F5看到的被拆分成了好几个函数,无法看到正常逻辑。对于indibra的处理,也是写脚本将br想办法替换为b addr
* strcry 字符串加密,字符在编译时就计算好异或值,并在每个函数在开头处加入解密逻辑。对于scrcpy,可以用unicorn模拟跑解密,原地还原字符串
* funcwra 包装函数,比如一个open,套了好几层很短的wrapper_open跳转。对于funcwrap可以找到其规律批量识别并重命名
* fco 函数调用混淆,指定符号在编译期自动dlsym化
 注意: 难点在fla的恢复,普通跳转会被混淆为br,条件跳转会被混淆为csel,能将case按正确顺序串起来,就完成了恢复工作。因为混淆器是人开发的,无论多复杂也存在某种指令模式,只要针对该模式写脚本恢复即可,因此最后均是可恢复的,只是不存在通用脚本做恢复罢了
## Vmp
 Vmp是一种Win时代就很成熟的混淆技术,核心思想是将原生指令转换自定义指令集并在运行时使用解释器解释。阿里系App在2015年就开始研发此技术,而抖音在2019年开始研发此技术,不过抖音的指令集较为简单也就二十几个,笔者最近也看了一款商业级Vmp加固的iOS软件并做了还原。近几年AppStore上架要求不能使用太多Ollvm混淆。
 从原理上分析Vmp是比Ollvm更彻底的技术了,Ollvm只是打乱了执行逻辑而已,而Vmp则是将函数逻辑拆分成一堆数据和一个解释器,当然执行效率肯定是比原生指令慢个O(n),Ollvm可以算作O(1),这点不如Ollvm。那么还原Vmp也是有固定思路的:
* 定位到解释器函数,解释器对应着一个结构体(vmp_ctx),其中使用内存模拟32个64位寄存器,还要分配模拟栈空间,最后返回值返回的是模拟的X0
* 定位到指令处理函数,如果指令较多,可以在解释器里找到自定义指令处理函数的数组,如果指令不多则这部分就在解释器函数里了
页:
[1]