Unidbg学习笔记

Unidbg 是一个基于 Unicorn 的动态分析框架,主要用于分析 Android 和 Java 应用,支持动态分析、内存操作、JNI 函数调用、Hook 和逆向工程等操作。Unicorn 是一个轻量级的 CPU 模拟器,支持多种架构(如 ARM、x86 等),而 Unidbg 则是基于 Unicorn 的封装,专注于 Android 应用的动态分析。

1.Unicorn & Unidbg

1.1 Unicorn 简介

Unicorn 是一个由新加坡南洋理工大学团队开发的一个基于 QEMU 的轻量级 CPU 模拟器,支持多种架构(如 ARM、x86、MIPS 等),具有高性能和良好的可移植性,并且支持多种指令集(如 Thumb、Thumb-2、ARM64 等)。Unicorn 的主要特点如下:

  • 多架构支持:支持多种架构(如 ARM、x86、MIPS 等)。
  • 高性能:Unicorn 通过使用 JIT(Just-In-Time)编译技术,将模拟的指令集转换为本地指令集,从而提高模拟的性能。因此,Unicorn 的性能比 QEMU 要高。
  • 可移植性:Unicorn 支持跨平台运行,可以在多种操作系统上运行,如 Linux、Windows、macOS 等。
  • 丰富的接口:Unicorn 提供了丰富的 API 接口,使得在不同的编译环境下都能方便地进行指令模拟、内存操作、寄存器读写等操作。
  • Hook 和拦截:Unicorn 支持 Hook 和拦截功能,可以在指令执行前后进行一些操作,如记录日志、修改指令、内存访问等。
  • 支持多种指令集:Unicorn 支持多种指令集,如 Thumb、Thumb-2、ARM64 等。

1.2 Unidbg 简介

