首页
论坛
课程
招聘
[原创]一种新的Android Runtime环境仿真及调试方法
2022-5-4 10:11 9792

[原创]一种新的Android Runtime环境仿真及调试方法

2022-5-4 10:11
9792

现有的Android so库调试方法

有次我在调试某Android so库的时候,遇到一个很棘手的问题:如何更方便地对so库进行调试?

 

常规的Android下so库的调试方法是在Android环境里放一个gdbserver或者IDA的android_server,然后通过adb将端口转出来,使用本机GDB或者IDA进行远程连接调试。但是这种方法本身依赖于完整的Android环境、用起来比较麻烦且笨重不说,更重要的是这种方法无法实现一些比较高级的调试方式。比如在常规情况下,ARM Android环境里是无法设置内存断点watchpoint的,更不用说受限于调试原理无法实现高效的trace记录功能。

 

想要实现高效的trace,可以使用Frida之类的DBI工具来进行插桩。Frida还提供了MemoryAccessMonitor API可以做内存监控,不过是通过修改页的权限实现的,粒度比较大,实际情况下并不好用,并且难以对栈上的数据进行监控。

 

常规调试下watchpoint功能的受限及trace的低效是由于我们是使用软件方式在用户态进行操作,受到了CPU及操作系统的限制。而如果能在CPU层进行操作,便可以进行更加精细的控制。但我们无法修改CPU硬件,一些CPU层的相关功能(如Intel PT)功能上也比较受限并且难以使用。但还有另一个方法,便是使用软件仿真的CPU。

 

软件仿真的CPU最有名的莫过于QEMU了,通过它我们可以在一个CPU架构上仿真其他CPU架构的操作系统。但QEMU主要关注于仿真,对于安全分析来说并不友好。基于QEMU的PANDA框架给我们提供了更多的相关接口,可以方便对特定位置做hook、进行插桩分析等。PANDA曾经的1.x版本还集成了DroidScope可以对Android环境进行调试,不过在最新版中把这个功能去掉了。PANDA因为要用软仿真实现的CPU运行完整操作系统,本身运行非常慢,但在PANDA中进行插桩操作由于相当于是在CPU层做的,带来的额外开销并不高。不过PANDA本身是全系统级的,虽然可以方便地对内核层进行分析,不过对于我们日常对用户态程序分析来说还是太重了。

 

如果是进行一些简单的仿真,目前最常用的是Unicorn引擎。Unicorn是把QEMU的CPU功能剥离出来,给我们提供一个裸的CPU运行接口,并在特定的hook位置添加了回调接口。

 

不过如果想要使用Unicorn运行二进制程序的话还需要考虑程序的内存加载以及系统调用实现的问题,不能直接使用。而对于我们的需求,针对Android so的仿真调试来说,可以使用基于Unicorn的AndroidNativeEmuExAndroidNativeEmu、以及基于多个后端的unidbg等工具。这几款工具中,只有unidbg提供了GDB的调试接口,并且主要是为IDA进行连接设计的,在32位情况下无法使用GDB进行连接,也不支持watchpoint。另一方面,这几款工具一个共有的缺陷是JNI中的函数是手工模拟实现的,难免存在实现不完全或者实现与真实情况有出入的情况。

 

前面这些方法都不能很好地满足我对Android so进行仿真调试的需求,我希望能在X86环境下通过仿真方式启起来一个完整的ARM Android Runtime环境,然后提供GDB接口可以连接上去进行相关调试。结合这个需求,一个新的基于Unicorn的仿真框架映入我的眼帘,那就Qiling仿真框架。

使用Qiling仿真框架运行Android Runtime

Qiling是一个高级的二进制仿真框架,并且它师出名门,是由Unicorn作者主导,主要解决的就是前面所提到的Unicorn无法直接运行二进制程序的问题。Qiling框架的关键就在于它完成了常规应该由操作系统来完成的一些事,如二进制程序内存加载、syscall功能提供,并且得益于Unicorn本身支持多种CPU架构的特性,使得Qiling成为了一个跨平台、跨架构的相对完善的仿真框架。作为一个框架,Qiling提供了一些接口和相关组件,其中一个很让我激动的功能就是Qiling自带一个GDB的远程调试接口,可以使用本地GDB进行连接调试。

 

不过Qiling框架支不支持运行Android so呢?Qiling提供了一个样例库,包含了不同操作系统、不同CPU架构的测试文件。我很高兴地看到,其中有arm64_android这个文件夹,说明Qiling能运行ARM64架构的Android程序。不过我仔细研究才发现,这个样例与我的需求还有一些距离。原因在于这个程序只是在控制台打印了HelloWorld,并没有涉及到JNI相关操作。

 

既然这样的话,我们如果能给Qiling适配上对Android JNI的支持,就能满足前面的需求。按说我们已经有了AndroidNativeEmu这类同样基于Unicorn的Python项目,把其中的相关部分直接照搬进Qiling应该就可以了吧?

 

不过在我仔细研究后发现,Qiling框架与AndroidNativeEmu这类工具的底层原理是不同的。AndroidNativeEmu这类工具是模拟了Android下linker的功能,把Android so及相关依赖加载到内存里,并手工实现了JNI的相关接口。但为了做成一个更为通用的框架,Qiling模拟的是操作系统加载ld.so/linker之类动态链接器的过程,后续把程序及相关依赖加载进内存完全是由动态链接器来做的。这就导致我们想用Qiling加载程序的话必须通过动态链接器完整启动ELF程序,而不是像AndroidNativeEmu那样铺设好环境加载好so就直接可以去调用so中的指定函数了。Qiling的这种做法,以最小的成本保证了对各类各个版本的系统最大的适配性,并且也保证了程序运行状态与真实环境差异较小。

 

