Unidbg調(diào)試so

在 Android逆向之ARM64靜態(tài)分析 對(duì)ARM64匯編進(jìn)行了介紹,網(wǎng)傳ARMV9要出來了,難道又要重新學(xué)習(xí)ARMV9? 在Frida高級(jí)篇-免ROOT使用Frida(不修改源代碼) 中對(duì)elf文件進(jìn)行了介紹,本文使用unidbg模擬執(zhí)行so來分析native方法。首先來介紹Unicorn。
Unicorn
Unicorn is a lightweight multi-platform, multi-architecture CPU emulator framework.
本文使用無名俠大神使用的Unicorn入門教程來看看Unicorn是怎么模擬CPU的。
from unicorn import *
from unicorn.arm_const import *
from capstone import *
ARM_CODE = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0"
# Disassemble ARM32 binary
md = Cs(CS_ARCH_ARM, CS_MODE_ARM)
for i in md.disasm(ARM_CODE, 0x1000):
print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))
# mov r0, #0x37;
# sub r1, r2, r3
# Test ARM
# callback for tracing instructions
def hook_code(uc, address, size, user_data):
print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size))
def test_arm():
print("Emulate ARM code")
try:
# Initialize emulator in ARM mode
? ? ? ?mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)
# map 2MB memory for this emulation
? ? ? ?ADDRESS = 0x10000
? ? ? ?mu.mem_map(ADDRESS, 2 * 0x10000)
? ? ? ?mu.mem_write(ADDRESS, ARM_CODE)
? ? ? ?mu.reg_write(UC_ARM_REG_R0, 0x1234)
? ? ? ?mu.reg_write(UC_ARM_REG_R2, 0x6789)
? ? ? ?mu.reg_write(UC_ARM_REG_R3, 0x3333)
? ? ? ?mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS+8)
# emulate machine code in infinite time
? ? ? ?mu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE))
? ? ? ?r0 = mu.reg_read(UC_ARM_REG_R0)
? ? ? ?r1 = mu.reg_read(UC_ARM_REG_R1)
print(">>> R0 = 0x%x" % r0)
print(">>> R1 = 0x%x" % r1)
except UcError as e:
print("ERROR: %s" % e)
test_arm()
運(yùn)行結(jié)果:

添加指令級(jí)的Hook
這個(gè)有點(diǎn)像單步調(diào)試的感覺。
mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS)
在begin...end范圍內(nèi)的每一條指令被執(zhí)行前都會(huì)調(diào)用callback。
讓我們來看看hook_code 的實(shí)現(xiàn)吧
# callback for tracing instructions
def hook_code(uc, address, size, user_data):
print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))
這段代碼僅打印指令執(zhí)行的地址和長度信息。 實(shí)際應(yīng)用中可配合capstone反匯編引擎玩一些更騷的操作。
UCHOOKCODE的callback中可以修改PC或EIP等寄存器來改變程序運(yùn)行流程。實(shí)際上,Unicorn調(diào)試器的單步調(diào)試就是以這個(gè)為基礎(chǔ)實(shí)現(xiàn)的。
Unidbg
Allows you to emulate an Android native library, and an experimental iOS emulation.
下載代碼: https://github.com/zhkl0228/unidbg/releases/tag/v0.9.3,使用IntelliJ IDEA打開工程即可。
運(yùn)行代碼: com/bytedance/frameworks/core/encrypt/TTEncrypt.java, 出現(xiàn)下面的信息說明運(yùn)行成功。

