Brida:使用Frida进行移动应用渗透测试
简介
Brida是一款 Burp Suite 扩展,作为一座桥梁连接着Burp Suite以及Frida,以帮助用户修改应用程序与后端服务器之间的通信数据为己任。在分析移动端应用时遇到应用使用随机密钥式对称加密,如果不知道其使用的密钥就无法篡改其通信数据,通过Burp也就无法对所有的交换数据进行更改了,于是Brida就这样出现在我们视野中。
Frida
Frida是一款非常强大的框架,其可向Windows、MacOS、Linux、iOS、Android,QNX的应用中注入Javascript,进而拦截应用传输的数据。本文仅使用到其中少部分功能,更多可以参考Frida文档,强烈建议读者最好是在熟悉了其基本概念以及常用功能后再进行阅读。推荐两篇不错的译文:使用Frida配合Burp Suite追踪API调用; 如何在iOS应用程序中用Frida来绕过“越狱检测”?
必备
工具设计初衷是为了提高渗透测试人员工作效率,因此读者可能需要先了解该应用以及系统内部构件,渗透测试,反编译,逆向工程等有一定理解。有使用过Burp Suite的经验将会给你带来帮助,本次测试将用到以下软件:
- Burp Suite Pro 1.7.25
- Frida 10.11
- Pyro 4.60 (pip install pyro4)
- Python 2.7
- Java 1.8
- 反编译软件
我们决定使用Pyro4作为Burp与Frida之间的接口,以便直接访问Java和Python扩展。Brida项目地址:https://github.com/federicodotta/Brida,之后将在BAppStore上架
常见场景
如果你需要对应用(Android,iOS等)进行一个完整评估,也就是说你需要对应用与后端服务器之间的交互进行研究。身为渗透测试人员几乎每天都有做这样的事情,因此假设你熟悉如何重定向设备/应用的通信,与此同时假设你能设法绕过SSL Pinning验证机制以及/或者anti-root检测。
为了与服务器之间进行发送/接收数据,该应用程序似乎有是使用某种(自定义)编码或者是加密例程。一般而言,应用程序的数据传输可能是基于加密令牌,也可能是使用复杂的Challenge-Response协议进行身份验证。那么我们如何篡改这些信息呢?大多数的时间唯一可行的方法就是对应用进行反编译工作,然后识别我们感兴趣部分的使用的函数或者方法,之后再重新实现这些功能。显然该方法耗时较长并且不是每次都那么凑效:即令牌生成以及加密例程都是与设备(状态)有强关联的,或者是存储在不能直接访问的保护区域。这时Brida的用处就体现出来:它会尝试提取密钥或者证书并重写加密例程,这些脏活累活就交给它吧!
工作原理
Brida由以下三个组件构成:
- Brida.jar:Burp Suite扩展
- bridaServicePyro:用于连接Frida和Burp的一个Python脚本,其存储在扩展之中,在执行Brida过程中会复制到一个临时目录中
- script.js:需要注入到目标应用的JavaScript脚本,它会通过Frida自带的rpc.exports将获取的函数信息返回给扩展
'use strict';
// 1 - FRIDA EXPORTS
rpc.exports = {
exportedFunction: function() {
// Do stuff...
// This functions can be called from custom plugins or from Brida "Execute method" dedicated tab
},
// Function executed when executed Brida contextual menu option 1.
// Input is passed from Brida encoded in ASCII HEX and must be returned in ASCII HEX (because Brida will decode the output
// from ASCII HEX). Use auxiliary functions for the conversions.
contextcustom1: function(message) {
return "6566";
},
// Function executed when executed Brida contextual menu option 2.
// Input is passed from Brida encoded in ASCII HEX and must be returned in ASCII HEX (because Brida will decode the output
// from ASCII HEX). Use auxiliary functions for the conversions.
contextcustom2: function(message) {
return "6768";
},
// Function executed when executed Brida contextual menu option 3.
// Input is passed from Brida encoded in ASCII HEX and must be returned in ASCII HEX (because Brida will decode the output
// from ASCII HEX). Use auxiliary functions for the conversions.
contextcustom3: function(message) {
return "6768";
},
// Function executed when executed Brida contextual menu option 4.
// Input is passed from Brida encoded in ASCII HEX and must be returned in ASCII HEX (because Brida will decode the output
// from ASCII HEX). Use auxiliary functions for the conversions.
contextcustom4: function(message) {
return "6768";
}
}
// 2 - AUXILIARY FUNCTIONS
// Convert a hex string to a byte array
function hexToBytes(hex) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return bytes;
}
// Convert a ASCII string to a hex string
function stringToHex(str) {
return str.split("").map(function(c) {
return ("0" + c.charCodeAt(0).toString(16)).slice(-2);
}).join("");
}
// Convert a hex string to a ASCII string
function hexToString(hexStr) {
var hex = hexStr.toString();//force conversion
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
// Convert a byte array to a hex string
function bytesToHex(bytes) {
for (var hex = [], i = 0; i < bytes.length; i++) {
hex.push((bytes[i] >>> 4).toString(16));
hex.push((bytes[i] & 0xF).toString(16));
}
return hex.join("");
}
// 3 - FRIDA HOOKS (if needed)
if(ObjC.available) {
// Insert here Frida interception methods, if needed
// (es. Bypass Pinning, save values, etc.)
}
关于上述这个script.js框架,你可以根据需求定义输出函数,再加上你可以从Burp右键菜单(即在选中文本中单击右键)中调用的4个输出函数。请注意,JS中所有实现方法都必须是小写(避免因为bridaServicePyro报错)Brida提供了三种不同的操作模式:
- 使用自定义参数直接调用函数
- 结合右键菜单功能
- 生成自定义插件桩代码(桩代码即临时性的/待编辑的代码)
本文我们通过使用iOS 10下的Signal作为目标应用进行测试(源码可用),虽然本文是在iOS环境下进行测试,但其方法Android, Windows, Linux等都适用。
使用方法
将Brida.jar扩展文件添加到Burp Suite Pro,这一步不需要Jpython,但是必须要使用合适的Python环境(Python 2.7)
Extender -> Add -> Extension file (.jar)
配置选项:
- Python binary path: Python所在目录, 用于运行Pyro服务(for RPC).
- Pyro host, Pyro port: Pyro服务的主机以端口; 一般保持默认即可,如有需要可更改端口
- Frida JS file path: 用于注入到目标应用的Frida JavaScript脚本
- Application ID: 类似于org.whispersystems.signal
完成配置选项,接下来瞧瞧这些按钮都有些什么功能:
- Start server, 启动用于连接Burp与Frida之间的服务 (其在后台运行一个python/Pyro RPC服务)
- Kill server, 停止连接服务
- Spawn application, 在设备中打开应用,之后将Frida JS注入其中
- Kill application, 关闭应用程序
- Reload JS, 在不重启程序的情况下重新加载Frida脚本
- Java Stub, 利用Brida为你使用的插件打印Java Stub
- Python Stub, 利用Brida为你使用的插件打印Python Stub
- Save settings to file, 将设置导出并保存到文件
- Load settings from file, 从文件中导入设置
- Execute Method, 运行execute method函数 (see below for an example)
开始我们今天愉快的学习之路吧,单击Start server启动服务之后,再点击Spawn application
案例一:Direct method调用自定义参数
通过自定义JS函数,Brida可能执行一个App类函数,下面来看一个小例子。在scriptSignal.js我们定义一个使用NSString uppercaseString方法的ObjC函数
touppercase: function(message) {
var a1 = ObjC.classes.NSString.stringWithString(message);
var a2 = a1.uppercaseString();
return a2.toString();
}
确认所有参数设置合适之后,接着就来配置Direct method部分了
单击Execute Method,Brida将通过Pyro调用JS中定义的方法,该方法将在iOS上执行其结果将显示在Output区域
案例二:右键菜单
在Burp的右键菜单栏添加几个选项以调用一些预定义的Brida函数;使用该方法,对于一些基础功能(即自定义加密或者解密)你可以直接开发合适的JS脚本,而不必再去写一个插件了,以下则为一个菜单列表:
- Brida Custom1:可通过右键菜单可编辑视图进行访问(它将调用contextcustom1 JS脚本)
- Brida Custom2:可通过右键菜单可编辑视图进行访问(它将调用contextcustom2 JS脚本)
- Brida Custom3:可通过右键菜单非可编辑视图进行访问(它将调用contextcustom3 JS脚本)
- Brida Custom4:可通过右键菜单非可编辑视图进行访问(它将调用contextcustom4 JS脚本)
默认情况下,为了管理二进制数据,Brida将输入以及输出都进行了16进制编码处理,用于转换的辅助函数都在JS文件中。
假设可编辑视图的选项会直接替换JS执行的结果,在非可编辑视图则生成一个包含结果的消息框
在这个JS文件中我们实现了以下方法:contextcustom1会将所选字符串以小写输出:
contextcustom1: function(message) {
var a1 = ObjC.classes.NSString.stringWithString
(hexToString(message));
var a2 = a1.lowercaseString();
return stringToHex(a2.toString());
}
contextcustom2将生成所选文本的base64编码:
contextcustom2: function(message) {
var inputByte = hexToBytes(message);
var ptrMessage = Memory.alloc(inputByte.length);
Memory.writeByteArray(ptrMessage,inputByte);
var objMessage = ObjC.classes.NSData.alloc().initWithByteslength(ptrMessage,inputByte.length);
var encodedMessage = objMessage.base64EncodedString();
return stringToHex(encodedMessage.toString());
}
contextcustom3会将所选字符串以大写输出:
contextcustom3: function(message) {
var a1 = ObjC.classes.NSString.stringWithString(hexToString(message));
var a2 = a1.uppercaseString();
return stringToHex(a2.toString());
}
contextcustom4将解码所选文本的base64:
contextcustom4: function(message) {
var a2 = ObjC.classes.NSString.stringWithString
(hexToString(message));
var encodedString = ObjC.classes.NSData.dataFromBase64String_(a2);
var ptrBytesReturned = encodedString.bytes();
var ptrBytesLength = encodedString.length();
var bytesReturneded = Memory.readByteArray(ptrBytesReturned, ptrBytesLength);
return bytesToHex(bytesReturneded);
}
案例三:自定义插件 – 修改Signal (iOS)传输过程中的加密报文
Brida最给力的地方还得看案例三演示的这个操作模式。接下来我们将编写一个自定义Burp插件:我们希望在进行通信传输时Burp能够对信息进行拦截,向应用发起请求对我们之前就定义好的信息进行加密,最后将原始信息进行替换,所有代码都可以通过Brida-examples查看。你需要在Burp中加载Brida插件,然后为Signal应用加载python或java插件。您可以使用Brida的Java Stub或Python Stub功能轻松生成有效代码以通过Pyro4连接到Brida,之后执行Frida的导出功能。接下来只需专注于注入脚本
scriptSignal.js
脚本的最后包含了插件使用的Frida Hooks,钩子sendMessage:recipient:thread:attempts:success:failure:是用于获取最后一条信息的终点编号,执行该函数将生成新的加密信息,该值存储在JS中的局部变量中,在之后的自定义函数中会使用到。
var hooksendMessage = ObjC.classes.OWSMessageSender["- sendMessage:recipient:thread:attempts:success:failure:"];
Interceptor.attach(hooksendMessage.implementation, {
onEnter: function(args) {
var obj2 = ObjC.Object(args[3]);
destNum = obj2.recipientId().toString();
}
});
为了能够通过Burp Proxy拦截传输数据,需要绕过SSL pinning身份验证
var hookevaluateServerTrust = ObjC.classes.OWSHTTPSecurityPolicy["- evaluateServerTrust:forDomain:"];
Interceptor.attach(hookevaluateServerTrust.implementation, {
onLeave: function(retval) {
retval.replace(ptr(1));
}
});
这是Frida导出的核心函数,其将调用Burp插件来改变Signal发出的信息,之后生成新的信息并将结果返回给我们的插件
changemessage: function(message) {
var env = ObjC.classes.Environment.getCurrent();
var messageSender = env.messageSender();
var signalRecipient = ObjC.classes.SignalRecipient.alloc().initWithTextSecureIdentifierrelay(destNum,null);
var contactThread = ObjC.classes.TSContactThread.alloc().initWithContactId_(destNum);
var mex = ObjC.classes.TSOutgoingMessage.alloc().initWithTimestamp_inThreadmessageBody(Math.round(+new Date()/1000),null,message);
var retVal = messageSender.deviceMessages_forRecipientinThread(mex,signalRecipient,contactThread);
var retValMessage = retVal.objectAtIndex_(0);
return retValMessage.toString();
}
BurpBridaSignal.py
该Burp Suite插件文件将利用Brida生成一条新信息来替换应用发出的信息,以下使用Burp Suite各项功能来分析所有请求,以及从请求中检测是否包含特定字符串(destinationRegistrationId)
if messageIsRequest:
# Get request bytes
request = messageInfo.getRequest()
# Get a IRequestInfo object, useful to work with the request
analyzedRequest = self.helpers.analyzeRequest(request)
headers = list(analyzedRequest.getHeaders())
bodyOffset = int(analyzedRequest.getBodyOffset())
body = request[bodyOffset:]
bodyString = "".join(map(chr,body))
if "destinationRegistrationId" in bodyString:
以下是插件的核心,使用Brida调用scriptSignal.js中定义的changemessage(pp.callexportfunction(‘changemessage‘, args))生成一条新的信息。它会生成一个内容为“pwned”的文本信息
jsonBody = json.loads(bodyString)
uri = 'PYRO:BridaServicePyro@localhost:9999'
pp = Pyro4.Proxy(uri)
args = []
args.append("pwned")
newMessage = pp.callexportfunction('changemessage',args)
pp._pyroRelease()
最后用刚才使用Brida生成的新信息替换原始信息
m = re.search(".content = \"(.?)\".", newMessage)
if m:
newMessage = m.group(1)
jsonBody["messages"][0]["content"] = newMessage
newBodyString = json.dumps(jsonBody)
newBodyString = newBodyString.replace("/", "\/")
newRequest = self.helpers.buildHttpMessage(headers, self.helpers.stringToBytes(newBodyString))
messageInfo.setRequest(newRequest)
参考来源:mediaservice,freebuf小编鸢尾编译,转载请注明来自FreeBuf.com