由于JNI函数的实现都是在JVM里面,所以我们首先要解决的就是如何写一个控制台的ELF程序可以把JVM加载起来。网上有篇Creating a Java VM from Android Native Code的文章就讲了如何直接加载起来JVM。不过这篇文章还是加载的libdvm.so里面的Dalvik VM,对于今天来说未免太老了。结合Stackoverflow上的一个提问及两个网页中的Github链接,最终形成了一个执行在Android命令行下的可以直接调起Android Runtime的程序,测试可以在Android 6.0下运行,并加载起来libart.so

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
#include <jni.h>
#include <dlfcn.h>
#include <stdio.h>
 
typedef int(*JNI_CreateJavaVM_t)(void *, void *, void *);
JNIEXPORT void InitializeSignalChain () {}
JNIEXPORT void ClaimSignalChain() {}
 
int init_jvm(JavaVM **m_jvm, JNIEnv **m_env)
{
    JavaVMOption opt[1];
    opt[0].optionString = "-Xnorelocate";
 
    JavaVMInitArgs args;
    args.version = JNI_VERSION_1_6;
    args.options = opt;
    args.nOptions = 1;
 
    void *libart_dso = dlopen("libart.so", RTLD_NOW);
    if (!libart_dso )
        return -1;
 
    JNI_CreateJavaVM_t JNI_CreateJavaVM;
    JNI_CreateJavaVM = (JNI_CreateJavaVM_t)dlsym(libart_dso, "JNI_CreateJavaVM");
    if (!JNI_CreateJavaVM)
        return -1;
 
    signed int result = JNI_CreateJavaVM(&(*m_jvm), &(*m_env), &args);
 
    if ( result != 0)
        return -1;
 
    return 0;
}
 
int main()
{
    JavaVM * vm = NULL;
    JNIEnv * env = NULL;
    int status = init_jvm(&vm, &env);
    if (status == 0) {
      printf("Initialization success (vm=%p, env=%p)\n", vm, env);
    } else {
      printf("Initialization failure (%i)\n", status);
      return -1;
    }
    jstring testy = (*env)->NewStringUTF(env, "Hello world!");
    const char *str = (*env)->GetStringUTFChars(env, testy, NULL);
    printf("JNI: %s\n", str);
    return 0;
}

可以通过Android NDK下的armv7a-linux-androideabi23-clangaarch64-linux-android23-clang对该文件进行编译:

1
2
3
/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang -Wl,--export-dynamic jniart.c -o arm_android_jniart
 
/opt/android-ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android23-clang -Wl,--export-dynamic jniart.c -o arm64_android_jniart

现在我们要做的就是让这个程序在Qiling下能运行起来。因为Qiling运行需要linker以及相关的库,所以我们需要把Android的rootfs复制过来,然后执行脚本:

1
2
3
from qiling import *
ql = Qiling(["android6.0/bin/arm64_android_jniart"], "android6.0")
ql.run()

不过事情怎么会这么简单,不出所料,果然失败了……

 

然后就是一番艰苦卓绝的分析调试过程,尝试运行,找到运行不下去的地方,然后对Qiling的相关部分进行分析修复,然后程序在这个地方能跑通了,又到了下一个运行不下去的地方,再分析再修复…… 这其中主要发现的问题是Qiling的syscall实现还不完善,以及Qiling本身、甚至是Qiling所依赖的Unicorn引擎中存在着一些bug。

 

其中的具体细节就不多说了,我把所做的修复都提交到了上游,在这里可以看到我在这个过程中所有提交的PR。

 

在这个过程中我发现/dev/ashmem/dev/pmsg0这两个设备文件不好模拟,所以修改了AOSP中的 USE_ASHMEMFAKE_LOG_DEVICE 两个宏,重新编译了不访问这两个文件的版本。

 

我们可以只编译仿真所需要的部分:

1
make framework linker linker_32 libart libart_32 libstdc++ libstdc++_32

