Unicorn

Last updated on May 11, 2024 pm

记录Unicorn学习笔记

安装

Capstone

我的系统是Mac M1

brew install capstone

brew install unicorn

pip install capstone

然后还需要将brew安装的capstone里面的一个库加到环境变量,即添加如下语句至~/.zhsrc或者~/bash_profile

export DYLD_LIBRARY_PATH=/opt/homebrew/Cellar/capstone/5.0.1/lib:$DYLD_LIBRARY_PATH

随后就能在Python中使用capstone了

Keystone

keystone安装稍微麻烦一点

1
pip install keystone

然后需要从源码构建得到一个共享库

1
2
3
4
5
git clone https://github.com/keystone-engine/keystone.git
mkdir build
cd build
cmake -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release -G Ninja ..
ninja

然后会在/build/llvm/lib目录下得到一个libkeystone.dylib

将这个复制去/usr/local/lib/目录,因为keystone.py会尝试去这个目录找

1
sudo cp libkeystone.dylib /usr/local/lib

然后就可以在python中使用keystone

JavaVM、JNIEnv

JavaVM

JavaVM是虚拟机在JNI层的代表,一个进程只有一个JavaVM,所有的线程共用一个JavaVM。

JNIInvokeInterface_ 结构封装和JVM相关的功能函数,如销毁JVM,获得当前线程的Java执行环境

在C和C++中JavaVM的定义有所不同,在C中JavaVM是JNIInvokeInterface_类型指针,而在C++中又对JNIInvokeInterface_进行了一次封装

有两种方式可以获得JavavM

  1. 在JNI_OnLoad中作为参数获得,如下
1
JNIEXPORT jint JNI_OnLOad(JavaVM *vm,void* reserved)

该函数由ART负责自动化查找和传入参数进行调用

  1. 通过JNIEnv的GetJavaVM函数获取
1
2
JavaVM* thisjvm = nullptr;
env.GetJavaVM(&thisjvm)

JNIEnv

JNIEnv表示Java调用native语言的环境,是一个封装了大量JNI方法的指针

JNIEnv只在创建它的线程生效,不能跨线程传递,不同线程的JNIEnv彼此独立

native环境中创建的线程,如果需要访问JNI,必须要调用AttachCurrentThread关联,并使用DetachCurrentThread解除链接

JavaVM和JNIEnv在C语言环境和C++环境下调用是有所区别的,主要表现在

C: (*env)->NewStringITF(env,”HELLO WORLD”);

C++:env->NewStringUTF(“HELLO WORLD”);

静态注册与动态注册

对于任意一个JNI函数来说,在该函数被调用前,必须要完成java函数与so中地址的绑定。这个绑定过程可以是被动的,即由Dalvik/ART虚拟机在调用前查找并完成地址的绑定;也可以是主动的,即由app自己完成地址的绑定。

静态注册

  1. 对应的函数名:Java_包名_类名_方法名

    其中使用下划线将每部分分开,如果名称中本来就包含下划线,将使用下划线加数字替换

  2. 优点:简单明了,语义清晰

  3. 缺点:不够安全

必须遵循注册规则从而导致名字过长;由于要保留符号,很容易使用IDA等直接定位地址

动态注册

  1. 定义:通过RegisterNatives方法手动完成native方法和so中的方法绑定,这样虚拟机就可以通过这个函数映射关系直接找到相应的方法。

  2. 示例

    假设有两个native方法如下

    1
    2
    public native int add(int a,int b);
    public native String myStr(String a);

    在cpp中这样定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int add(JNIEnv *env, jobject thiz,jint a, jint b) {
    int sum = a+b;
    return sum;
    }

    jstring myStr(JNIEnv *env,jobject thiz,jstring str){
    std::string my_str_ = "Hello from my_str";
    return env->NewStringUTF(my_str_.c_str());
    }

    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
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm,void* reserved){
    JNIEnv *env = nullptr;
    if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
    return -1; // 或者进行其他错误处理
    }

    JNINativeMethod methods[] = {
    {"add", "(II)I", (void*)add},
    {"myStr", "(Ljava/lang/String;)Ljava/lang/String;",(void*)myStr}
    };

    jclass clazz = env->FindClass("com/yring/unicorn/MainActivity");
    if (clazz == NULL) {
    return -1; // 或者进行其他错误处理
    }

    if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
    return -1; // 或者进行其他错误处理
    }

    return JNI_VERSION_1_6;
    }

    类型签名可以空着,让AS自动提示更正

Unicorn

放一个学习脚本

基本流程就是 地址映射 -> 写入汇编指令 -> 添加回调 -> 模拟执行

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
import unicorn
import capstone
import binascii

def printArm32Regs(mu):
for i in range(66, 78):
print("R%d,value:%x" % (i - 66, mu.reg_read(i)))


def hook_code(mu: unicorn.Uc, address, size, user_data):
code = mu.mem_read(address, size)
CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
for i in CP.disasm(code, 0, len(code)):
print("[addr:%x]:%s %s\n" % (address, i.mnemonic, i.op_str))
return


def hook_mem(mu: unicorn.Uc, type, address, size, value, user_data):
if type == unicorn.UC_MEM_WRITE:
print(f"write addr:{hex(address)},size:{size},value:{hex(value)}")

if type == unicorn.UC_MEM_READ:
print(f"read addr:{hex(address)},{size:},value:{hex(value)}")

print(f"hook_mem type:{type},addr:{address},size:{size},value:{hex(value)}")
return


def hook_mem_write_unmapped(mu: unicorn.Uc, type, address, size, value, user_data):
if type == unicorn.UC_MEM_WRITE_UNMAPPED:
print(
f"UC_MEM_WRITE_UNMAPPED addr:{hex(address)},size:{size},value:{hex(value)}"
)
mu.mem_map(0x0, 0x1000)
print(f"hook_mem type:{type},addr:{address},size:{size},value:{hex(value)}")
return True


