欢迎光临散文网 会员登陆 & 注册

Unidbg调试so

2021-08-24 15:05 作者:无情剑客Burning  | 我要投稿


在 Android逆向之ARM64静态分析 对ARM64汇编进行了介绍,网传ARMV9要出来了,难道又要重新学习ARMV9? 在Frida高级篇-免ROOT使用Frida(不修改源代码) 中对elf文件进行了介绍,本文使用unidbg模拟执行so来分析native方法。首先来介绍Unicorn。

Unicorn

Unicorn is a lightweight multi-platform, multi-architecture CPU emulator framework.

本文使用无名侠大神使用的Unicorn入门教程来看看Unicorn是怎么模拟CPU的。

  1. from unicorn import *

  2. from unicorn.arm_const import *

  3. from capstone import *


  4. ARM_CODE = b"\x37\x00\xa0\xe3\x03\x10\x42\xe0"


  5. # Disassemble ARM32 binary

  6. md = Cs(CS_ARCH_ARM, CS_MODE_ARM)

  7. for i in md.disasm(ARM_CODE, 0x1000):

  8. print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))



  9. # mov r0, #0x37;

  10. # sub r1, r2, r3

  11. # Test ARM


  12. # callback for tracing instructions

  13. def hook_code(uc, address, size, user_data):

  14. print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size))


  15. def test_arm():

  16. print("Emulate ARM code")

  17. try:

  18. # Initialize emulator in ARM mode

  19.        mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)


  20. # map 2MB memory for this emulation

  21.        ADDRESS = 0x10000

  22.        mu.mem_map(ADDRESS, 2 * 0x10000)

  23.        mu.mem_write(ADDRESS, ARM_CODE)


  24.        mu.reg_write(UC_ARM_REG_R0, 0x1234)

  25.        mu.reg_write(UC_ARM_REG_R2, 0x6789)

  26.        mu.reg_write(UC_ARM_REG_R3, 0x3333)


  27.        mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS+8)

  28. # emulate machine code in infinite time

  29.        mu.emu_start(ADDRESS, ADDRESS + len(ARM_CODE))

  30.        r0 = mu.reg_read(UC_ARM_REG_R0)

  31.        r1 = mu.reg_read(UC_ARM_REG_R1)

  32. print(">>> R0 = 0x%x" % r0)

  33. print(">>> R1 = 0x%x" % r1)

  34. except UcError as e:

  35. print("ERROR: %s" % e)



  36. test_arm()

运行结果:

添加指令级的Hook

这个有点像单步调试的感觉。

  1. mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS)

在begin...end范围内的每一条指令被执行前都会调用callback。

让我们来看看hook_code 的实现吧

  1. # callback for tracing instructions

  2. def hook_code(uc, address, size, user_data):

  3. print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))

这段代码仅打印指令执行的地址和长度信息。 实际应用中可配合capstone反汇编引擎玩一些更骚的操作。

UCHOOKCODE的callback中可以修改PC或EIP等寄存器来改变程序运行流程。实际上,Unicorn调试器的单步调试就是以这个为基础实现的。

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打开工程即可。

运行代码: com/bytedance/frameworks/core/encrypt/TTEncrypt.java, 出现下面的信息说明运行成功。

代码分析

入口点:

  1. public static void main(String[] args) throws Exception {

  2. TTEncrypt test = new TTEncrypt(true);


  3. byte[] data = test.ttEncrypt();

  4. Inspector.inspect(data, "ttEncrypt");


  5.     test.destroy();

  6. }

第一步: 补环境 跟踪TTEncrypt函数,注释写的很清楚了,不做过多分析。基本套路都是这个样子。

  1. TTEncrypt(boolean logging) {

  2. this.logging = logging;


  3.        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.qidian.dldl.official").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分

  4. final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口

  5.        memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析


  6.        vm = emulator.createDalvikVM(null); // 创建Android虚拟机

  7.        vm.setVerbose(logging); // 设置是否打印Jni调用细节

  8. DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libttEncrypt.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数

  9.        dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数

  10.        module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块


  11. TTEncryptUtils = vm.resolveClass("com/bytedance/frameworks/core/encrypt/TTEncryptUtils");

  12. }

