利用unicron去除ollvm混淆
作者:互联网
测试程序是2022 TX的一道安卓赛题,主函数以及所有的子函数都被混淆了。
去混淆思路
- 首先需要找出被混淆函数中所有的控制流基本块
- 根据真实块的特点,从所有的控制流基本块中筛选出所有真实块(特殊情况需要特殊处理)
- 利用符号执行或者模拟执行找到所有真实块能够跳转到的目标块(路径),如果基本快中有条件判断指令其路径就有两条,否则只有一条。
- 根据所有真实块以及其对应的路径去patch程序,构造具体的跳转指令来恢复程序原来的控制流。
- nop所有的虚假块
去混淆具体过程
main函数去混淆前的流程图如下
利用反汇编引擎找到左右的控制流基本块
控制流基本块的划分是通过b,bge,bgt,ble,blt,bne,beq,beq.w
等指令分割的,同时注意ret块比较特殊是以pop popeq
等指令划分的。
for i in md.disasm(bin1[offset:end],offset):
insStr += "0x%x:\t%s\t%s\n" %(i.address, i.mnemonic, i.op_str)
if isNew:
isNew = False
block_item = {}
block_item["saddress"] = i.address
if (i.mnemonic == 'b' or \
i.mnemonic == 'beq.w' or \
i.mnemonic == 'beq' or \
i.mnemonic == 'bne' or \
i.mnemonic == 'bgt' or \
i.mnemonic == 'bge' or \
i.mnemonic == 'ble' or \
i.mnemonic == 'blt' or \
i.mnemonic == 'popeq' or \
i.mnemonic == 'pop'): #所有的跳转指令作为基本块区分
isNew = True
block_item["eaddress"] = i.address
block_item['ins'] = insStr
print(insStr)
insStr = ''
for op in i.operands:
if op.type == ARM_OP_IMM: #如果是立即数
block_item["naddress"] = op.value.imm
if op.value.imm not in processors: #计算后继块的引用计数
processors[op.value.imm] = 1
else:
processors[op.value.imm] += 1
if "naddress" not in block_item: #如果操作数中不含有立即数(ret块)
block_item['naddress'] = None
list_blocks[block_item["saddress"]] = block_item #保存所有的块的信息
找到所有基本块中的真实块
我们寻找此混淆代码中真实块的特点发现,凡是有内存操作的都是真实块,没有内存操作的都是虚假块。(其他混淆中不一定,重要的是寻找真实块的特点)
#真实块中需要过滤的块
fake_blocks = []
for i in real_blocks:
insns = list_blocks[i]['ins'].split('\n')
flag = 0
for ins in insns:
if ins.find('[') != -1: #包含内存操作,则一定是真实块
flag = 1
break
if flag == 0:
fake_blocks.append(i)
for x in fake_blocks: #去除虚假块(去除后其中也可能包含虚假控制流的块,永远不会执行的)
real_blocks.remove(x)
利用unicorn寻找真实块的路径
在利用unicorn寻找路径的时候只关心控制块能够寻找到的路径,其他非我们预留堆栈内存操作的指令都直接pass。
#指令如果是一些非堆栈的内存操作直接pass不执行
if ins.op_str.find('[') != -1:
if ins.op_str.find('[sp') == -1:
flag_pass = True #如果是非栈内存访问pass
for op in ins.operands:
if op.type == ARM_OP_MEM: #如果是内存操作数
addr = 0
if op.value.mem.base != 0:
addr += mu.reg_read(reg_ctou(ins.reg_name(op.value.mem.base)))
elif op.value.index != 0:
addr += mu.reg_read(reg_ctou(ins.reg_name(op.value.mem.index)))
elif op.value.disp != 0:
addr += op.value.disp
if addr >= 0x80000000 and addr < 0x80000000 + 0x10000 * 8:
flag_pass = False #如果是我们自己的栈内存操作就no pass
此混淆代码中真实块中的状态变量条件控制指令如下,。状态变量为R0,我们通过unicron模拟执行的时候主动控制状态变量值的修改,进而寻找不同路径下对应的真实块。
通过判断当前指令是否为cmp,然后判断紧接着的指令是否为movwne和movtne。branch_control = 0 时执行第一条路径,否则走第二条路径。
#cpm指令
if ins.mnemonic == 'cmp':
#人工更改流程的走向#ollvm branch
#解析movwne
for ins1 in md.disasm(bin1[address+size:address+size+4],address+size):
if ins1.mnemonic != 'movwne':
return
else:
regs = [reg_ctou(x) for x in ins1.op_str.split(', ')] #获取movne操作的寄存器
for op1 in ins1.operands:
if op1.type == ARM_OP_IMM: #获取立即数(操作数)
v1 = op1.value.imm
print("v1:%x" % v1)
#解析movtne
for ins1 in md.disasm(bin1[address+size+4:address+size+4+4],address+size+4):
if ins1.mnemonic != 'movtne':
return
else:
regs = [reg_ctou(x) for x in ins1.op_str.split(', ')] #获取movne操作的寄存器
for op2 in ins1.operands:
if op2.type == ARM_OP_IMM: #获取立即数(操作数)
v1 = (op2.value.imm << 16) + v1
print("v1:%x" % v1)
#第一条路径
if branch_control == 0:
print("first")
#第二条路径
else:
uc.reg_write(regs[0],v1) #修改寄存器的值,人为控制状态变量的值
uc.reg_write(UC_ARM_REG_PC, address + size + size + size) #掠过三条指令
#cmp
#movwne
#movtne
patch程序重建控制流
- 对于不包含条件判断的真实块(只有一条路径),直接在真实块尾部将构建b指令跳转到目的真实块
- 对于包含条件判断的真实块(包含两条路径),利用bne指令跳转到第一条路径,然后利用b指令跳转到第二条路径。
- 其他虚拟块还需要nop掉
for k, v in flow.items():
node = None
if(flow[k] == [None]): continue
for i in list_blocks:
if list_blocks[i]['saddress'] == k:
node = list_blocks[i]
#分割代码块中的每一条指令
insns = node['ins'].split('\n')
if len(flow[k]) == 2: #如果此代码块有分支
if insns[-5].find('cmp') != -1:
branch1 = flow[k][0] #第一个路径(==跳转
branch2 = flow[k][1] #第二个路径(!=跳转
print("ins: %s" % node['ins'])
if insns[-4].find('ne') != -1:
#patch地址
patch_offset = int(insns[-4][:6], 16)
bytes(_ks_assemble(("bne #0x%x" % branch),patch_offset))
bin1 = bin1[:patch_offset] + bytes(_ks_assemble(("bne #0x%x" % branch2),patch_offset)) + bytes(_ks_assemble(("b #0x%x" % branch1),patch_offset+ 4)) + bin1[patch_offset+8:]
if len(flow[k]) == 1: #如果代码无分支
branch = flow[k][0]
patch_offset = int(insns[-2][:6], 16)
print ("patch_offset: %x" % patch_offset)
bin1 = bin1[:patch_offset] + bytes(_ks_assemble(("b #0x%x" % branch),patch_offset)) + bin1[patch_offset+4:]
修复后的控制流程图
参考链接:https://bbs.pediy.com/thread-252321.htm
标签:mnemonic,混淆,value,patch,offset,ollvm,ins,unicron,op 来源: https://www.cnblogs.com/revercc/p/16339476.html