def hook_syscall(mu: unicorn.Uc, intno, user_data):
print("------syscall------")

printArm32Regs(mu)

print("------syscall------")

return


def hook_block(mu: unicorn.Uc, address, size, user_data):
code = mu.mem_read(address, size)
CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
for i in CP.disasm(code, 0, len(code)):
print("[addr:%x]:%s %s\n" % (address, i.mnemonic, i.op_str))

print("------block------")
printArm32Regs(mu)
print("------block------")

return


def testthumb():
CODE = b"\x0a\x46\x03\x46\x04\x92\x00\xdf" # MOV R2,R1; MOV R3,R0; STR R2 [SP,#0x40+vae_30];svc
CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
# for i in CP.disasm(CODE, 0, len(CODE)):
# print("[addr:%x]:%s %s\n" % (i.address, i.mnemonic, i.op_str))

mu = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB)
ADDRESS = 0x1000
SIZE = 1024

mu.mem_map(ADDRESS, SIZE)
mu.mem_write(ADDRESS, CODE)

bytes = mu.mem_read(ADDRESS, 10)
print("ADDRESS:%x,content:%s" % (ADDRESS, binascii.b2a_hex(bytes)))

mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, 0x100)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, 0x200)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R2, 0x300)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R3, 0x400)

# printArm32Regs(mu)

# mu.hook_add(unicorn.UC_HOOK_CODE, hook_code)
mu.hook_add(unicorn.UC_HOOK_MEM_WRITE, hook_mem)
mu.hook_add(unicorn.UC_HOOK_MEM_WRITE_UNMAPPED, hook_mem_write_unmapped)
mu.hook_add(unicorn.UC_HOOK_INTR, hook_syscall)
mu.hook_add(unicorn.UC_HOOK_BLOCK, hook_block)

try:
mu.emu_start(ADDRESS + 1, ADDRESS + len(CODE))
except unicorn.UcError as e:
print(e)
print("over")
printArm32Regs(mu)

return


if __name__ == "__main__":
testthumb()

调用so中函数

参数传递

ARM32中参数传递:当参数少于等于4个时,直接使用R0-R3寄存器,多于4个时使用栈来传递,从右到左依次入栈

ARM64中参数传递:当参数少于等于8个时,直接使用X0-X7寄存器,多于8个时使用栈来传递,从右到左依次入栈

原函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int add(int a,int b){
int sum = a+b;
return sum;
}

int add_six(int a,int b,int c,int d,int e,int f){
int sum = 0;
sum = add(a,b);
sum = add(sum,c);
sum = add(sum,d);
sum = add(sum,e);
sum = add(sum,f);
return sum;
}

相关汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
000113ac  movs    r0, #3
000113ae str r0, [sp, #8] {var_40} {0x3}
000113b0 movs r1, #4
000113b2 str r1, [sp, #0xc] {var_3c} {0x4}
000113b4 bl #add
000113b8 ldr r2, [sp, #8] {var_40} {0x3}
000113ba ldr r3, [sp, #0xc] {var_3c} {0x4}
000113bc str r0, [sp, #0x24] {var_24}
000113be mov r1, sp {var_48}
000113c0 movs r0, #6
000113c2 str r0, [r1, #4] {var_44} {0x6}
000113c4 movs r0, #5
000113c6 str r0, [r1] {var_48} {0x5}
000113c8 movs r0, #1
000113ca movs r1, #2
000113cc bl #add_six

调用add(a,b)

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

def add():
CODE = None

with open("so/libunicorn.so", "rb") as f:
CODE = f.read()

# CODE = b"\x0a\x46\x03\x46\x04\x92\x00\xdf" # MOV R2,R1; MOV R3,R0; STR R2 [SP,#0x40+vae_30];svc
CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
for i in CP.disasm(CODE[0x11324:], 0, 20):
print("[addr:%x]:%s %s\n" % (0x11324 + i.address, i.mnemonic, i.op_str))

mu = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB)
ADDRESS = 0x1000
SIZE = 10 * 1024 * 1024

mu.mem_map(ADDRESS, SIZE)
mu.mem_write(ADDRESS, CODE)

mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, 0x1)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, 0x2)
SP = ADDRESS + SIZE - 1
mu.reg_write(unicorn.arm_const.UC_ARM_REG_SP, SP)
# printArm32Regs(mu)

mu.hook_add(unicorn.UC_HOOK_CODE, hook_code)

add = ADDRESS + 0x00011324
add_end = ADDRESS + 0x00011336
try:
mu.emu_start(add_six + 1, add_end)

print("over")
printArm32Regs(mu)

except unicorn.UcError as e:
print(e)

return

调用add(a,b,c,d,e,f)

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
def add_six():
CODE = None

with open("so/libunicorn.so", "rb") as f:
CODE = f.read()

CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
for i in CP.disasm(CODE[0x00011338:], 0, 20):
print("[addr:%x]:%s %s\n" % (0x00011338 + i.address, i.mnemonic, i.op_str))

mu = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB)
ADDRESS = 0x1000
SIZE = 10 * 1024 * 1024

mu.mem_map(ADDRESS, SIZE)
mu.mem_write(ADDRESS, CODE)

mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, 0x1)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, 0x2)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R2, 0x3)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R3, 0x4)
SP = ADDRESS + SIZE - 0x100
mu.reg_write(unicorn.arm_const.UC_ARM_REG_SP, SP)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_LR, ADDRESS + 0x456)

mu.mem_write(SP, struct.pack("I", 5))
mu.mem_write(SP + 4, struct.pack("I", 6))

mu.hook_add(unicorn.UC_HOOK_CODE, hook_code)
add_six = ADDRESS + 0x00011338
add_six_end = ADDRESS + 0x00011388
try:
mu.emu_start(add_six + 1, add_six_end + 1)

