type
status
date
slug
tags
summary
category
icon
password
以前一直没有完整写一个去混淆脚本出来,这两天一并学完它们。ollvm这块总体思路参考deflat,进行了一定的优化,脚本基于angr和idapython,最后均适配到idapro9.0上面去。

demo1

识别

首先是ollvm的识别,对于标准的ollvm,其序言,主分发器,retn块,预处理器都比较容易判别,块与块的连接关系很明显,然后就能通过里面的内容判断真实块(更复杂的ollvm中,往往会将真实块的判定作为一个难点,例如一定程度上混淆真实块和子分发器,甚至模糊ollvm的各个组成部分)
notion image
首先,我们要知道我们最终的目的:将所有真实块按原有逻辑串联起来,所以首先是要将所有真实块识别出来。
观察前面的图,我们可以得出一个结论(并不普适)
一个程序只有一个序言,序言块没有入度 序言的后继是主分发器 主分发器的入度除了序言块就是预处理器 预处理器的入度是真实块 没有后继的是retn块 其它的块是子分发器
 
可以获得所有的块
notion image
 

控制流恢复

找到一个伪代码,可以看到这里的v9完全接管了控制流,这里也可以作为ollvm判断的一个特征
notion image
我们知道,通常控制流有三种变化方式,姑且归纳为call,jmp,jz/jnz,更多的控制流变化我们暂时归在这里面
这三种情况我们都需要处理。
  • call
angr是比较怕掉系统函数的,这个demo中我们把call全部hook掉,具体操作而言,扫描所有hook的地址
给它们挂上hook,,proj.hook默认跳过length长度的指令,函数只用声明state参数然后return即可。
  • jmp
notion image
比较明显,执行到这个块后,这个块就会给v9赋值,就相当于跳转到下一个块
  • jz/jnz
notion image
针对一个非v9变量做一个判断,依此对v9做赋值,我们这里提取关键:cmovxx这个指令作为识别标准,检测到cmov后知道这个块存在不同的分叉
 
angr本身的模拟执行特别容易路径爆炸,测试过从头到尾都模拟,很容易陷进奇怪的地方。因此策略是,从一个基本块开始,执行到下一个基本块就结束,获得一条路径;如果有路径分叉,就本策略走两次获得两条路径:
执行过程:
对cmov的情况,是这样处理的:
(最后给完整代码)
目前就能跑出来控制流了
notion image

写回控制流

有前面的铺垫,到这就比较容易了
  • jmp直接patch基本块最后一条指令
  • jz/jnz从cmov开始patch
  • 序言块特判一下
完整代码:
没有patch很干净,但伪代码清晰了,够用
notion image
 

demo2

这个和上一个差的没那么大,还是属于相对标准的处理

差异

其它部分差不多,主要是控制流这部分需要注意,找一个来看看
初始化:
notion image
中间的控制流:
notion image
notion image
首先,call不能无脑跳过了,5720这个call必须保留出来
其次,v13的值会被修改和记忆,导致每个控制流之间没有完全独立了,用前面的方法会去不掉(实际上,这个demo虽然简单,但d810也去不掉它的)
 
这时候应该从头执行到尾了吗?
答案是不能,我的测试是还会爆(也许有更好的优化方法?目前我剪枝剪很久没弄出来,有的话以后再学学)
那么还是得用前面的策略,一个基本块执行到下一个就cut掉,这时候可以在每次创建的时候不使用blank_state,而是记录上一条入度(注意只用一条)的state并继承来用。这时候整个循环也做修改,从顺序跑改为dfs跑一遍所有的块
其它部分不变:
这样跑完的话,控制流出来了,但还需要nop掉这些真实块中的无用函数才能完全恢复,不过总体来说不影响阅读
notion image
虚假控制流去除间接跳转混淆去除
Loading...