Android移动安全相关工具

没想到吧,我又润来学安卓了。。。实习就是这样的啦,多点尝试。

1. MobSF

MobSF是Mobile Security Framework的缩写,是一个移动端应用安全问题检测的框架和工具,它适用于Android/iOS/Windows,能够执行动态和静态的恶意软件的分析和检测,无论是二进制方式还是压缩的源代码都可以进行检测。静态分析适用于安卓、苹果应用程序,而动态分析暂时只支持安卓应用程序。

详细安装教程可看官方文档:https://mobsf.github.io/docs/#/zh-cn/

注:Python版本按照上面官方文档下载,安装Python 3.10会出错。

将APK文件(比如:迅雷安卓移动端)上传到MobSF,并等待它自动分析。分析完成后MobSF后界面如下:

1.1 基本信息

在基本信息那一栏,可以看到该APK的安全得分、文件信息和App信息等。App信息包括了包名和Main Activity等。基本信息中还会给出Activities、Services、Receivers、Providers这四大组件的数目,以及可导出组件的数目。(可导出组件是较为严重的安全漏洞,因此这里单独列出了可导出组件的数目)

1.2 扫描信息和反编译代码

可以对APK进行重扫,也可以开始动态分析。动态分析就需要Genymotion模拟器或者真机辅助。点击动态分析,MobSF会将该APK下载至安卓设备中,但目前的APK和真机大多是ARM架构的,所以下载至真机应该没多大问题。但Genymotion是x86架构的,下载ARM架构的APK会导致下载失败。

1
2
3
4
[ERROR] 26/Jul/2022 15:19:56 - This APK cannot be installed. Is this APK compatible the Android VM/Emulator?
adb install failed
[ERROR] 26/Jul/2022 15:19:56 - Internal Server Error: /android_dynamic/d165c0577f92a5ea85d964d853c6e15d
ERROR:django.request:Internal Server Error: /android_dynamic/d165c0577f92a5ea85d964d853c6e15d

解决方法是下载安装转换工具Genymotion_ARM_Translation,目前只更新到Android 9.0,所以使用的模拟器安卓版本不能太高。据自己的模拟器系统版本下载对应的ZIP包,然后将包直接拖入到模拟器安装,安装完成后重启模拟器即可。

在MobSF中连接安卓设备,重新选择动态分析,此时成功下载迅雷,进入动态调试界面。

同时在Dynamic Analyzer那一栏可以看到APK在模拟器中的安装路径。

呃动态分析好像也是MobSF自动分析的。动态分析主要功能如下:

功能菜单 说明
Show/Stop Screen 开启/关闭屏幕
Install/Remove MobSF RootCA 安装/卸载MobSF证书
Set/Unset HTTP(s) Proxy 设置/取消HTTP(s)代理
TLS/SSL Security Tester TLS/SSL安全测试
Exported Activity Tester 测试导出类型的Activity
Activity Tester 测试Activity
Get Dependencies 获取依赖项
Take a Screenshot 截屏
Logcat Stream 日志流信息
Generate Report 生成动态分析报告

如果APK比较大,在Activity Tester测试时等待时间会比较漫长。

在反编译代码那一个框里可以查看并下载App的Java代码,或者查看并下载Smali代码,再或者查看Manifest文件。另外,在这部分中也可以动态分析。

1.3 签名者证书

这部分主要说明了对该APK签名的签名者信息。

1.4 权限信息

在权限信息中,罗列了被检测App在Manifest文件中申请的所有权限,并标出了每个权限的危险指数,对于有安全隐患的权限标记为危险。在每个权限后面都加上了该权限的作用简介,并对其功能及安全风险进行了描述。

以android.permission.ACCESS_COARSE_LOCATION为例:

MobSF检测到App请求了这一权限,这项权限用于获取设备的粗略位置信息。MobSF将其标记为dangerous,即认为这项权限是有安全风险的。在描述中介绍了这一权限的功能,可以通过基站定位等方式获取用户位置,恶意程序可以通过该权限来获取用户的大致位置。

1.5 安卓API