print("over")
printArm32Regs(mu)
print(f"INIT SP:{SP} NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)}")
except unicorn.UcError as e:
print(e)
print(f"INIT SP:{SP} NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)}")

return

当涉及到其他库函数调用时,需要做对应的patch

如修改add_six

1
2
3
4
5
6
7
8
9
10
11
12
int add_six(char* flag,int b,int c,int d,int e,int f){
int sum = 0;
if(strstr(flag,"add")){
sum = add(sum,c);
sum = add(sum,d);
}else{
sum = add(sum,d);
sum = add(sum,e);
}

return sum;
}

则需要patch对应call strstr地址,然后转为自己的实现

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

def add_patch():
CODE = None

with open("so/unicorn_patch.so", "rb") as f:
CODE = f.read()

CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
for i in CP.disasm(CODE[0x00011360:], 0, 20):
print("[addr:%x]:%s %s\n" % (0x00011360 + i.address, i.mnemonic, i.op_str))

mu = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB)
ADDRESS = 0x10000
SIZE = 10 * 1024 * 1024

mu.mem_map(ADDRESS, SIZE)
mu.mem_write(ADDRESS, CODE)

mu.mem_write(ADDRESS + 0x00011390, b"\x00\xbf\x00\xbf") # patch

mu.mem_map(ADDRESS + SIZE + 0x10000, 1024)
mu.mem_write(ADDRESS + SIZE + 0x10000, b"1add")

mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, ADDRESS + SIZE + 0x10000)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, 0x2)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R2, 0x3)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R3, 0x4)
SP = ADDRESS + SIZE - 0x100
mu.reg_write(unicorn.arm_const.UC_ARM_REG_SP, SP)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_LR, ADDRESS + 0x456)

# printArm32Regs(mu)
mu.mem_write(SP, struct.pack("I", 5))
mu.mem_write(SP + 4, struct.pack("I", 6))

mu.hook_add(unicorn.UC_HOOK_CODE, hook_code)

add_six = ADDRESS + 0x00011360
add_six_end = ADDRESS + 0x000113C8
try:
mu.emu_start(add_six + 1, add_six_end)

print("over")
printArm32Regs(mu)
print(f"INIT SP:{SP} NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)}")
except unicorn.UcError as e:
print(e)
print(f"INIT SP:{SP} NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)}")
printArm32Regs(mu)

return

模拟调用jni接口函数

对于一般的jni函数来说,必然会使用到jni提供的一系列的api,如GetStringChars,NewStringUTF,FindClass,CallObjectMethod等,那么如何设计和模拟实现jni函数对这些jni中的接口函数调用呢?

在native中编写如下C函数

1
2
3
4
5
6
7
8
9
10
11
12
JNICALL
Java_com_yring_unicorn_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */,
jstring content) {

const char* content_ptr = env->GetStringUTFChars(content,0);
__android_log_print(4,"jni","%s",content_ptr);

std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

这里JNIEnv是一个结构体,里面保存了大量Jni函数接口,跟进查看

其实就是一个JNINativeInterface*的指针,继续跟进

就会发现定义了大量的接口函数

截屏2024-05-01 18.17.13

将如上Native代码编译成apk后,解包,将so丢进IDA中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __fastcall Java_com_yring_unicorn_MainActivity_stringFromJNI(int a1, int a2, int a3)
{
int v3; // ST24_4
int v4; // ST18_4
int v5; // r0
int result; // r0
int v7; // [sp+Ch] [bp-34h]
char v8; // [sp+28h] [bp-18h]
int v9; // [sp+34h] [bp-Ch]

v3 = a1;
v4 = sub_113E4(a1, a3, 0);
_android_log_print(4, "jni", "%s", v4);
sub_11404();
v5 = sub_11472(&v8);
v7 = sub_11458(v3, v5);
std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::~basic_string(&v8);
result = _stack_chk_guard;
if ( _stack_chk_guard == v9 )
result = v7;
return result;
}

这里三个参数就与在Native中三个参数对应,将第一个参数改为JNIEnv*类型,IDA就会自动识别函数

函数sub_113E4

1
2
3
4
int __fastcall sub_113E4(void *a1)
{
return (*(int (**)(void))(*(_DWORD *)a1 + 676))();
}

改为JniEnv*

1
2
3
4
int __fastcall sub_113E4(JNIEnv *a1)
{
return ((int (*)(void))(*a1)->GetStringUTFChars)();
}

那么使用Unicorn模拟执行就需要把这些接口函数给补回去,在这里就是需要手动实现GetStringUTFCharsNewStringUTF

这里直接给出全部代码

核心思路就是找一片内存,初始化JNIEnv结构体即可,然后可以简单实现里面的一些接口函数。

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
import unicorn
import capstone
import struct

ADDRESS = 0x10000
SIZE = 10 * 1024 * 1024


def printArm32Regs(mu: unicorn.Uc):
print("-----REG-----")
for i in range(66, 78):
print("R%d,value:%x" % (i - 66, mu.reg_read(i)))
print("SP-Value:%x" % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)))
print("PC-Value:%x" % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_PC)))
print("-----REG-----")


def readstring(mu: unicorn.Uc, address):
result = b""
# print(hex(address))
tmp = mu.mem_read(address, 1)
while tmp[0] != 0:
result += tmp
address += 1
tmp = mu.mem_read(address, 1)
return str(result)


def hook_code(mu: unicorn.Uc, address, size, user_data):
code = mu.mem_read(address, size)
CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
for i in CP.disasm(code, 0, len(code) - 1):
print()
print("[addr:%x]:%s %s" % (address, i.mnemonic, i.op_str))

# 0 - 0x1000 地址用于存放JNIEnv结构体
if address >= 0 and address <= 300 * 4:
print("-----------in jni interface-----------")
index = address / 4
if index == 169: # 676 // 4
print("-----call GetStringUTFChars-----\n")
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0)
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1)
r2value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R2)

