Table of Contents
1. Android Hook框架Frida实践Part2-自动化安全检测
在上一章节中,我们初步了解了Frida安装和使用,并且进行简单的hook测试,这个章节,我们将通过实际的例子,来完成自动化安全检测。
1.1. Frida API
Frida官方文档:https://frida.re/docs/home/
Frida官方文档中提供的API接口包括 JavaScript, C, Swift, 但是具有详细文档的只有 JavaScript.
JavaScript接口相当丰富了,接口功能包括但不限于进程操作、模块操作、内存操作、函数操作、线程操作、网络通信、数据流操作、文件操作、数据库操作、寄存器操作, 并且官网有着详细的说明文档(https://www.frida.re/docs/javascript-api/),对于每个接口的功能和参数给出了详细的解释,并且一部分接口还给出了例子。
除了官方文档提到的API接口以外,其实还提供了一套Python接口,Python接口提供的功能很少,而且基本都是用来获取进程、模块、函数的信息的。
其中部分接口:
# 启动应用,应用处于挂起状态 def spawn(*args, **kwargs): return get_local_device().spawn(*args, **kwargs) # 恢复应用运行状态 def resume(target, **kwargs): get_local_device().resume(target, **kwargs) # 结束应用 def kill(target, **kwargs): get_local_device().kill(target, **kwargs) # 附加到目标进程中 def attach(target, *args, **kwargs): return get_local_device().attach(target, *args, **kwargs) # 获取连接的USB设备 def get_usb_device(timeout=0, **kwargs): return get_device_matching(lambda d: d.type == 'usb', timeout, **kwargs) # 创建javascript脚本 def create_script(self, *args, **kwargs): return Script(self._impl.create_script(*args, **kwargs))
更多接口请查看源码:frida-python
下边我们尝试使用Python接口枚举所有进程:
import frida device = frida.get_usb_device() for process in device.enumerate_processes(): print(process)
执行以后,终端就会打印出所有的进程:
> python testing.py Process(pid=1, name="init", parameters={}) Process(pid=146, name="init", parameters={}) Process(pid=148, name="ueventd", parameters={}) Process(pid=173, name="logd", parameters={}) Process(pid=174, name="lmkd", parameters={}) Process(pid=176, name="servicemanager", parameters={}) Process(pid=181, name="hwservicemanager", parameters={}) Process(pid=182, name="sh", parameters={}) Process(pid=183, name="qemu-props", parameters={}) Process(pid=186, name="vold", parameters={}) Process(pid=194, name="android.hardware.keymaster@4.1-service", parameters={}) Process(pid=219, name="android.system.suspend@1.0-service", parameters={}) Process(pid=220, name="android.hardware.atrace@1.0-service", parameters={}) Process(pid=287, name="createns", parameters={}) Process(pid=292, name="tombstoned", parameters={}) Process(pid=298, name="statsd", parameters={}) ...
1.2. 自动化测试
上一章节中我们初步使用了frida内置的工具来进行简单的测试,下边我们将通过编写代码来实现自动化测试。
测试代码依然是hook open函数,python代码如下:
import sys import time import frida js_code = """ Interceptor.attach(Module.findExportByName("libc.so" , "open"), { onEnter: function (args) { console.log('open called args[0] address: ' + args[0] + ', value: ' + Memory.readUtf8String(args[0])); } }); """ device = frida.get_usb_device() pid = device.spawn('com.android.chrome') device.resume(pid) time.sleep(3) session = device.attach('com.android.chrome:privileged_process0') script = session.create_script(js_code) script.load() print('starting...') sys.stdin.read()
这里的hook部分实现使用的是JavaScript API,相关API用法请参考官方文档。
执行过后,控制台将显示出open函数的参数内容,如下所示:
> python testing.py starting... open called args[0] address: 0xd342f290, value: /proc/self/statm open called args[0] address: 0xd3428ab0, value: /proc/29242/status open called args[0] address: 0xd3437ea0, value: /proc/29242/stat open called args[0] address: 0xd3437ea0, value: /proc/self/status open called args[0] address: 0xd3437ea0, value: /proc/self/clear_refs open called args[0] address: 0xb5c3e332, value: /proc/self/pagemap open called args[0] address: 0xd3425db0, value: /proc/self/statm open called args[0] address: 0xd34317b0, value: /proc/29242/status open called args[0] address: 0xd34260b0, value: /proc/29242/stat open called args[0] address: 0xd34260b0, value: /proc/self/status open called args[0] address: 0xd34260b0, value: /proc/self/clear_refs open called args[0] address: 0xb5c3e332, value: /proc/self/pagemap
上边的示例中,hook的数据通过 console.log 打印在了控制台中,如果我们要记录每次的参数数据,后续做二次分析, 我们可以在 script.load() 之前使用script对象的on方法,捕捉js脚本输出的信息。 Js脚本提供了向主控端发送数据的接口 send, 而在主控端是通过python脚本的回调来接收数据,使用方法如下:
js_code = """ Interceptor.attach(Module.findExportByName("libc.so" , "open"), { onEnter: function (args) { // console.log('open called args[0] address: ' + args[0] + ', value: ' + Memory.readUtf8String(args[0])); send('open args[0]: ' + Memory.readUtf8String(args[0])); } }); """ def on_message(message, data): print('recv: {}'.format(message)) device = frida.get_usb_device() pid = device.spawn('com.android.chrome') device.resume(pid) time.sleep(3) session = device.attach('com.android.chrome:privileged_process0') script = session.create_script(js_code) script.on("message", on_message) script.load() print('starting...') sys.stdin.read()
执行后,控制台将打印接受的数据:
> python testing.py starting... recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/statm'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/31578/status'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/31578/stat'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/status'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/clear_refs'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/pagemap'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/statm'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/31578/status'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/31578/stat'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/status'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/clear_refs'} recv: {'type': 'send', 'payload': 'open args[0]: /proc/self/pagemap'}
至此,我们就完成了初步的自动化测试环境,后续可以按照不通的需求,来实现更强大的功能。
1.3. 安全检测
在尝试了自动化测试之后,我们如何将这种流程应用到实际的漏洞检测当中呢,下边以漏洞检测和漏洞挖掘两个方面来进行实践。
- Wormhole漏洞检测
Wormhole 是百度 moplus SDK 的一个后门, 当用户启动一个应用程序, Moplus SDK 会偷偷地在设备上自动设置一个本地HTTP服务器,用来监控通过socket的消息。
该部分通过反汇编定位代码位置为 com.baidu.hello.patch.moplus.nebula.b.a
Moplus SDK 被包含在一个独立的进程中,服务名为 com.baidu.android.moplus.MoPlusService, 可以通过不同的广播事件触发,其中包括系统启动的广播。
与其说这是一个漏洞,倒不如说这是百度自己留下的一个后门,百度全系APP连同使用了百度地图的APP纷纷中枪,影响用户同样过亿。
该后门之流氓程度与XcodeGhost相比有过之而无不及,包括静默下载任意文件、后台上传用户文件等各种流氓行径。
该后门通过在本机利用socket开了一个简易的WebServer,绑定的端口包括 6259 (com.ufo.dcb.lingyi), 40310 (com.baidu.BaiduMap), 等等。
所以我们只需要检测应用启动是否存在绑定端口 6259 和 40310 的行为即可。
应用要绑定端口,最终都会调用到libc层的bind函数,我们看下bind函数的参数:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); ipv4: AF_INET = 2 struct sockaddr_in { __kernel_sa_family_t sin_family; __be16 sin_port; struct in_addr sin_addr; }; ipv6: AF_INET6 = 10 struct sockaddr_in6 { sa_family_t sin6_family; in_port_t sin6_port; uint32_t sin6_flowinfo; struct in6_addr sin6_addr; uint32_t sin6_scope_id; };
我们通过解析第二个参数,即可解析出绑定的端口,然后判断即可。
JavaScript脚本代码如下所示:
Interceptor.attach(Module.findExportByName('libc.so', 'bind'), { onEnter: function(args) { console.log('sockfd: ' + sockfd); var sin_port = Memory.readU8(sa.add(2)) * 0x100 + Memory.readU8(sa.add(3)); var sin_ip = Memory.readU8(sa.add(4)) + '.' + Memory.readU8(sa.add(5)) + '.' + Memory.readU8(sa.add(6)) + '.' + Memory.readU8(sa.add(7)); console.log('bind to ' + sin_ip + ':' + sin_port); if(sin_port) { var VUL_PORT = [6259, 40310]; if(VUL_PORT.indexOf(parseInt(sin_port) >= 0) { console.log('wormhole found!'); } } } });
之后我们找到带漏洞版本的应用,就可以进行检测了,检测结果如下所示:
> python testing.py sockfd: 2 bind to 10.0.2.16:50662 sockfd: 3 bind to 10.0.2.16:42856 sockfd: 7 bind to 127.0.0.1:40310 wormhole found! ...
- 某APP漏洞挖掘
除了检测常规漏洞外,frida还可以辅助我们进行漏洞挖掘,先看下边一段反汇编代码:
该部分代码源于某APP中调用Native层so库的导出函数,其中存在bug的代码为:
if ( a1[1] == 0x45 && a1[2] == 0x41 && a1[3] == 0x44 ) { puts("buf[]=DEAD"); MEMORY[0xDEADBEEF] = 1; }
我们触发第32行代码 MEMORY[0xDEADBEEF] = 1 就会造成内存非法访问。
需要满足的条件为 a1[1]
= 0x45 && a1[2] =
0x41 && a1[3] == 0x44参数 a 为该函数 vul_func_buf 传入的一个是指针类型的buffer,因此我们只要随机生成buffer进行测试即可。
该过程实现的javascript代码如下:
var target_so = 'lib-xxx.so'; var target_func = 'vul_func_buf'; var target_address = Module.findExportByName(target_so , target_func); console.log('[+] target address: ' + target_address); var func_handle = new NativeFunction(target_address, 'int', ['pointer', 'int']); console.log('[+] func handle: ' + func_handle); function getString(num) { // a1[1] == 0x45 && a1[2] == 0x41 && a1[3] == 0x44 var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; return chars.charAt(Math.floor(Math.random() * chars.length)) + String.fromCharCode(0x45) + String.fromCharCode(0x41) + String.fromCharCode(0x44); } var index = 0; while (true) { var test_str = getString(index); console.log(index + ': ' + test_str); var p_str = Memory.allocUtf8String(test_str); try { var p_str_ret = func_handle(p_str, 4); } catch(err) { console.log('[+] ' + 'bugs found! ' + err); break; } index += 1; }
改代码的核心部分在于frida用于调用c层函数的API NativeFunction(address, returnType, argTypes[, abi])
- address:要hook的函数地址
- returnType:返回值类型
- argTypes[, abi]: 参数类型 这里参数可以是多个
参数说明详见官方API:NativeFunction
执行以后,大概几秒就会找出满足bug的字符串,如下图所示:
至此本章节内容暂时结束,在安全检测中,尤其是在漏洞挖掘方向,frida的能力远不止如此。
如上边的漏洞挖掘过程中,我们为了触发漏洞位置,通过部分逆向分析加上字符串随机的方式进行测试,虽然能够成功拿到case,
但是在实际环境中,对于复杂函数的逆向分析是非常困难的,并且字符串随机效率也是个问题,单纯的随机根本无法触发复杂的代码路径,除了浪费CPU资源没什么实际意义。
在详读frida官方文档之后我们发现,frida内置了一套代码跟踪引擎 Stalker,
通过这套代码跟踪引擎我们就可以对代码块跟踪并统计覆盖率,然后结合AFL数据变异规则,一套强大且高效的真机Fuzz就可以实现了。
该部分内容计划在后续章节中逐渐讲解,敬请期待!
Created: 2020-02-25 Tue 18:51
没有评论:
发表评论