上传为安卓应用时展示,列举了被检测App调用的所有安卓API,并给出了调用API的代码的位置,这一功能在代码研究分析时比较实用,但在安全检测分析中实际作用并不大。

1.6 Browsable Activities

browsable的意思就是浏览器在特定条件下可以打开用户的activity。(这个不太懂)

1.7 安全分析

安全分析是MobSF的最重要部分,分为六个分析点,分别是网络安全、Manifest分析、源码分析、二进制分析、NIAP分析和文件分析。

2. Unicorn

Unicorn基于qemu开发,是一个轻量级、多平台、多架构的CPU模拟器框架,可以跨平台执行Arm, Arm64 (Armv8), M68K, Mips, Sparc, & X86 (include X86_64)等指令集的原生程序,让我们更好地关注CPU操作,忽略机器设备的差异。

Unicorn安装:

1
pip install unicorn

2.1 Unicorn的编写

对于利用Unicorn编写的代码思路一般为:设置好参数->加载模拟的代码->添加hook->run。

2.1.1 设置参数并加载模拟执行的代码

头文件:

1
2
from unicorn import *#调用unicorn库
from unicorn.x86_const import *#我们构造的是x86寄存器,所以还需要使用一些x86寄存器的常量,所以还要调用这个库,同理如果是x64的话就改成unicorn.x64_const

Uc类初始化:

1
2
3
#mu = Uc(arch, mode)
#这个Uc类接受两个参数分别是硬件架构(arch)和硬件模式(mode),在这个样例中我们选用的是X86体系结构和32位代码
mu = Uc(UC_ARCH_X86, UC_MODE_32)

arch和mode相关常量有:

1
2
3
arch:UC_ARCH_ARM、UC_ARCH_ARM64、UC_ARCH_M68K、UC_ARCH_MAX、UC_ARCH_MIPS、UC_ARCH_PPC、UC_ARCH_SPARC、UC_ARCH_X86

mode:UC_MODE_16、UC_MODE_32、UC_MODE_64、UC_MODE_ARM、UC_MODE_BIG_ENDIAN、UC_MODE_LITTLE_ENDIAN、UC_MODE_MCLASS、UC_MODE_MICRO、UC_MODE_MIPS3、UC_MODE_MIPS32、UC_MODE_MIPS32R6、UC_MODE_MIPS64、UC_MODE_PPC32、UC_MODE_PPC64、UC_MODE_QPX、UC_MODE_SPARC32、UC_MODE_SPARC64、UC_MODE_THUMB、UC_MODE_V8、UC_MODE_V9

定义虚拟地址:

1
ADDRESS = 0x1000000#注意一定要与0x1000对齐

映射代码内存,所有CPU操作都只能访问此内存,默认权限为rwx。mem_map()函数要求 address 和 size 参数都与0x1000对齐,否则会报UC_ERR_ARG异常。

1
mu.mem_map(ADDRESS, 2*1024*1024)#接受两个参数,分别是地址和大小,要注意地址与大小一定都要是0x1000对齐

把要模拟的代码加载到我们刚刚映射的内存上,mem_write()函数的第二个参数只支持python的byte数组。

1
2
3
4
5
6
7
#这个有两种实现方式第一种是直接把调试代码写在此代码中,还有一种就是调用此代码外的二进制代码
#第一种:
x86_CODE = b'\x41\x4a'#这两个x86的指令为“INC ecx”(+1指令)和“DEC edx”(-1指令)
mu.mem_write(ADDRESS, x86_CODE)

#第二种:
mu.mem_write(ADDRESS, open('./test').read())

2.1.2 添加指令级的Hook

这个有点像单步调试的感觉,在begin…end范围内的每一条指令被执行前都会调用callback。

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

hook_code()是Python自带的函数,类似于C语言中的printf()hook_code()用来跟踪指令的,仅打印指令执行的地址和长度信息。实际应用中可配合capstone反汇编引擎玩一些更骚的操作。

1
2
3
# callback for tracing instructions
def hook_code(uc, address, size, user_data):
print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))

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

2.1.3 启动虚拟机

我们已经映射内存并将数据写入到内存,并设置好执行Hook以监视指令是否正常执行,但是虚拟机还没有启动,所以需要用emu_start()函数来启动虚拟机。

1
2
3
mu.emu_start(ADDRESS, ADDRESS + len(x86_CODE))
#这个函数本来是有四个参数的,分别是需要模拟代码的初始地址、结束地址、模拟的时间、模拟的指令数量,我们通常忽略后面两个参数,这样就会在无限的时间中模拟无限数量的指令
#mu.emu_start(self, begin, until, timeout=0, count=0)

2.1.4 获取和修改寄存器内容

当然对于调试,最重要的就是可以查看和修改寄存器的值,Unicorn也提供了这样的功能:

1
2
3
4
5
6
mu.reg_write(UC_X86_REG_ECX, 0x1234)
mu.reg_write(UC_X86_REG_EDX, 0x7890)
#这个函数接受两个参数,分别是寄存器地址(这个在常量中有)、要写入的内容
r_ecx = mu.reg_read(UC_X86_REG_ECX)
r_edx = mu.reg_read(UC_X86_REG_EDX)
#这个函数只接受一个参数,那就是寄存器地址,返回这个寄存器的内容

2.1.5 上述样例的完整代码

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
from unicorn import *
from unicorn.x86_const import *

def hook_code(uc, address, size, user_data):
print(">>> Tracing instruction at 0x%x, instruction size = 0x%x" %(address, size))

mu = Uc(UC_ARCH_X86, UC_MODE_32)
ADDRESS = 0x1000000
mu.mem_map(ADDRESS, 2*1024*1024)
x86_CODE = b'\x41\x4a'
r_ecx = mu.reg_read(UC_X86_REG_ECX)
r_edx = mu.reg_read(UC_X86_REG_EDX)
print('>>> ecx:', r_ecx)
print('>>> edx:', r_edx)
mu.reg_write(UC_X86_REG_ECX, 0x1234)
mu.reg_write(UC_X86_REG_EDX, 0x7890)
r_ecx = mu.reg_read(UC_X86_REG_ECX)
r_edx = mu.reg_read(UC_X86_REG_EDX)
print('>>> ecx:', r_ecx)
print('>>> edx:', r_edx)
mu.mem_write(ADDRESS, x86_CODE)
mu.hook_add(UC_HOOK_CODE, hook_code, begin=ADDRESS, end=ADDRESS)
mu.emu_start(ADDRESS, ADDRESS + len(x86_CODE))
r_ecx = mu.reg_read(UC_X86_REG_ECX)
r_edx = mu.reg_read(UC_X86_REG_EDX)
print('>>> ecx:', r_ecx)
print('>>> edx:', r_edx)

运行试试:

1
2
3
4
5
6
7
8
PS C:\Users\v5le0n9\Desktop> python unicorn.py
Traceback (most recent call last):
File "unicorn.py", line 1, in <module>
from unicorn import *
File "C:\Users\v5le0n9\Desktop\unicorn.py", line 2, in <module>
from unicorn.x86_const import *
ModuleNotFoundError: No module named 'unicorn.x86_const'; 'unicorn' is not a package
PS C:\Users\v5le0n9\Desktop>

运行错误,这里是一个点,不能将测试文件命名为unicorn.py,因为Unicorn模块中就有一个PY文件叫unicorn.py,导致文件冲突。此时应该修改测试文件名,再次运行。

1
2
3
4
5
6
7
8
C:\Users\v5le0n9\Desktop>python unicorn_test.py
>>> ecx: 0
>>> edx: 0
>>> ecx: 4660
>>> edx: 30864
>>> Tracing instruction at 0x1000000, instruction size = 0x1
>>> ecx: 4661
>>> edx: 30863

2.2 Capstone反汇编

Unicorn 并没有反汇编功能,虽然它的内部一定有与反汇编相关的代码。我们只能自己想办法反汇编。Unicorn 有一个兄弟,它叫Capstone。Capstone是一款支持多种处理器和开发语言的反汇编框架。我将使用Capstone 作为调试模块的反汇编器。