编译生成的所有文件(rootfs):

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
├── bin
│   ├── linker
│   └── linker64
├── framework
│   ├── arm
│   │   ├── boot.art
│   │   └── boot.oat
│   ├── arm64
│   │   ├── boot.art
│   │   └── boot.oat
│   ├── framework.jar
│   └── framework-res.apk
├── lib
│   ├── libart.so
│   ├── libaudioutils.so
│   ├── libbacktrace.so
│   ├── libbase.so
│   ├── libbinder.so
│   ├── libcamera_client.so
│   ├── libcamera_metadata.so
│   ├── libcommon_time_client.so
│   ├── libcrypto.so
│   ├── libc++.so
│   ├── libc.so
│   ├── libcutils.so
│   ├── libdl.so
│   ├── libEGL.so
│   ├── libexpat.so
│   ├── libGLES_trace.so
│   ├── libGLESv2.so
│   ├── libGLESv3.so -> libGLESv2.so
│   ├── libgui.so
│   ├── libhardware.so
│   ├── libicui18n.so
│   ├── libicuuc.so
│   ├── libjavacore.so
│   ├── libjavacrypto.so
│   ├── libkeymaster1.so
│   ├── libkeymaster_messages.so
│   ├── libkeystore_binder.so
│   ├── libkeystore-engine.so
│   ├── liblog.so
│   ├── libmedia.so
│   ├── libm.so
│   ├── libnativebridge.so
│   ├── libnativehelper.so
│   ├── libnbaio.so
│   ├── libpowermanager.so
│   ├── libprotobuf-cpp-lite.so
│   ├── librtp_jni.so
│   ├── libsigchain.so
│   ├── libsoftkeymasterdevice.so
│   ├── libsonivox.so
│   ├── libspeexresampler.so
│   ├── libssl.so
│   ├── libstagefright_amrnb_common.so
│   ├── libstagefright_foundation.so
│   ├── libstdc++.so
│   ├── libsync.so
│   ├── libui.so
│   ├── libunwind.so
│   ├── libutils.so
│   └── libz.so
├── lib64
│   ├── libart.so
│   ├── libaudioutils.so
│   ├── libbacktrace.so
│   ├── libbase.so
│   ├── libbinder.so
│   ├── libcamera_client.so
│   ├── libcamera_metadata.so
│   ├── libcommon_time_client.so
│   ├── libcrypto.so
│   ├── libc++.so
│   ├── libc.so
│   ├── libcutils.so
│   ├── libdl.so
│   ├── libEGL.so
│   ├── libexpat.so
│   ├── libGLES_trace.so
│   ├── libGLESv2.so
│   ├── libGLESv3.so -> libGLESv2.so
│   ├── libgui.so
│   ├── libhardware.so
│   ├── libicui18n.so
│   ├── libicuuc.so
│   ├── libjavacore.so
│   ├── libjavacrypto.so
│   ├── libkeymaster1.so
│   ├── libkeymaster_messages.so
│   ├── libkeystore_binder.so
│   ├── libkeystore-engine.so
│   ├── liblog.so
│   ├── libmedia.so
│   ├── libm.so
│   ├── libnativebridge.so
│   ├── libnativehelper.so
│   ├── libnbaio.so
│   ├── libpowermanager.so
│   ├── libprotobuf-cpp-lite.so
│   ├── librtp_jni.so
│   ├── libsigchain.so
│   ├── libsoftkeymasterdevice.so
│   ├── libsonivox.so
│   ├── libspeexresampler.so
│   ├── libssl.so
│   ├── libstagefright_amrnb_common.so
│   ├── libstagefright_foundation.so
│   ├── libstdc++.so
│   ├── libsync.so
│   ├── libui.so
│   ├── libunwind.so
│   ├── libutils.so
│   └── libz.so
└── usr
    ├── icu
    │   └── icudt55l.dat
    └── share
        └── zoneinfo
            └── tzdata

运行脚本也需要进行一些相应的修改,最终版本脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3
 
from qiling import *
from qiling.os.mapper import QlFsMappedObject
from collections import defaultdict
 
class Fake_maps(QlFsMappedObject):
    def __init__(self, ql):
        self.ql = ql
    def read(self, size):
        stack = next(filter(lambda x : x[3]=='[stack]', self.ql.mem.map_info))
        return ('%x-%x %s\n' % (stack[0], stack[1], stack[3])).encode()
    def fstat(self):
        return defaultdict(int)
    def close(self):
        return 0
 
if __name__ == "__main__":
    rootfs = "android6.0"
    test_binary = "android6.0/bin/arm64_android_jniart"
    env = {"ANDROID_DATA":"/data", "ANDROID_ROOT":"/system"}
    ql = Qiling([test_binary], rootfs, env, multithread = True)
    ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql))
    ql.run()

由于一个关键的PR还没有被合并到上游,大家可以尝试在我fork的仓库中进行尝试。下载下来之后只需要运行python android.py便可看到创建完整ART环境的全过程(记得通过pip install -e .安装相关依赖到最新版本)。我们可以看到运行的过程中共启动了6个线程,进行了上千次syscall调用,最终创建完成JavaVMJNIEnv并调用JNI函数进行相关操作。

 