第二步: HOOK相关的函数 跟踪ttEncrypt,可知代码hook了ssencrypt和ssencrypted_size两个函数。

  1. byte[] ttEncrypt() {

  2. if (logging) {

  3. Symbol sbox0 = module.findSymbolByName("sbox0"); // 在libttEncrypt.so模块中查找sbox0导出符号

  4. Symbol sbox1 = module.findSymbolByName("sbox1");

  5. Inspector.inspect(sbox0.createPointer(emulator).getByteArray(0, 256), "sbox0"); // 打印sbox0导出符号在unicorn中的内存数据

  6. Inspector.inspect(sbox1.createPointer(emulator).getByteArray(0, 256), "sbox1");


  7. IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz

  8.            hookZz.enable_arm_arm64_b_branch(); // 测试enable_arm_arm64_b_branch,可有可无

  9.            hookZz.wrap(module.findSymbolByName("ss_encrypt"), new WrapCallback<RegisterContext>() { // inline wrap导出函数

  10. @Override

  11. public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {

  12. Pointer pointer = ctx.getPointerArg(2);

  13. int length = ctx.getIntArg(3);

  14. byte[] key = pointer.getByteArray(0, length);

  15. Inspector.inspect(key, "ss_encrypt key");

  16. }

  17. @Override

  18. public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {

  19. System.out.println("ss_encrypt.postCall R0=" + ctx.getLongArg(0));

  20. }

  21. });

  22.            hookZz.disable_arm_arm64_b_branch();

  23.            hookZz.instrument(module.base + 0x00000F5C + 1, new InstrumentCallback<Arm32RegisterContext>() {

  24. @Override

  25. public void dbiCall(Emulator<?> emulator, Arm32RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些

  26. System.out.println("R3=" + ctx.getLongArg(3) + ", R10=0x" + Long.toHexString(ctx.getR10Long()));

  27. }

  28. });


  29. Dobby dobby = Dobby.getInstance(emulator);

  30.            dobby.replace(module.findSymbolByName("ss_encrypted_size"), new ReplaceCallback() { // 使用Dobby inline hook导出函数

  31. @Override

  32. public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {

  33. System.out.println("ss_encrypted_size.onCall arg0=" + context.getIntArg(0) + ", originFunction=0x" + Long.toHexString(originFunction));

  34. return HookStatus.RET(emulator, originFunction);

  35. }

  36. @Override

  37. public void postCall(Emulator<?> emulator, HookContext context) {

  38. System.out.println("ss_encrypted_size.postCall ret=" + context.getIntArg(0));

  39. }

  40. }, true);


  41. IxHook xHook = XHookImpl.getInstance(emulator); // 加载xHook,支持Import hook,文档看https://github.com/iqiyi/xHook

  42.            xHook.register("libttEncrypt.so", "strlen", new ReplaceCallback() { // hook libttEncrypt.so的导入函数strlen

  43. @Override

  44. public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {

  45. Pointer pointer = context.getPointerArg(0);

  46. String str = pointer.getString(0);

  47. System.out.println("strlen=" + str);

  48.                    context.push(str);

  49. return HookStatus.RET(emulator, originFunction);

  50. }

  51. @Override

  52. public void postCall(Emulator<?> emulator, HookContext context) {

  53. System.out.println("strlen=" + context.pop() + ", ret=" + context.getIntArg(0));

  54. }

  55. }, true);

  56.            xHook.register("libttEncrypt.so", "memmove", new ReplaceCallback() {

  57. @Override

  58. public HookStatus onCall(Emulator<?> emulator, long originFunction) {

  59. RegisterContext context = emulator.getContext();

  60. Pointer dest = context.getPointerArg(0);

  61. Pointer src = context.getPointerArg(1);

  62. int length = context.getIntArg(2);

  63. Inspector.inspect(src.getByteArray(0, length), "memmove dest=" + dest);

  64. return HookStatus.RET(emulator, originFunction);

  65. }

  66. });

  67.            xHook.register("libttEncrypt.so", "memcpy", new ReplaceCallback() {

  68. @Override

  69. public HookStatus onCall(Emulator<?> emulator, long originFunction) {

  70. RegisterContext context = emulator.getContext();

  71. Pointer dest = context.getPointerArg(0);

  72. Pointer src = context.getPointerArg(1);

  73. int length = context.getIntArg(2);

  74. Inspector.inspect(src.getByteArray(0, length), "memcpy dest=" + dest);

  75. return HookStatus.RET(emulator, originFunction);

  76. }

  77. });

  78.            xHook.refresh(); // 使Import hook生效

  79. }

第三步: 添加调试及主动调用

  1. if (logging) {

  2. Debugger debugger = emulator.attach(DebuggerType.ANDROID_SERVER_V7); // 附加IDA android_server,可输入c命令取消附加继续运行

  3. }

  4. byte[] data = new byte[16];

  5. ByteArray array = TTEncryptUtils.callStaticJniMethodObject(emulator, "ttEncrypt([BI)[B", new ByteArray(vm, data), data.length); // 执行Jni方法

  6. return array.getValue();

  7. }

第四步: 销毁环境

跟踪destroy

  1. void destroy() throws IOException {

  2.      emulator.close();

  3. if (logging) {

  4. System.out.println("destroy");

  5. }

  6. }

运行结果

这个时候按c,继续,可以看到hook的结果以及JNI调用细节。

单步调试

ida_server的Debug方式相对简单,对于unidbg的强大之一在于它的单步调试-- Console Debugger

写在最后

作者的例子是以抖音作为例子的,还是很不错的。注释都写的比较清楚了。unidbg单步调试做的很棒,这个弥补了frida调试能力比较弱的缺点。

公众号

更多内容,欢迎关注我的微信公众号: 无情剑客。


Unidbg调试so的评论 (共 条)

分享到微博请遵守国家法律