Capstone安装:

1
pip install capstone

2.2.1 Capstone例子

1
2
3
4
5
6
7
8
from capstone import *
from capstone.arm import *

CODE = b"\xf1\x02\x03\x0e\x00\x00\xa0\xe3\x02\x30\xc1\xe7\x00\x00\x53\xe3"

md = Cs(CS_ARCH_ARM, CS_MODE_ARM)
for i in md.disasm(CODE, 0x1000):
print("%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))

运行结果:

1
2
3
4
5
C:\Users\v5le0n9\Desktop>python capstone_test.py
1000: mcreq p2, #0, r0, c3, c1, #7
1004: mov r0, #0
1008: strb r3, [r1, r2]
100c: cmp r3, #0

2.2.2 UnicornDebbuger调试器

无名大佬写的调试器,直接拿来用:

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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
from unicorn import *
from unicorn import arm_const
import sys
import hexdump
import capstone as cp

BPT_EXECUTE = 1
BPT_MEMREAD = 2
UDBG_MODE_ALL = 1
UDBG_MODE_FAST = 2

REG_ARM = {arm_const.UC_ARM_REG_R0: "R0",
arm_const.UC_ARM_REG_R1: "R1",
arm_const.UC_ARM_REG_R2: "R2",
arm_const.UC_ARM_REG_R3: "R3",
arm_const.UC_ARM_REG_R4: "R4",
arm_const.UC_ARM_REG_R5: "R5",
arm_const.UC_ARM_REG_R6: "R6",
arm_const.UC_ARM_REG_R7: "R7",
arm_const.UC_ARM_REG_R8: "R8",
arm_const.UC_ARM_REG_R9: "R9",
arm_const.UC_ARM_REG_R10: "R10",
arm_const.UC_ARM_REG_R11: "R11",
arm_const.UC_ARM_REG_R12: "R12",
arm_const.UC_ARM_REG_R13: "R13",
arm_const.UC_ARM_REG_R14: "R14",
arm_const.UC_ARM_REG_R15: "R15",
arm_const.UC_ARM_REG_PC: "PC",
arm_const.UC_ARM_REG_SP: "SP",
arm_const.UC_ARM_REG_LR: "LR"
}

REG_TABLE = {UC_ARCH_ARM: REG_ARM}


def str2int(s):
if s.startswith('0x') or s.startswith("0X"):
return int(s[2:], 16)
return int(s)


def advance_dump(data, base):
PY3K = sys.version_info >= (3, 0)
generator = hexdump.genchunks(data, 16)
retstr = ''
for addr, d in enumerate(generator):
# 00000000:
line = '%08X: ' % (base + addr * 16)
# 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
dumpstr = hexdump.dump(d)
line += dumpstr[:8 * 3]
if len(d) > 8: # insert separator if needed
line += ' ' + dumpstr[8 * 3:]
# ................
# calculate indentation, which may be different for the last line
pad = 2
if len(d) < 16:
pad += 3 * (16 - len(d))
if len(d) <= 8:
pad += 1
line += ' ' * pad

for byte in d:
# printable ASCII range 0x20 to 0x7E
if not PY3K:
byte = ord(byte)
if 0x20 <= byte <= 0x7E:
line += chr(byte)
else:
line += '.'
retstr += line + '\n'
return retstr


def _dbg_trace(mu, address, size, self):

self._tracks.append(address)
if not self._is_step and self._tmp_bpt == 0:
if address not in self._list_bpt:
return

if self._tmp_bpt != address and self._tmp_bpt != 0:
return

return _dbg_trace_internal(mu, address, size, self)

def _dbg_memory(mu, access, address, length, value, self):
pc = mu.reg_read(arm_const.UC_ARM_REG_PC)
print ("memory error: pc: %x access: %x address: %x length: %x value: %x" %
(pc, access, address, length, value))
_dbg_trace_internal(mu, pc, 4, self)
mu.emu_stop()
return True

def _dbg_trace_internal(mu, address, size, self):

self._is_step = False
print ("======================= Registers =======================")
self.dump_reg()
print ("======================= Disassembly =====================")
self.dump_asm(address, size * self.dis_count)

while True:
raw_command = input(">")
if raw_command == '':
raw_command = self._last_command
self._last_command = raw_command
command = []
for c in raw_command.split(" "):
if c != "":
command.append(c)
try:
if command[0] == 'set':
if command[1] == 'reg':# set reg regname value
self.write_reg(command[2], str2int(command[3]))
elif command[1] == 'bpt':
self.add_bpt(str2int(command[2]))
else:
print("[Debugger Error]command error see help.")

elif command[0] == 's' or command[0] == 'step':
# self._tmp_bpt = address + size
self._tmp_bpt = 0
self._is_step = True
break
elif command[0] == 'n' or command[0] == 'next':
self._tmp_bpt = address + size
self._is_step = False
break

elif command[0] == 'r' or command[0] == 'run':
self._tmp_bpt = 0
self._is_step = False
break
elif command[0] == 'dump':
if len(command) >= 3:
nsize = str2int(command[2])
else:
nsize = 4 * 16
self.dump_mem(str2int(command[1]), nsize)
elif command[0] == 'list':
if command[1] == 'bpt':
self.list_bpt()
elif command[0] == 'del':
if command[1] == 'bpt':
self.del_bpt(str2int(command[2]))
elif command[0]=='stop':
exit(0)
elif command[0] == 't':
self._castone = self._capstone_thumb
print ("======================= Disassembly =====================")
self.dump_asm(address, size * self.dis_count)
elif command[0] == 'a':
self._castone = self._capstone_arm
print ("======================= Disassembly =====================")
self.dump_asm(address, size * self.dis_count)
elif command[0] == 'f':
print (" == recent ==")
for i in self._tracks[-10:-1]:
print (self.sym_handler(i))
else:
print ("Command Not Found!")

except:
print("[Debugger Error]command error see help.")


class UnicornDebugger:
def __init__(self, mu, mode = UDBG_MODE_ALL):
self._tracks = []
self._mu = mu
self._arch = mu._arch
self._mode = mu._mode
self._list_bpt = []
self._tmp_bpt = 0
self._error = ''
self._last_command = ''
self.dis_count = 5
self._is_step = False
self.sym_handler = self._default_sym_handler
self._capstone_arm = None
self._capstone_thumb = None

if self._arch != UC_ARCH_ARM:
mu.emu_stop()
raise RuntimeError("arch:%d is not supported! " % self._arch)

if self._arch == UC_ARCH_ARM:
capstone_arch = cp.CS_ARCH_ARM
elif self._arch == UC_ARCH_ARM64:
capstone_arch = cp.CS_ARCH_ARM64
elif self._arch == UC_ARCH_X86:
capstone_arch = cp.CS_ARCH_X86
else:
mu.emu_stop()
raise RuntimeError("arch:%d is not supported! " % self._arch)

if self._mode == UC_MODE_THUMB:
capstone_mode = cp.CS_MODE_THUMB
elif self._mode == UC_MODE_ARM:
capstone_mode = cp.CS_MODE_ARM
elif self._mode == UC_MODE_32:
capstone_mode = cp.CS_MODE_32
elif self._mode == UC_MODE_64:
capstone_mode = cp.CS_MODE_64
else:
mu.emu_stop()
raise RuntimeError("mode:%d is not supported! " % self._mode)

self._capstone_thumb = cp.Cs(cp.CS_ARCH_ARM, cp.CS_MODE_THUMB)
self._capstone_arm = cp.Cs(cp.CS_ARCH_ARM, cp.CS_MODE_ARM)

self._capstone = self._capstone_thumb

if mode == UDBG_MODE_ALL:
mu.hook_add(UC_HOOK_CODE, _dbg_trace, self)

mu.hook_add(UC_HOOK_MEM_UNMAPPED, _dbg_memory, self)
mu.hook_add(UC_HOOK_MEM_FETCH_PROT, _dbg_memory, self)


self._regs = REG_TABLE[self._arch]

def dump_mem(self, addr, size):
data = self._mu.mem_read(addr, size)
print (advance_dump(data, addr))


def dump_asm(self, addr, size):
md = self._capstone
code = self._mu.mem_read(addr, size)
count = 0
for ins in md.disasm(code, addr):
if count >= self.dis_count:
break
print("%s:\t%s\t%s" % (self.sym_handler(ins.address), ins.mnemonic, ins.op_str))

def dump_reg(self):
result_format = ''
count = 0
for rid in self._regs:
rname = self._regs[rid]
value = self._mu.reg_read(rid)
if count < 4:
result_format += rname + '=' + hex(value) + '\t'
count += 1
else:
count = 1
result_format += '\n' + rname + '=' + hex(value) + '\t'
print (result_format)

def write_reg(self, reg_name, value):
for rid in self._regs:
rname = self._regs[rid]
if rname == reg_name:
self._mu.reg_write(rid, value)
return
print ("[Debugger Error] Reg not found:%s " % reg_name)



def show_help(self):
help_info = """
# commands
# set reg <regname> <value>
# set bpt <addr>
# n[ext]
# s[etp]
# r[un]
# dump <addr> <size>
# list bpt
# del bpt <addr>
# stop
# a/t change arm/thumb
# f show ins flow
"""
print (help_info)

def list_bpt(self):
for idx in range(len(self._list_bpt)):
print ("[%d] %s" % (idx, self.sym_handler(self._list_bpt[idx])))

def add_bpt(self, addr):
self._list_bpt.append(addr)

def del_bpt(self, addr):
self._list_bpt.remove(addr)

def get_tracks(self):
for i in self._tracks[-100:-1]:
#print (self.sym_handler(i))
pass
return self._tracks

def _default_sym_handler(self, address):
return hex(address)

def set_symbol_name_handler(self, handler):
self.sym_handler = handler

将它放入Python库中,比如我放到了C:\Users\v5le0n9\AppData\Local\Programs\Python\Python38\Lib\site-packages\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
from unicorn import *
from unicorn.arm_const import *
from unicorn.UnicornDebugger import *
THUMB = b"\x83\xb0\x83\xb0\x83\xb0"
# sub sp, #0xc
# sub sp, #0xc
# sub sp, #0xc
def test_arm():
print("Emulate Thumb code")
try:
# Initialize emulator in ARM mode
mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB)


