ollvm一直是程序保护中的一种强有力的手段,将原本的控制流打散,用一套分发器去控制流程(网上资料很多,这里不再赘述,请了解ollvm的基础知识再来阅读此文章,比如啥是序言,啥是后继,啥是分发器),本文将着重于一些被别人忽略的细节,让你能够用一套通法根据不同的变异ollvm特征写出不同的脚本 静态的对抗手段有一些ida插件,比如d810,但是这些静态处理的方法在面对变异ollvm时就显得有些乏力,比如双循环头,汇聚块与循环头合并等问题 我们不经想既然是控制流混淆,那么动态执行一遍找到正确的执行顺序不久行了嘛?
第一步我们就是要找出所有的代码,也就是真实块,我们可以根据一套固定流程来确定寻找 先祭出老生常谈的一张图
假如说又回到了B,那么B就是循环头,我们根据BFS算法可以找出来
循环头有两个前驱,一个是序言,用与开辟栈空间,初始化变量之类的,一个是汇聚块 怎么判断哪个是汇聚块呢?汇聚块有很多前驱,而哪些前驱就是我们最关心的————真实快 ps:其实序言也是真实块之一,别忘了(
这里也是考虑了多循环头的情况,可以自己看情况改 我们以[RoarCTF 2019]polyre 为例子,附件和所有脚本我都放github上了cbdK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6c8L8h3g2A6L8h3g2A6x3e0l9H3z5o6k6Q4x3V1k6V1k6h3k6D9j5i4c8Q4x3X3c8S2L8X3N6J5 ida运行结果是
这就到了本文最精髓的地方了,我们要找到每个块的后继 对于每一个真实块,我们遵循一套这样的法则 序言执 -> 主分发器 -> 直接跳到我们要的真实块 -> 继续执行看会到哪一个块 重点:
cmovxx通过修改寄存器的值,影响上面分发器的分发,达到不同的块 但是我们angr遇到不会cmovxx这些汇编分裂出两个分支,而是通过积累一个约束实现,所以我们要手动分裂两个状态,一个执行这条,一个不执行,就会到达两不同的块,然后我们这样储存
左边放我们zf=1的,右边放zf=0的,方便我们接下来patch
几个关键点
我们通过hook主分发器的最后一个指令,运行到直接跳到我们要的块,然后记得unhook,不然到时候执行回来又跳到那里去了,就循环了 为什么要 -6,我只能说理论上应该不用加偏移,但是实际上不加的化angr不能正确的识别指令,capstone解码出来的混乱的,这个-6是我摸索出来的,如果不行你们可以试试别的,记得把汇编输出出来对一下就行(我把它注释了) 运行结果
剩下的地方用nop填充 然后再后面把没用到的块给nop了
这里一个小细节,我们先找出所有的无用块列表,得到开始地址与结束地址,然后在进行控制流的patch,最后根据前面获取的无用块列表,nop无用块,如果先patch控制流在寻找无用块,找出来的无用块是错误的 恢复完的到代码
最后exp:
A -> B -> C -> D -> B
def find_loop_head (start_ea ):
loop_heads = set ()
queue = deque()
blcok = get_basic_block(start_ea)
queue.append((blcok,[]))
while len (queue) > 0 :
cur_block, path = queue.popleft()
if cur_block.start_ea in path:
loop_heads.add(cur_block.start_ea)
continue
path = path + [cur_block.start_ea]
queue.extend((s, path) for s in cur_block.succs())
all_loop_heads = list (loop_heads)
all_loop_heads.sort()
print ("[+]Find loop heads:" ,[hex (lh) for lh in all_loop_heads]," -- total:" ,len (all_loop_heads))
return all_loop_heads
def find_converge_addr (loop_head_addr ):
converge_addr = 0
block = get_basic_block(loop_head_addr)
preds = block.preds()
pred_list = list (preds)
if len (pred_list) == 2 :
for pred in pred_list:
tmp_list = list (pred.preds())
if len (tmp_list) > 1 :
converge_addr = pred.start_ea
print ("[+]Find converge_addr:" ,hex (converge_addr))
return converge_addr
for loop_head_addr in loop_heads:
loop_head_block = get_basic_block(loop_head_addr)
converge_addr = find_converge_addr(loop_head_addr)
real_blocks = []
loop_head_preds = list (loop_head_block.preds())
loop_head_preds_addr = [b.start_ea for b in loop_head_preds]
if loop_head_addr != converge_addr:
loop_head_preds_addr.remove(converge_addr)
print ("序言块:" ,[hex (x) for x in loop_head_preds_addr])
def find_ret_block (blocks ):
for block in blocks:
succs = list (block.succs())
succs_list = list (succs)
end_ea = block.end_ea
last_inst_ea = idc.prev_head(end_ea)
mnem = idc.print_insn_mnem(last_inst_ea)
if len (succs_list) == 0 :
if mnem == "retn" :
ori_ret_block = block
while True :
tmp_block = block.preds()
pred_list = list (tmp_block)
if len (pred_list) == 1 :
block = pred_list[0 ]
if get_block_size(block) == 4 :
continue
else :
break
else :
break
block2 = block
num = 0
i = 0
while True :
i += 1
succs_block = block2.succs()
for succ in succs_block:
child_succs = succ.succs()
succ_list = list (child_succs)
if len (succ_list) != 0 :
block2 = succ
num += 1
if num > 2 :
block = ori_ret_block
break
if i > 2 :
break
print ("[+]ret块" ,hex (block.start_ea))
return block.start_ea
def find_all_real_blocks (fun_ea ):
blocks = idaapi.FlowChart(idaapi.get_func(fun_ea))
loop_heads = find_loop_head(fun_ea)
all_real_blocks = []
for loop_head_addr in loop_heads:
loop_head_block = get_basic_block(loop_head_addr)
converge_addr = find_converge_addr(loop_head_addr)
real_blocks = []
loop_head_preds = list (loop_head_block.preds())
loop_head_preds_addr = [b.start_ea for b in loop_head_preds]
if loop_head_addr != converge_addr:
loop_head_preds_addr.remove(converge_addr)
print ("序言块:" ,[hex (x) for x in loop_head_preds_addr])
real_blocks.extend(loop_head_preds_addr)
converge_block = get_basic_block(converge_addr)
list_preds = list (converge_block.preds())
for pred in list_preds:
end_ea = pred.end_ea
last_inst_ea = idc.prev_head(end_ea)
mnem = idc.print_insn_mnem(last_inst_ea)
size = get_block_size(pred)
if size > 5 :
start_ea = pred.start_ea
real_blocks.append(start_ea)
real_blocks.sort()
all_real_blocks.append(real_blocks)
print ("子循环头及其子真实块" , [hex (child_block_ea) for child_block_ea in real_blocks])
ret_addr = find_ret_block(blocks)
all_real_blocks.append(ret_addr)
print ("all_real_blocks:" ,all_real_blocks)
all_real_block_list = []
for real_blocks in all_real_blocks:
if isinstance (real_blocks,list ):
all_real_block_list.extend(real_blocks)
else :
all_real_block_list.append(real_blocks)
print (f"\n所有真实块获取完成 真实块数量: {len (all_real_block_list)} " )
print (all_real_block_list)
[+]Find loop heads: ['0x40063f'] -- total: 1
[+]Find converge_addr: 0x4020cc
序言块: ['0x400620']
子循环头及其子真实块 ['0x400620', '0x401121', '0x401198', '0x4011de', '0x40124f', '0x40125e', '0x4012a4', '0x4012f6', '0x401305', '0x401326', '0x40136c', '0x4013b2', '0x4013cf', '0x4013ef', '0x401435', '0x401481', '0x401490', '0x4014ae', '0x4014d2', '0x4014e8', '0x4014f7', '0x401506', '0x401521', '0x401567', '0x4015b6', '0x4015c5', '0x4015d4', '0x4015ed', '0x4015fc', '0x401642', '0x401691', '0x4016a0', '0x4016e6', '0x401739', '0x401748', '0x401765', '0x4017ab', '0x4017fc', '0x40180b', '0x401830', '0x401849', '0x401861', '0x4018a7', '0x4018fa', '0x401909', '0x401926', '0x401940', '0x401960', '0x40197d', '0x40199b', '0x4019e1', '0x401a3d', '0x401a4c', '0x401a73', '0x401a8d', '0x401ad3', '0x401b25', '0x401b34', '0x401b4e', '0x401b5d', '0x401b75', '0x401bbb', '0x401c0d', '0x401c1c', '0x401c2b', '0x401c46', '0x401c69', '0x401caf', '0x401d03', '0x401d12', '0x401d2d', '0x401d45', '0x401d54', '0x401d9a', '0x401e00', '0x401e0f', '0x401e2d', '0x401e73', '0x401eb9', '0x401ed6', '0x401efa', '0x401f09', '0x401f2d', '0x401f3c', '0x401f60', '0x401f97', '0x401fa6', '0x401fb5', '0x401fcd', '0x401fe5', '0x401ff4', '0x40200c', '0x40201b', '0x402033', '0x40204d', '0x402072', '0x402096', '0x4020b3', '0x4020c2']
[+]ret块 0x401f54
all_real_blocks: [[4195872, 4198689, 4198808, 4198878, 4198991, 4199006, 4199076, 4199158, 4199173, 4199206, 4199276, 4199346, 4199375, 4199407, 4199477, 4199553, 4199568, 4199598, 4199634, 4199656, 4199671, 4199686, 4199713, 4199783, 4199862, 4199877, 4199892, 4199917, 4199932, 4200002, 4200081, 4200096, 4200166, 4200249, 4200264, 4200293, 4200363, 4200444, 4200459, 4200496, 4200521, 4200545, 4200615, 4200698, 4200713, 4200742, 4200768, 4200800, 4200829, 4200859, 4200929, 4201021, 4201036, 4201075, 4201101, 4201171, 4201253, 4201268, 4201294, 4201309, 4201333, 4201403, 4201485, 4201500, 4201515, 4201542, 4201577, 4201647, 4201731, 4201746, 4201773, 4201797, 4201812, 4201882, 4201984, 4201999, 4202029, 4202099, 4202169, 4202198, 4202234, 4202249, 4202285, 4202300, 4202336, 4202391, 4202406, 4202421, 4202445, 4202469, 4202484, 4202508, 4202523, 4202547, 4202573, 4202610, 4202646, 4202675, 4202690], 4202324]
loc_40199B:
mov eax, ds:dword_603054
mov ecx, ds:dword_603058
mov edx, eax
sub edx, 1
imul eax, edx
and eax, 1
cmp eax, 0
setz sil
cmp ecx, 0Ah
setl dil
or sil, dil
test sil, 1
mov eax, 0F37184F0h
mov ecx, 0A105D2C4h
cmovnz ecx, eax
mov [rbp+var_114], ecx
jmp loc_4020CC
{'0xaaaa' :['0xbbb' ,'0xccc' ]}
import logging
import angr
from tqdm import tqdm
logging.getLogger('angr' ).setLevel(logging.ERROR)
def capstone_decode_cmovxx (insn ):
operands = insn.op_str.replace(" " , "" ).split("," )
dst_reg = operands[0 ]
src_reg = operands[1 ]
print (f"cmovxx解析结果: 目标寄存器:{dst_reg} , 源寄存器:{src_reg} " )
return dst_reg, src_reg
def find_state_succ_cmovxx (proj, base, local_state, flag, real_blocks, real_block_addr, path ):
ins = local_state.block().capstone.insns[0 ]
dst_reg, src_reg = capstone_decode_cmovxx(ins)
if not flag:
try :
src_val = getattr (local_state.regs, src_reg)
setattr (local_state.regs, dst_reg, src_val)
except Exception as e:
print (f"寄存器访问错误: {e} " )
local_state.regs.ip += ins.size
sm = proj.factory.simgr(local_state)
while (len (sm.active)):
for active_state in sm.active:
try :
ins_offset = active_state.addr - base
if ins_offset in real_blocks:
value = path[real_block_addr]
if ins_offset not in value:
value.append(ins_offset)
return ins_offset
except :
pass
sm.step(num_inst=1 )
def find_block_succ (proj, base, func_offset, state, real_block_addr, real_blocks, path ):
msm = proj.factory.simgr(state)
while len (msm.active):
for active_state in msm.active:
offset = active_state.addr - base
if offset == real_block_addr:
print ("找到真实块:" , hex (real_block_addr))
mstate = active_state.copy()
msm2 = proj.factory.simgr(mstate)
msm2.step(num_inst=1 )
while len (msm2.active):
for mactive_state in msm2.active:
ins_offset = mactive_state.addr - base
if ins_offset in real_blocks:
msm2_len = len (msm2.active)
if msm2_len > 1 :
tmp_addrs = []
for s in msm2.active:
moffset = s.addr - base
tmp_value = path[real_block_addr]
if moffset in real_blocks and moffset not in tmp_value:
tmp_addrs.append(moffset)
if len (tmp_addrs) > 1 :
print ("当前至少有两个路径同时执行到真实块:" , [hex (tmp_addr) for tmp_addr in tmp_addrs])
ret_addr = real_blocks[len (real_blocks) - 1 ]
if ret_addr in tmp_addrs:
tmp_addrs.remove(ret_addr)
ins_offset = tmp_addrs[0 ]
print ("两个路径同时执行到真实块最后取得:" , hex (ins_offset))
value = path[real_block_addr]
if ins_offset not in value:
value.append(ins_offset)
print (f"无条件跳转块关系:{hex (real_block_addr)} -->{hex (ins_offset)} " )
return
ins = mactive_state.block().capstone.insns[0 ]
if ins.mnemonic == 'cmovnz' or ins.mnemonic == 'cmovne' :
print ("发现 cmovnz/cmovne 指令,进行分支处理:" , hex (ins_offset))
state_true = mactive_state.copy()
state_true_succ_addr = find_state_succ_cmovxx(proj, base, state_true, True , real_blocks, real_block_addr, path)
state_false = mactive_state.copy()
state_false_succ_addr = find_state_succ_cmovxx(proj, base, state_false, False , real_blocks, real_block_addr, path)
if state_true_succ_addr is None or state_false_succ_addr is None :
print ("cmovnz/cmovne错误指令地址:" , hex (ins_offset))
print (f"cmovnz/cmovne后继有误:{hex (real_block_addr)} -->{hex (state_true_succ_addr) if state_true_succ_addr is not None else state_true_succ_addr} ,"
f"{hex (state_false_succ_addr) if state_false_succ_addr is not None else state_false_succ_addr} " )
return "erro"
print (f"cmovnz/cmovne分支跳转块关系:{hex (real_block_addr)} -->{hex (state_true_succ_addr)} zf = 1, {hex (state_false_succ_addr)} zf != 1" )
return
if ins.mnemonic == 'cmovz' or ins.mnemonic == 'cmove' :
print ("发现 cmovz/cmove 指令,进行分支处理:" , hex (ins_offset))
state_true = mactive_state.copy()
state_true_succ_addr = find_state_succ_cmovxx(proj, base, state_true, False , real_blocks, real_block_addr, path)
state_false = mactive_state.copy()
state_false_succ_addr = find_state_succ_cmovxx(proj, base, state_false, True , real_blocks, real_block_addr, path)
if state_true_succ_addr is None or state_false_succ_addr is None :
print ("cmovz/cmove误指令地址:" , hex (ins_offset))
print (f"cmovz/cmove后继有误:{hex (real_block_addr)} -->{hex (state_true_succ_addr) if state_true_succ_addr is not None else state_true_succ_addr} ,"
f"{hex (state_false_succ_addr) if state_false_succ_addr is not None else state_false_succ_addr} " )
return "erro"
print (f"cmovz/cmove分支跳转块关系:{hex (real_block_addr)} -->{hex (state_true_succ_addr)} zf = 1, {hex (state_false_succ_addr)} zf != 1" )
return
msm2.step(num_inst=1 )
return
msm.step(num_inst=1 )
def angr_main (real_blocks,func_offset,file_path ):
proj = angr.Project(file_path, auto_load_libs=False )
base = 0
func_addr = base + func_offset
init_state = proj.factory.blank_state(addr=func_addr)
init_state.options.add(angr.options.CALLLESS)
path = {addr: [] for addr in real_blocks}
ret_addr = real_blocks[len (real_blocks) - 1 ]
first_block = proj.factory.block(func_addr)
first_block_insns = first_block.capstone.insns
first_block_last_ins = first_block_insns[len (first_block_insns) - 1 ]
print (hex (first_block_last_ins.address))
for real_block_addr in tqdm(real_blocks):
if ret_addr == real_block_addr:
continue
state = init_state.copy()
print ("正在寻找:" ,hex (real_block_addr))
def jump_to_address (state ):
state.regs.pc = base + real_block_addr - 6
print ("跳转到地址:" , hex (base + real_block_addr - 6 ))
proj.unhook(0x400675 )
print (hex (real_block_addr),hex (func_offset))
if real_block_addr != func_offset:
print ("序言结束" )
proj.hook(0x400675 , jump_to_address, first_block_last_ins.size)
ret = find_block_succ(proj, base, func_offset, state, real_block_addr, real_blocks, path)
if ret == "erro" :
return
hex_dict = {
hex (key): [hex (value) for value in values]
for key, values in path.items()
}
for i in hex_dict.keys():
print (f"{i} : {hex_dict[i]} " )
print (hex_dict)
return hex_dict
all_real_blocks: list [int ] =[4195872 , 4198689 , 4198808 , 4198878 , 4198991 , 4199006 , 4199076 , 4199158 , 4199173 , 4199206 , 4199276 , 4199346 , 4199375 , 4199407 , 4199477 , 4199553 , 4199568 , 4199598 , 4199634 , 4199656 , 4199671 , 4199686 , 4199713 , 4199783 , 4199862 , 4199877 , 4199892 , 4199917 , 4199932 , 4200002 , 4200081 , 4200096 , 4200166 , 4200249 , 4200264 , 4200293 , 4200363 , 4200444 , 4200459 , 4200496 , 4200521 , 4200545 , 4200615 , 4200698 , 4200713 , 4200742 , 4200768 , 4200800 , 4200829 , 4200859 , 4200929 , 4201021 , 4201036 , 4201075 , 4201101 , 4201171 , 4201253 , 4201268 , 4201294 , 4201309 , 4201333 , 4201403 , 4201485 , 4201500 , 4201515 , 4201542 , 4201577 , 4201647 , 4201731 , 4201746 , 4201773 , 4201797 , 4201812 , 4201882 , 4201984 , 4201999 , 4202029 , 4202099 , 4202169 , 4202198 , 4202234 , 4202249 , 4202285 , 4202300 , 4202336 , 4202391 , 4202406 , 4202421 , 4202445 , 4202469 , 4202484 , 4202508 , 4202523 , 4202547 , 4202573 , 4202610 , 4202646 , 4202675 , 4202690 , 4202324 ]
angr_main(all_real_blocks, 0x400620 , "D:\\reverse\\Angr\\polyre" )
def jump_to_address (state ):
state.regs.pc = base + real_block_addr - 6
print ("跳转到地址:" , hex (base + real_block_addr - 6 ))
proj.unhook(0x400675 )
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于 1天前
被Qmeimei10086编辑
,原因: