使用unicorn模拟执行去除混淆

0. 前言

在分析某app的so时遇到了间接跳转类型的混淆,不去掉的话无法使用ida f5来静态分析,f5之后就长下面这样:

本文记录一下使用python+unicorn模拟执行来去掉混淆的过程。

1. 分析混淆的模式

混淆的汇编代码如下:

可以看到,这个代码块进行了一通运算,然后通过 br x8,跳转到寄存器x8中保存的地址,仔细分析这个x8的来源,可以观察到如下的固定模式:

|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 | CMP X19, ``#0 MOV W9, ``#0x40 ; '@' ; X9 = 0x40 MOV W10, ``#0x38 ; '8' ; X10 = 0x38 ADRP X25, ``#off_212C70@PAGE CSEL X9, X10, X9, EQ ; ``if X19 ``=``= 0 then X9 ``= X10 ADD X25, X25, ``#off_212C70@PAGEOFF ; X25 = 0x212C70 LDR X8, [X25,X9] ; X8 ``= qword[X25 ``+ X9] MOV W9, ``#0xFE53 ; X9 = 0xFE53 MOV W10, ``#0x82B4 ; X10 = 0x82B4 CSEL X9, X10, X9, EQ ; ``if X19 ``=``= 0 then X9 ``= X10 SUB X8, X8, X9 ; X8 ``= X8 ``- X9 BR X8 ; 跳转到X8 |

先看 LDR X8, [X25,X9],X25寄存器是一张偏移表的基址, 这条指令从偏移表+X9出取出8字节数据放到了X8中,而X9的值来源于csel指令是0x38或者是0x40,由cmp的结果决定,如果X19等于0,则X9此时等于0x38,负责等于0x40。

再看 SUB X8, X8, X9,从偏移表取出一个8字节数据到X8之后,用X8减X9,结果放到X8,X9的值也是来源于csel指令,0x82B4或者是0xFE53

最后,通过br X8跳转到目标地址

也就是说,根据X9值的不同,最终跳转的地址会有两个,把正常的分支指令混淆成了上面这种模式,手动还原混淆可以把br X8 patch成:

|-----|-----------------------|
| 1 2 | beq addr1 b addr2 |

2. 去混淆的方式

本来想的是直接用python来匹配这种模式,然后手动计算出两个分支的地址,最后patch,但是后面发现,一个被混淆的函数中只有第一个混淆块会给X25赋值偏移表的地址,其他的块就直接用X25的值了,不会再次赋值,比如下面这个:

这样的话就不能把这些被混淆的块单独的拿出来看,因为缺少计算分支地址的必要条件,必须从函数的第一个被混淆的块出发,获取到偏移表的地址才行。如果还想通过手动的计算出目的地址,那就需要手动去确定函数的边界,这样就太麻烦了。

所以最后还是选择使通过模拟执行的方式,从函数头开始执行,跑通每一个块,在执行到混淆块的时候,计算出分支地址,最后进行patch

这里模拟执行的框架选择unicorn,之前学习过无名侠大佬用unicorn去ollvm混淆的文章,这里借鉴一下思路

3. 写脚本

3.1 加载so

由于代码中有需要访问偏移表,这些偏移表是在so的第二个segment,这个segment的内存便宜和文件便宜不一样,跟windows加载pe一样存在一个拉伸的效果,所以为了模拟执行代码时可以正常访问到偏移表的数据,我们手动将so拉伸成内存视图:

|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def load_elf(filename): ``global img_size ``global out_data ``segs ``= [] ``with ``open``(filename, ``'rb'``) as f: ``out_data ``= f.read() ``for seg ``in ELFFile(f).iter_segments(``'PT_LOAD'``): ``print``(``'file_off:%s, va: %s, size: %s' %``(``hex``(seg[``'p_offset'``]), ``hex``(seg[``'p_vaddr'``]), ``hex``(seg[``'p_filesz'``]))) ``segs.append((seg[``'p_offset'``],seg[``'p_vaddr'``], seg[``'p_filesz'``], seg.data())) ``img_size ``= segs[``-``1``][``1``] ``+ segs[``-``1``][``2``] ``byte_arr ``= bytearray([``0``] ``* img_size) ``for seg ``in segs: ``vaddr ``= seg[``1``] ``size ``= seg[``2``] ``data ``= seg[``3``] ``byte_arr[vaddr: vaddr ``+ size] ``= bytearray(data) ``return byte_arr |