# map 2MB memory for this emulation
ADDRESS = 0x10000
mu.mem_map(ADDRESS, 2 * 0x10000)
mu.mem_write(ADDRESS, THUMB)

mu.reg_write(UC_ARM_REG_SP, 0x1234)
mu.reg_write(UC_ARM_REG_R2, 0x6789)

#debugger attach
udbg = UnicornDebugger(mu)
udbg.add_bpt(ADDRESS)

# emulate machine code in infinite time
mu.emu_start(ADDRESS, ADDRESS + len(THUMB))
r0 = mu.reg_read(UC_ARM_REG_SP)
r1 = mu.reg_read(UC_ARM_REG_R1)
print(">>> SP = 0x%x" % r0)
print(">>> R1 = 0x%x" % r1)
except UcError as e:
print("ERROR: %s" % e)

if __name__ == '__main__':
test_arm()

在调试过程中,发现无名大佬写的调试器还是有些致命性的错误的,比如在执行n或s命令时,正常来说是执行一条汇编指令的,但这里执行了两条指令。而且指令执行完后SP的值并没有改变。

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
C:\Users\v5le0n9\Desktop>python unicorn_test.py
Emulate Thumb code
======================= Registers =======================
R0=0x0 R1=0x0 R2=0x6789 R3=0x0
R4=0x0 R5=0x0 R6=0x0 R7=0x0
R8=0x0 R9=0x0 R10=0x0 R11=0x0
R12=0x0 SP=0x1234 LR=0x0 PC=0x10000
======================= Disassembly =====================
0x10000: sub sp, #0xc
0x10002: sub sp, #0xc
0x10004: sub sp, #0xc
0x10006: movs r0, r0
0x10008: movs r0, r0
0x1000a: movs r0, r0
0x1000c: movs r0, r0
0x1000e: movs r0, r0
0x10010: movs r0, r0
0x10012: movs r0, r0
>s
======================= Registers =======================
R0=0x0 R1=0x0 R2=0x6789 R3=0x0
R4=0x0 R5=0x0 R6=0x0 R7=0x0
R8=0x0 R9=0x0 R10=0x0 R11=0x0
R12=0x0 SP=0x1234 LR=0x0 PC=0x10004
======================= Disassembly =====================
0x10004: sub sp, #0xc
0x10006: movs r0, r0
0x10008: movs r0, r0
0x1000a: movs r0, r0
0x1000c: movs r0, r0
0x1000e: movs r0, r0
0x10010: movs r0, r0
0x10012: movs r0, r0
0x10014: movs r0, r0
0x10016: movs r0, r0
>

