coldnight 发表于 2019-7-23 21:22

基于Miasm符号执行移除OLLVM虚假控制流

本帖最后由 coldnight 于 2019-7-23 21:29 编辑

背景

分析被OLLVM混淆后的样本,该样本开启了虚假控制流保护。编译器会在正常的控制流中会通过不透明谓词添加额外控制流块阻止静态分析。人工去除该保护较简单,本文期望自动化去除虚假控制流,减少人力。例如:
sub_27810

代码块1中的y_149_ptr值为变量y_149的地址,还有其他以x_num或y_num为名称的变量:

在程序运行时,这些变量(x_num)初始化为0。因此代码块1中的值为0,jl跳转永远成立,代码块2和3不会被执行。此外代码块2中的jz跳转也永远成立。
代码块3中的指令add esp, 2E68B6h阻碍了静态分析,使IDA F5功能失效:


分析

手动修复:
[*]虚假控制流:代码块1中jl改为jmp,同时以NOP填充代码块2和3。
自动修复:
对于仅使用虚假控制流混淆的样本,需要识别jl测试条件是否为不透明谓词,如果是则修改jl为jmp即可。对于jz也如此。在去除jx等测试条件后,遍历所有基本块,未被遍历的块即为虚假控制流生成的块,可以去除。

实现
Miasm 2.0是一款逆向工程框架,提供了基于IR的符号执行引擎。在Miasm中,jl的测试条件的IR可以表示为:(@32[@32] <s 0xA)?(addr_succ, addr_fail)即在地址x_ptr读取32位整数值a,以a作为地址,读取32位整数b,如果b < 0xA,则跳转到addr_succ,否则跳转到addr_fail。构造以下表达式匹配测试条件ptr_jk = ExprId("x_ptr", 32)
memop = ExprMem(ExprMem(ptr_jk, 32), 32)
j_succ_jk = ExprId("addr_succ", 32)
j_fail_jk = ExprId("addr_fail", 32)
to_match = ExprCond(ExprOp("<s", memop, ExprInt(0xA, 32)), j_succ_jk, j_fail_jk)
如果地址的名称以x.或y.开头,则认为是不透明谓词的变量:
def is_dummy_var(s):# type: (str) -> bool
    return s.startswith("x.") or s.startswith("y.")
使用符号执行引擎执行基本块,匹配基本块的分支条件,如果属于不透明谓词,则修改跳转指为为JMP即可:
loc = todo.pop()# 获得未遍历的基本块nxt_addr = sb.run_block_at(ircfg, loc)
r = match_expr(nxt_addr, to_match, )
if r and any(filter(is_dummy_var, loc_db.get_location_names(ircfg.get_loc_key(r)))) \
and isinstance(r, ExprInt):
    # 不透明谓词 生成补丁
    ablk = asmcfg.loc_key_to_block(loc_db.get_offset_location(offset))# type: AsmBlock
    j_asm = ablk.lines[-1]# type: instruction 汇编语句块最后一行即为跳转指令
    j_offset = j_asm.offset
    j_size = len(j_asm.b)
    j_dst = int(r)# addr_succ
    # 生成JMP addr_succ的op_code
    new_j_asm = gen_j_ins("JMP", j_dst, j_offset, j_size, strict=True)
    patches.append((j_offset, new_j_asm))
    todo.add(ExprInt(j_dst, 32))# 遍历下一基本块
else:
    for dst in possible_values(nxt_addr):
      value = dst.value
      if value.is_mem():
            logging.warning('Bad destination: %s', value)
            continue
      todo.add(value)# 添加所有应遍历的分支基本块遍历完成后未被未遍历的块属于虚假控制流,可以清空:for ablk in asmcfg.blocks:
    blk_off = loc_db.get_location_offset(ablk.loc_key)
    if ExprInt(blk_off, 32) not in visited_asm_blk:
      b_start, b_end = ablk.get_range()
      patches.append((b_start, bytes(NOP_B * (b_end - b_start))))# 以nop清空
效果
去除不透明谓词前(手动删除阻止F5的语句):


去除后:

hlrlqy 发表于 2019-7-24 11:09

以前也看过用这个东西搞OLLVM的,不过还是楼主的帖子看起来浅显易懂

数据摧毁 发表于 2019-7-24 14:40

牛人!厉害了!!

smartdone 发表于 2019-7-24 15:28

试下效果,最近也在研究这个

yaoyao7 发表于 2019-10-21 13:05

感谢师傅,刚好在看ollvm,学习流

dolphinzhu 发表于 2020-9-22 09:42

符号执行的资料太少了,非常感谢! 正在学习!

o10pwno 发表于 2020-12-14 15:14

https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html 这里还有一篇古老的去混淆
页: [1]
查看完整版本: 基于Miasm符号执行移除OLLVM虚假控制流