3.2 初始化unicorn

|----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | def init_unicorn(file_name): ``global bin_data ``global uc ``#装载一下so到内存 ``bin_data ``= bytes(load_elf(file_name)) ``uc ``= Uc(UC_ARCH_ARM64, UC_MODE_ARM) ``uc.mem_map(``0x80000000``, ``8 * 0x1000 * 0x1000``) ``uc.mem_map(``0``, ``8 * 0x1000 * 0x1000``) ``# 写入so数据 ``uc.mem_write(``0``, bin_data) ``#设置sp寄存器 ``uc.reg_write(UC_ARM64_REG_SP, ``0x80000000 + 0x1000 * 0x1000 * 6``) ``#设置指令执行hook,执行每条指令都会走hook_code ``uc.hook_add(UC_HOOK_CODE, hook_code) ``#设置非法内存访问hook ``uc.hook_add(UC_HOOK_MEM_UNMAPPED, hook_mem_access) ``barr ``= uc.mem_read(``0x144320``, ``8``) ``print``(barr) |

3.3 如何跑通一个函数的所有路径

这里借鉴无名侠大佬的思路,先把入口节点放到队列中,然后不停从队列中取节点,以这个节点为起点模拟执行,直到下一个br reg,或者是ret。

一个节点包括地址和上下文环境(寄存器),在模拟执行之前,需要把寄存器的值设置好,同时在找到分支之后,也需要保存现场的上下文环境。

在找到下一个br reg之后,计算出分支地址,将分支地址放到队列中,继续循环即可。

|-------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | def deobf(): ``# 初始化unicorn ``filename ``= 'libxxxx.so' ``patched_filename ``= 'out.so' ``start_addr ``= 0x0103168 ``init_unicorn(filename) ``q ``= queue.Queue() ``q.put((start_addr, ``None``)) ``# 入口函数是第一个节点,放到队列中去,队列中是(地址,上下文) ``traced ``= {} ``# 跑过的节点 ``while not q.empty(): ``#一直循环,直到队列为空 ``addr, context ``= q.get() ``traced[addr] ``= 1 # 跑过了 ``s ``= run(addr, context) ``#开始模拟执行,找br reg ``if s ``is None``: ``continue ``if len``(s) ``=``= 2``: ``#单分支 ``if s[``0``] ``not in traced: ``q.put(s) ``#将分支节点放到队列中 ``else``: ``#双分支 ``if s[``0``] ``not in traced: ``q.put((s[``0``], s[``2``]))``#将分支节点放到队列中 ``if s[``1``] ``not in traced: ``q.put((s[``1``], s[``2``]))``#将分支节点放到队列中 def run(addr, context): ``global uc ``global is_success ``global block_flow ``#开始模拟执行,函数返回说明在hook_code中执行了emu_stop ``set_context(uc, context)``#设置寄存器环境 ``uc.emu_start(addr, ``0x10000``) ``if is_success ``=``= True``: ``is_success ``= False ``return block_flow[addr] ``#返回分支信息和context |

3.4 hook_code

hook_code是在unicorn中注册的指令执行回调,当执行uc.emu_start(addr, 0x10000)之后,就会开始模拟执行指令,同时调用hook_code,在hook_code中有很多重要的逻辑。

保存指令栈

进入hook_code之后,需要保存执行的汇编指令以及上下文环境,供后续判断是否到达混淆块使用:

|----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | def hook_code(uc, address, size, user_data): ``global ins_stack ``global is_success ``if is_success ``=``= True``: ``uc.emu_stop() ``return ``ins_help ``= InsHelp() ``code ``= uc.mem_read(address, size) ``ins ``= list``(ins_help.disasm(code, address, ``False``))[``0``] ``print``(``"[+] tracing instruction\t0x%x:\t%s\t%s" % (ins.address, ins.mnemonic, ins.op_str)) ``#记录指令和上下文环境 ``ins_stack.append((address, get_context(uc))) |