2.3 Unicorn调用so之加载模块

Android是基于Linux开发的,Android Native原生库是ELF文件格式。Unicorn 并不能加载ELF文件,所以我们要自己将ELF文件加载到Unicorn虚拟机的内存中去。 加载ELF 文件是一个很复杂的过程,涉及到ELF文件解析、重定位、符号解析、依赖库加载等。

Python 可以使用elftools库解析ELF 文件。elftools安装:

1
pip install pyelftools

2.3.1 映射ELF文件

ELF 文件有两种视图,链接视图和执行视图。elftools 是基于链接视图解析ELF格式的,然而现在有一些ELF文件的section信息是被抹掉的。

加载ELF文件第一步需要将ELF文件映射到内存。如何映射呢?只需要找到类型为PT_LOAD的segment,按照segment的信息映射即可。

1
2
3
4
5
6
7
# - LOAD (determinate what parts of the ELF file get mapped into memory)
load_segments = [x for x in elf.iter_segments() if x.header.p_type == 'PT_LOAD']

for segment in load_segments:
prot = UC_PROT_ALL
self.emu.memory.mem_map(load_base + segment.header.p_vaddr, segment.header.p_memsz, prot)
self.emu.memory.mem_write(load_base + segment.header.p_vaddr, segment.data())

3. Frida