content = readstring(mu, r1value)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, r1value)
print(str(r0value) + "---" + content + "---" + str(r2value))
print("\n-----call GetStringUTFChars-----")

if index == 167: # 668 // 4
print("-----call NewStringUTF-----")
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0)
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1)

content = readstring(mu, r1value)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, r1value)
print(str(r0value) + "---" + content)
print("-----call NewStringUTF-----")

print("-----------in jni interface-----------")

# 原代码中使用了C++的构造函数用于创建字符串,这里也要hook掉
if address == 0x10000 + 0x11386:
print("-----call init-----")
mu.mem_map(ADDRESS + SIZE + 0x11000, 0x1000)
mu.mem_write(ADDRESS + SIZE + 0x11000, b"hello from C++")
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, ADDRESS + SIZE + 0x11000)
print("-----call init-----")

if address == 0x10000 + 0x11390:
print("-----call 1390-----")
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, ADDRESS + SIZE + 0x11000)
print("-----call 1390-----")
# printArm32Regs(mu)
# print()
return


def testthumb_calljni():
CODE = None
with open("so/libunicorn_jni.so", "rb") as f:
CODE = f.read()

CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
for i in CP.disasm(CODE[0x1134C:], 0, 20):
print(f"[addr:{hex(i.address)}]:{i.mnemonic} {i.op_str}")

mu = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB)

# 0 - 0x1000 全部存放接口函数实现,这里就是简单实现栈平衡
# push {lr} 00 B5
# pop {pc} 00 BD
JNIFUNCTIONLISTBASE = 0x0
JNIFUNCTIONLISTSIZE = 0x1000
JNINATIVEINTERFACE = 301
mu.mem_map(0, 0x1000)

# 初始化jni接口中每一个函数
for i in range(0, 300):
mu.mem_write(i * 4 + JNIFUNCTIONLISTBASE, b"\x00\xB5\x00\xBD") # 函数内容

# 初始化JNINativeInterface结构体
for i in range(300, 600):
mu.mem_write(i * 4, struct.pack("I", ((i - 300) * 4 + 1)))

# 初始化JNIEnv* env
JNIEnv_pointer = 601 * 4
mu.mem_write(JNIEnv_pointer, struct.pack("I", (300 * 4)))

mu.mem_map(ADDRESS, SIZE)
mu.mem_write(ADDRESS, CODE)

mu.mem_write(
ADDRESS + 0x11352, b"\x00\xBF\x00\xBF\x00\xBF\x00\xBF"
) # patch check_stack

mu.mem_write(ADDRESS + 0x1137A, b"\x00\xBF\x00\xBF") # patch android_log
# mu.mem_write(ADDRESS + 0x11386, b"\x00\xBF\x00\xBF") # patch 构造
# mu.mem_write(ADDRESS + 0x11390, b"\x00\xBF\x00\xBF")

mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, JNIEnv_pointer) # JNIEvn* env
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, 0) # jobject

# GetStringUTFChars参数
mu.mem_map(ADDRESS + SIZE + 0x10000, 1024)
mu.mem_write(ADDRESS + SIZE + 0x10000, b"i am from jni")
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R2, ADDRESS + SIZE + 0x10000) # jstring

SP = ADDRESS + SIZE - 0x100
mu.reg_write(unicorn.arm_const.UC_ARM_REG_SP, SP)

mu.hook_add(unicorn.UC_HOOK_CODE, hook_code)

start = ADDRESS + 0x0001134C
end = ADDRESS + 0x000113BA

try:
mu.emu_start(start + 1, end, count=80)

print("over")
printArm32Regs(mu)
print(f"INIT SP:{SP} NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)}")

# 这里可以用于验证hook是否成功
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0)
content = readstring(mu, r0value)
print("return value-> ", content)

except unicorn.UcError as e:
print("\n-----Something went wrong-----")
print(e)
print(f"INIT SP:{SP} NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)}")
printArm32Regs(mu)

return


if __name__ == "__main__":
testthumb_calljni()

这里可以注意一点android_log是外部函数,如果很多地方都调用这个函数,可以在plt表直接hook,相当于hook了全部函数。

模拟调用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
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm,void* reserved){
JNIEnv *env = nullptr;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
return -1; // 或者进行其他错误处理
}

JNINativeMethod methods[] = {
{"add", "(II)I", (void*)add},
{"myStr", "(Ljava/lang/String;)Ljava/lang/String;",(void*)myStr}
};

jclass clazz = env->FindClass("com/yring/unicorn/MainActivity");
if (clazz == NULL) {
return -1; // 或者进行其他错误处理
}

if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
return -1; // 或者进行其他错误处理
}

return JNI_VERSION_1_6;
}

同样这里,就需要实现vm->GetEnvenv->FindClass等操作

JavaVM结构体,比JNIEnv要少很多

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
struct _JavaVM {
const struct JNIInvokeInterface* functions;

#if defined(__cplusplus)
jint DestroyJavaVM()
{ return functions->DestroyJavaVM(this); }
jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThread(this, p_env, thr_args); }
jint DetachCurrentThread()
{ return functions->DetachCurrentThread(this); }
jint GetEnv(void** env, jint version)
{ return functions->GetEnv(this, env, version); }
jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
{ return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};

// JNIInvokeInterface结构体
/*
* JNI invocation interface.
*/
struct JNIInvokeInterface {
void* reserved0;
void* reserved1;
void* reserved2;

jint (*DestroyJavaVM)(JavaVM*);
jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
jint (*DetachCurrentThread)(JavaVM*);
jint (*GetEnv)(JavaVM*, void**, jint);
jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};

同样,需要hook掉GetEnvRegisterNatives

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
210
211
import unicorn
import capstone
import struct

ADDRESS = 0x10000
SIZE = 10 * 1024 * 1024