代碼分析
入口點(diǎn):
public static void main(String[] args) throws Exception {
TTEncrypt test = new TTEncrypt(true);
byte[] data = test.ttEncrypt();
Inspector.inspect(data, "ttEncrypt");
? ? test.destroy();
}
第一步: 補(bǔ)環(huán)境 跟蹤TTEncrypt函數(shù),注釋寫的很清楚了,不做過多分析?;咎茁范际沁@個(gè)樣子。
TTEncrypt(boolean logging) {
this.logging = logging;
? ? ? ?emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.qidian.dldl.official").build(); // 創(chuàng)建模擬器實(shí)例,要模擬32位或者64位,在這里區(qū)分
final Memory memory = emulator.getMemory(); // 模擬器的內(nèi)存操作接口
? ? ? ?memory.setLibraryResolver(new AndroidResolver(23)); // 設(shè)置系統(tǒng)類庫解析
? ? ? ?vm = emulator.createDalvikVM(null); // 創(chuàng)建Android虛擬機(jī)
? ? ? ?vm.setVerbose(logging); // 設(shè)置是否打印Jni調(diào)用細(xì)節(jié)
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libttEncrypt.so"), false); // 加載libttEncrypt.so到unicorn虛擬內(nèi)存,加載成功以后會(huì)默認(rèn)調(diào)用init_array等函數(shù)
? ? ? ?dm.callJNI_OnLoad(emulator); // 手動(dòng)執(zhí)行JNI_OnLoad函數(shù)
? ? ? ?module = dm.getModule(); // 加載好的libttEncrypt.so對(duì)應(yīng)為一個(gè)模塊
TTEncryptUtils = vm.resolveClass("com/bytedance/frameworks/core/encrypt/TTEncryptUtils");
}
第二步: HOOK相關(guān)的函數(shù) 跟蹤ttEncrypt,可知代碼hook了ssencrypt和ssencrypted_size兩個(gè)函數(shù)。
byte[] ttEncrypt() {
if (logging) {
Symbol sbox0 = module.findSymbolByName("sbox0"); // 在libttEncrypt.so模塊中查找sbox0導(dǎo)出符號(hào)
Symbol sbox1 = module.findSymbolByName("sbox1");
Inspector.inspect(sbox0.createPointer(emulator).getByteArray(0, 256), "sbox0"); // 打印sbox0導(dǎo)出符號(hào)在unicorn中的內(nèi)存數(shù)據(jù)
Inspector.inspect(sbox1.createPointer(emulator).getByteArray(0, 256), "sbox1");
IHookZz hookZz = HookZz.getInstance(emulator); // 加載HookZz,支持inline hook,文檔看https://github.com/jmpews/HookZz
? ? ? ? ? ?hookZz.enable_arm_arm64_b_branch(); // 測(cè)試enable_arm_arm64_b_branch,可有可無
? ? ? ? ? ?hookZz.wrap(module.findSymbolByName("ss_encrypt"), new WrapCallback<RegisterContext>() { // inline wrap導(dǎo)出函數(shù)
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
Pointer pointer = ctx.getPointerArg(2);
int length = ctx.getIntArg(3);
byte[] key = pointer.getByteArray(0, length);
Inspector.inspect(key, "ss_encrypt key");
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
System.out.println("ss_encrypt.postCall R0=" + ctx.getLongArg(0));
}
});
? ? ? ? ? ?hookZz.disable_arm_arm64_b_branch();
? ? ? ? ? ?hookZz.instrument(module.base + 0x00000F5C + 1, new InstrumentCallback<Arm32RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, Arm32RegisterContext ctx, HookEntryInfo info) { // 通過base+offset inline wrap內(nèi)部函數(shù),在IDA看到為sub_xxx那些
System.out.println("R3=" + ctx.getLongArg(3) + ", R10=0x" + Long.toHexString(ctx.getR10Long()));
}
});
Dobby dobby = Dobby.getInstance(emulator);
? ? ? ? ? ?dobby.replace(module.findSymbolByName("ss_encrypted_size"), new ReplaceCallback() { // 使用Dobby inline hook導(dǎo)出函數(shù)
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println("ss_encrypted_size.onCall arg0=" + context.getIntArg(0) + ", originFunction=0x" + Long.toHexString(originFunction));
return HookStatus.RET(emulator, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("ss_encrypted_size.postCall ret=" + context.getIntArg(0));
}
}, true);
IxHook xHook = XHookImpl.getInstance(emulator); // 加載xHook,支持Import hook,文檔看https://github.com/iqiyi/xHook
? ? ? ? ? ?xHook.register("libttEncrypt.so", "strlen", new ReplaceCallback() { // hook libttEncrypt.so的導(dǎo)入函數(shù)strlen
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
Pointer pointer = context.getPointerArg(0);
String str = pointer.getString(0);
System.out.println("strlen=" + str);
? ? ? ? ? ? ? ? ? ?context.push(str);
return HookStatus.RET(emulator, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("strlen=" + context.pop() + ", ret=" + context.getIntArg(0));
}
}, true);
? ? ? ? ? ?xHook.register("libttEncrypt.so", "memmove", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
RegisterContext context = emulator.getContext();
Pointer dest = context.getPointerArg(0);
Pointer src = context.getPointerArg(1);
int length = context.getIntArg(2);
Inspector.inspect(src.getByteArray(0, length), "memmove dest=" + dest);
return HookStatus.RET(emulator, originFunction);
}
});
? ? ? ? ? ?xHook.register("libttEncrypt.so", "memcpy", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
RegisterContext context = emulator.getContext();
Pointer dest = context.getPointerArg(0);
Pointer src = context.getPointerArg(1);
int length = context.getIntArg(2);
Inspector.inspect(src.getByteArray(0, length), "memcpy dest=" + dest);
return HookStatus.RET(emulator, originFunction);
}
});
? ? ? ? ? ?xHook.refresh(); // 使Import hook生效
}
第三步: 添加調(diào)試及主動(dòng)調(diào)用
if (logging) {
Debugger debugger = emulator.attach(DebuggerType.ANDROID_SERVER_V7); // 附加IDA android_server,可輸入c命令取消附加繼續(xù)運(yùn)行
}
byte[] data = new byte[16];
ByteArray array = TTEncryptUtils.callStaticJniMethodObject(emulator, "ttEncrypt([BI)[B", new ByteArray(vm, data), data.length); // 執(zhí)行Jni方法
return array.getValue();
}
第四步: 銷毀環(huán)境
跟蹤destroy
void destroy() throws IOException {
? ? ?emulator.close();
if (logging) {
System.out.println("destroy");
}
}
運(yùn)行結(jié)果

這個(gè)時(shí)候按c,繼續(xù),可以看到hook的結(jié)果以及JNI調(diào)用細(xì)節(jié)。

單步調(diào)試
ida_server的Debug方式相對(duì)簡單,對(duì)于unidbg的強(qiáng)大之一在于它的單步調(diào)試-- Console Debugger
寫在最后
作者的例子是以抖音作為例子的,還是很不錯(cuò)的。注釋都寫的比較清楚了。unidbg單步調(diào)試做的很棒,這個(gè)彌補(bǔ)了frida調(diào)試能力比較弱的缺點(diǎn)。
公眾號(hào)
更多內(nèi)容,歡迎關(guān)注我的微信公眾號(hào): 無情劍客。