Unidbg(Unicorn Debugger)是一个基于 Unicorn 的动态分析框架,主要用于分析 Android 和 Java 应用,它结合了 Unicorn 的 CPU 模拟能力和 Android 应用的运行时环境,支持动态分析、内存操作、JNI 函数调用、Hook 和操作内存中的数据等操作。它由凯神在2019年开源,基于Maven构建,使用Java语言编写,可以在IDE中打开和运行。下载源码后,测试运行unidbg-android/src/test/java/com/sun/jna/JniDispatch32.java,若环境配置正确,则可以看到以下输出:

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
JNIEnv->FindClass(java/lang/Object) was called from RX@0x40008c4c[libjnidispatch.so]0x8c4c
JNIEnv->NewGlobalRef(class java/lang/Object) was called from RX@0x40008c6c[libjnidispatch.so]0x8c6c
JNIEnv->FindClass(java/lang/Class) was called from RX@0x40008c90[libjnidispatch.so]0x8c90
JNIEnv->NewGlobalRef(class java/lang/Class) was called from RX@0x40008cb0[libjnidispatch.so]0x8cb0
JNIEnv->FindClass(java/lang/reflect/Method) was called from RX@0x40008cd4[libjnidispatch.so]0x8cd4
JNIEnv->NewGlobalRef(class java/lang/reflect/Method) was called from RX@0x40008cf4[libjnidispatch.so]0x8cf4
JNIEnv->FindClass(java/lang/String) was called from RX@0x40008d18[libjnidispatch.so]0x8d18
JNIEnv->NewGlobalRef(class java/lang/String) was called from RX@0x40008d38[libjnidispatch.so]0x8d38
JNIEnv->FindClass(java/nio/Buffer) was called from RX@0x40008d5c[libjnidispatch.so]0x8d5c
JNIEnv->NewGlobalRef(class java/nio/Buffer) was called from RX@0x40008d7c[libjnidispatch.so]0x8d7c
JNIEnv->FindClass(java/nio/ByteBuffer) was called from RX@0x40008da4[libjnidispatch.so]0x8da4
JNIEnv->NewGlobalRef(class java/nio/ByteBuffer) was called from RX@0x40008dc8[libjnidispatch.so]0x8dc8
JNIEnv->FindClass(java/nio/CharBuffer) was called from RX@0x40008dec[libjnidispatch.so]0x8dec
JNIEnv->NewGlobalRef(class java/nio/CharBuffer) was called from RX@0x40008e0c[libjnidispatch.so]0x8e0c
JNIEnv->FindClass(java/nio/ShortBuffer) was called from RX@0x40008e30[libjnidispatch.so]0x8e30
JNIEnv->NewGlobalRef(class java/nio/ShortBuffer) was called from RX@0x40008e50[libjnidispatch.so]0x8e50
JNIEnv->FindClass(java/nio/IntBuffer) was called from RX@0x40008e74[libjnidispatch.so]0x8e74
JNIEnv->NewGlobalRef(class java/nio/IntBuffer) was called from RX@0x40008e94[libjnidispatch.so]0x8e94
JNIEnv->FindClass(java/nio/LongBuffer) was called from RX@0x40008eb8[libjnidispatch.so]0x8eb8
JNIEnv->NewGlobalRef(class java/nio/LongBuffer) was called from RX@0x40008ed8[libjnidispatch.so]0x8ed8
JNIEnv->FindClass(java/nio/FloatBuffer) was called from RX@0x40008f00[libjnidispatch.so]0x8f00
JNIEnv->NewGlobalRef(class java/nio/FloatBuffer) was called from RX@0x40008f24[libjnidispatch.so]0x8f24
JNIEnv->FindClass(java/nio/DoubleBuffer) was called from RX@0x40008f48[libjnidispatch.so]0x8f48
JNIEnv->NewGlobalRef(class java/nio/DoubleBuffer) was called from RX@0x40008f68[libjnidispatch.so]0x8f68
JNIEnv->FindClass(java/lang/Void) was called from RX@0x40008f8c[libjnidispatch.so]0x8f8c
JNIEnv->NewGlobalRef(class java/lang/Void) was called from RX@0x40008fac[libjnidispatch.so]0x8fac
JNIEnv->GetStaticFieldID(java/lang/Void.TYPELjava/lang/Class;) => 0xb11da53c was called from RX@0x40008fe8[libjnidispatch.so]0x8fe8
JNIEnv->GetStaticObjectField(class java/lang/Void, TYPE Ljava/lang/Class; => class void) was called from RX@0x40008ffc[libjnidispatch.so]0x8ffc
JNIEnv->NewGlobalRef(class void) was called from RX@0x4000901c[libjnidispatch.so]0x901c
JNIEnv->FindClass(java/lang/Boolean) was called from RX@0x40009040[libjnidispatch.so]0x9040
JNIEnv->NewGlobalRef(class java/lang/Boolean) was called from RX@0x40009060[libjnidispatch.so]0x9060
JNIEnv->GetStaticFieldID(java/lang/Boolean.TYPELjava/lang/Class;) => 0x14813f68 was called from RX@0x400090ac[libjnidispatch.so]0x90ac
JNIEnv->GetStaticObjectField(class java/lang/Boolean, TYPE Ljava/lang/Class; => class boolean) was called from RX@0x400090cc[libjnidispatch.so]0x90cc
JNIEnv->NewGlobalRef(class boolean) was called from RX@0x400090ec[libjnidispatch.so]0x90ec
JNIEnv->FindClass(java/lang/Byte) was called from RX@0x40009110[libjnidispatch.so]0x9110
JNIEnv->NewGlobalRef(class java/lang/Byte) was called from RX@0x40009130[libjnidispatch.so]0x9130
JNIEnv->GetStaticFieldID(java/lang/Byte.TYPELjava/lang/Class;) => 0xb67a98b0 was called from RX@0x40009168[libjnidispatch.so]0x9168
JNIEnv->GetStaticObjectField(class java/lang/Byte, TYPE Ljava/lang/Class; => class byte) was called from RX@0x40009180[libjnidispatch.so]0x9180
JNIEnv->NewGlobalRef(class byte) was called from RX@0x400091a0[libjnidispatch.so]0x91a0
JNIEnv->FindClass(java/lang/Character) was called from RX@0x400091c4[libjnidispatch.so]0x91c4
JNIEnv->NewGlobalRef(class java/lang/Character) was called from RX@0x400091e4[libjnidispatch.so]0x91e4
JNIEnv->GetStaticFieldID(java/lang/Character.TYPELjava/lang/Class;) => 0xd8122869 was called from RX@0x40009218[libjnidispatch.so]0x9218
JNIEnv->GetStaticObjectField(class java/lang/Character, TYPE Ljava/lang/Class; => class char) was called from RX@0x4000922c[libjnidispatch.so]0x922c
JNIEnv->NewGlobalRef(class char) was called from RX@0x4000924c[libjnidispatch.so]0x924c
JNIEnv->FindClass(java/lang/Short) was called from RX@0x40009274[libjnidispatch.so]0x9274
JNIEnv->NewGlobalRef(class java/lang/Short) was called from RX@0x40009298[libjnidispatch.so]0x9298
JNIEnv->GetStaticFieldID(java/lang/Short.TYPELjava/lang/Class;) => 0xe9100ebc was called from RX@0x400092e4[libjnidispatch.so]0x92e4
JNIEnv->GetStaticObjectField(class java/lang/Short, TYPE Ljava/lang/Class; => class short) was called from RX@0x400092fc[libjnidispatch.so]0x92fc
JNIEnv->NewGlobalRef(class short) was called from RX@0x4000931c[libjnidispatch.so]0x931c
JNIEnv->FindClass(java/lang/Integer) was called from RX@0x40009340[libjnidispatch.so]0x9340
JNIEnv->NewGlobalRef(class java/lang/Integer) was called from RX@0x40009360[libjnidispatch.so]0x9360
JNIEnv->GetStaticFieldID(java/lang/Integer.TYPELjava/lang/Class;) => 0x69d2f27e was called from RX@0x40009394[libjnidispatch.so]0x9394
JNIEnv->GetStaticObjectField(class java/lang/Integer, TYPE Ljava/lang/Class; => class int) was called from RX@0x400093a8[libjnidispatch.so]0x93a8
JNIEnv->NewGlobalRef(class int) was called from RX@0x400093c8[libjnidispatch.so]0x93c8
JNIEnv->FindClass(java/lang/Long) was called from RX@0x400093ec[libjnidispatch.so]0x93ec
JNIEnv->NewGlobalRef(class java/lang/Long) was called from RX@0x4000940c[libjnidispatch.so]0x940c
JNIEnv->GetStaticFieldID(java/lang/Long.TYPELjava/lang/Class;) => 0xdec2e224 was called from RX@0x40009458[libjnidispatch.so]0x9458
JNIEnv->GetStaticObjectField(class java/lang/Long, TYPE Ljava/lang/Class; => class long) was called from RX@0x40009478[libjnidispatch.so]0x9478
JNIEnv->NewGlobalRef(class long) was called from RX@0x40009498[libjnidispatch.so]0x9498
JNIEnv->FindClass(java/lang/Float) was called from RX@0x400094bc[libjnidispatch.so]0x94bc
JNIEnv->NewGlobalRef(class java/lang/Float) was called from RX@0x400094dc[libjnidispatch.so]0x94dc
JNIEnv->GetStaticFieldID(java/lang/Float.TYPELjava/lang/Class;) => 0x73d70c9c was called from RX@0x40009514[libjnidispatch.so]0x9514
JNIEnv->GetStaticObjectField(class java/lang/Float, TYPE Ljava/lang/Class; => class float) was called from RX@0x4000952c[libjnidispatch.so]0x952c
JNIEnv->NewGlobalRef(class float) was called from RX@0x4000954c[libjnidispatch.so]0x954c
JNIEnv->FindClass(java/lang/Double) was called from RX@0x40009570[libjnidispatch.so]0x9570
JNIEnv->NewGlobalRef(class java/lang/Double) was called from RX@0x40009590[libjnidispatch.so]0x9590
JNIEnv->GetStaticFieldID(java/lang/Double.TYPELjava/lang/Class;) => 0xa88ffb79 was called from RX@0x400095c4[libjnidispatch.so]0x95c4
JNIEnv->GetStaticObjectField(class java/lang/Double, TYPE Ljava/lang/Class; => class double) was called from RX@0x400095d8[libjnidispatch.so]0x95d8
JNIEnv->NewGlobalRef(class double) was called from RX@0x400095f8[libjnidispatch.so]0x95f8
JNIEnv->GetMethodID(java/lang/Long.<init>(J)V) => 0xb744a242 was called from RX@0x40009634[libjnidispatch.so]0x9634
JNIEnv->GetMethodID(java/lang/Integer.<init>(I)V) => 0x4c76e5db was called from RX@0x40009660[libjnidispatch.so]0x9660
JNIEnv->GetMethodID(java/lang/Short.<init>(S)V) => 0x5f3e14a3 was called from RX@0x4000968c[libjnidispatch.so]0x968c
JNIEnv->GetMethodID(java/lang/Character.<init>(C)V) => 0xb8bf4dc0 was called from RX@0x400096b8[libjnidispatch.so]0x96b8
JNIEnv->GetMethodID(java/lang/Byte.<init>(B)V) => 0xd850acc6 was called from RX@0x400096e4[libjnidispatch.so]0x96e4
JNIEnv->GetMethodID(java/lang/Boolean.<init>(Z)V) => 0x13d20196 was called from RX@0x40009710[libjnidispatch.so]0x9710
JNIEnv->GetMethodID(java/lang/Float.<init>(F)V) => 0xcd2ab1b6 was called from RX@0x4000973c[libjnidispatch.so]0x973c
JNIEnv->GetMethodID(java/lang/Double.<init>(D)V) => 0xf1953c91 was called from RX@0x40009768[libjnidispatch.so]0x9768
JNIEnv->GetMethodID(java/lang/Class.getComponentType()Ljava/lang/Class;) => 0x3ca9f18c was called from RX@0x40009798[libjnidispatch.so]0x9798
JNIEnv->GetMethodID(java/lang/Object.toString()Ljava/lang/String;) => 0xd6cb375b was called from RX@0x400097c8[libjnidispatch.so]0x97c8
JNIEnv->GetMethodID(java/lang/String.getBytes()[B) => 0x8b04c6b3 was called from RX@0x40009808[libjnidispatch.so]0x9808
JNIEnv->GetMethodID(java/lang/String.getBytes(Ljava/lang/String;)[B) => 0x318b4ca9 was called from RX@0x40009834[libjnidispatch.so]0x9834
JNIEnv->GetMethodID(java/lang/String.toCharArray()[C) => 0x2d1b163b was called from RX@0x40009864[libjnidispatch.so]0x9864
JNIEnv->GetMethodID(java/lang/String.<init>([B)V) => 0xd5642694 was called from RX@0x40009898[libjnidispatch.so]0x9898
JNIEnv->GetMethodID(java/lang/String.<init>([BLjava/lang/String;)V) => 0x782c535e was called from RX@0x400098c4[libjnidispatch.so]0x98c4
JNIEnv->GetMethodID(java/lang/reflect/Method.getParameterTypes()[Ljava/lang/Class;) => 0x5a0a40b7 was called from RX@0x400098f4[libjnidispatch.so]0x98f4
JNIEnv->GetMethodID(java/lang/reflect/Method.getReturnType()Ljava/lang/Class;) => 0x699638fa was called from RX@0x40009924[libjnidispatch.so]0x9924
JNIEnv->GetMethodID(java/nio/Buffer.position()I) => 0x58d0a586 was called from RX@0x40009958[libjnidispatch.so]0x9958
JNIEnv->GetMethodID(java/nio/ByteBuffer.array()[B) => 0xb5e5f360 was called from RX@0x40009984[libjnidispatch.so]0x9984
JNIEnv->GetMethodID(java/nio/ByteBuffer.arrayOffset()I) => 0x7506617d was called from RX@0x400099b0[libjnidispatch.so]0x99b0
JNIEnv->GetMethodID(java/nio/CharBuffer.array()[C) => 0x1882e6d3 was called from RX@0x400099ec[libjnidispatch.so]0x99ec
JNIEnv->GetMethodID(java/nio/CharBuffer.arrayOffset()I) => 0xc011c54b was called from RX@0x40009a2c[libjnidispatch.so]0x9a2c
JNIEnv->GetMethodID(java/nio/ShortBuffer.array()[S) => 0xaa5e7c8d was called from RX@0x40009a58[libjnidispatch.so]0x9a58
JNIEnv->GetMethodID(java/nio/ShortBuffer.arrayOffset()I) => 0x834329e1 was called from RX@0x40009a80[libjnidispatch.so]0x9a80
JNIEnv->GetMethodID(java/nio/IntBuffer.array()[I) => 0xce6e5f70 was called from RX@0x40009aac[libjnidispatch.so]0x9aac
JNIEnv->GetMethodID(java/nio/IntBuffer.arrayOffset()I) => 0xcc3d1314 was called from RX@0x40009ad4[libjnidispatch.so]0x9ad4
JNIEnv->GetMethodID(java/nio/LongBuffer.array()[J) => 0xdeb2a474 was called from RX@0x40009b00[libjnidispatch.so]0x9b00
JNIEnv->GetMethodID(java/nio/LongBuffer.arrayOffset()I) => 0x9fa77f1 was called from RX@0x40009b28[libjnidispatch.so]0x9b28
JNIEnv->GetMethodID(java/nio/FloatBuffer.array()[F) => 0x5fa75aa0 was called from RX@0x40009b54[libjnidispatch.so]0x9b54
JNIEnv->GetMethodID(java/nio/FloatBuffer.arrayOffset()I) => 0xe5701fc1 was called from RX@0x40009b7c[libjnidispatch.so]0x9b7c
JNIEnv->GetMethodID(java/nio/DoubleBuffer.array()[D) => 0x751965b9 was called from RX@0x40009bb4[libjnidispatch.so]0x9bb4
JNIEnv->GetMethodID(java/nio/DoubleBuffer.arrayOffset()I) => 0x5a9fcd86 was called from RX@0x40009be4[libjnidispatch.so]0x9be4
JNIEnv->GetFieldID(java/lang/Boolean.value Z) => 0x8a25b88c was called from RX@0x40009ea4[libjnidispatch.so]0x9ea4
JNIEnv->GetFieldID(java/lang/Byte.value B) => 0x4f9df82c was called from RX@0x40009ed0[libjnidispatch.so]0x9ed0
JNIEnv->GetFieldID(java/lang/Short.value S) => 0xa884abb1 was called from RX@0x40009efc[libjnidispatch.so]0x9efc
JNIEnv->GetFieldID(java/lang/Character.value C) => 0x156a0d94 was called from RX@0x40009f28[libjnidispatch.so]0x9f28
JNIEnv->GetFieldID(java/lang/Integer.value I) => 0x7322fc25 was called from RX@0x40009f54[libjnidispatch.so]0x9f54
JNIEnv->GetFieldID(java/lang/Long.value J) => 0xbf027540 was called from RX@0x40009f80[libjnidispatch.so]0x9f80
JNIEnv->GetFieldID(java/lang/Float.value F) => 0x71da9c4 was called from RX@0x40009fac[libjnidispatch.so]0x9fac
JNIEnv->GetFieldID(java/lang/Double.value D) => 0xe8027c85 was called from RX@0x40009fd8[libjnidispatch.so]0x9fd8
JNIEnv->FindClass(java/lang/System) was called from RX@0x40009ffc[libjnidispatch.so]0x9ffc
JNIEnv->GetStaticMethodID(java/lang/System.getProperty(Ljava/lang/String;)Ljava/lang/String;) => 0x3e16b3f8 was called from RX@0x4000a028[libjnidispatch.so]0xa028
JNIEnv->NewByteArray(13) was called from RX@0x40003ffc[libjnidispatch.so]0x3ffc
JNIEnv->SetByteArrayRegion([B@0x00000000000000000000000000, 0, 13, RX@0x40013d70[libjnidispatch.so]0x13d70) was called from RX@0x40004024[libjnidispatch.so]0x4024
JNIEnv->NewByteArray(4) was called from RX@0x40003f30[libjnidispatch.so]0x3f30
JNIEnv->SetByteArrayRegion([B@0x00000000, 0, 4, RX@0x40012504[libjnidispatch.so]0x12504) was called from RX@0x40003f58[libjnidispatch.so]0x3f58
JNIEnv->NewObject(class java/lang/String, <init>([B@0x75746638) => "utf8") was called from RX@0x40003f7c[libjnidispatch.so]0x3f7c
JNIEnv->NewObject(class java/lang/String, <init>([B@0x66696c652e656e636f64696e67, "utf8") => "file.encoding") was called from RX@0x40004060[libjnidispatch.so]0x4060
JNIEnv->CallStaticObjectMethod(class java/lang/System, getProperty("file.encoding") => "UTF-8") was called from RX@0x4000a064[libjnidispatch.so]0xa064
JNIEnv->NewGlobalRef("UTF-8") was called from RX@0x4000a08c[libjnidispatch.so]0xa08c
JNIEnv->FindClass(java/lang/Object) was called from RX@0x4000c4b8[libjnidispatch.so]0xc4b8
JNIEnv->NewGlobalRef(class java/lang/Object) was called from RX@0x4000c4d8[libjnidispatch.so]0xc4d8
sdk=23, libc=LinuxModule{base=0x40017000, size=540672, name='libc.so'}
Find native function Java_com_sun_jna_Native_malloc => RX@0x40007bc0[libjnidispatch.so]0x7bc0
malloc=32
malloc=32, ret=RW@0x40168040

>-----------------------------------------------------------------------------<
[23:44:23 304]malloc ret=1075216448, offset=4ms, md5=53d4d04d3317d4b07cd955f6d12eece9, hex=636f6d2e73756e2e6a6e612e4a6e694469737061746368333200000000000000
size: 32
0000: 63 6F 6D 2E 73 75 6E 2E 6A 6E 61 2E 4A 6E 69 44 com.sun.jna.JniD
0010: 69 73 70 61 74 63 68 33 32 00 00 00 00 00 00 00 ispatch32.......
^-----------------------------------------------------------------------------^
Find native function Java_com_sun_jna_Native_getNativeVersion => RX@0x40008b6c[libjnidispatch.so]0x8b6c
JNIEnv->NewByteArray(5) was called from RX@0x40003ffc[libjnidispatch.so]0x3ffc
JNIEnv->SetByteArrayRegion([B@0x0000000000, 0, 5, RX@0x400134b0[libjnidispatch.so]0x134b0) was called from RX@0x40004024[libjnidispatch.so]0x4024
JNIEnv->NewByteArray(4) was called from RX@0x40003f30[libjnidispatch.so]0x3f30
JNIEnv->SetByteArrayRegion([B@0x00000000, 0, 4, RX@0x40012504[libjnidispatch.so]0x12504) was called from RX@0x40003f58[libjnidispatch.so]0x3f58
JNIEnv->NewObject(class java/lang/String, <init>([B@0x75746638) => "utf8") was called from RX@0x40003f7c[libjnidispatch.so]0x3f7c
JNIEnv->NewObject(class java/lang/String, <init>([B@0x342e302e31, "utf8") => "4.0.1") was called from RX@0x40004060[libjnidispatch.so]0x4060
getNativeVersion version=4.0.1, offset=48ms
Find native function Java_com_sun_jna_Native_getAPIChecksum => RX@0x40008b98[libjnidispatch.so]0x8b98
JNIEnv->NewByteArray(32) was called from RX@0x40003ffc[libjnidispatch.so]0x3ffc
JNIEnv->SetByteArrayRegion([B@0x0000000000000000000000000000000000000000000000000000000000000000, 0, 32, RX@0x400134b8[libjnidispatch.so]0x134b8) was called from RX@0x40004024[libjnidispatch.so]0x4024
JNIEnv->NewByteArray(4) was called from RX@0x40003f30[libjnidispatch.so]0x3f30
JNIEnv->SetByteArrayRegion([B@0x00000000, 0, 4, RX@0x40012504[libjnidispatch.so]0x12504) was called from RX@0x40003f58[libjnidispatch.so]0x3f58
JNIEnv->NewObject(class java/lang/String, <init>([B@0x75746638) => "utf8") was called from RX@0x40003f7c[libjnidispatch.so]0x3f7c
JNIEnv->NewObject(class java/lang/String, <init>([B@0x3161363034373436376235396538373438663937356530333031366365336439, "utf8") => "1a6047467b59e8748f975e03016ce3d9") was called from RX@0x40004060[libjnidispatch.so]0x4060
getAPIChecksum checksum=1a6047467b59e8748f975e03016ce3d9, offset=49ms
Find native function Java_com_sun_jna_Native_sizeof => RX@0x40007bf0[libjnidispatch.so]0x7bf0
sizeof POINTER_SIZE=4, offset=49ms
destroy

1.3 Unidbg 的使用场景与优缺点

目前 Unidbg 一共有三大使用场景,分别是模拟执行、监控观察、辅助算法分析和还原。

1.3.1 模拟执行

模拟执行是模拟器所提供的最基础的能力,即执行目标 SO 里用户所关切的函数,得到和真机等价的结果。这个场景在逆向工程中非常常见,比如我们可以通过模拟执行来获取某个函数的返回值,或者获取某个函数的参数值等。这里我们讨论的并不是模拟执行这种基础能力,而是在这个基础上,用 Unidbg Call 替代 Frida/Xposed Call,去做 RPC(Remote Procedure Call)调用,这样就可以实现在 PC 端调用 Android 端的函数,这个场景在逆向工程中也非常常见。
这里可以提出一个问题,Unidbg 实质上是基于 Unicorn 的辅助算法还原的分析与调试框架,拿他做 RPC 调用,是不是有点大材小用呢?其实不然,可以设想一下,哪些人对处理 Android Native 的需求最大呢?爬虫从业人员。爬虫从业人员在处理 Android Native 时,最常见的需求就是调用某个函数,获取某个函数的返回值,他们不是算法还原的专家,他们不会像逆向工程师那样,去做算法还原,他们需要的是稳定、可用、低成本的解决方案。原来使用 Frida/Xposed 调用算法,然后转发和暴露到公网上提供接口和服务,这种方式成本高、稳定性差。
而使用 Unidbg Call 替代 Frida/Xposed Call,能极大降低成本,提高稳定性。首先是减少设备成本,Frida/Xposed RPC 都需要大量购置维护真机,而 Unidbg 只需要一台 PC 即可,把算法跑通后,放到服务器上即可。其次是提高稳定性,Frida/Xposed RPC 为了应对设备指纹、风险校验等问题,需要不断刷新设备信息,在真机中需要购买改机软件或者定制改机系统,这是不小的成本。而对于 Unidbg 来说,由于所有的函数调用接口都由 Unidbg 模拟或者代理,所以我门只需要准备多套设备信息,在 Unidbg 补环境时返回其中的任意一套即可,这样就可以轻松应对设备指纹、风险校验等问题。
但 Unidbg 也不是完美的,他也有一些缺点,其中学习成本和性能是两个最显著的缺陷。Unidbg 的学习成本非常高,因为 Unidbg 是基于 Unicorn 的,所以你需要对 Unicorn 有一定的了解,而 Unicorn 是一个底层的模拟器,所以你还需要对 ARM、X86 等架构有一定的了解,这对于初学者来说是一个非常大的挑战。而且其中最大的挑战是补环境,需要大量的时间和精力。环境没补好,即使跑出结果也是白搭。而 Frida/Xposed Call 由于是基于真机的,只需几行代码即可,学习成本非常低。性能方面,Unidbg 的性能是非常低的,因为 Unidbg 是基于 Unicorn 的,而 Unicorn 是一个底层的模拟器,相较于真机性能就差很多了。为了提高性能,Unidbg 引入了 Dynarmic、KVM 等多个指令执行引擎,但是性能还是远远达不到真机的性能。

1.3.2 监控观察

监控观察主要指的是观察样本对环境做了哪些信息获取与修改。监控所有类型的外部信息访问,包括系统调用、库函数、JNI 调用、文件读写等。举几个例子:

  • 在执行某个函数时,提示检测到 Root,可以用 Unidbg 分析检测 Root 的逻辑。
  • 分析设备指纹生成逻辑中,收集了哪些信息,如 IMEI、Android ID、Mac 地址等。
  • 分析竞品 App 的安全 SDK。
    但是 Unidbg 在监控观察方面也有一些缺点,其中最大的缺点就是无法实现对所有系统调用的的良好模拟,因此有些逻辑会无法处理。

1.3.3 辅助算法分析和还原

在辅助分析和还原方面,Unidbg 也有一些优势,既具备 Hook/Debug/Trace 这三大基本分析能力,又支持许多高级分析功能,如内存分析、调用图分析、数据流分析、控制流分析等。而其最大的优势是可以结合时间旅行调试器(Time-Travel Debugging,TTD)进行分析。但是 Unidbg 在辅助分析和还原方面也有一些缺点,其中最大的缺点就是作为插件的扩展能力。作为一个 Java 框架,Unidbg 无法直接作为 IDA 或者 Ghidra 的插件,相较于 Qlling 等 Python 项目,Unidbg 就显得不够灵活。

Unidbg 的基本使用

下面将通过几个示例来介绍 Unidbg 的基本使用。

2.1 示例1 kanxue.test2

这个示例是包含在 Unidbg 源码中的一个示例,我们可以通过这个示例来了解 Unidbg 的基本使用。

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
package com.kanxue.test2;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.arm.backend.DynarmicFactory;
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.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;

import java.io.File;

/**
* <a href="https://bbs.pediy.com/thread-263345.htm">CrackMe</a>
* 示例原帖地址
*/
public class MainActivity {

public static void main(String[] args) {
long start = System.currentTimeMillis(); // 记录开始时间
MainActivity mainActivity = new MainActivity(); // 创建 MainActivity 对象
System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms"); // 输出调用时间
mainActivity.crack(); // 调用 crack 方法
}

private final AndroidEmulator emulator; // 安卓模拟器
private final VM vm; // Dalvik虚拟机

private MainActivity() {
emulator = AndroidEmulatorBuilder
.for32Bit()
.addBackendFactory(new DynarmicFactory(true))
.build(); // 创建安卓模拟器 .for32Bit() 表示 32 位模拟器,.addBackendFactory(new DynarmicFactory(true)) 表示使用 Dynarmic 模拟器 .build() 表示构建模拟器
Memory memory = emulator.getMemory(); // 获取内存
LibraryResolver resolver = new AndroidResolver(23); // 创建库解析器,23 表示 Android 6.0(SDK23)19 表示 Android 4.4(SDK19),处理64位so时,需要使用 SDK23
memory.setLibraryResolver(resolver); // 设置库解析器

vm = emulator.createDalvikVM(); // 创建 Dalvik 虚拟机
vm.setVerbose(false); // 设置是否打印 JNI 调用信息
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so"), false); // 加载 so 文件, false 表示不自动调用 JNI_OnLoad
dm.callJNI_OnLoad(emulator); // 手动调用 JNI_OnLoad
}

private static final char[] LETTERS = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
};

private void crack() {
DvmObject<?> obj = ProxyDvmObject.createObject(vm, this); // 创建一个代理对象 obj 用于模拟 Java 层的对象传递给 Native 层
long start = System.currentTimeMillis(); // 记录开始时间
for (char a : LETTERS) {
for (char b : LETTERS) {
for (char c : LETTERS) {
String str = "" + a + b + c; // 遍历所有可能的字符组合
boolean success = obj.callJniMethodBoolean(emulator, "jnitest(Ljava/lang/String;)Z", str); // 调用 jnitest 方法,传入 str 参数,返回是否成功
if (success) {
System.out.println("Found: " + str + ", off=" + (System.currentTimeMillis() - start) + "ms"); // 输出找到的字符串
return;
}
}
}
}
}
}

打开对应 so 文件的jnitest函数可以看到明显的 OLLVM 混淆,较难分析。
jnitest
所以我们借助 Unidbg 来进行模拟执行,运行上述代码,可以看到输出结果如下:
result
这里可以关注一些细节

  • for32Bit()的意思是创建32位 Android 模拟器实例,for64Bit()则是创建64位,apk lib 里只有armeabi-v7a,那就只能选择 32 位,apk lib 里只有arm64-v8a,就选择 64 位。区别:64位的执行速度较快,浮动10%左右;Unidbg 对 ARM32 的支持和完善程度高于 ARM64,所以 ARM32 的稳定性和兼容性更好。当出现 Unidbg 模拟执行64位失败时,可以尝试32位。
  • Unidbg 支持了数个后端,目前共五个 Backend,分别是 Unicorn、Unicorn2、Dynarmic(执行速度较快)、Hypervisor、KVM。new DynarmicFactory(true)中的true,标志着在出现异常时是否使用默认后端unicorn。如果为false,则在出现异常时会抛出异常,不会使用默认后端。
  • 可以借助.setRootDir()方法设置虚拟机的根目录,可以实现 IO 重定向。例如当 so 文件在执行时读取了/data/data/xxx.txt,我们将.setRootDir()设置为E:/unidbg,那么读取/data/data/xxx.txt时,实际读取的是E:/unidbg/data/data/xxx.txt
  • 另外.setProcessName()方法可以设置虚拟机的进程名,一般用于传入包名,用于模拟执行时的进程名。
    backend

2.1.1 emulator 常用 API

在 Unidbg 中,Emulator 是模拟器的核心类,用于创建和管理模拟环境。以下是一些常用的 API:

  • emulator.getMemory():获取内存,返回Memory对象。
  • emulator.getPid():获取进程 ID。返回int类型。
  • emulator.getPointerSize():获取指针大小。返回int类型。
  • emulator.createDalvikVM():创建 Dalvik 虚拟机,返回VM对象。
  • emulator.createDalvikVM(File apkFile):创建 Dalvik 虚拟机,并加载 apk 文件,返回VM对象。
  • emulator.getDalvikVM():获取 Dalvik 虚拟机,返回VM对象。
  • emulator.showRegs():显示寄存器信息。可以指定寄存器,返回void
  • emulator.getBackend():获取后端 CPU,返回Backend对象。
  • emulator.getProcessName():获取进程名,返回String类型。
  • emulator.getContext():获取上下文,返回RegisterContext对象。
  • emulator.traceRead(long address, int size):跟踪读取内存。返回void
  • emulator.traceWrite(long address, int size):跟踪写入内存。返回void
  • emulator.traceCode(long address, int size):跟踪汇编代码执行。返回void
  • emulator.attach():附加到进程。返回void
  • emulator.isRunning():判断是否正在运行。返回boolean

2.1.2 memory 常用 API

在 Unidbg 中,Memory 是内存的核心类,用于管理内存。以下是一些常用的 API:

  • memory.setLibraryResolver(AndroidResolver resolver):设置库解析器,返回void
  • memory.getStackPoint():获取当前栈指针,返回long类型。
  • memory.pointer(long address):获取指定地址的指针,返回UnidbgPointer对象。
  • memory.getMemoryMap():获取内存映射,返回MemoryMap对象。
  • memory.findModule(String name):查找模块,返回Module对象。
  • memory.findModuleByAddress(long address):根据地址查找模块,返回Module对象。
  • memory.loadLibrary(File libraryFile, boolean forceCallInit):加载库文件,返回ElfModule对象。
  • memory.allocateStack(int size):分配栈内存,返回UnidbgPointer类型。
  • memory.writestackstring(String str):写入字符串到栈内存,返回UnidbgPointer类型。
  • memory.write(long address, byte[] data):写入字符串到内存,返回void
  • memory.writestackBytes(byte[] data):写入字节数据到栈内存,返回UnidbgPointer类型。
  • memory.malloc(int size, boolean runtime):分配堆内存,返回UnidbgPointer类型。

2.1.3 vm 常用 API

在 Unidbg 中,VM 是 Dalvik 虚拟机的核心类,用于管理虚拟机。以下是一些常用的 API:

  • vm.createDalvikVM(File apkFile):创建 Dalvik 虚拟机,并加载 apk 文件,返回VM对象。
  • vm.setVerbose(boolean verbose):设置是否打印 JNI 调用信息,返回void
  • vm.loadLibrary(File libraryFile, boolean forceCallInit):加载库文件,forceCallInit,表示是否设置自动调用Init函数,返回DalvikModule对象。
  • vm.setJni(ProxyJni proxyJni):设置 JNI,返回void
  • vm.getJNIEnv():获取 JNI 环境,返回JNIEnv对象。
  • vm.getJavaVM():获取 Java 虚拟机,返回JavaVM对象。
  • vm.callJNI_OnLoad(Emulator<?> emulator, Module module):调用 JNI_OnLoad,返回void
  • vm.addGlobalObject(DvmObject<?> obj):添加全局对象,返回int,为该对象的 hash 值。
  • vm.getObject(int hash):根据 hash 值获取对象,返回DvmObject<?>对象。
  • vm.resolveClass(String className):解析类,返回DvmClass对象。
  • vm.getPackageName():获取包名,返回String类型。
  • vm.getVersionName():获取版本名,返回String类型。
  • vm.getVersionCode():获取版本号,返回String类型。
  • vm.openAsset(String fileName):打开资源文件,返回InputStream对象。
  • vm.getManifestXml():获取 AndroidManifest.xml 文件,返回String类型。
  • vm.getSignatures():获取签名,返回String类型。
  • vm.FindClass(String className):查找类,返回DvmClass对象。
  • vm.getEmulator():获取模拟器,返回Emulator<?>对象。

2.1.4 JNI 函数的调用

在 Unidbg 中,JNI 函数的调用是通过callJniMethodXXX方法实现的,其中XXX表示 JNI 函数的返回值类型。

1
2
3
4
boolean success = obj.callJniMethodBoolean(emulator, "jnitest(Ljava/lang/String;)Z", str); 
// 第一个参数表示传入的模拟器实例
// 第二个参数表示 JNI 函数的签名,可以在 Java 层查看
// 第三个参数表示传入的参数,基本类型直接传入

基本的类型包括intlongfloatdoublebooleancharStringbyte[]short[]int[]long[]float[]double[]boolean[]char[]Enum等。
对于其他数据类型需要借助resolveClass方法解析类,然后调用newObject方法创建对象,最后调用callJniMethod方法调用 JNI 函数。

1
DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);

2.1.5 符号调用与偏移调用

符号调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Symbol symbol = module.findSymbolByName("导出符号");  
if (symbol != null){
//第一个模拟器实例,第二个jnienv,第三个jclass,第四个可变参数
Number numbers = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(实例类), 可变参数);
int result = numbers.intValue();
System.out.println(result);
//如果返回值是string,可以通过vm.getObject(retval)获取
System.out.println(vm.getObject(result).getValue());
}else {
System.out.println("符号未找到");
}
```
`偏移调用`
```java
//第一个模拟器实例,第二个偏移地址(thumb记得+1),第三个jnienv,第四个jclass,第五个可变参数
Number number = module.callFunction(emulator, 0x11240, vm.getJNIEnv(),vm.addLocalObject(SecurityUtils),vm.addLocalObject(new StringObject(vm, "超级")));
DvmObject<?> object = vm.getObject();
System.out.println("result:" + object.getValue());

2.2 示例2 zj.wuaipojie.util

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package com.zj.wuaipojie.util;

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.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 SecurityUtil extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;

private final DvmClass SecurityUtils;

private final boolean logging;

SecurityUtil(boolean logging) {
this.logging = logging;

emulator = AndroidEmulatorBuilder.for64Bit()
.setProcessName("com.zj.wuaipojie")
.addBackendFactory(new Unicorn2Factory(true))
.build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分

final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setJni(this);
vm.setVerbose(logging); // 设置是否打印Jni调用细节
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/lib52pojie.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
SecurityUtils = vm.resolveClass("com/zj/wuaipojie/util/SecurityUtil");
}

void destroy() {
IOUtils.close(emulator);
if (logging) {
System.out.println("destroy");
}
}

public static void main(String[] args) throws Exception {
SecurityUtil test = new SecurityUtil(false);
test.crack();
test.destroy();

}

private void crack() {

int retval = SecurityUtils.callStaticJniMethodInt(emulator, "diamondNum()I"); // 执行Jni方法
System.out.println("retval:" + retval);

StringObject Result = SecurityUtils.callStaticJniMethodObject(emulator, "vipLevel(Ljava/lang/String;)Ljava/lang/String;", "超级111"); // 执行Jni方法
System.out.println("Result: " + Result.getValue());
Symbol symbol = module.findSymbolByName("Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
if (symbol != null){
Number result = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(SecurityUtils), vm.addLocalObject(new StringObject(vm, "超级")));
DvmObject<?> returnObj = vm.getObject(result.intValue());
System.out.println(returnObj.getValue());
}else {
System.out.println("符号未找到");
}

Number number = module.callFunction(emulator, 0x11240, vm.getJNIEnv(),vm.addLocalObject(SecurityUtils),vm.addLocalObject(new StringObject(vm, "超级")));
DvmObject<?> object = vm.getObject(number.intValue());
System.out.println("result:" + object.getValue());

IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
hookZz.wrap(module.findSymbolByName("Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel"), new WrapCallback<HookZzArm64RegisterContext>() { // inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {

UnidbgPointer input = ctx.getPointerArg(2);
System.out.println("preCall:"+vm.getObject(input.toIntPeer()));

}
@Override
public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
UnidbgPointer input1 = ctx.getXPointer(0);
UnidbgPointer input = ctx.getPointerArg(0);
System.out.println("postCall:"+vm.getObject(input1.toIntPeer()));

}
});
StringObject Result = SecurityUtils.callStaticJniMethodObject(emulator, "vipLevel(Ljava/lang/String;)Ljava/lang/String;", "123"); // 执行Jni方法
System.out.println("Result: " + Result.getValue());

IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.wrap(module.findSymbolByName("Java_com_zj_wuaipojie_util_SecurityUtil_diamondNum"), new WrapCallback<RegisterContext>() { // inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
System.out.println("ss_encrypt.postCall R0=" + ctx.getLongArg(0));
}
});


hookZz.instrument(module.base + 0x11470, new InstrumentCallback<Arm64RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
System.out.println("W0=" + ctx.getXInt(0));
}
});

int retval = SecurityUtils.callStaticJniMethodInt(emulator, "diamondNum()I"); // 执行Jni方法
System.out.println("retval:" + retval);
Dobby dobby = Dobby.getInstance(emulator);
dobby.replace(module.findSymbolByName("Java_com_zj_wuaipojie_util_SecurityUtil_diamondNum"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
return HookStatus.LR(emulator,12);
}
});
int retval = SecurityUtils.callStaticJniMethodInt(emulator, "diamondNum()I"); // 执行Jni方法
System.out.println("retval:" + retval);

Dobby dobby = Dobby.getInstance(emulator);
dobby.replace(module.findSymbolByName("Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
emulator.getUnwinder().unwind();
return HookStatus.LR(emulator, vm.addLocalObject(new StringObject(vm, "超级")));
}
});

emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
Arm64RegisterContext context = emulator.getContext();
if (address == module.base + 0x11330) {
int x0=emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0).intValue();
System.out.println("x0:"+vm.getObject(x0));
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,vm.addLocalObject(new StringObject(vm, "超级")));
}
}
@Override
public void onAttach(UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base + 0x11240,module.base + 0x11340,null);
Debugger attach = emulator.attach();
// attach.addBreakPoint(module.base + 0x11070); //断点地址
attach.addBreakPoint(module.base + 0x11330, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
UnidbgPointer pointer = UnidbgPointer.register(emulator,Arm64Const.UC_ARM64_REG_X0);
System.out.println("str:"+vm.getObject(pointer.toIntPeer()));
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,vm.addLocalObject(new StringObject(vm, "超级")));
return true;
}
});

Number number = module.callFunction(emulator, 0x11014, vm.getJNIEnv(),vm.addLocalObject(SecurityUtils),vm.addLocalObject(new StringObject(vm, "123456")));
System.out.println("result:" + number.intValue());
StringObject Result = SecurityUtils.callStaticJniMethodObject(emulator, "vipLevel(Ljava/lang/String;)Ljava/lang/String;", "572782"); // 执行Jni方法
System.out.println("Result: " + Result.getValue());

UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x1146C);
Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
String s = "MOV W0, #0x8";
byte[] machineCode = keystone.assemble(s).getMachineCode();
pointer.write(machineCode);

int retval = SecurityUtils.callStaticJniMethodInt(emulator, "diamondNum()I"); // 执行Jni方法
System.out.println("retval: 0x" + Integer.toHexString(retval));

}

}

2.2.1 HookZz

调用HookZz.getInstance(emulator)获取 HookZz 实例,然后调用hookZz.wrap方法对导出函数进行 Hook 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
IHookZz hookZz = HookZz.getInstance(emulator); // 加载HookZz,支持inline hook,文档看https://github.com/jmpews/HookZz
hookZz.wrap(module.findSymbolByName("Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel"), new WrapCallback<HookZzArm64RegisterContext>() { // inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
// 函数调用前
UnidbgPointer input = ctx.getPointerArg(2);
System.out.println("preCall:"+vm.getObject(input.toIntPeer()));

}
@Override
public void postCall(Emulator<?> emulator, HookZzArm64RegisterContext ctx, HookEntryInfo info) {
// 函数调用后
UnidbgPointer input1 = ctx.getXPointer(0);
UnidbgPointer input = ctx.getPointerArg(0);
System.out.println("postCall:"+vm.getObject(input1.toIntPeer()));

}
});
StringObject Result = SecurityUtils.callStaticJniMethodObject(emulator, "vipLevel(Ljava/lang/String;)Ljava/lang/String;", "123"); // 执行Jni方法
System.out.println("Result: " + Result.getValue());

HookZz
另外也可以使用hookZz.instrument方法对导出函数内部函数进行 Hook。

1
2
3
4
5
6
7
8
hookZz.instrument(module.base + 0x11470, new InstrumentCallback<Arm64RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, Arm64RegisterContext ctx, HookEntryInfo info) { // 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
System.out.println("W0=" + ctx.getXInt(0));
}
});
int retval = SecurityUtils.callStaticJniMethodInt(emulator, "diamondNum()I"); // 执行Jni方法
System.out.println("retval: 0x" + Integer.toHexString(retval));

instrument

2.2.2 Dobby

调用Dobby.getInstance(emulator)获取 Dobby 实例,然后调用dobby.replace方法对导出函数进行 Hook 即可。

1
2
3
4
5
6
7
8
9
Dobby dobby = Dobby.getInstance(emulator);
dobby.replace(module.findSymbolByName("Java_com_zj_wuaipojie_util_SecurityUtil_diamondNum"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
return HookStatus.LR(emulator,12);
}
});
int retval = SecurityUtils.callStaticJniMethodInt(emulator, "diamondNum()I"); // 执行Jni方法
System.out.println("retval:" + retval);

Dobby
如果替换返回值为字符串,则需要返回vm.addLocalObject(new StringObject(vm, "超级"))

1
2
3
4
5
6
7
8
9
10
Dobby dobby = Dobby.getInstance(emulator);
dobby.replace(module.findSymbolByName("Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
emulator.getUnwinder().unwind();
return HookStatus.LR(emulator, vm.addLocalObject(new StringObject(vm, "超级")));
}
});
StringObject Result = SecurityUtils.callStaticJniMethodObject(emulator, "vipLevel(Ljava/lang/String;)Ljava/lang/String;", "572782"); // 执行Jni方法
System.out.println("Result: " + Result.getValue());

Dobby2

2.2.3 Unicorn

调用emulator.getBackend().hook_add_new方法对导出函数进行 Hook 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
Arm64RegisterContext context = emulator.getContext();
if (address == module.base + 0x11330) {
int x0=emulator.getBackend().reg_read(Arm64Const.UC_ARM64_REG_X0).intValue();
System.out.println("x0:"+vm.getObject(x0));
emulator.getBackend().reg_write(Arm64Const.UC_ARM64_REG_X0,vm.addLocalObject(new StringObject(vm, "超级")));
}
}
@Override
public void onAttach(UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base + 0x11240,module.base + 0x11340,null);
StringObject Result = SecurityUtils.callStaticJniMethodObject(emulator, "vipLevel(Ljava/lang/String;)Ljava/lang/String;", "572782"); // 执行Jni方法
System.out.println("Result: " + Result.getValue());

Unicorn

2.2.4 Console Debugger

Console Debugger(控制台调试器)是 Unidbg 提供的一个强大工具,允许用户在模拟执行过程中设置断点、单步调试、查看和修改内存及寄存器等操作,从而深入分析目标程序的行为。调用emulator.attach()获取 Debugger 实例,然后调用attach.addBreakPoint方法对导出函数进行断点调试即可。

1
2
3
4
Debugger attach = emulator.attach();
attach.addBreakPoint(module.base + 0x11070); //断点地址
Number number = module.callFunction(emulator, 0x11014, vm.getJNIEnv(),vm.addLocalObject(SecurityUtils),vm.addLocalObject(new StringObject(vm, "123456")));
System.out.println("result:" + number.intValue());

Console Debugger
常用的命令有:

命令 功能说明
c 继续执行程序
n 跨过当前指令
bt 回溯堆栈
st hex 搜索堆栈
shw hex 搜索可写堆
shr hex 搜索可读堆
shx-hex 搜索可执行堆
nb 在下一个区块中断
s 步入当前指令
s [decimal] 执行指定数量的指令
s (blx) 执行直到 blx 助记符(性能较低)
m (op) [size] 显示内存,默认大小为 0x70,大小可为十六进制或十进制
mr0-mr7, mfp, mip, msp [size] 显示指定寄存器的内存
m (address) [size] 显示指定地址的内存,地址需以 0x 开头
wr0-wr7, wfp, wip, wsp <value> 写入指定寄存器
wb(address), ws(address), wi(address) <value> 写入指定地址的(字节、短、整数)内存,地址需以 0x 开头
wx (address) <hex> 将字节写入指定地址的内存,地址需以 0x 开头
b (address) 添加临时断点,地址需以 0x 开头,可为模块偏移量
b 添加寄存器 PC 的断点
r 删除寄存器 PC 的断点
blr 添加寄存器 LR 的临时断点
p (assembly) 在 PC 地址修补汇编指令
where 显示 Java 堆栈跟踪
trace [begin-end] 设置指令跟踪
traceRead [begin-end] 设置内存读取跟踪
traceWrite [begin-end] 设置内存写入跟踪
vm 查看已加载的模块
vbs 查看断点
d 显示反汇编代码
d (0x) 在指定地址显示反汇编代码
stop 停止模拟
run [arg] 运行测试
gc 运行 System.gc()
threads 显示线程列表
cc size 将地址范围的汇编代码转为 C 函数

Console Debugger2

2.2.5 patch

调用UnidbgPointer.pointer(emulator,module.base + 0x1146C)获取指定地址的指针,然后调用pointer.write方法写入汇编指令即可。

1
2
3
4
5
6
7
8
9
UnidbgPointer pointer = UnidbgPointer.pointer(emulator,module.base + 0x1146C);
//在进行 Patch 操作前,需确保已正确定位目标函数的地址和指令集类型(如 ARM 或 Thumb)
//如果是32位的,代码如下:Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb);
Keystone keystone = new Keystone(KeystoneArchitecture.Arm64, KeystoneMode.LittleEndian);
String s = "MOV W0, #0x8";// 要写入的汇编指令
byte[] machineCode = keystone.assemble(s).getMachineCode();// 将汇编指令转为机器码
pointer.write(machineCode);// 将机器码写入指定地址
int retval = SecurityUtils.callStaticJniMethodInt(emulator, "diamondNum()I"); // 执行Jni方法
System.out.println("retval: 0x" + Integer.toHexString(retval));

patch

总结

Unidbg 是一个功能强大的动态分析框架,结合 Unicorn 的高性能 CPU 模拟能力和 Android 应用的运行时环境,能够帮助逆向工程师深入分析 Android 应用的保护机制。通过学习 Unidbg 的使用,可以掌握动态分析、内存操作、Hook 和逆向工程等技能,从而更好地应对复杂的逆向分析任务。