遇到ret或者bl .__stack_chk_fail需要停止

这两个是函数的边界,执行到这里就需要停下,我没有找到如何优雅的判断执行到了bl .__stack_chk_fail,所以就判断bl后面的地址了。

|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | #遇到ret直接挺停止 if ins.mnemonic.lower() ``=``= 'ret'``: ``#uc.reg_write(UC_ARM64_REG_PC, 0) ``print``(``"[+] encountered ret, stop"``) ``ins_stack.clear() ``uc.emu_stop() ``return #遇到bl .__stack_chk_fail停止 if ins.mnemonic.lower() ``=``= 'bl' and ins.operands[``0``].imm ``=``= 0x237C0``: ``#uc.reg_write(UC_ARM64_REG_PC, 0) ``print``(``"[+] encountered bl .__stack_chk_fail, stop"``) ``ins_stack.clear() ``uc.emu_stop() ``return |

跳过函数调用、非栈或者so本身的内存访问、svc

需要跳过这些指令,并不影响寻路

|----------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | ``#跳过bl、非栈、so本身内存访问、svc ``if ins.mnemonic.lower().startswith(``'bl'``) ``or is_ref_ilegel_emm(uc, ins) ``or ins.mnemonic.lower().startswith(``'svc'``): ``print``(``"[+] pass instruction 0x%x\t%s\t%s" % (ins.address, ins.mnemonic, ins.op_str)) ``uc.reg_write(UC_ARM64_REG_PC, address ``+ size) ``return def is_ref_ilegel_emm(mu, ins): ``if ins.op_str.find(``'['``) !``= -``1``: ``if ins.op_str.find(``'[sp'``) ``=``= -``1``: ``# 不是通过sp访问内存 ``for op ``in ins.operands: ``if op.``type =``= ARM64_OP_MEM: ``addr ``= 0 ``if op.value.mem.base !``= 0``: ``addr ``+``= mu.reg_read(reg_ctou(ins.reg_name(op.value.mem.base))) ``if op.value.mem.index !``= 0``: ``addr ``+``= mu.reg_read(reg_ctou(ins.reg_name(op.value.mem.index))) ``if op.value.mem.disp !``= 0``: ``addr ``+``= op.value.mem.disp ``if 0x0 <``= addr <``= img_size: ``# 访问so中的数据,允许 ``return False ``elif 0x80000000 <``= addr < ``0x80000000 + 0x1000 * 0x1000 * 8``: ``#访问栈中的数据,允许 ``return False ``else``: ``return True ``else``:``# 是通过sp的内存访问,允许 ``return False ``else``: ``return False |

特征是否到达混淆块、计算分支地址

先判断是否是br指令,如果是,调用get_double_branch尝试去进一步匹配特征

|----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | if ins.mnemonic ``=``= "br"``: ``#判断是否到达间接跳转 ``is_success ``= True ``block_base ``= ins_stack[``0``][``0``] ``jmp_addr ``= ins_stack[``-``1``][``0``] ``ret ``= get_double_branch(uc, ins_stack) ``if ret !``= None``: ``print``(``'find double branch: %x => %x, %x' % (block_base, ret[``0``], ret[``1``])) ``block_flow[ins_stack[``0``][``0``]] ``= ret ``patch_double_branch(uc, jmp_addr, ret) ``else``: ``ret ``= get_single_branch(uc, ins_stack) ``if ret ``=``= None``: ``print``(``"[+] find dest failed 0x%x\t%s\t%s" % (ins.address, ins.mnemonic, ins.op_str)) ``is_success ``= False ``else``: ``print``(``'find single branch: %x => %x' % (block_base, ret[``0``])) ``block_flow[block_base] ``= ret ``patch_single_branch(jmp_addr, ret[``0``]) ``ins_stack.clear() ``uc.emu_stop() ``return |