def printArm32Regs(mu: unicorn.Uc):
print("-----REG-----")
for i in range(66, 78):
print("R%d,value:%x" % (i - 66, mu.reg_read(i)))
print("SP-Value:%x" % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)))
print("PC-Value:%x" % (mu.reg_read(unicorn.arm_const.UC_ARM_REG_PC)))
print("-----REG-----")


def readstring(mu: unicorn.Uc, address):
result = b""
# print(hex(address))
tmp = mu.mem_read(address, 1)
while tmp[0] != 0:
result += tmp
address += 1
tmp = mu.mem_read(address, 1)
return str(result)


def hook_code(mu: unicorn.Uc, address, size, user_data):
code = mu.mem_read(address, size)
CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
for i in CP.disasm(code, 0, len(code) - 1):
print()
print("[addr:%x]:%s %s" % (address, i.mnemonic, i.op_str))

if address >= 700 * 4 and address <= 710 * 4:
print("-----------in javavm interface-----------")
index = (address - 700 * 4) / 4
if index == 6:
print("-----call GetEnv-----\n")
# jint GetEnv(void** env, jint version)
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1)
mu.mem_write(r1value, struct.pack("I", 601 * 4))
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, 0)

if address >= 0 and address <= 300 * 4:
print("-----------in jni interface-----------")
index = address / 4

if index == 6: # 676 // 4
# jclass (*FindClass)(JNIEnv*, const char*);
print("-----call FindClass-----\n")
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1)
classname = readstring(mu, r1value)
print(f"FindClass {classname}")
# com/yring/unicorn/MainActivity
# 设置引用
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, 666)
print("\n-----call FindClass-----")

if index == 215:
# jint RegisterNatives(JNIEnv*, jclass , const JNINativeMethod* ,jint)
print("-----call RegisterNatives-----\n")
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0)
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1)
r2value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R2)
r3value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R3)

function_addr = struct.unpack("I", mu.mem_read(r2value, 4))[0]
function_name = readstring(mu, function_addr)
print(f"-----function name:{function_name}-----")

print(f"----JNIEnv:{r0value}----")
print(f"----jcalss:{r1value}----")
print(f"----JNINativeMethod:{r2value}----")
print(f"----jint:{r3value}----")

print("\n-----call RegisterNatives-----")

print("-----------in jni interface-----------")

if address == 0x10000 + 0x1162E:
print("-----patch VLD1.32 {D16-D17}, [R1]-----")
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1)
d16 = mu.mem_read(r1value, 4)
d17 = mu.mem_read(r1value + 4, 4)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_D16, struct.unpack("I", d16)[0])
mu.reg_write(unicorn.arm_const.UC_ARM_REG_D17, struct.unpack("I", d17)[0])
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, r1value + 2)

if address == 0x10000 + 0x11636:
print("-----patch VST1.64 {D16-D17}, [R0]!-----")
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0)
d16 = mu.reg_read(unicorn.arm_const.UC_ARM_REG_D16)
d17 = mu.reg_read(unicorn.arm_const.UC_ARM_REG_D17)

mu.mem_write(r0value, struct.pack("I", d16))
mu.mem_write(r0value + 4, struct.pack("I", d17))
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, r0value + 2)

if address == 0x10000 + 0x1163A:
print("-----patch VLDR D16, [R1]-----")
r1value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R1)
r1value = mu.mem_read(r1value, 4)
mu.reg_write(unicorn.arm_const.UC_ARM_REG_D16, struct.unpack("I", r1value)[0])

if address == 0x10000 + 0x1163E:
print("-----patch VSTR D16, [R0]-----")
r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0)
d16 = mu.reg_read(unicorn.arm_const.UC_ARM_REG_D16)
mu.mem_write(r0value, struct.pack("I", d16))

# printArm32Regs(mu)
# print()
return


def testthumb_calljni():
CODE = None
with open("so/libunicorn_jnionload.so", "rb") as f:
CODE = f.read()

CP = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_THUMB)
for i in CP.disasm(CODE[0x11540:], 0, 20):
print(f"[addr:{hex(i.address)}]:{i.mnemonic} {i.op_str}")

mu = unicorn.Uc(unicorn.UC_ARCH_ARM, unicorn.UC_MODE_THUMB)

# 0 - 0x1000
# push {lr} 00 B5
# pop {pc} 00 BD
JNIFUNCTIONLISTBASE = 0x0
JNIFUNCTIONLISTSIZE = 0x1000
JNINATIVEINTERFACE = 301
mu.mem_map(0, 0x1000)

# 初始化jni接口中每一个函数
for i in range(0, 300):
mu.mem_write(i * 4 + JNIFUNCTIONLISTBASE, b"\x00\xB5\x00\xBD") # 函数内容

# 初始化JNINativeInterface结构体
for i in range(300, 600):
mu.mem_write(i * 4, struct.pack("I", ((i - 300) * 4 + 1)))

# 初始化JNIEnv* env
JNIEnv_pointer = 601 * 4
mu.mem_write(JNIEnv_pointer, struct.pack("I", (300 * 4)))

# 初始化JavaVM
JAVAVMFUNCTIONLISTBASE = 700 * 4
# 初始化JavaVM接口中每一个函数
for i in range(0, 10):
mu.mem_write(i * 4 + JAVAVMFUNCTIONLISTBASE, b"\x00\xB5\x00\xBD")

# 初始化JNIInvokeInterface结构体
for i in range(0, 10):
mu.mem_write(
i * 4 + JAVAVMFUNCTIONLISTBASE + 40,
struct.pack("I", i * 4 + JAVAVMFUNCTIONLISTBASE + 1),
)

# 初始化JavaVM
JavaVM_pointer = 700 * 4 + 80
mu.mem_write(JavaVM_pointer, struct.pack("I", JAVAVMFUNCTIONLISTBASE + 40))

mu.mem_map(ADDRESS, SIZE)
mu.mem_write(ADDRESS, CODE)

mu.mem_write(
ADDRESS + 0x115FE, b"\x00\xBF\x00\xBF\x00\xBF\x00\xBF"
) # patch check_stack
mu.mem_write(ADDRESS + 0x1162E, b"\x00\xBF\x00\xBF") # patch VLD1.32
mu.mem_write(ADDRESS + 0x11636, b"\x00\xBF\x00\xBF") # patch VST1.64
mu.mem_write(ADDRESS + 0x1163A, b"\x00\xBF\x00\xBF") # patch VLDR
mu.mem_write(ADDRESS + 0x1163E, b"\x00\xBF\x00\xBF") # patch VSTR

mu.reg_write(unicorn.arm_const.UC_ARM_REG_R0, JavaVM_pointer) # JNIEvn* env
mu.reg_write(unicorn.arm_const.UC_ARM_REG_R1, 0) # reserverd

SP = ADDRESS + SIZE - 0x100
mu.reg_write(unicorn.arm_const.UC_ARM_REG_SP, SP)

mu.hook_add(unicorn.UC_HOOK_CODE, hook_code)

start = ADDRESS + 0x115F8
end = ADDRESS + 0x11666

try:

mu.emu_start(start + 1, end)

print("over")
printArm32Regs(mu)
print(f"INIT SP:{SP} NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)}")

r0value = mu.reg_read(unicorn.arm_const.UC_ARM_REG_R0)
content = readstring(mu, r0value)
print("return value-> ", content)

except unicorn.UcError as e:
print("\n-----Something went wrong-----")
print(e)
print(f"INIT SP:{SP} NOW SP:{mu.reg_read(unicorn.arm_const.UC_ARM_REG_SP)}")
printArm32Regs(mu)

return


if __name__ == "__main__":
testthumb_calljni()

AndroidNativeEmu

安装

直接通过pip install安装会有问题,似乎是pip那边的源没有更新,构建的unicorn版本也是1.0.6,比较古早。可以直接下载源码

1
git clone https://github.com/AeonLucid/AndroidNativeEmu.git

然后把src/androidemu文件夹整个copy到python的包目录

1
cp -r  src/androidemu /Users/yongrin/opt/anaconda3/envs/frida14/lib/python3.8/site-packages/

然后就能成功运行自带的一些examples了,不过前提是还需要正确安装keystone

普通函数hook

直接看给的example.py

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
import logging
import sys

from unicorn import UC_HOOK_CODE
from unicorn.arm_const import *

from androidemu.emulator import Emulator

# 设置日志输出级别
logging.basicConfig(
stream=sys.stdout,
level=logging.DEBUG,
format="%(asctime)s %(levelname)7s %(name)34s | %(message)s"
)

logger = logging.getLogger(__name__)

# 初始化一个模拟器,并加载相关的库
emulator = Emulator(vfp_inst_set=True)
emulator.load_library("example_binaries/32/libc.so", do_init=False)
lib_module = emulator.load_library("example_binaries/32/libnative-lib.so", do_init=False)

# 加载的模块
logger.info("Loaded modules:")

for module in emulator.modules:
logger.info("[0x%x] %s" % (module.base, module.filename))


# 添加hook代码
def hook_code(uc, address, size, user_data):
instruction = uc.mem_read(address, size)
instruction_str = ''.join('{:02x} '.format(x) for x in instruction)

print('# Tracing instruction at 0x%x, instruction size = 0x%x, instruction = %s' % (address, size, instruction_str))


emulator.uc.hook_add(UC_HOOK_CODE, hook_code)

# Runs a method of "libnative-lib.so" that calls an imported function "strlen" from "libc.so".
emulator.call_symbol(lib_module, '_Z4testv')

print("String length is: %i" % emulator.uc.reg_read(UC_ARM_REG_R0))

如果将libc.so注释,那么模拟器就不会加载这个库,而Z4testv使用了strlen函数,此函数在libc.so中,那么此时就会报错,相关日志也会提醒

截屏2024-05-04 00.44.25

一般而言对于找不到的外部函数,要么就将外部函数所在库给加载进来,要么就是打patch然后手动实现。

这里给出手动实现方式

  1. 定义要hook函数的python实现
1
2
3
4
5
6
7
8
9
from androidemu.utils import memory_helpers
from androidemu.java.helpers.native_method import native_method

@native_method
def strlen(uc, buffer):
content = memory_helpers.read_utf8(uc, buffer)
length = len(content)
return length

  1. 将函数hook进so中
1
emulator.modules.add_symbol_hook('strlen', emulator.hooker.write_function(strlen) + 1)

除此外,也可以直接hook已有的函数。

JNI函数hook

基本一样

1
2
3
4
5
emulator.call_symbol(lib_module,
"Java_com_yring_unicorn_MainActivity_stringFromJNI",
emulator.java_vm.jni_env.address_ptr,
0,
"hello emu")

模拟实现jni与Java交互

直接调用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
import logging
import sys

from unicorn import UC_HOOK_CODE
from unicorn.arm_const import *

from androidemu.emulator import Emulator
import debug_utils
from androidemu.utils import memory_helpers
from androidemu.java.helpers.native_method import native_method, native_read_args

@native_method
def __stack_chk_fail(uc, buffer):
pass


@native_method
def __stack_chk_guard(uc, buffer):
pass


@native_method
def __android_log_print(uc, arg1, arg2, arg3,arg4):
t = memory_helpers.read_utf8(uc, arg2)
s = memory_helpers.read_utf8(uc, arg4)
print(f"prio-{arg1} tag-{t} string-{s}")


# Configure logging
logging.basicConfig(
stream=sys.stdout,
level=logging.DEBUG,
format="%(asctime)s %(levelname)7s %(name)34s | %(message)s",
)

logger = logging.getLogger(__name__)

# Initialize emulator
emulator = Emulator(vfp_inst_set=True)