我们还可以打印出整个启动过程中的内存加载情况:

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
Start            End              Perm    Label
00000012c00000 - 00000012c01000   rw-     [syscall_mmap]
00000012c01000 - 00000012e01000   rw-     [syscall_mmap]
00000012e01000 - 00000022c00000   ---     [syscall_mmap]
00000022c00000 - 00000022c01000   rw-     [syscall_mmap]
00000022c01000 - 00000032c00000   ---     [syscall_mmap]
00000032c00000 - 00000042c00000   rw-     [syscall_mmap]
00000070000000 - 00000070cf2000   rw-     [mmap] boot.art
00000070cf2000 - 00000072bcb000   r--     [mmap] boot.oat
00000072bcb000 - 00000074b95000   r-x     [mmap] boot.oat
00000074b95000 - 00000074b96000   rw-     [mmap] boot.oat
00000074b96000 - 00000074b97000   rw-     [syscall_mmap]
00000074b97000 - 00000078b96000   ---     [syscall_mmap]
00555555554000 - 00555555555000   r--     arm64_android_jniart
00555555555000 - 00555555556000   r-x     arm64_android_jniart
00555555556000 - 00555555557000   r--     arm64_android_jniart
00555555557000 - 00555555558000   rw-     arm64_android_jniart
00555555558000 - 0055555555a000   rwx     [hook_mem]
007fffb7dd6000 - 007fffb7dd7000   ---     [syscall_mmap]
007fffb7dd7000 - 007fffb7ddb000   rw-     [syscall_mmap]
007fffb7ddb000 - 007fffb7ddc000   r--     [syscall_mmap]
007fffb7ddc000 - 007fffb7ddd000   r--     [syscall_mmap]
007fffb7ddd000 - 007fffb7dde000   rw-     [syscall_mmap]
007fffb7dde000 - 007fffb7ddf000   rw-     [syscall_mmap]
007fffb7ddf000 - 007fffb7de0000   rw-     [syscall_mmap]
007fffb7de0000 - 007fffb7de1000   r--     [syscall_mmap]
007fffb7de6000 - 007fffb7ea9000   r-x     [mmap] libc.so
007fffb7ea9000 - 007fffb7eb8000   ---     [syscall_mmap]
007fffb7eb8000 - 007fffb7ebe000   r--     [mmap] libc.so
007fffb7ebe000 - 007fffb7ec1000   rw-     [mmap] libc.so
007fffb7ec1000 - 007fffb7ecf000   rw-     [syscall_mmap]
007fffb7ed0000 - 007fffb7ed1000   rw-     [syscall_mmap]
007fffb7f40000 - 007fffb7f80000   rw-     [syscall_mmap]
007fffb7f91000 - 007fffb7f92000   r--     [syscall_mmap]
007fffb8000000 - 007fffb8040000   rw-     [syscall_mmap]
007fffb8140000 - 007fffb8200000   rw-     [syscall_mmap]
007fffb8300000 - 007fffb83c0000   rw-     [syscall_mmap]
007fffb83d4000 - 007fffb89b5000   r-x     [mmap] libart.so
007fffb89b5000 - 007fffb89c5000   ---     [syscall_mmap]
007fffb89c5000 - 007fffb89d8000   r--     [mmap] libart.so
007fffb89d8000 - 007fffb89da000   rw-     [mmap] libart.so
007fffb89da000 - 007fffb89dd000   rw-     [syscall_mmap]
007fffb89e2000 - 007fffb89ea000   r-x     [mmap] libnativehelper.so
007fffb89ea000 - 007fffb89f9000   ---     [syscall_mmap]
007fffb89f9000 - 007fffb89fa000   r--     [mmap] libnativehelper.so
007fffb89fa000 - 007fffb89fb000   rw-     [mmap] libnativehelper.so
007fffb8a00000 - 007fffb8a03000   r-x     [mmap] libnativebridge.so
007fffb8a03000 - 007fffb8a12000   ---     [syscall_mmap]
007fffb8a12000 - 007fffb8a13000   r--     [mmap] libnativebridge.so
007fffb8a13000 - 007fffb8a14000   rw-     [mmap] libnativebridge.so
007fffb8a19000 - 007fffb8a1a000   r-x     [mmap] libsigchain.so
007fffb8a1a000 - 007fffb8a29000   ---     [syscall_mmap]
007fffb8a29000 - 007fffb8a2a000   r--     [mmap] libsigchain.so
007fffb8a2a000 - 007fffb8a2b000   rw-     [mmap] libsigchain.so
007fffb8a30000 - 007fffb8a3b000   r-x     [mmap] libbacktrace.so
007fffb8a3b000 - 007fffb8a4a000   ---     [syscall_mmap]
007fffb8a4a000 - 007fffb8a4c000   r--     [mmap] libbacktrace.so
007fffb8a4c000 - 007fffb8a4d000   rw-     [mmap] libbacktrace.so
007fffb8a52000 - 007fffb8a75000   r-x     [mmap] libutils.so
007fffb8a75000 - 007fffb8a84000   ---     [syscall_mmap]
007fffb8a84000 - 007fffb8a86000   r--     [mmap] libutils.so
007fffb8a86000 - 007fffb8a87000   rw-     [mmap] libutils.so
007fffb8a8c000 - 007fffb8a9d000   r-x     [mmap] libcutils.so
007fffb8a9d000 - 007fffb8aad000   ---     [syscall_mmap]
007fffb8aad000 - 007fffb8aae000   r--     [mmap] libcutils.so
007fffb8aae000 - 007fffb8aaf000   rw-     [mmap] libcutils.so
007fffb8aaf000 - 007fffb8ab0000   r--     [syscall_mmap]
007fffb8ab5000 - 007fffb8b8c000   r-x     [mmap] libc++.so
007fffb8b8c000 - 007fffb8b9b000   ---     [syscall_mmap]
007fffb8b9b000 - 007fffb8ba2000   r--     [mmap] libc++.so
007fffb8ba2000 - 007fffb8ba3000   rw-     [mmap] libc++.so
007fffb8ba3000 - 007fffb8ba6000   rw-     [syscall_mmap]
007fffb8bab000 - 007fffb8be3000   r-x     [mmap] libm.so
007fffb8be3000 - 007fffb8bf3000   ---     [syscall_mmap]
007fffb8bf3000 - 007fffb8bf4000   r--     [mmap] libm.so
007fffb8bf4000 - 007fffb8bf5000   rw-     [mmap] libm.so
007fffb8bfa000 - 007fffb8bff000   r-x     [mmap] liblog.so
007fffb8bff000 - 007fffb8c0e000   ---     [syscall_mmap]
007fffb8c0e000 - 007fffb8c0f000   r--     [mmap] liblog.so
007fffb8c0f000 - 007fffb8c10000   rw-     [mmap] liblog.so
007fffb8c15000 - 007fffb8c1e000   r-x     [mmap] libbase.so
007fffb8c1e000 - 007fffb8c2d000   ---     [syscall_mmap]
007fffb8c2d000 - 007fffb8c2e000   r--     [mmap] libbase.so
007fffb8c2e000 - 007fffb8c2f000   rw-     [mmap] libbase.so
007fffb8c34000 - 007fffb8c4f000   r-x     [mmap] libunwind.so
007fffb8c4f000 - 007fffb8c5e000   ---     [syscall_mmap]
007fffb8c5e000 - 007fffb8c5f000   r--     [mmap] libunwind.so
007fffb8c5f000 - 007fffb8c60000   rw-     [mmap] libunwind.so
007fffb8c60000 - 007fffb8cc9000   rw-     [syscall_mmap]
007fffb8cca000 - 007fffb8ccb000   rw-     [syscall_mmap]
007fffb8d40000 - 007fffb8d80000   rw-     [syscall_mmap]
007fffb8e00000 - 007fffb8e40000   rw-     [syscall_mmap]
007fffb8e54000 - 007fffb8e7a000   r--     [mmap] boot.art
007fffb8e7f000 - 007fffb8f7f000   rw-     [syscall_mmap]
007fffb8f7f000 - 007fffb907f000   rw-     [syscall_mmap]
007fffb907f000 - 007fffb908f000   rw-     [syscall_mmap]
007fffb908f000 - 007fffb948f000   rw-     [syscall_mmap]
007fffb948f000 - 007fffb988f000   rw-     [syscall_mmap]
007fffb988f000 - 007fffb989f000   rw-     [syscall_mmap]
007fffb989f000 - 007fffb9c9f000   rw-     [syscall_mmap]
007fffb9c9f000 - 007fffba09f000   rw-     [syscall_mmap]
007fffba09f000 - 007fffba0bf000   rw-     [syscall_mmap]
007fffba0bf000 - 007fffba0df000   rw-     [syscall_mmap]
007fffba0df000 - 007fffba15f000   rw-     [syscall_mmap]
007fffba15f000 - 007fffbae1f000   rw-     [syscall_mmap]
007fffbae1f000 - 007fffbae5f000   rw-     [syscall_mmap]
007fffbae5f000 - 007fffbb660000   rw-     [syscall_mmap]
007fffbb660000 - 007fffbbe61000   rw-     [syscall_mmap]
007fffbbe61000 - 007fffbbe63000   rw-     [syscall_mmap]
007fffbbe63000 - 007fffbbe65000   rw-     [syscall_mmap]
007fffbbe65000 - 007fffbbe67000   rw-     [syscall_mmap]
007fffbbe67000 - 007fffbbf2f000   rw-     [syscall_mmap]
007fffbbf2f000 - 007fffbbff7000   rw-     [syscall_mmap]
007fffbbff7000 - 007fffbbff9000   rw-     [syscall_mmap]
007fffbc040000 - 007fffbc080000   rw-     [syscall_mmap]
007fffbc0bd000 - 007fffbc10e000   r-x     [mmap] libjavacore.so
007fffbc10e000 - 007fffbc11e000   ---     [syscall_mmap]
007fffbc11e000 - 007fffbc120000   r--     [mmap] libjavacore.so
007fffbc120000 - 007fffbc123000   rw-     [mmap] libjavacore.so
007fffbc123000 - 007fffbc124000   rw-     [syscall_mmap]
007fffbc129000 - 007fffbc22b000   r-x     [mmap] libcrypto.so
007fffbc22b000 - 007fffbc23a000   ---     [syscall_mmap]
007fffbc23a000 - 007fffbc24e000   r--     [mmap] libcrypto.so
007fffbc24e000 - 007fffbc24f000   rw-     [mmap] libcrypto.so
007fffbc24f000 - 007fffbc250000   r--     [syscall_mmap]
007fffbc255000 - 007fffbc276000   r-x     [mmap] libexpat.so
007fffbc276000 - 007fffbc286000   ---     [syscall_mmap]
007fffbc286000 - 007fffbc288000   r--     [mmap] libexpat.so
007fffbc288000 - 007fffbc289000   rw-     [mmap] libexpat.so
007fffbc28e000 - 007fffbc411000   r-x     [mmap] libicuuc.so
007fffbc411000 - 007fffbc421000   ---     [syscall_mmap]
007fffbc421000 - 007fffbc433000   r--     [mmap] libicuuc.so
007fffbc433000 - 007fffbc434000   rw-     [mmap] libicuuc.so
007fffbc434000 - 007fffbc438000   rw-     [syscall_mmap]
007fffbc43d000 - 007fffbc659000   r-x     [mmap] libicui18n.so
007fffbc659000 - 007fffbc669000   ---     [syscall_mmap]
007fffbc669000 - 007fffbc67d000   r--     [mmap] libicui18n.so
007fffbc67d000 - 007fffbc67e000   rw-     [mmap] libicui18n.so
007fffbc683000 - 007fffbc69f000   r-x     [mmap] libz.so
007fffbc69f000 - 007fffbc6ae000   ---     [syscall_mmap]
007fffbc6ae000 - 007fffbc6af000   r--     [mmap] libz.so
007fffbc6af000 - 007fffbc6b0000   rw-     [mmap] libz.so
007fffbc6b5000 - 007fffbc6b8000   r-x     [mmap] libstdc++.so
007fffbc6b8000 - 007fffbc6c7000   ---     [syscall_mmap]
007fffbc6c7000 - 007fffbc6c8000   r--     [mmap] libstdc++.so
007fffbc6c8000 - 007fffbc6c9000   rw-     [mmap] libstdc++.so
007fffbc6ce000 - 007fffbc6ee000   rw-     [syscall_mmap]
007fffbc6ee000 - 007fffbdcee000   r--     [mmap] icudt55l.dat
007fffbdcee000 - 007fffbdcef000   ---     [syscall_mmap]
007fffbdcef000 - 007fffbdcf0000   ---     [syscall_mmap]
007fffbdcf0000 - 007fffbdded000   rw-     [syscall_mmap]
007fffbdded000 - 007fffbddee000   ---     [syscall_mmap]
007fffbddee000 - 007fffbddf2000   rw-     [syscall_mmap]
007fffbde40000 - 007fffbde80000   rw-     [syscall_mmap]
007fffbdeb1000 - 007fffbdeb3000   rw-     [syscall_mmap]
007fffbdeb3000 - 007fffbdeb5000   rw-     [syscall_mmap]
007fffbdeb5000 - 007fffbdeb6000   ---     [syscall_mmap]
007fffbdeb6000 - 007fffbdeb7000   ---     [syscall_mmap]
007fffbdeb7000 - 007fffbdfba000   rw-     [syscall_mmap]
007fffbdfba000 - 007fffbdfbc000   rw-     [syscall_mmap]
007fffbdfbc000 - 007fffbdfbd000   ---     [syscall_mmap]
007fffbdfbd000 - 007fffbdfbe000   ---     [syscall_mmap]
007fffbdfbe000 - 007fffbe0c1000   rw-     [syscall_mmap]
007fffbe0c1000 - 007fffbe0c3000   rw-     [syscall_mmap]
007fffbe0c3000 - 007fffbe0c4000   ---     [syscall_mmap]
007fffbe0c4000 - 007fffbe0c5000   ---     [syscall_mmap]
007fffbe0c5000 - 007fffbe1c8000   rw-     [syscall_mmap]
007fffbe1c8000 - 007fffbe1ca000   rw-     [syscall_mmap]
007fffbe1ca000 - 007fffbe1cb000   ---     [syscall_mmap]
007fffbe1cb000 - 007fffbe1cc000   ---     [syscall_mmap]
007fffbe1cc000 - 007fffbe2cf000   rw-     [syscall_mmap]
007fffbe2cf000 - 007fffbe2d0000   ---     [syscall_mmap]
007fffbe2d0000 - 007fffbe2d4000   rw-     [syscall_mmap]
007fffbe2d4000 - 007fffbe2d5000   ---     [syscall_mmap]
007fffbe2d5000 - 007fffbe2d9000   rw-     [syscall_mmap]
007fffbe2d9000 - 007fffbe2da000   ---     [syscall_mmap]
007fffbe2da000 - 007fffbe2de000   rw-     [syscall_mmap]
007fffbe2de000 - 007fffbe2df000   ---     [syscall_mmap]
007fffbe2df000 - 007fffbe2e3000   rw-     [syscall_mmap]
007ffff7dd5000 - 007ffff7e0d000   r-x     linker64
007ffff7e1d000 - 007ffff7e1e000   r--     linker64
007ffff7e1e000 - 007ffff7e24000   rw-     linker64
007ffffffde000 - 007ffffffdf000   ---     [stack]
007ffffffdf000 - 0080000000e000   rwx     [stack]

