ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

利用unicron去除ollvm混淆

2022-06-03 03:31:31  阅读:248  来源: 互联网

标签:mnemonic 混淆 value patch offset ollvm ins unicron op


测试程序是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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有