本帖最后由 coldnight 于 2019-7-23 21:29 编辑
背景
分析被OLLVM混淆后的样本,该样本开启了虚假控制流保护。编译器会在正常的控制流中会通过不透明谓词添加额外控制流块阻止静态分析。人工去除该保护较简单,本文期望自动化去除虚假控制流,减少人力。例如:
sub_27810
代码块1中的y_149_ptr值为变量y_149的地址,还有其他以x_num或y_num为名称的变量:
在程序运行时,这些变量(x_num)初始化为0。因此代码块1中[eax]的值为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[x_ptr]] <s 0xA)?(addr_succ, addr_fail)即在地址x_ptr读取32位整数值a,以a作为地址,读取32位整数b,如果b < 0xA,则跳转到addr_succ,否则跳转到addr_fail。构造以下表达式匹配测试条件[Python] 纯文本查看 复制代码 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.开头,则认为是不透明谓词的变量:
[Python] 纯文本查看 复制代码 def is_dummy_var(s): # type: (str) -> bool
return s.startswith("x.") or s.startswith("y.")
使用符号执行引擎执行基本块,匹配基本块的分支条件,如果属于不透明谓词,则修改跳转指为为JMP即可:
[Python] 纯文本查看 复制代码 loc = todo.pop() # 获得未遍历的基本块nxt_addr = sb.run_block_at(ircfg, loc)
r = match_expr(nxt_addr, to_match, [mem_addr_jk, jl_dst_jk, njl_dst_jk])
if r and any(filter(is_dummy_var, loc_db.get_location_names(ircfg.get_loc_key(r[mem_addr_jk])))) \
and isinstance(r[jl_dst_jk], 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[jl_dst_jk]) # 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) # 添加所有应遍历的分支基本块 遍历完成后未被未遍历的块属于虚假控制流,可以清空:[Python] 纯文本查看 复制代码 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的语句):
去除后:
|