完成这些之后,如果想要运行一个Android so,我们可以通过dlopen动态加载起来,通过dlsym获取要运行的函数地址,然后将JNIEnv作为第一个参数传进去运行。如果函数还需要其他什么参数的话,可以通过JNI进行创建。我们这里提供的是一个完整的Android Runtime环境,基本不用担心JNI实现不完全的问题(当然有可能有些JNI函数调用的syscall实现还不完全)。

 

总结:我这里通过Qiling模拟了启动Android Runtime的全过程,展示了Qiling对复杂系统的仿真能力,也是对Qiling各项功能的充分验证。这里我选择尝试模拟执行Android 6.0环境,一方面是因为手头有设备方便对照测试,一方面这是支持ART的比较早期的Android版本,相对来说代码量比较小运行过程比较简单,后面如果有谁有兴趣的话可以尝试对更高版本的Android环境进行仿真模拟。目前运行Android so的方法还有些笨拙,后面如果有谁有兴趣的话可以试着像Frida一样在此基础上添加Java接口,更加方便地进行相关调用。

对基于Unicorn的Qiling的新的调试方法

前面我们使用Qiling仿真框架运行了一个完整的Android Runtime环境,我们可以利用Qiling的一些功能对整个过程进行细致的分析,从而更加深入地理解Android Runtime的启动过程。我们也可以对想要执行的Android so进行相关的分析。

 

