使用Frida和BurpSuite测试Android客户端
在做Android客户端客户端测试的时候,如果客户端和服务器传输的数据经过加密处理,传统的中间人攻击面对加密数据毫无办法,除非在抓包工具中将加密数据解密出来,然后再修改明文数据,最后把修改的数据再加密回去。但是这一过程特别繁琐和复杂,面前的文章介绍过了通过xposed hook来解决这一问题,具体原理就是获取加密前的数据,配置好代理(代理配置为burp的地址和端口)发往中转服务器(中转负责将修改的数据返回到hook函数中),这样就绕开了加密的限制,使中间人攻击得以正常进行。
1 Frida拦截函数
整个测试方案的第一步就是需要获取未加密的数据,这个过程需要利用frida来拦截目标函数,获取参数内容。frida中内置的java api可以实现这个功能。承接之前Xposed学习-插件开发(二)这篇文章,class为MainActivity,method为encryptDataWithKey,methon的参数为两个String类型,以下示例代码可以将加密钱的数据打印出来,然后调用原函数继续走正常流程。
# -*- coding:utf-8 -*- import sys import frida package_name = "com.flysands.mockapp" def get_messages_from_js(message, data): print(message) def hook_log_on_command(): hook_code = """ Java.perform(function () { console.log("[*] Hook data encrypt function"); var hclass = Java.use("com.flysands.mockapp.EncryptUtil"); hclass.encryptDataWithKey.implementation = function (a, b) { console.log("Hook Start..."); console.log(arguments[0]); return this.encryptDataWithKey(a, b); } }); """ return hook_code def main(): process = frida.get_device_manager().enumerate_devices()[-1].attach( package_name) script = process.create_script(hook_log_on_command()) script.on('message', get_messages_from_js) script.load() sys.stdin.read() if __name__ == '__main__': main()
如果要实现抓包并与burp交互的话。需要一个转发服务器,将post body的内容作为response的body返回,除此之外还需要将我们在JavaScript中拦截的数据,经过burp代理发送到转发服务器。
2 数据发送和修改
要将数据经过代理发送到目标服务器,有以下三个可行方案,经过分析选择了第三个方案,第一个方案貌似不支持,没有frida开放这样的接口,第二个方案理论可行,但是太繁琐,第三个方案利用frida的python和JavaScript的message交互机制可以很简单实现数据发送的功能:
- 直接在JavaScript中通过http请求将数据发送到中转服务器
- 在JavaScript代码中用Java.use等api进行反射,像在xposed插件中实现的代码一样新建一个线程,并在新线程通过http请求将数据发送到中转服务器,主线程等待返回结果
- 利用JavaScript的rpc机制将控制权交给python,由python代码负责将数据发送到中转服务器,也由python代码将结果发送给JavaScript代码
具体的实现原理如下图所示:
图1 frida-burp原理
3 中转服务器
使用java代码实现一个简单的http server,将获取的request body的内容作为response的body返回。
package com.xwapi.test; import com.alibaba.fastjson.JSON; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; /** * Created by chenxuesong on 19/12/14. */ public class ApiTestForwardHandler implements HttpHandler { @Override public void handle(HttpExchange httpExchange) throws IOException { String response = "hello world"; httpExchange.sendResponseHeaders(200, 0); InputStream is = httpExchange.getRequestBody(); String url = httpExchange.getRequestURI().toString(); byte[] bytes = new byte[is.available()]; is.read(bytes); String str = new String(bytes); System.out.println("read post body " + str); try { Map maps = (Map) JSON.parse(str); for (Object map : maps.entrySet()) { System.out .println("key: " + ((Map.Entry) map).getKey() + " value: " + ((Map.Entry) map).getValue()); } response = JSON.toJSONString(maps); } catch (Exception e) { e.printStackTrace(); } OutputStream os = httpExchange.getResponseBody(); os.write(response.getBytes()); os.close(); } }
4 python和js代码
最终的完整代码如下:
# -*- coding:utf-8 -*- import sys import requests import frida package_name = "com.flysands.mockapp" g_script = None # 将数据原样返回的服务器的值 FORWARD_SERVER_IP = '192.168.50.254' FORWARD_SERVER_PORT = 8088 # proxies里面的配置为burp的地址 PROXIES = {"http": "http://127.0.0.1:8080"} def get_messages_from_js(message, data): print(message) if message['type'] == 'send': if message['payload']['from'] == 'jscode': req = requests.post( 'http://%s:%d/apiforward' % (FORWARD_SERVER_IP, FORWARD_SERVER_PORT), proxies=PROXIES, headers={'content-type': 'text/plain'}, data=message['payload']['payload']) g_script.post({'type': 'python_send', 'payload': req.content}) def hook_log_on_command(): hook_code = """ Java.perform(function () { console.log("[*] Hook data encrypt function"); var hclass = Java.use("com.flysands.mockapp.EncryptUtil"); hclass.encryptDataWithKey.implementation = function (a, b) { console.log("Hook Start..."); console.log(arguments[0]); send({'from': 'jscode', 'payload': arguments[0]}) var op = recv('python_send', function(value) { // callback function console.log("Rec from forward server content: " + value.payload) a = value.payload }); op.wait(); return this.encryptDataWithKey(a, b); } }); """ return hook_code def main(): process = frida.get_device_manager().enumerate_devices()[-1].attach( package_name) script = process.create_script(hook_log_on_command()) script.on('message', get_messages_from_js) global g_script g_script = script script.load() sys.stdin.read() if __name__ == '__main__': main()