emulator.modules.add_symbol_hook('__stack_chk_fail', emulator.hooker.write_function(__stack_chk_fail) + 1)
emulator.modules.add_symbol_hook('__stack_chk_guard', emulator.hooker.write_function(__stack_chk_guard) + 1)

lib_module = emulator.load_library("so/libunicorn_jnionload.so", do_init=False)

# Show loaded modules.
logger.info("Loaded modules:")

for module in emulator.modules:
logger.info("[0x%x] %s" % (module.base, module.filename))

emulator.uc.hook_add(UC_HOOK_CODE, debug_utils.hook_code)

# Runs a method of "libnative-lib.so" that calls an imported function "strlen" from "libc.so".
function_name = "Java_com_yring_unicorn_MainActivity_stringFromJNI"
emulator.call_symbol(lib_module,
"JNI_OnLoad",
emulator.java_vm.address_ptr,
0)

当执行到JNIEnv->FindClass(com/yring/unicorn/MainActivity)时,会报错,需要使用python实现这个类

截屏2024-05-04 02.44.19

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Create java class.
class MainActivity(metaclass=JavaClassDef, jvm_name='com/yring/unicorn/MainActivity'):

def __init__(self):
pass

@java_method_def(name='myStr', signature='(Ljava/lang/String;)Ljava/lang/String;', native=True)
def myStr(self, mu, arg0):
pass

@java_method_def(name='add', signature='(II)I', native=True)
def add(self, mu, arg0, arg1):
# print(f"arg0: {arg0}, arg1: {arg1}")
# return arg0 + arg1
pass

def test(self):
pass

然后再注册进模拟器

1
emulator.java_classloader.add_class(MainActivity)

随后就能以python方式使用

1
2
main_activity = MainActivity()
main_activity.myStr(emulator, "test emu")

不过当返回值是基本类型比如int的时候,会出现问题,因为AndroidNativeEmu不知道返回值是否是基本类型,所以还是会去找它自己维护的一个引用表,这样就会出问题。解决办法就是在返回地方,新增一个print然后自己判断返回值是啥。

Unidbg

Unidbg也是基于Unicorn的,但是用Java所写,比Python的要方便许多。

大体流程如下

  1. 创建32位模拟器实例,
    emulator = AndroidEmulatorBuilder.for32Bit().build();
  2. 创建模拟器内存接口
    final Memory memory = emulator.getMemory();
  3. 设置系统类库解析
    memory.setLibraryResolver(new AndroidResolver(23));
  4. 创建 Android 虚拟机
    vm = emulator.createDalvikVM();
  5. 加载 so 到虚拟内存,第二个参数的意思表示是否执行动态库的初始化代码
    DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xxx/xxx.so"),true);
  6. 获取 so 模块的句柄
    module = dm.getModule();
  7. 设置 JNI 需要继承AbstractJni
    vm.setJni(this);
  8. 打印日志
    vm.setVerbose(true);
  9. 调用 JNI_Onload
    dm.callJNI_OnLoad(emulator);
  10. 创建 jobject, 如果没用到的话可以不写 ,要用需要调用函数所在的Java类完整路径,比如a/b/c/d等等,注意.需要用/代替
    cNative = vm.resolveClass("com/xxx/xxx")

参数构建

基本方式

1
List<Object> args = new ArrayList<>(10);

*JNIEnv env
args.add(vm.getJNIEnv());

jobject 或 jclass

DvmObject<?> cnative = cNative.newObject(null);
args.add(cnative.hashCode());

如果用不到直接填0即可

args.add(0);

字符串对象

String input = "abcdef";
args.add(vm.addLocalObject(new StringObject(vm, input)));

bytes 数组

String str= "abcdef";
byte[] str_bytes = str.getBytes(StandardCharsets.UTF_8);
ByteArray str_bytes _array = new ByteArray(vm,str_bytes );
args.add(vm.addLocalObject(str_bytes _array));

bool

// false 填 0,true 填 1
args.add(1);

AndroidEmulator 实例

使用 AndroidEmulatorBuilder 可以来帮助快速创建一个 AndroidEmulator 的实例。

AndroidEmulator emulator = AndroidEmulatorBuilder
//指定32位CPU
.for32Bit()
//添加后端,推荐使用Dynarmic,运行速度快,但并不支持某些新特性
.addBackendFactory(new DynarmicFactory(true))
//指定进程名,推荐以安卓包名做进程名
.setProcessName(“com.github.unidbg”)
//设置根路径
.setRootDir(new File(“target/rootfs/default”))
//生成AndroidEmulator实例
.build();

常用API

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
//获取内存操作接口
Memory memory = emulator.getMemory();
//获取进程pid
int pid = emulator.getPid();
//创建虚拟机
VM dalvikVM = emulator.createDalvikVM();
//创建虚拟机并指定APK文件
VM dalvikVM = emulator.createDalvikVM(new File("apk file path"));
//获取已创建的虚拟机
VM dalvikVM = emulator.getDalvikVM();
//显示当前寄存器状态 可指定寄存器
emulator.showRegs();
//获取后端CPU
Backend backend = emulator.getBackend();
//获取进程名
String processName = emulator.getProcessName();
//获取寄存器
RegisterContext context = emulator.getContext();
//Trace读内存
emulator.traceRead(1,0);
//Trace写内润
emulator.traceWrite(1,0);
//Trace汇编
emulator.traceCode(1,0);
//是否正在运行
boolean running = emulator.isRunning();

Memory实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Memory memory = emulator.getMemory();
//指定Android SDK 版本,目前支持19和23两个版本
memory.setLibraryResolver(new AndroidResolver(23));

//拿到一个指针,指向内存地址,通过该指针可操作内存
UnidbgPointer pointer = memory.pointer(address);

//获取当前内存映射情况
Collection<MemoryMap> memoryMap = memory.getMemoryMap();

//根据模块名来拿到某个模块
Module module = memory.findModule("module name");

//根据地址拿到某个模块
Module module = memory.findModuleByAddress(address);

