看雪论坛作者ID:34r7hm4n
接触OLLVM也有好长一段时间了,但一直停留在应用层面,接下来一段时间打算从进一步研究OLLVM。
俗话说柿子先挑软的捏,如果说OLLVM提供的几种混淆方式有辈分之分,那么虚假控制流(Bogus Control Flow)跟它的兄弟控制流平坦化(Control Flow Flattening)比起来就是弟中弟,我们就从去除OLLVM虚假控制流混淆开始吧!
GitHub仓库:bluesadi/debogus
0x00. 虚假控制流初探
我们先用一个简单的例子来看看OLLVM虚假控制流混淆的效果:
int main(){
char name[100];
scanf("%s", name);
if (strcmp(name, "Alice") == 0) {
printf("hello, %s.n", name) ;
} else if (strcmp(name, "Bob") == 0) {
printf ("hello, %sn", name);
} else {
printf("no permission.n") ;
}
}
因此下图中用框起来的代码块永远不会被执行,那些永远不会执行到的代码块,就叫做不可达的基本块:
0x01.
利用angr符号执行去除不可达的基本块
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-f','--file', help='The path of binary file to deobfuscate')
parser.add_argument('-s','--start', help='Start address of target function')
parser.add_argument('-e','--end', help='End address of target function')
args = parser.parse_args()
if args.file == None or args.start == None or args.end == None:
parser.print_help()
exit(0)
filename = args.file
start_address = int(args.start, 16)
end_address = int(args.end, 16)
import angr
proj = angr.Project(filename, load_options={'auto_load_libs': False})
target_blocks = set()
cfg = proj.analyses.CFGFast()
cfg = cfg.functions.get(start_address).transition_graph
for node in cfg.nodes():
if node.addr >= start_address and node.addr <= end_address:
target_blocks.add(node)
function_size = end_address - start_address + 1
target_block = proj.factory.block(start_address,function_size)
for ins in target_block.capstone.insns:
if ins.mnemonic == 'call':
proj.hook(int(ins.op_str, 16), angr.SIM_PROCEDURES["stubs"]["ReturnUnconstrained"](), replace=True)
在我研究过程中发现的另一个脚本:cq674350529/deflat显然就没有处理这个问题。
control_flow = set()
state = proj.factory.blank_state(addr=start_address, remove_options={angr.sim_options.LAZY_SOLVES})
simgr = proj.factory.simulation_manager(state)
control_flow.add(state.addr)
while len(simgr.active) > 0:
for active in simgr.active:
control_flow.add(active.addr)
simgr.step()
step的过程有点像BFS的过程,每碰到一个跳转就会分裂出两个新的active状态(前提是两个状态都是可达的)。
一边符号执行一边将符号执行能遍历到的所以基本块的地址保存到control_flow中。
base_address = proj.loader.main_object.mapped_base
handled_blocks = set()
patched_addrs = []
with open(filename, 'rb') as inp:
data = bytearray(inp.read())
for block in target_blocks:
if block.addr in handled_blocks:
continue
handled_blocks.add(block.addr)
if block.addr in control_flow:
for child in cfg.successors(block):
if child.addr < start_address or child.addr > end_address:
continue
if child.addr not in control_flow:
handled_blocks.add(child.addr)
patched_addrs.append(hex(child.addr))
write_nops(data, child.addr - base_address, child.size)
else:
write_nops(data, block.addr - base_address, block.size)
name, suffix = split_suffix(filename)
outpath = name + '_recovered' + suffix
with open(outpath,'wb') as out:
out.write(data)
print(f'Patched {len(patched_addrs)} unreachable blocks: {patched_addrs}')
print(f'Recovered file is saved to: {outpath}')
0x02. 去混淆效果分析
可以看到很明显是虚假控制流混淆:

它的流程图是这样的:
0x03. 另一种方法:去除不透明谓词
# 去除虚假控制流 idapython脚本
import ida_xref
import ida_idaapi
from ida_bytes import get_bytes, patch_bytes
# 将 mov 寄存器, 不透明谓词 修改为 mov 寄存器, 0
def do_patch(ea):
if get_bytes(ea, 1) == b"x8B": # mov eax-edi, dword
reg = (ord(get_bytes(ea + 1, 1)) & 0b00111000) >> 3
patch_bytes(ea, (0xB8 + reg).to_bytes(1,'little') + b'x00x00x00x00x90')
else:
print('error')
# 不透明谓词在.bss段的范围
start = 0x00428298
end = 0x00428384
for addr in range(start,end,4):
ref = ida_xref.get_first_dref_to(addr)
print(hex(addr).center(20,'-'))
# 获取所有交叉引用
while(ref != ida_idaapi.BADADDR):
do_patch(ref)
print('patch at ' + hex(ref))
ref = ida_xref.get_next_dref_to(addr, ref)
print('-' * 20)
0x04. 一些改进的想法
0x05. 之后的打算
打算继续研究基于LLVM的混淆了,angr也会继续学习,届时还会推出一些相关的文章,欢迎大家交流学习!
0x06. 参考
看雪ID:34r7hm4n
https://bbs.pediy.com/user-home-910514.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!
本文始发于微信公众号(看雪学院):利用angr符号执行去除虚假控制流
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论