如前面所说,选用Qiling框架一个比较重要的原因是它自带一个GDB的远程调试接口,那是不是就可以通过这个对Android Runtime进行调试呢?

 

然后我发现,事情并没有想象的那么美好…… 原因是,Qiling的GDB调试功能只能在单线程模式下使用,而运行Android Runtime需要多线程的支持。

 

那是不是可以对Qiling做一些修改,使得GDB调试功能在多线程下也能使用?在我阅读了Qiling相关代码后,发现这一点并不好实现,因为目前的GDB调试功能与整个执行过程耦合度太高了,难以迁移至多线程模式。

 

这就有点尴尬了,本来做这些工作的一个很大目的就是能在跑起来之后对Android so进行调试的,费了这么大力气跑起来了,却没办法进行调试。

 

又想到目前基于Unicorn的项目如果想支持GDB远程调试都要自己实现一套相关逻辑,那有没有办法给Unicorn提供一个通用的GDB调试功能呢?

 

说干就干!于是我就基于Unicorn自身提供的功能,实现了一套通用的GDB调试接口。不仅支持查看寄存器、查看内存、单步调试等基本操作,还支持了很多调试端都不支持的watchpoint功能,使得对数据的监控更加方便。

 

为了使得做出的项目更加通用,同时又能让实现比较优雅,代码通过Rust语言进行编写,编译成与C兼容的so文件,同时还提供了对Go、Java、Python等语言的接口。

 