VM

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
//推荐指定APK文件,Unidbg会自动做许多固定的操作
VM vm = emulator.createDalvikVM();

//是否输出JNI运行日志
vm.setVerbose(true);

//加载SO模块 参数二设置是否自动调用init函数
DalvikModule dalvikModule = vm.loadLibrary(new File("so 文件路径"), true);

//设置JNI交互接口 参数需实现Jni接口,推荐使用this继承AbstractJni
vm.setJni(this);

//获取JNIEnv指针,可作为参数传递
Pointer jniEnv = vm.getJNIEnv();

//获取JavaVM指针,可作为参数传递
Pointer javaVM = vm.getJavaVM();

//调用JNI_OnLoad函数
vm.callJNI_OnLoad(emulator,dalvikModule.getModule());

//向VM添加全局对象,返回该对象的hash值
int hash = vm.addGlobalObject(dvmObj);

//获取虚拟机中的对象,参数为该对象的hash值
DvmObject<?> object = vm.getObject(hash);

四种函数调用

  1. 加载so,并调用init以及init_array中函数
  2. 调用so中普通函数
  3. 调用jni函数
  4. 调用JNI_OnLoad函数

测试的native-lib.cpp

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
#include <jni.h>
#include <string>
#include <android/log.h>

extern "C" void _init(void ){
__android_log_print(4,"jin","go into _init");
}

void __attribute__ ((constructor)) myConstructor1(void){
__android_log_print(4,"jin","go into myConstructor1");
}

void __attribute__ ((constructor)) myConstructor2(void){
__android_log_print(4,"jin","go into myConstructor2");
}

int add(JNIEnv *env, jobject thiz,jint a, jint b) {
int sum = a+b;
return sum;
}

int add_(int a,int b){
return a+b;
}


int add_4(jint a, jint b,jint c,jint d) {
int sum = add_(a,b) + add_(c ,d);
return sum;
}



jstring myStr(JNIEnv *env, jobject thiz, jstring str) {
// 定义std::string并初始化
std::string my_str_ = "Hello from ";

// 将jstring转换为char*
const char* nativeStr = env->GetStringUTFChars(str, nullptr);
if (nativeStr == nullptr) {
// 如果转换失败,返回nullptr或处理错误
return nullptr;
}

// 拼接字符串
my_str_ += nativeStr;

// 释放jstring资源
env->ReleaseStringUTFChars(str, nativeStr);

// 将std::string转换为jstring并返回
return env->NewStringUTF(my_str_.c_str());
}


extern "C" JNIEXPORT jstring JNICALL
Java_com_yring_unicorn_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */,
jstring content) {

const char* content_ptr = env->GetStringUTFChars(content,0);
__android_log_print(4,"jni","%s",content_ptr);

std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}


JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm,void* reserved){
JNIEnv *env = nullptr;
if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
return -1; // 或者进行其他错误处理
}

JNINativeMethod methods[] = {
{"add", "(II)I", (void*)add},
{"myStr", "(Ljava/lang/String;)Ljava/lang/String;",(void*)myStr}
};

jclass clazz = env->FindClass("com/yring/unicorn/MainActivity");
env->RegisterNatives(clazz, methods, 2);

int sum = add_4(1,2,3,4);
__android_log_print(4,"add_4","%d",sum);

return JNI_VERSION_1_6;
}

加载so

在加载so的时候可以自行选择是否执行init等函数

1
2
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/native/so/libunicorn.so"), true);

调用普通函数

一般来说,普通函数都没有导出符号,需要使用地址调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void call_add2() {
DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);
// 0x11510是地址偏移(加过1的)
Number numbers = module.callFunction(emulator, 0x11510 | 1, 1,2);
System.out.println("add2 ====> result: " + numbers.intValue());
}

private void call_add4() {
DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);

// 0x11520是地址偏移(加过1的)
Number numbers = module.callFunction(emulator, 0x11520 | 1, 1,2,3,4);
System.out.println("add4 ====> result: " + numbers.intValue());
}

调用JNI函数

1
2
3
4
5
6
7
8
9
10
11
12
13
public void jni(){
/* 符号调用
创建项目要和包名一致, 因为unidbg会根据this的包名进行匹配JNI方法,所以this所属类的包名应该与目标函数相同(com.yring.unicorn)
例如这里this的包名是com.yring.unicorn,那么就相当于调用Java_com_yring_unicorn_MainActivity_md5(JNIEnv *env, jobject thiz, jstring str);
*/
DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);
String data = "dta";
// 这个地方注意调用方法是callJniMethodObject,如果是返回类型是基本数据类型应该调用方法callJniMethodInt,callJniMethodBool等
DvmObject<?> result = obj.callJniMethodObject(emulator,"stringFromJNI(Ljava/lang/String;)Ljava/lang/String;", data);
String value = (String) result.getValue();

System.out.println("jni ====> result: " + value);
}

调用JNI_OnLoad

需要先调用callJNI_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
 private MainActivity() {
emulator = AndroidEmulatorBuilder
.for32Bit()
.addBackendFactory(new DynarmicFactory(true))
.build();
Memory memory = emulator.getMemory();
LibraryResolver resolver = new AndroidResolver(23);
memory.setLibraryResolver(resolver);

vm = emulator.createDalvikVM();
vm.setVerbose(false);
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/native/so/libunicorn.so"), true);

module = dm.getModule();

dm.callJNI_OnLoad(emulator);

DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);

DvmObject<?> result = obj.callJniMethodObject(emulator,"myStr(Ljava/lang/String;)Ljava/lang/String;", "");
String value = (String) result.getValue();
System.out.println("myStr:" + value);

int res = obj.callJniMethodInt(emulator,"add(II)I", 3,4);
// value = (String) result.getValue();
System.out.println("add:" + res);
}

Unicorn
http://example.com/2024/04/27/Unicorn/
Author
yring
Posted on
April 27, 2024
Licensed under