在Windows上安装好Frida-tools和Frida后,在安卓上安装Frida-server。

首先要准备好你的安卓机,真机、模拟器都可以,还需要adb工具包。使用adb查看和连接安卓设备,查看安卓设备的架构类型。

1
2
3
4
5
6
7
8
9
C:\Users\v5le0n9\Downloads\platform-tools_r33.0.2-windows>adb devices
List of devices attached
192.168.24.101:5555 device


C:\Users\v5le0n9\Downloads\platform-tools_r33.0.2-windows>adb shell
vbox86p:/ # getprop ro.product.cpu.abi
x86
vbox86p:/ #

查看Frida在Windows上的版本,从 https://github.com/frida/frida/releases 下载对应Frida版本和对应安卓架构的Frida-server。

1
2
C:\Users\v5le0n9>frida --version
15.2.2

frida-server-15.2.2-android-x86.xz

解压后进入到存放Frida-server目录,Shift + 右键打开Powershell。运行以下命令将Frida-server上传到安卓设备的/data/local/tmp目录下。

1
2
3
PS C:\Users\v5le0n9\Downloads> adb push .\frida-server-15.2.2-android-x86 /data/local/tmp
.\frida-server-15.2.2-android-x86: 1 file pushed, 0 skipped. 206.2 MB/s (46387888 bytes in 0.215s)
PS C:\Users\v5le0n9\Downloads>

将安卓设备中的Frida-server赋予777权限并运行。

1
2
3
vbox86p:/ # cd /data/local/tmp
vbox86p:/data/local/tmp # chmod 777 frida-server-15.2.2-android-x86
vbox86p:/data/local/tmp # ./frida-server-15.2.2-android-x86

在Powershell中输入以下命令:

1
frida-ps -U

如果存在android.process.acore等信息表示Windows和安卓设备的Frida框架搭建成功。