# 前言
这几天一直在和 so 层的强混淆对抗,前几天有朋友问我有没有试过 IL
层面的反混淆,正好这个月遇到了一个 dexguard 的强混淆样本,所以就想试试 IL
层面的反混淆,ida 中最为出名的应该就是 ida microcode
了,但即使在两年前曾经为了研究区块链合约的字节码而学习过李樾和谭添老师的静态分析,然而并没有什么太大的进展… 果然 static analysis 是一门大学问呐 ref Hex-Rays Microcode API vs. Obfuscating Compiler
之后又去试了试 Binary Ninja
, 现在它的 medium IL
从编译器层面已经优化已经棒了,甚至有些 br 跳转的地址都可以自动在 IL 中体现出来
不过还是没有办法直接去操作 IL, 所以最终回归到了 aarch64 汇编层面的 patch, 只是倘若遇到不透明谓词和指令替换的话,bninja 就没有办法自动计算出来这个这个自动计算的地址了
所以为了可以完整的去除 br 寄存器跳转,还得是 unidbg 呀!
关于 unidbg 的基本用法,可以参考 unidbg 学习笔记
一篇非常好的文章 [原创] 记一次基于 unidbg 模拟执行的去除 ollvm 混淆
# unidug 环境搭建
首先在 unidbg-android/src/test/java
新建一个我们自己的 package 和类,然后初始化一下
antibr(){ | |
instructions = new ArrayDeque<>(); | |
patch_list = new ArrayList<>(); | |
emulator = AndroidEmulatorBuilder.for64Bit() | |
.setProcessName("com.test.antibr") | |
//.addBackendFactory(new Unicorn2Factory(true)) | |
.build(); // 创建模拟器实例,要模拟 32 位或者 64 位,在这里区分 | |
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口 | |
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析 | |
vm = emulator.createDalvikVM(); // 创建 Android 虚拟机 | |
vm.setVerbose(false); // 设置是否打印 Jni 调用细节 | |
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/test/libbdae.so"), false); // 加载 libttEncrypt.so 到 unicorn 虚拟内存,加载成功以后会默认调用 init_array 等函数 | |
module = dm.getModule(); | |
code_walk(); | |
dm.callJNI_OnLoad(emulator); // 手动执行 JNI_OnLoad 函数 | |
doPatch(); | |
} |
接下来,我们将对每一条运行过的指令利用 unidbg 的 unicorn 引擎进行 hook, 基本用法如下
public void code_walk() | |
{ | |
emulator.getBackend().hook_add_new(new CodeHook() { | |
@Override | |
public void hook(Backend backend, long address, int size, Object user) { | |
Capstone capstone = new Capstone(Capstone.CS_ARCH_ARM64,Capstone.CS_MODE_ARM); | |
byte[] bytes = emulator.getBackend().mem_read(address, 4); | |
Instruction[] disasm = capstone.disasm(bytes, 0); | |
System.out.printf("%x:%s %s %s\n",address-module.base ,disasm[0].getMnemonic(),disasm[0].getOpStr(),disasm[0].getOperands()); | |
} | |
@Override | |
public void onAttach(UnHook unHook) { | |
} | |
@Override | |
public void detach() { | |
} | |
}, module.base, module.base+module.size, null); | |
} |
# 寄存器跳转基本类型
接下来我们考虑寄存器跳转混淆的基本类型,总体可以分为直接跳转和分支跳转这两种类型
# 直接跳转
对于直接跳转来说,我们只需要在 BR
或 BLR
跳转的地方直接 patch 这一行汇编即可完成去混淆
BR 直接跳转
BLR 直接跳转
# 分支跳转
一个标准的分支跳转由下面的四行指令构成,依次为比较 ( CMP
或 TST
), 条件赋值 ( CSEL
), 地址取值 ( LDR
), 跳转 ( BR
)
假设条件 EQ
满足时赋值为 [X12]
, 所跳转的地址为 loc_a
; 条件 EQ
不满足时赋值为 [X9]
, 所跳转的地址为 loc_b
由此我们可以将一个分支跳转还原成如下的指令:
NOP #原 CMP 或 TST 需要被 NOP | |
NOP #原 CSEL 需要被 NOP | |
B.EQ loc_a #LDR 指令 patch 为 B.EQ 条件满足时的地址 | |
B loc_b #BR 指令 patch 为 B 条件不满足时跳转的地址 |
这样即可完成一次分支跳转的修复
当然我们需要考虑到一些特殊的情况,例如下图所示
在 LDR
指令和 BR
指令间包含了其他的运行相关的指令,此时假如我们将 LDR
指令 patch 为分支跳转,将会导致参数的丢失,所以我们需要进行指令移动,将 LDR 指令下移到 BR 指令的上一行,同时将 BR
和 LDR
之间的指令上移一行
这一种情况经过修复之后的汇编应如下所示
NOP | |
NOP | |
MOV W19, #0x5D1D8BC9 | |
MOV X20, XZR | |
B.NE loc_a | |
B loc_b |
# unidbg 反混淆
要想对寄存器跳转去混淆,我们首先需要去辩别这个 BR 跳转究竟有没有分支,即判断之前运行过的指令中有没有 CSEL
相关的指令,为了实现指令和寄存器的回溯,我们必须要去维护一个数据结构去保存他们,倘若使用 Stack 这种后进先出的模式,那么随着指令的不断运行,这个 stack 会变地越来越大,这其实是我们不希望看到的。而 Queue 这种先进先出的模式,确实可以让我们维护一个最近运行的十条指令的循环队列,但是这也会带来一个问题就是当我们想进行指令回溯的时候,获取到的第一条指令并非 BR
跳转指令的上一条指令,所以这里的数据结构我们可以选择 Deque 这种双端队列的结构,来方便的去实现 添加元素到队尾
, 取队尾元素并删除
以及当队列长度达到设定的阈值时,进行 取队首元素并删除
的操作
首先定义一个指令类 InsAndCtx
,在这个类定义了每运行一条指令后,需要记录的地址,汇编以及寄存器
// 保存指令和寄存器环境类: | |
class InsAndCtx | |
{ | |
long addr; | |
Instruction ins; | |
List<Number> regs; | |
public long getAddr() { | |
return addr; | |
} | |
public void setAddr(long addr) { | |
this.addr = addr; | |
} | |
public void setIns(Instruction ins) { | |
this.ins = ins; | |
} | |
public Instruction getIns() { | |
return ins; | |
} | |
public void setRegs(List<Number> regs) { | |
this.regs = regs; | |
} | |
public List<Number> getRegs() { | |
return regs; | |
} | |
} |
随后在 code_walk
中维护一个大小为 10 的循环双端队列
public void code_walk() | |
{ | |
emulator.getBackend().hook_add_new(new CodeHook() { | |
@Override | |
public void hook(Backend backend, long address, int size, Object user) { | |
Capstone capstone = new Capstone(Capstone.CS_ARCH_ARM64,Capstone.CS_MODE_ARM); | |
byte[] bytes = emulator.getBackend().mem_read(address, 4); | |
Instruction[] disasm = capstone.disasm(bytes, 0); | |
InsAndCtx iac = new InsAndCtx(); | |
iac.setIns(disasm[0]); | |
iac.setRegs(saveRegs(backend)); | |
iac.setAddr(address); | |
// 可以当作一个队列去理解,当队列的大小超过 10 时,就删除队首的元素 | |
if(instructions.size()>10){ | |
instructions.removeFirst(); | |
} | |
// 向队列的队尾添加元素 | |
instructions.addLast(iac); | |
} | |
@Override | |
public void onAttach(UnHook unHook) { | |
} | |
@Override | |
public void detach() { | |
} | |
}, module.base, module.base+module.size, null); | |
} |
而在 br 寄存器跳转去混淆之前,因为所有的 patch 都是在 unidbg 运行结束后进行 patch 的,所以我们需要定义一个 patch 类,用来记录 patch 相关的信息
private List<PatchBR> patch_list; | |
//patch 类 | |
class PatchIns{ | |
long addr;//patch 地址 | |
String ins;//patch 的指令 | |
public long getAddr() { | |
return addr; | |
} | |
public void setAddr(long addr) { | |
this.addr = addr; | |
} | |
public String getIns() { | |
return ins; | |
} | |
public void setIns(String ins) { | |
this.ins = ins; | |
} | |
} | |
enum br_type | |
{ | |
direct_br,direct_blr,indirect_br; | |
} | |
enum error_type{ | |
ok, | |
conflict_br_type, | |
conflict_b_jump_address | |
} | |
class PatchBR{ | |
List<PatchIns> patchs; | |
List<Long> br_jump_to; | |
long br_addr; | |
br_type type; | |
error_type errorno; | |
String error_info; | |
PatchBR() { | |
patchs = new ArrayList<>(); | |
br_jump_to = new ArrayList<>(); | |
errorno = error_type.ok; | |
} | |
} |
之后便是寄存器跳转核心识别代码的编写了,主要就是将前面找出的基本类型所对应的情况一一判断过去
而 dexguard 的 BLR
寄存器跳转,是会跳转到外部的导入函数中去执行的,对于这种情况跳转,待跳转的地址在混淆库的外部,符号是不可知的,具体形如这种样子
在这种情况下,我们可以使用 unidbg 提供的 findClosestSymbolByAddress
函数来找到对应的符号
unidbg自实现地址反推符号 findHookedSymbol
自实现地址反推符号被确认其实是非必要的,此处仅作记录用
首先在 unidbg-api/src/main/java/com/github/unidbg/spi/
下增加一个 HookedSymbol
类
package com.github.unidbg.spi; | |
public class HookedSymbol { | |
private final long address; | |
private final String library; | |
private final String symbol; | |
public HookedSymbol(long address, String library, String symbol) { | |
this.address = address; | |
this.library = library; | |
this.symbol = symbol; | |
} | |
public long getAddress() { | |
return address; | |
} | |
public String getLibrary() { | |
return library; | |
} | |
public String getSymbol() { | |
return symbol; | |
} | |
} |
随后修改 unidbg-api/src/main/java/com/github/unidbg/spi/AbstractLoader.java
, 修改的部分如图所示
protected final Map<Long, HookedSymbol> hookedSymbolMap = new HashMap<>(); | |
public final HookedSymbol findHookedSymbol(long address) { | |
return hookedSymbolMap.get(address); | |
} | |
@Override | |
public final void addHookListener(HookListener listener) { | |
//hookListeners.add(listener); | |
hookListeners.add((svcMemory, libraryName, symbolName, old) -> { | |
long addr = listener.hook(svcMemory, libraryName, symbolName, old); | |
if (addr > 0) { | |
hookedSymbolMap.put(addr, new HookedSymbol(addr, libraryName, symbolName)); | |
} | |
return addr; | |
}); | |
} |
之后在 unidbg-api/src/main/java/com/github/unidbg/spi/Loader.java
中加上先前添加的函数 findHookedSymbol
HookedSymbol findHookedSymbol(long address); |
随后来到 unidbg-android/src/main/java/com/github/unidbg/linux/LinuxModule.java
, 在此处重载 findClosestSymbolByAddress
函数
@Override | |
public Symbol findClosestSymbolByAddress(long address, boolean fast) { | |
class Mutable { | |
Symbol bestSymbol = null; | |
} | |
Mutable mutable = new Mutable(); | |
symbols.forEach((name, value) -> { | |
if (value.peer > address) return; | |
if (mutable.bestSymbol == null || mutable.bestSymbol.getAddress() < value.peer) { | |
mutable.bestSymbol = new VirtualSymbol(name, this, value.peer); | |
} | |
}); | |
return mutable.bestSymbol; | |
} |
通过地址寻找符号的代码如下,但是需要注意的是,其实 JNI 函数的调用也会用到 BLR 寄存器跳转,所以说如果我们发现一个非法地址无法找到对应的符号,那么保留这个 BLR
的寄存器跳转就可以啦
if(pb.br_jump_to.get(0)<0||pb.br_jump_to.get(0)>module.size){ | |
long debug_addr = getRegValue(blr.ins.getOpStr(),blr.getRegs()).longValue(); | |
Module module2 = emulator.getMemory().findModuleByAddress(debug_addr); | |
Symbol symbol = module2 == null ? null : module2.findClosestSymbolByAddress(debug_addr, true); | |
String moduleName = null; | |
if (module2 != null) { | |
moduleName = module2.name; | |
} | |
String symbolName = null; | |
if (symbol != null) { | |
symbolName = symbol.getName(); | |
} | |
long symbolAddr = 0; | |
if (symbol != null) { | |
symbolAddr = symbol.getAddress(); | |
System.out.println("[debug] try to resolve symbol: "+moduleName+","+symbolName+","+symbolAddr); | |
} | |
} |
所以说最终的 br 反混淆代码如下~
package com.test.antibr; | |
import capstone.Capstone; | |
import capstone.api.Instruction; | |
import com.alibaba.fastjson.util.IOUtils; | |
import com.github.unidbg.AndroidEmulator; | |
import com.github.unidbg.Module; | |
import com.github.unidbg.Symbol; | |
import com.github.unidbg.arm.backend.Backend; | |
import com.github.unidbg.arm.backend.CodeHook; | |
import com.github.unidbg.arm.backend.UnHook; | |
import com.github.unidbg.arm.backend.Unicorn2Factory; | |
import com.github.unidbg.linux.android.AndroidEmulatorBuilder; | |
import com.github.unidbg.linux.android.AndroidResolver; | |
import com.github.unidbg.linux.android.dvm.DalvikModule; | |
import com.github.unidbg.linux.android.dvm.VM; | |
import com.github.unidbg.memory.Memory; | |
import com.github.unidbg.spi.HookedSymbol; | |
import com.github.unidbg.spi.LibraryFile; | |
import com.github.unidbg.utils.Inspector; | |
import keystone.Keystone; | |
import keystone.KeystoneArchitecture; | |
import keystone.KeystoneEncoded; | |
import keystone.KeystoneMode; | |
import unicorn.Arm64Const; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.FileOutputStream; | |
import java.util.*; | |
public class antibr { | |
private final AndroidEmulator emulator; | |
private final VM vm; | |
private final Module module; | |
private Deque<InsAndCtx> instructions; | |
private List<PatchBR> patch_list; | |
private static final String inName = "D:\\work\\test\\aiti-br\\libbdae.so"; | |
private static final String outName = "D:\\work\\test\\aiti-br\\libbdae_fix.so"; | |
// 保存指令和寄存器环境类: | |
class InsAndCtx | |
{ | |
long addr; | |
Instruction ins; | |
List<Number> regs; | |
public long getAddr() { | |
return addr; | |
} | |
public void setAddr(long addr) { | |
this.addr = addr; | |
} | |
public void setIns(Instruction ins) { | |
this.ins = ins; | |
} | |
public Instruction getIns() { | |
return ins; | |
} | |
public void setRegs(List<Number> regs) { | |
this.regs = regs; | |
} | |
public List<Number> getRegs() { | |
return regs; | |
} | |
} | |
//patch 类 | |
class PatchIns{ | |
long addr;//patch 地址 | |
String ins;//patch 的指令 | |
public long getAddr() { | |
return addr; | |
} | |
public void setAddr(long addr) { | |
this.addr = addr; | |
} | |
public String getIns() { | |
return ins; | |
} | |
public void setIns(String ins) { | |
this.ins = ins; | |
} | |
} | |
enum br_type | |
{ | |
direct_br,direct_blr,indirect_br; | |
} | |
enum error_type{ | |
ok, | |
conflict_br_type, | |
conflict_b_jump_address | |
} | |
class PatchBR{ | |
List<PatchIns> patchs; | |
List<Long> br_jump_to; | |
long br_addr; | |
br_type type; | |
error_type errorno; | |
String error_info; | |
PatchBR() { | |
patchs = new ArrayList<>(); | |
br_jump_to = new ArrayList<>(); | |
errorno = error_type.ok; | |
} | |
} | |
public List<Number> saveRegs(Backend bk) | |
{ | |
List<Number> nb = new ArrayList<>(); | |
for(int i=0;i<29;i++) | |
{ | |
nb.add(bk.reg_read(i+ Arm64Const.UC_ARM64_REG_X0)); | |
} | |
nb.add(bk.reg_read(Arm64Const.UC_ARM64_REG_FP)); | |
nb.add(bk.reg_read(Arm64Const.UC_ARM64_REG_LR)); | |
return nb; | |
} | |
public Number getRegValue(String reg,List<Number> regsaved) | |
{ | |
if(reg.equals("xzr")) | |
{ | |
return 0; | |
} | |
return regsaved.get(Integer.parseInt(reg.substring(1))); | |
} | |
public boolean validBRPatch(PatchBR pb){ | |
for (PatchBR _pb : patch_list) { | |
if (pb.br_addr == _pb.br_addr) { | |
if(pb.type!=_pb.type){ | |
pb.errorno = error_type.conflict_br_type; | |
pb.error_info = "[error]confict br jump type at 0x"+Integer.toHexString((int) pb.br_addr)+"type->"+pb.type+" "+_pb.type; | |
} | |
else if(pb.br_jump_to!=_pb.br_jump_to){ | |
pb.errorno = error_type.conflict_b_jump_address; | |
pb.error_info = "[error]confict b jump address at 0x"+Integer.toHexString((int) pb.br_addr)+"addr->"+pb.br_jump_to.toString()+" "+_pb.br_jump_to.toString(); | |
} | |
return false; | |
} | |
} | |
return true; | |
} | |
public long readInt64(Backend bk,long addr) | |
{ | |
byte[] bytes = bk.mem_read(addr, 8); | |
long res = 0; | |
for (int i=0;i<bytes.length;i++) | |
{ | |
res =((bytes[i]&0xffL) << (8*i)) + res; | |
} | |
return res; | |
} | |
antibr(){ | |
instructions = new ArrayDeque<>(); | |
patch_list = new ArrayList<>(); | |
emulator = AndroidEmulatorBuilder.for64Bit() | |
.setProcessName("com.test.antibr") | |
//.addBackendFactory(new Unicorn2Factory(true)) | |
.build(); // 创建模拟器实例,要模拟 32 位或者 64 位,在这里区分 | |
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口 | |
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析 | |
vm = emulator.createDalvikVM(); // 创建 Android 虚拟机 | |
vm.setVerbose(false); // 设置是否打印 Jni 调用细节 | |
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/test/libbdae.so"), false); // 加载 libttEncrypt.so 到 unicorn 虚拟内存,加载成功以后会默认调用 init_array 等函数 | |
module = dm.getModule(); | |
code_walk(); | |
dm.callJNI_OnLoad(emulator); // 手动执行 JNI_OnLoad 函数 | |
doPatch(); | |
} | |
public static void main(String[] args) throws Exception { | |
antibr test = new antibr(); | |
test.destroy(); | |
} | |
public void code_walk() | |
{ | |
emulator.getBackend().hook_add_new(new CodeHook() { | |
@Override | |
public void hook(Backend backend, long address, int size, Object user) { | |
Capstone capstone = new Capstone(Capstone.CS_ARCH_ARM64,Capstone.CS_MODE_ARM); | |
byte[] bytes = emulator.getBackend().mem_read(address, 4); | |
Instruction[] disasm = capstone.disasm(bytes, 0); | |
InsAndCtx iac = new InsAndCtx(); | |
iac.setIns(disasm[0]); | |
iac.setRegs(saveRegs(backend)); | |
iac.setAddr(address); | |
// 可以当作一个队列去理解,当队列的大小超过 10 时,就删除队首的元素 | |
if(instructions.size()>10){ | |
instructions.removeFirst(); | |
} | |
// 向队列的队尾添加元素 | |
instructions.addLast(iac); | |
do_processbr(backend); | |
//System.out.printf("%x:%s %s %s\n",address-module.base ,disasm[0].getMnemonic(),disasm[0].getOpStr(),disasm[0].getOperands()); | |
} | |
@Override | |
public void onAttach(UnHook unHook) { | |
} | |
@Override | |
public void detach() { | |
} | |
}, module.base, module.base+module.size, null); | |
} | |
public void doPatch() | |
{ | |
try { | |
File f = new File(inName); | |
FileInputStream fis = new FileInputStream(f); | |
byte[] data = new byte[(int) f.length()]; | |
fis.read(data); | |
fis.close(); | |
for(PatchBR pb:patch_list) | |
{ | |
if(pb.errorno==error_type.ok){ | |
for(PatchIns pi:pb.patchs){ | |
System.out.println("procrss addr: 0x"+Integer.toHexString((int) pi.addr)+",code: "+pi.getIns()); | |
Keystone ks = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian); | |
KeystoneEncoded assemble = ks.assemble(pi.getIns(),(int)pi.getAddr()); | |
for(int i=0;i<assemble.getMachineCode().length;i++) | |
{ | |
data[(int) pi.addr+i] = assemble.getMachineCode()[i]; | |
} | |
} | |
} | |
else{ | |
System.out.println("[error] unable to patch at 0x"+Integer.toHexString((int)(pb.br_addr))+pb.error_info); | |
} | |
} | |
File fo = new File(outName); | |
FileOutputStream fos = new FileOutputStream(fo); | |
fos.write(data); | |
fos.flush(); | |
fos.close(); | |
System.out.println("finish"); | |
} | |
catch (Exception e) | |
{ | |
e.printStackTrace(); | |
} | |
} | |
public void do_processbr(Backend backend) | |
{ | |
InsAndCtx br = null,ldr = null,csel = null,cmp = null; | |
List<InsAndCtx> others = new ArrayList<>(); | |
InsAndCtx blr = null; | |
InsAndCtx iac = instructions.peekLast(); | |
if(iac.ins.getMnemonic().equals("br")) | |
{ | |
br = iac; | |
instructions.removeLast(); | |
try { | |
while (!instructions.isEmpty()) | |
{ | |
iac = instructions.removeLast(); | |
String[] split = iac.ins.getOpStr().split(","); | |
if(ldr==null && iac.ins.getMnemonic().equals("ldr")){ | |
if(split[0].toLowerCase(Locale.ROOT).trim().equals(br.ins.getOpStr())){ | |
ldr = iac; | |
} | |
} | |
else if(csel==null && iac.ins.getMnemonic().equals("csel")){ | |
csel = iac; | |
} | |
else if(cmp==null && csel!=null && (iac.ins.getMnemonic().equals("cmp")||iac.ins.getMnemonic().equals("tst"))){ | |
cmp = iac; | |
} | |
else{ | |
others.add(iac); | |
} | |
} | |
//br 直接跳转 | |
if(csel==null){ | |
PatchBR pb = new PatchBR(); | |
pb.type = br_type.direct_br; | |
pb.br_addr = br.getAddr()-module.base; | |
long pi1_addr = br.getAddr()-module.base; | |
String pi1_ins = "b 0x" + Integer.toHexString((int)(getRegValue(br.ins.getOpStr(),br.getRegs()).longValue()-module.base)); | |
pb.br_jump_to.add(getRegValue(br.ins.getOpStr(),br.getRegs()).longValue()-module.base); | |
PatchIns pi1 = new PatchIns(); | |
pi1.setAddr(pi1_addr); | |
pi1.setIns(pi1_ins); | |
pb.patchs.add(pi1); | |
if(validBRPatch(pb)){ | |
System.out.println("[info] find br jump at 0x"+Integer.toHexString((int)(pb.br_addr))+" type:"+pb.type); | |
patch_list.add(pb); | |
} | |
//System.out.println("0x"+Integer.toHexString((int)(pi1_addr)) +" "+pi1_ins); | |
} | |
//csel br 间接跳转 | |
else{ | |
PatchBR pb = new PatchBR(); | |
pb.type = br_type.indirect_br; | |
pb.br_addr = br.getAddr()-module.base; | |
//System.out.println("[debug] found cesl-br at 0x"+Integer.toHexString((int)(pb.br_addr))+" type:"+pb.type); | |
if(cmp!=null){ | |
PatchIns pi1 = new PatchIns(); | |
pi1.setAddr(cmp.getAddr()-module.base); | |
pi1.setIns("nop"); | |
pb.patchs.add(pi1); | |
} | |
else{ | |
System.out.println("[warn] cmp/tst not found at 0x"+Integer.toHexString((int)(pb.br_addr))+" type:"+pb.type); | |
} | |
String[] sp = csel.ins.getOpStr().toLowerCase(Locale.ROOT).split(","); | |
String reg1 = sp[1].trim(); | |
String reg2 = sp[2].trim(); | |
String cond = sp[3].trim(); | |
long branch1 = readInt64(backend,getRegValue(reg1,csel.getRegs()).longValue())-module.base; | |
long branch2 = readInt64(backend,getRegValue(reg2,csel.getRegs()).longValue())-module.base; | |
PatchIns pi2 = new PatchIns(); | |
pi2.setAddr(csel.getAddr()-module.base); | |
pi2.setIns("nop"); | |
pb.patchs.add(pi2); | |
if(ldr!=null){ | |
// 需要进行指令上移 | |
if(ldr.getAddr()-br.getAddr()>4){ | |
for(InsAndCtx _iac: others){ | |
if(_iac.getAddr()< ldr.getAddr() && _iac.getAddr()>br.getAddr()){ | |
PatchIns _pi = new PatchIns(); | |
_pi.setAddr(_iac.getAddr()-module.base-4); | |
_pi.setIns(_iac.ins.toString()); | |
pb.patchs.add(_pi); | |
} | |
} | |
} | |
long current_br_jump_to = getRegValue(br.ins.getOpStr(),br.getRegs()).longValue()-module.base; | |
if((!(current_br_jump_to==branch1))&&(!(current_br_jump_to==branch2))){ | |
System.out.println("[error] wrong jump address at 0x"+Integer.toHexString((int)(pb.br_addr))+" type:"+pb.type); | |
} | |
else{ | |
PatchIns pi3 = new PatchIns(); | |
pi3.setAddr(pb.br_addr-4); | |
pi3.setIns("b"+cond.toLowerCase(Locale.ROOT) + " 0x"+ Integer.toHexString((int) (branch1))); | |
pb.br_jump_to.add(branch1); | |
pb.patchs.add(pi3); | |
PatchIns pi4 = new PatchIns(); | |
pi4.setAddr(pb.br_addr); | |
pi4.setIns("b" + " 0x"+ Integer.toHexString((int) (branch2))); | |
pb.br_jump_to.add(branch2); | |
pb.patchs.add(pi4); | |
} | |
if(validBRPatch(pb)){ | |
System.out.println("[info] find br jump at 0x"+Integer.toHexString((int)(pb.br_addr))+" type:"+pb.type); | |
patch_list.add(pb); | |
} | |
} | |
else{ | |
System.out.println("[error] ldr not found at 0x"+Integer.toHexString((int)(pb.br_addr))+" type:"+pb.type); | |
} | |
} | |
}catch (Exception e) | |
{ | |
e.printStackTrace(); | |
} | |
instructions.clear(); | |
} | |
//blr 直接跳转 | |
else if(iac.ins.getMnemonic().equals("blr")){ | |
blr = iac; | |
instructions.removeLast(); | |
PatchBR pb = new PatchBR(); | |
pb.type = br_type.direct_blr; | |
pb.br_addr = blr.getAddr()-module.base; | |
long pi1_addr = blr.getAddr()-module.base; | |
String pi1_ins = "bl 0x" + Integer.toHexString((int)(getRegValue(blr.ins.getOpStr(),blr.getRegs()).longValue()-module.base)); | |
pb.br_jump_to.add(getRegValue(blr.ins.getOpStr(),blr.getRegs()).longValue()-module.base); | |
PatchIns pi1 = new PatchIns(); | |
pi1.setAddr(pi1_addr); | |
pi1.setIns(pi1_ins); | |
pb.patchs.add(pi1); | |
if(validBRPatch(pb)){ | |
if(pb.br_jump_to.get(0)<0||pb.br_jump_to.get(0)>module.size){ | |
long debug_addr = getRegValue(blr.ins.getOpStr(),blr.getRegs()).longValue(); | |
Module module2 = emulator.getMemory().findModuleByAddress(debug_addr); | |
Symbol symbol = module2 == null ? null : module2.findClosestSymbolByAddress(debug_addr, true); | |
String moduleName = null; | |
if (module2 != null) { | |
moduleName = module2.name; | |
} | |
String symbolName = null; | |
long symbolAddr = 0; | |
if (symbol != null) { | |
symbolName = symbol.getName(); | |
symbolAddr = symbol.getAddress(); | |
System.out.println("[symbol] resolve symbol ok: "+moduleName+","+symbolName+","+symbolAddr); | |
System.out.println("[info] find br jump at 0x"+Integer.toHexString((int)(pb.br_addr))+" type:"+pb.type); | |
patch_list.add(pb); | |
} | |
} | |
else{ | |
System.out.println("[info] find br jump at 0x"+Integer.toHexString((int)(pb.br_addr))+" type:"+pb.type); | |
patch_list.add(pb); | |
} | |
} | |
instructions.clear(); | |
} | |
} | |
void destroy() { | |
IOUtils.close(emulator); | |
System.out.println("destroy"); | |
} | |
} |
# 参考资料
- [原创] 记一次基于 unidbg 模拟执行的去除 ollvm 混淆
- 吾爱破解 2024 春节红包活动 WP (全,含 Android 高级题)