0x01 函数分析

函数后方使用了BR X9作为间接跳转,IDA无法分析控制流了,因为在此处X9为寄存器,在未执行时不知道寄存器的值为多少,所以静态看我们无法了解程序往哪走


使用unidbg进行trace
可以看到在执行完0x87114的跳转指令后,直接执行了0x87118,也就是JNI_Onload下方许多对寄存器操作的汇编代码,猜测下方的汇编也为JNI_Onload执行的一部分
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 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| package com.test.demo; import capstone.Capstone; import capstone.api.Instruction; import com.alibaba.fastjson.util.IOUtils; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Emulator; import com.github.unidbg.Module; import com.github.unidbg.Symbol; import com.github.unidbg.arm.HookStatus; 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.arm.context.Arm32RegisterContext; import com.github.unidbg.arm.context.Arm64RegisterContext; import com.github.unidbg.arm.context.RegisterContext; import com.github.unidbg.debugger.BreakPointCallback; import com.github.unidbg.debugger.Debugger; import com.github.unidbg.hook.HookCallback; import com.github.unidbg.hook.HookContext; import com.github.unidbg.hook.ReplaceCallback; import com.github.unidbg.hook.hookzz.*; import com.github.unidbg.hook.xhook.IxHook; import com.github.unidbg.linux.android.AndroidEmulatorBuilder; import com.github.unidbg.linux.android.AndroidResolver; import com.github.unidbg.linux.android.XHookImpl; import com.github.unidbg.linux.android.dvm.*; import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject; import com.github.unidbg.memory.Memory; import com.github.unidbg.pointer.UnidbgPointer; import com.github.unidbg.utils.Inspector; import com.sun.jna.Pointer; import keystone.Keystone; import keystone.KeystoneArchitecture; import keystone.KeystoneMode; import unicorn.Arm64Const; import unicorn.ArmConst; import unicorn.Unicorn;
import java.io.File; import java.util.ArrayList;
public class AntiOllvm extends AbstractJni{ private final AndroidEmulator emulator; private final VM vm; private final Module module; private final DalvikModule dm; public AntiOllvm() { emulator=AndroidEmulatorBuilder.for64Bit().setProcessName("com.test.demo").build(); final Memory memory = emulator.getMemory(); memory.setLibraryResolver(new AndroidResolver(23));
vm=emulator.createDalvikVM(); vm.setJni(this); vm.setVerbose(true);
vm.loadLibrary(new File("unidbg-android/src/main/resources/android/sdk23/lib64/libc.so"),false); vm.loadLibrary(new File("unidbg-android/src/main/resources/android/sdk23/lib64/libm.so"),false); vm.loadLibrary(new File("unidbg-android/src/main/resources/android/sdk23/lib64/libdl.so"),false); vm.loadLibrary(new File("unidbg-android/src/main/resources/android/sdk23/lib64/libstdcpp.so"),false);
dm=vm.loadLibrary(new File("unidbg-android/src/test/java/com/test/demo/libtprt.so"), false); module=dm.getModule(); }
public void callJniOnload(){ dm.callJNI_OnLoad(emulator); }
private void crack(){ emulator.traceCode();
}
public void logIns() { 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\n",address-module.base ,disasm[0].getMnemonic(),disasm[0].getOpStr()); }
@Override public void onAttach(UnHook unHook) {
}
@Override public void detach() {
} }, module.base, module.base+module.size, null); }
public static void main(String[] args) { AntiOllvm antiOllvm=new AntiOllvm(); antiOllvm.logIns(); antiOllvm.callJniOnload();
}
}
|
0x02 去除间接跳转
1 2 3 4 5 6
| sub_87118 .text:0000000000087118 000 1F 01 1C 6B CMP W8, W28 .text:000000000008711C 000 09 32 8F 9A CSEL X9, X16, X15, CC .text:0000000000087120 000 09 6B 69 F8 LDR X9, [X24,X9] .text:0000000000087124 000 29 01 1B 8B ADD X9, X9, X27 .text:0000000000087128 000 20 01 1F D6 BR X9
|
CMP W8, W28
比较w8和w28的值,若w8<w28则设置CC标志位为1,否则为0
CSEL X9, X16, X15
条件选择,若CC为1(w8<w28),则将x16的值赋值给x9,否则(w8>=w28),将x15的值赋值给x9
LDR X9, [X24,X9]
以X24为基址,加上X9中的偏移量,从内存中加载一个64位地址到X9。这通常用于访问跳转表(如函数指针表)。
ADD X9, X9, X27
将X27的值加到X9中,进一步调整目标地址。X27可能存储固定偏移或基址,用于定位最终跳转位置。
BR X9
无条件跳转到X9指向的地址,执行对应代码
w8的值是写死的 w8:0x3202B1A5
w25的值也是写死的 w25:58F48322
x21:0x30
x26:0xF8
x24为一个数组,存了许多指令,以便后续调用
X27的值由MOV和MOVK分别赋值8位和16位的值,固定为 ==> 0x84FA7910
整体逻辑就是每次根据比较结果在数组中选择一个offset,然后用offset + base,得到真实的跳转地址
1 2 3 4 5
| CMP W8,W25 CSEL X9,X21,X25,CC LDR X9,[X24,X9] ADD X9,X9,X27 BR X9
|
以上方代码为例
当CC条件满足时,X21的值赋给X9作为一个offset,在LDR X9,[X24,X9]中使用X24的数组+偏移
根据CSEL的CC条件有两个分支如下:
1 2
| True Addr: (*(X24+X21) + X27) False Addr: (*(X24+X25) + X27)
|
那么我们可以根据CMP的结果使用BCC / BLO和B对True Addr和False Addr进行跳转
替换后的汇编如下
1 2 3 4 5 6
| CMP W8,W25 ... B.cond True Addr LDR X9,[X24,X9] ADD X9,X9,X27 B False addr
|
原始混淆代码:
1 2 3 4 5
| cmp w0, w1 ; 比较,设置条件标志 (e.g., EQ, NE) ; ... 可能有其他指令 ... csel x9, x20, x21, cond ; 如果条件eq为真, x9 = x20, 否则 x9 = x21 (x20/x21存有地址或地址的基址) ; ... 可能有其他指令, 可能会修改 x9 (e.g., ldr x9, [x24, x9]) ... br x9 ; 跳转到 x9 中的地址
|
修复后代码:
1 2 3 4 5
| cmp w0, w1 ; 保留比较 ; ... 保留其他指令 ... b.cond <目标地址1> ; Patch 1: 在原 csel 位置替换为条件跳转 (如果cond为真,跳到b1) ; ... 保留其他指令 ... b <目标地址2> ; Patch 2: 在原 br 位置替换为无条件跳转 (对应cond为假,跳到b2)
|