ollvm原理

ollvm原理
foresta.yangollvm原理
Obfuscator-LLVM
Ollvm大致可分为 bcf(虚假块), fla(控制流展开/扁平化), sub(指令膨胀), Split(基本块分割)
bcf:
克隆一个真实块,并随机替换其中的一些指令,然后用一个永远为真的条件建立一个分支。克隆后的块是不会被执行的。
Fla:
将所有的真实块使用一个switch case结构包裹起来,每个真实块执行完毕后都会重新赋值switch var,对于有分支的块会使用select指令,并跳转到switch起始代码块(分发器)上,根据switch var来执行下一个真实块。
Sub:
指令膨胀,将一条运算指令,替换为多条等价的运算指令。
Split:
利用随机数产生分割点,将一个基本块分割为两个,并使用绝对跳转连接起来。
关于ollvm具体的实现,可参考源码。
- 指令替换 -mllvm -sub
- 虚假控制流 -mllvm -bcf
- 控制流展平 -mllvm -fla
- 函数(Funtions)注解
还原思路
网上有很多还原ollvm的脚本,但是只能还原特征很明显的ollvm,或者说只是debug版的ollvm。在debug版中ollvm的特征非常明显,一个分发器,和引用了这个分发器的真实块。但经过编译器优化后,分发器可能会变成多个,基本块会合并造成虚假块也可能会和真实块合并,等等。
现实情况是,你基本上碰不到简单的ollvm,所以那些东西个人感觉意义不是很大,还是需要靠自己。
谈下还原思路
Bcf:
Bcf块是执行不到的块,所以说当使用unicorn 跑过一遍函数后,其中没有执行到的块肯定有包括bcf块,我们只需要将它挑出来标记下就好。
但函数中可能存在分支,只跑一遍函数是无法覆盖到所有分支的,所以要想办法找到函数的所有分支。**一开始采用的是无名侠大佬的方法,当碰到csel指令时人工干预让其覆盖所有分支,但整个函数经常陷入死循环,分析过后发现虚假块的跳转也有可能使用csel指令。后来想到了在二进制漏洞挖掘中的思路fuzz(模糊测试),即变异函数的参数传递给函数,来覆盖更多的分支。**这样做也不能说能够找到函数的所有分支。影响一个函数的分支执行大概有三种情况,参数,全局变量,内部函数调用的返回值。后两种情况的话留意下模糊执行的trace应该能找到些蛛丝马迹,可能会比较麻烦。
Fla
这个环节会产生控制流块,我们只需要将这些块挑出来标记,找出所有的真实块,并通过模拟执行还原真实块之间的关系就好。
控制流块的剔除采用了无名侠大佬对基本块签名的方法。
Sub:
指令膨胀的还原,使用llvm的pass优化效果还可以,但目前一些ir翻译工具对arm64的支持不怎么样。
Split:
基本块分割更多是用来增加bcf和fla效果的。
总结整体思路:
1 利用模拟执行和fuzz技术,找出bcf块并剔除。
2 使用基本块签名剔除控制流块。
3 将剩余的块标记为真实块,并使用模拟执行找出对应关系。
4 根据对应关系,重构cfg。