进入get_double_branch,遍历指令栈,判断是否存在特定的指令,如果有则获取指定寄存器的值,最后计算出两个分支地址,这几个指令存在先后顺序,所以需要几个标志变量来控制。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | def get_double_branch(uc, ins_stack): ``#变量声明略 ``ins_help ``= InsHelp() ``cond ``= '' ``for tup ``in ins_stack[::``-``1``]: ``addr ``= tup[``0``] ``context ``= tup[``1``] ``ins ``= list``(ins_help.disasm(bin_data[addr: addr``+``5``], addr, ``False``))[``0``] ``# BR X8 ``if ins.mnemonic.lower() ``=``= 'br' and flag_br ``=``= False``: ``flag_br ``= True ``br_reg ``= ins.operands[``0``].reg ``# SUB X8, X8, X9 ``if flag_br ``=``= True and (ins.mnemonic.lower() ``=``= 'add' or ins.mnemonic.lower() ``=``= 'sub'``) \ ``and ins.operands[``0``].reg ``=``= br_reg ``and flag_sub_add ``=``= False``: ``if ins.operands[``1``].``type =``= 1 and ins.operands[``2``].``type =``= 1``: ``op_reg1 ``= ins.operands[``1``].reg ``op_reg2 ``= ins.operands[``2``].reg ``flag_sub_add ``= True ``# CSEL X9, X10, X9, EQ ``if flag_sub_add ``=``= True and ins.mnemonic.lower() ``=``= 'csel' and ins.operands[``0``].reg ``=``= op_reg2 \ ``and flag_csel1 ``=``= False``: ``cond ``= ins.op_str.split(``', '``)[``-``1``] ``regname1 ``= ins.reg_name(ins.operands[``1``].reg) ``regname2 ``= ins.reg_name(ins.operands[``2``].reg) ``# index1 = reg_ctou(regname1) - arm64_const.UC_ARM64_REG_X0 ``# index2 = reg_ctou(regname2) - arm64_const.UC_ARM64_REG_X0 ``reg2_value1 ``= 0 if regname1.lower() ``=``= 'xzr' else context[reg_ctou(regname1) ``- arm64_const.UC_ARM64_REG_X0] ``reg2_value2 ``= 0 if regname2.lower() ``=``= 'xzr' else context[reg_ctou(regname2) ``- arm64_const.UC_ARM64_REG_X0] ``flag_csel1 ``= True ``# LDR X8, [X25,X9] ``if flag_sub_add ``=``= True and ins.mnemonic.lower() ``=``= 'ldr' and ins.operands[``0``].reg ``=``= op_reg1 \ ``and flag_ldr ``=``= False``: ``pattern ``= r``'\[(.*?)\]' ``matches ``= re.findall(pattern, ins.op_str) ``assert len``(matches) ``=``= 1``, ``'not find []: %x\t%s\t%s' % (addr, ins.mnemonic, ins.op_str) ``op2_str ``= matches[``0``] ``regs ``= op2_str.split(``', '``) ``assert len``(regs) ``=``= 2``, ``'ins invalid!: %x\t%s\t%s' % (addr, ins.mnemonic, ins.op_str) ``table_base ``= context[reg_ctou(regs[``0``]) ``- arm64_const.UC_ARM64_REG_X0] ``op_reg3 ``= reg_ctou(regs[``1``]) ``flag_ldr ``= True ``# CSEL X9, X10, X9, EQ ``if flag_ldr ``=``= True and ins.mnemonic.lower() ``=``= 'csel' and reg_ctou(ins.reg_name(ins.operands[``0``].reg)) ``=``= op_reg3 \ ``and flag_csel2 ``=``= False``: ``regname1 ``= ins.reg_name(ins.operands[``1``].reg) ``regname2 ``= ins.reg_name(ins.operands[``2``].reg) ``# index1 = reg_ctou(regname1) - arm64_const.UC_ARM64_REG_X0 ``# index2 = reg_ctou(regname2) - arm64_const.UC_ARM64_REG_X0 ``reg3_value1 ``= 0 if regname1.lower() ``=``= 'xzr' else context[reg_ctou(regname1) ``- arm64_const.UC_ARM64_REG_X0] ``reg3_value2 ``= 0 if regname2.lower() ``=``= 'xzr' else context[reg_ctou(regname2) ``- arm64_const.UC_ARM64_REG_X0] ``flag_csel2 ``= True ``if flag_csel1 ``=``= True and flag_csel2 ``=``= True``: ``# 满足条件时走的分支 ``barr1 ``= uc.mem_read(table_base ``+ reg3_value1, ``8``) ``#直接从文件中读数据,注意内存偏移和文件偏移的转换 ``base1 ``= struct.unpack(``'q'``,barr1) ``offset1 ``= base1[``0``] ``- reg2_value1 ``# 不满足条件时走的分支 ``barr2 ``= uc.mem_read(table_base ``+ reg3_value2, ``8``) ``base2 ``= struct.unpack(``'q'``,barr2) ``offset2 ``= base2[``0``] ``- reg2_value2 ``return (offset1, offset2, get_context(uc), cond) ``else``: ``return None |