这个项目的耦合度很低,只需要一个Code Hook便可以集成到已有的项目中。并且支持在正在运行的Unicorn实例的hook中进行调用,可以随时随地把调试接口启起来。

 

项目的地址在这里:https://github.com/bet4it/udbserver

 

在安装好so库和python bindings后,只需要在ql.run()的前面加上两句

1
2
from udbserver import udbserver
udbserver(ql.uc, 1234, 0x5555555558D4)

便可以在运行程序main函数的时候开启一个gdbserver,可以使用GDB远程挂上去进行调试。

 

注意因为这个项目是基于Unicorn实现的,所以并不支持Qiling自己实现的多线程功能。不过也是可以用的,开启udbserver的是哪个线程就对哪个线程进行控制。

 

我们同样可以在其他项目中使用udbserver,比如对于ExAndroidNativeEmu中的example_jni.py,只需要加上两句:

1
2
from udbserver import udbserver
udbserver(emulator.mu, 1234, 0xcbbd2dec)

便可以开启GDB远程调试功能。

 

还可以搭配使用我之前写的hyperpwn,获得最佳的调试体验。

 

 

怎么样,是不是很方便?觉得我所做的工作对你有帮助的话记得在Github上点一个


【看雪培训】目录重大更新!《安卓高级研修班》2022年春季班开始招生!

收藏
点赞14
打赏
分享
最新回复 (24)
雪    币: 8209
活跃值: 活跃值 (3387)
能力值: ( LV8,RANK:134 )
在线值:
发帖
回帖
粉丝
hanbingle 活跃值 2 2022-5-4 10:35
2
2
师弟棒棒的,给你一个大大的赞字,针对libart进行加载执行完全摈弃了前辈们通过unicorn逐个模拟jni接口的实现方式,甚至后面在此基础上可以实现对dex的加载,class以及ArtMethod的加载初始化甚至是函数的解释执行;udbserver也是非常的实用,配合hyperpwn堪称完美,使用上也是极其简单,一行代码就可以嵌入AndroidEmu以及Unidbg了
雪    币: 1013
活跃值: 活跃值 (222)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
Bet4 活跃值 1 2022-5-4 11:03
3
0

多谢师兄捧场!后续可以看看能不能整合进FART的相关功能,在仿真环境下进行脱壳工作。

最后于 2022-5-4 11:07 被Bet4编辑 ,原因:
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-4 11:26
4
0
点赞关注三连。大佬666
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
愿看人间繁华 活跃值 2022-5-4 11:39
5
0
我竟然看明白了,佬带带弟弟吧
雪    币: 190
活跃值: 活跃值 (76)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wsgaoshou 活跃值 2022-5-4 12:06
6
0
专业互捧
雪    币: 218
活跃值: 活跃值 (528)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
乐不思蜀1 活跃值 2022-5-4 21:13
7
0
已点赞starred
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-5 18:38
8
0
认真看了一遍。如果要运行一个android so的能不能写一个简单example。不是很明白应该怎么写,是用qiling里面提供的方法加载so么?
雪    币: 1013
活跃值: 活跃值 (222)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
Bet4 活跃值 1 2022-5-5 22:59
9
0
misskings 认真看了一遍。如果要运行一个android so的能不能写一个简单example。不是很明白应该怎么写,是用qiling里面提供的方法加载so么?
dlopen加载你想运行的so,dlsym获取想运行函数的地址,然后带着参数调用想运行的函数即可
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-6 09:30
10
0
Bet4 dlopen加载你想运行的so,dlsym获取想运行函数的地址,然后带着参数调用想运行的函数即可
那这个dlopen是在arm64_android_jniart这个源码里面初始化jvm之后调用么?
还是在python代码里面的ql.run()之前调用dlopen?
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-6 11:06
11
0

关于qiling的模拟运行android测试我碰到一个报错,想请问下这是我用法不对么?


def my_sandbox(path,rootfs):

    env = {"ANDROID_DATA": "/data", "ANDROID_ROOT": "/system"}

    ql = Qiling(path, rootfs, env, multithread=True)

    ql.add_fs_mapper("/proc/self/task/2000/maps", Fake_maps(ql))

    ql.run()


if __name__ == '__main__':

    my_sandbox(["/Users/king/CLionProjects/qdemo/a.out"],"/Users/king/git_src/bet4it_qiling/android6.0")


这个a.out是用ndk编译的,代码就是helloworld打印例子。


运行的错误如下


  File "/Users/king/Library/Python/3.8/lib/python/site-packages/qiling/utils.py", line 159, in wrapper

    return func(*args, **kw)

  File "/Users/king/Library/Python/3.8/lib/python/site-packages/qiling/core_hooks.py", line 59, in _hook_intr_cb

    raise QlErrorCoreHook("_hook_intr_cb : not handled")

qiling.exception.QlErrorCoreHook: _hook_intr_cb : not handled


