没想到吧,我又润来学安卓了。。。实习就是这样的啦,多点尝试。
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安装:
2.1 Unicorn的编写 对于利用Unicorn编写的代码思路一般为:设置好参数->加载模拟的代码->添加hook->run。
2.1.1 设置参数并加载模拟执行的代码 头文件:
1 2 from unicorn import *from unicorn.x86_const import *
Uc类初始化:
1 2 3 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
定义虚拟地址:
映射代码内存,所有CPU操作都只能访问此内存,默认权限为rwx。mem_map()
函数要求 address 和 size 参数都与0x1000对齐,否则会报UC_ERR_ARG异常。
1 mu.mem_map(ADDRESS, 2 *1024 *1024 )
把要模拟的代码加载到我们刚刚映射的内存上,mem_write()
函数的第二个参数只支持python的byte数组。
1 2 3 4 5 6 7 x86_CODE = b'\x41\x4a' 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 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))
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安装:
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_constimport sysimport hexdumpimport 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): line = '%08X: ' % (base + addr * 16 ) dumpstr = hexdump.dump(d) line += dumpstr[:8 * 3 ] if len (d) > 8 : line += ' ' + dumpstr[8 * 3 :] pad = 2 if len (d) < 16 : pad += 3 * (16 - len (d)) if len (d) <= 8 : pad += 1 line += ' ' * pad for byte in d: 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' : 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 = 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 ]: 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" def test_arm (): print ("Emulate Thumb code" ) try : mu = Uc(UC_ARCH_ARM, UC_MODE_THUMB) 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 ) udbg = UnicornDebugger(mu) udbg.add_bpt(ADDRESS) 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安装:
2.3.1 映射ELF文件 ELF 文件有两种视图,链接视图和执行视图。elftools 是基于链接视图解析ELF格式的,然而现在有一些ELF文件的section信息是被抹掉的。
加载ELF文件第一步需要将ELF文件映射到内存。如何映射呢?只需要找到类型为PT_LOAD的segment,按照segment的信息映射即可。
1 2 3 4 5 6 7 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中输入以下命令:
如果存在android.process.acore
等信息表示Windows和安卓设备的Frida框架搭建成功。