除了双分支,还有单分支的情况:

这种只有一个固定的分支,所以直接patch成 b 0xxxxxxxxx即可

所以如果上述特征匹配不成功,则认为是单分支

3.5 patch

当遇到混淆块,计算出分支地址之后,就要进行patch了,双分支的patch需要两条指令的空间,但是有时候混淆块的倒数第二个指令是原来的指令,不能被覆盖,那么能用的就只有一条指令的空间。

那就只能找代码段中别的的空闲空间,调b跳转到空闲空间,然后在跳转到两个分支,找一个跳板。

当我在so中搜索nop时,居然发现了一段很长的nop,那用这里不就行了吗,反正去混淆也只是为了静态分析,不需要塞回去让so正常跑。

patch代码:

|----------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | def patch_double_branch(uc, addr, branch): ``global out_data ``nop_addr ``= find2nop(uc) ``assert nop_addr ``is not None``, ``'no find 2 nop' ``offset1 ``= branch[``0``] ``offset2 ``= branch[``1``] ``cond ``= branch[``3``] ``ks ``= keystone.Ks(keystone.KS_ARCH_ARM64, keystone.KS_MODE_LITTLE_ENDIAN) ``# 1. 把bx reg修改成跳转到nop_addr ``jmp1_asm ``= 'b ' + hex``(nop_addr) ``jmp1_bin ``= ks.asm(jmp1_asm, addr)[``0``] ``# 2. bcond addr1 ``jmp2_asm ``= 'b' + cond ``+ ' ' + hex``(offset1) ``jmp2_bin ``= ks.asm(jmp2_asm, nop_addr)[``0``] ``#3. b addr2 ``jmp3_asm ``= 'b ' + hex``(offset2) ``jmp3_bin ``= ks.asm(jmp3_asm, nop_addr ``+ 4``)[``0``] ``#print(jmp3_bin) ``#patching ``print``(``'patch code: %x\t%s => %s' % (addr, ``list``(out_data[addr: addr ``+ 4``]), jmp1_bin)) ``out_data ``= patch_bytes(out_data, bytearray(jmp1_bin), addr, ``4``) ``print``(``'patch code: %x\t%s => %s' % (nop_addr, ``list``(out_data[nop_addr: nop_addr ``+ 4``]), jmp2_bin)) ``out_data ``= patch_bytes(out_data, bytearray(jmp2_bin), nop_addr, ``4``) ``print``(``'patch code: %x\t%s => %s' % (nop_addr ``+ 4``, ``list``(out_data[nop_addr ``+ 4``: nop_addr ``+ 8``]), jmp3_bin)) ``out_data ``= patch_bytes(out_data, bytearray(jmp3_bin) , nop_addr ``+ 4``, ``4``) |

最后将so数据写回文件

|-------|-----------------------------------------------------------------------------------------|
| 1 2 3 | #保存patch后的so with ``open``(patched_filename, ``'wb'``) as f: ``f.write(out_data) |

4. 效果

还存在一些虚假控制流,但是已经不影响分析了

5. 问题

  1. 如果没有找到那一长串nop,就需要给代码段扩展一下放跳板,不知道咋搞
  2. 混淆块的特征可能还需要进一步测试修改,只测了几个函数
相关推荐
王俊山IT7 分钟前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。9 分钟前
c++多线程
java·开发语言
小政爱学习!11 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。16 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼23 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k093326 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
神奇夜光杯34 分钟前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
Themberfue36 分钟前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
plmm烟酒僧38 分钟前
Windows下QT调用MinGW编译的OpenCV
开发语言·windows·qt·opencv
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端