雪    币: 25
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
万里星河 活跃值 2022-5-6 11:29
12
0
Bet4 dlopen加载你想运行的so,dlsym获取想运行函数的地址,然后带着参数调用想运行的函数即可
如果so函数和java层有交互怎么办 感觉unidbg更好一些呀 而且unidbg也支持so的调试以及hook 只是补环境麻烦一些
雪    币: 1013
活跃值: 活跃值 (222)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
Bet4 活跃值 1 2022-5-6 18:07
13
0
misskings 关于qiling的模拟运行android测试我碰到一个报错,想请问下这是我用法不对么?def my_sandbox(path,rootfs):&nbsp; &nbsp; env = { ...
在arm64_android_jniart这个源码里面初始化jvm之后调用

你直接把我的仓库clone下来啥都不改python android6.0.py能跑吗
雪    币: 1013
活跃值: 活跃值 (222)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
Bet4 活跃值 1 2022-5-6 18:08
14
0
万里星河 如果so函数和java层有交互怎么办 感觉unidbg更好一些呀 而且unidbg也支持so的调试以及hook 只是补环境麻烦一些
现在来说确实还不够方便,不过这是做一些前置的比较核心的工作,后面看谁有兴趣可以把这个做的更完善
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-7 08:41
15
0
Bet4 在arm64_android_jniart这个源码里面初始化jvm之后调用 你直接把我的仓库clone下来啥都不改python android6.0.py能跑吗
你的项目拉下来能跑通那个android6.0.py的。不过udbserver有点问题,我是提issues的那个。
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-8 19:42
16
0
udbserver在ubuntu测试没问题,能确定是m1有些什么小细节的问题。另外dlopen的时候那个so放哪里啊?我测试了一下填绝对路径都不行。

int main()
{
    JavaVM * vm = NULL;
    JNIEnv * env = NULL;
    int status = init_jvm(&vm, &env);
    if (status == 0) {
        printf("Initialization success (vm=%p, env=%p)\n", vm, env);
    } else {
        printf("Initialization failure (%i)\n", status);
        return -1;
    }
    void* handler=dlopen("/home/king/CLionProjects/qdemo/example.so",RTLD_NOW);
    if(!handler){
        printf("dlopen error: %s\n", dlerror());
    }
    jstring testy = (*env)->NewStringUTF(env, "Hello world!");
    const char *str = (*env)->GetStringUTFChars(env, testy, NULL);
    printf("JNI: %s\n", str);
    return 0;
}


报错如下

Initialization success (vm=0x7fffb8d42140, env=0x7fffb8e1b000)
dlopen error: dlopen failed: library "/home/king/CLionProjects/qdemo/example.so" not found
JNI: Hello world!

能确定example.so这个文件是存在的。不知道里面是还有什么未知的坑么
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-8 21:24
17
0
解决了。加载成功,路径直接设置"example.so",然后文件拷贝到android6.0/system/lib64目录下即可
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-8 22:10
18
0
加载so调用函数成功,不过如果是没有符号的函数,应该怎么调用呢?
下面0xEFF8是我用ida看到的add函数偏移地址,demoPtr2这个如果调用就会崩溃,地址也不相同。是不是不能这样干?
        sdemo demoPtr= dlsym(handler,"_Z3addii");
        sdemo demoPtr2= handler+0xEFF8;
        printf("handler : %p ; d1 : %p ; d2 : %p\n",handler,demoPtr,demoPtr2);
        int res=demoPtr(12,15);
        printf("call add res:%d\n",res);
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-8 22:16
19
0
我检查好像是要默认再加个0x2098458的偏移,貌似这个偏移值是固定的。
雪    币: 1013
活跃值: 活跃值 (222)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
Bet4 活跃值 1 2022-5-8 23:12
20
0
`demoPtr`能调用`demoPtr2`不能调用?你用udbserver跟进去调一下嘛,是不是调用约定什么的问题
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-9 10:06
21
0
Bet4 `demoPtr`能调用`demoPtr2`不能调用?你用udbserver跟进去调一下嘛,是不是调用约定什么的问题
已经解决了,下面那样就可以调用了,有一个固定的偏移量。测试改成下面那样就可以调用了。非常感谢,再提个问题啊。我mac m1下无法使用gdb,有办法改成支持lldb调试,或者ida调试的吗?
sdemo demoPtr2= handler+0xEFF8+ 0x2098458;
雪    币: 1013
活跃值: 活跃值 (222)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
Bet4 活跃值 1 2022-5-9 18:04
22
0
misskings 已经解决了,下面那样就可以调用了,有一个固定的偏移量。测试改成下面那样就可以调用了。非常感谢,再提个问题啊。我mac m1下无法使用gdb,有办法改成支持lldb调试,或者ida调试的吗? sdem ...
虽然提供的是GDB的调试接口,不过按道理lldb用`gdb-remote localhost:1234`和IDA用Remote GDB Server应该都能连。

还有你不是在m1上python bindings跑不起来吗,解决了吗
雪    币: 9680
活跃值: 活跃值 (6963)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
misskings 活跃值 4 2022-5-9 18:19
23
0
Bet4 虽然提供的是GDB的调试接口,不过按道理lldb用`gdb-remote localhost:1234`和IDA用Remote GDB Server应该都能连。 还有你不是在m1上python ...
m1的木有解决,这不是等你修复么。需要配合测试可以喊我。或者加下我wx,方便测试这个bug
雪    币: 33
活跃值: 活跃值 (686)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小爱逆天 活跃值 2022-5-12 17:07
24
0
我嫉妒了,为什么怎么厉害!
雪    币: 213
活跃值: 活跃值 (62)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jasonzhou 活跃值 2022-5-13 17:08
25
0
冒个泡,涉及到的知识面、钻研的耐心、分析问题的逻辑都很不错,赞!
游客
登录 | 注册 方可回帖
返回