Brida

Brida简介

Brida是BurpSuite的一个插件,它可以将Burp和Frida结合起来使用,可以在 BurpSuite中直接调用目标应用程序中的加/解密函数,这样就可以根据你的需求修改移动端APP与服务器的通信流量。而不用去逆向它,从而节省测试人员的精力。

https://github.com/federicodotta/Brida

Brida安装

Brida目前只支持python 2.7。

1
2
3
pip install frida
pip install frida-tools
pip install pyro4

Brida可以直接从BurpSuite中安装

1580895376662

Brida功能模块

Brida控制台

填好配置项后先点击Start Server然后在点击Spawn application

1580895596968
Brida由以下三部分组成:

  • Brida.jar为Burpsuite插件;

  • bridaServicePyro是用于Frida适配到burpsuite上的python脚本,这一部分存储在插件中,在执行brida过程中复制到缓存文件夹中;

  • script.js是要注入到目标应用程序的javascript脚本,它会通过Frida带有的rpc.exports功能将信息返回到拓展程序中,同时该script.js脚本会被Frida注入到我们在 Brida中指定的进程中所以我们可以直接使用 Frida的API。

Brida的几个配置参数:

配置参数 说明
Python binary path 即Python可执行程序路径,用于启动Pyro服务
Pyro host, Pyro port 即Pyro 服务的主机以及端口,可以保持默认
Frida JS file path 需要注入的Frida脚本存放的位置
Application ID 目标进程名(APP的包名)
Frida Remote”/“Frida Loca 如果您使用的是Frida USB操作模式,则必须选择“ Frida Local”。如果使用端口转发模式,则必须选择“ Frida Remote”。

JS编辑器

可以实时对hook脚本进行编辑。

1580897477541

Analyze Binary

切换到Analyze Binary,点击Load tree,然后可能会卡一会,因为在加载类列表,加载完点开Java,可以看到这个进程里的所有类,可在下面的搜索框直接搜crypt来找加解密类。

1580899345827

其功能和TraceView 相似,我们在操作APP的时候,他会打印出所有加载的类和方法。

点开java下拉找到app包名通过一个一个插桩 然后进行提交测试 看响应框是否会有信息。

1580901009778

通过插桩可以看到此方法前后的输入值与返回值,同时可以通过change return value修改方法返回值。

Execute method

在通用js脚本中的rpc.exports里帮写了四个contextcustom,这四个是给右键菜单预留的,contextcustom1、contextcustom2会出现在repeater等模块中request的右键菜单,contextcustom2、contextcustom3则会出现在response的右键菜单。主要就是为了实现手动加解密的功能。这四个函数接收的参数都是hex形式的,所以返回的时候也要转成hex再传出去。

1580901402956

例如:我们掉模块中调用函数contextcustom1返回值为上面代码中的6566。

1580901465948

Generate stubs

这里可以生成java/python代码,METHOD_NAME你要调用hook脚本的函数,即如上的contextcustom1,另外一处标红为参数列表。

1580902444668

我们要实现的功能只是要让Repeater, Intruder and Scanner模块能对数据进行自动加/解密。

如下代码是brida官方文档给出的通用代码,我们只需修改调用的函数名和参数并对相关的加密解密数据进行处理即可。

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
package burp;

import java.io.PrintWriter;
import java.util.Arrays;
import org.apache.commons.lang3.ArrayUtils;
import net.razorvine.pyro.PyroProxy;
import net.razorvine.pyro.PyroURI;

public class BurpExtender implements IBurpExtender, IHttpListener {
private PrintWriter stdout;
private PrintWriter stderr;
private IBurpExtenderCallbacks callbacks;
private IExtensionHelpers helpers;

public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
// Set the name of the extension
callbacks.setExtensionName("Brida Demo Search Plugin");
// Initialize stdout and stderr (configurable from the Extension pane)
stdout = new PrintWriter(callbacks.getStdout(), true);
stderr = new PrintWriter(callbacks.getStderr(), true);
// Save references to useful objects
this.callbacks = callbacks;
this.helpers = callbacks.getHelpers();
// Register ourselves as an HttpListener, in this way all requests and responses will be forwarded to us
callbacks.registerHttpListener(this);
}

public void processHttpMessage(int toolFlag, boolean messageIsRequest, IHttpRequestResponse messageInfo) {

// Process only Repeater, Scanner and Intruder requests
if(toolFlag == IBurpExtenderCallbacks.TOOL_SCANNER ||
toolFlag == IBurpExtenderCallbacks.TOOL_REPEATER ||
toolFlag == IBurpExtenderCallbacks.TOOL_INTRUDER) {

// Modify "test" parameter of Repeater requests
if(messageIsRequest) {
// Get request bytes
byte[] request = messageInfo.getRequest();
// Get a IRequestInfo object, useful to work with the request
IRequestInfo requestInfo = helpers.analyzeRequest(request);
// Get "test" parameter
IParameter contentParameter = helpers.getRequestParameter(request, "content");
if(contentParameter != null) {
String urlDecodedContentParameterValue = helpers.urlDecode(contentParameter.getValue());
String ret = "";
// Ask Brida to encrypt our attack vector
String pyroUrl = "PYRO:BridaServicePyro@localhost:9999";
try {
PyroProxy pp = new PyroProxy(new PyroURI(pyroUrl));
ret = (String)pp.call("callexportfunction","encryptrequest",new String[]{urlDecodedContentParameterValue});
pp.close();
} catch(Exception e) {
stderr.println(e.toString());
StackTraceElement[] exceptionElements = e.getStackTrace();
for(int i=0; i< exceptionElements.length; i++) {
stderr.println(exceptionElements[i].toString());
}
}
// Create the new parameter
IParameter newTestParameter = helpers.buildParameter(contentParameter.getName(), helpers.urlEncode(ret), contentParameter.getType());
// Create the new request with the updated parameter
byte[] newRequest = helpers.updateParameter(request, newTestParameter);
// Update the messageInfo object with the modified request (otherwise the request remains the old one)
messageInfo.setRequest(newRequest);
}

// Response
} else {
// Get request bytes in order to check if the request contain "content" parameter
byte[] request = messageInfo.getRequest();
IRequestInfo requestInfo = helpers.analyzeRequest(request);
IParameter contentParameter = helpers.getRequestParameter(request, "content");
if(contentParameter != null) {
// Get response bytes
byte[] response = messageInfo.getResponse();
// Get a IResponseInfo object, useful to work with the request
IResponseInfo responseInfo = helpers.analyzeResponse(response);
// Get the offset of the body
int bodyOffset = responseInfo.getBodyOffset();
// Get the body (byte array and String)
byte[] body = Arrays.copyOfRange(response, bodyOffset, response.length);
String bodyString = helpers.bytesToString(body);
String ret = "";
// Ask Brida to decrypt the response
String pyroUrl = "PYRO:BridaServicePyro@localhost:9999";
try {
PyroProxy pp = new PyroProxy(new PyroURI(pyroUrl));
ret = (String)pp.call("callexportfunction","decryptresponse",new String[]{bodyString});
pp.close();
} catch(Exception e) {
stderr.println(e.toString());
StackTraceElement[] exceptionElements = e.getStackTrace();
for(int i=0; i< exceptionElements.length; i++) {
stderr.println(exceptionElements[i].toString());
}
}
// Update the messageInfo object with the modified request (otherwise the request remains the old one)
byte[] newResponse = ArrayUtils.addAll(Arrays.copyOfRange(response, 0, bodyOffset),ret.getBytes());
messageInfo.setResponse(newResponse);
}
}
}
}
}

实战

如图app对数据进行了加密。

1580902822525

这里我们可以通过直接分析APK或者通过Hook来看他是使用的什么加密,且调用的是那个类的那个方法。

逆向APP可以知道调用的类和方法。

1580903281942

同时得到加密密钥为:9876543210123456

我们可以利用Execute method模块进行函数调用测试。修改contextcustom1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
contextcustom1: function(message) {
console.log("Brida Java Starting script ---->ok");
var enc;
Java.perform(function () {
try {
var key = "9876543210123456";
var text = "admin";
//hook class
var AesEncryptionBase64 = Java.use('com.ese.http.encrypt.AesEncryptionBase64');
console.log("Brida start : encrypt before--->"+text);
//hook method
enc = AesEncryptionBase64.encrypt(key,text);
console.log("Brida start : encrypt after--->"+enc);

} catch (error) {
console.log("[!]Exception:" + error.message);
}
});
return enc;
},

1580903583962

如上是hookcom.ese.http.encrypt.AesEncryptionBase64类的encrypt方法,并传入key值和加密内容。

1580904137989

可得到加密后的密文。

4个方法与请求数据包与返回数据包相互一 一对应:

  • Brida Custom 1:通过右键菜单进行访问,它会调用contextcustom1 JS脚本;
  • Brida Custom 2:通过右键菜单进行访问,它会调用contextcustom2 JS脚本;
  • Brida Custom 3:通过右键菜单进行访问,它会调用contextcustom3 JS脚本;
  • Brida Custom 4:通过右键菜单进行访问,它会调用contextcustom4 JS脚本。

编写加密解密脚本:(函数接收的参数和返回的数据都是以 16进制编码的,所以我们使用时要先对他们进行16进制解码,然后返回的时候在进行16进制编码。)

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
//AesEncryptionBase64 encrypt
contextcustom1: function (message) {
console.log("Brida start :0--->" + message);
var data = hexToString(message)
console.log("Brida start :1--->" + data);
var enc;
Java.perform(function () {
try {
var key = "9876543210123456";
var text = data;
//hook class
var AesEncryptionBase64 = Java.use('com.ese.http.encrypt.AesEncryptionBase64');
console.log("Brida start : AesEncryptionBase64 ---> success");
console.log("Brida start : encrypt before--->"+text);
//hook method
enc = AesEncryptionBase64.encrypt(key,text);
console.log("Brida start : encrypt after--->"+enc);

} catch (error) {
console.log("[!]Exception:" + error.message);
}
});
return stringToHex(enc);
},

//AesEncryptionBase64 decrypt
contextcustom2: function (message) {
console.log("Brida start :0--->" + message);
var data = hexToString(message)
console.log("Brida start :1--->" + data);
var text;
Java.perform(function () {
try {
var key = "9876543210123456";
var enc = data;
//hook class
var AesEncryptionBase64 = Java.use('com.ese.http.encrypt.AesEncryptionBase64');
console.log("Brida start : AesEncryptionBase64 ---> success");
console.log("Brida start : decrypt before--->"+enc);
//hook method
text = AesEncryptionBase64.decrypt(key,enc);
console.log("Brida start : decrypt after--->"+text);
} catch (error) {
console.log("[!]Exception:" + error.message);
}
});
console.log("Brida start : decrypt after--->"+stringToHex(text));
return stringToHex(text);
},

//AesEncryptionBase64 encrypt
contextcustom3: function (message) {
console.log("Brida start :0--->" + message);
var data = hexToString(message)
console.log("Brida start :1--->" + data);
var enc;
Java.perform(function () {
try {
var key = "9876543210123456";
var text = data;
//hook class
var AesEncryptionBase64 = Java.use('com.ese.http.encrypt.AesEncryptionBase64');
console.log("Brida start : AesEncryptionBase64 ---> success");
console.log("Brida start : encrypt before--->"+text);
//hook method
enc = AesEncryptionBase64.encrypt(key,text);
console.log("Brida start : encrypt after--->"+enc);

} catch (error) {
console.log("[!]Exception:" + error.message);
}
});
return stringToHex(enc);
},

//AesEncryptionBase64 decrypt
contextcustom4: function (message) {
console.log("Brida start :0--->" + message);
var data = hexToString(message)
console.log("Brida start :1--->" + data);
var text;
Java.perform(function () {
try {
var key = "9876543210123456";
var enc = data;
//hook class
var AesEncryptionBase64 = Java.use('com.ese.http.encrypt.AesEncryptionBase64');
console.log("Brida start : AesEncryptionBase64 ---> success");
console.log("Brida start : decrypt before--->"+enc);
//hook method
text = AesEncryptionBase64.decrypt(key,enc);
console.log("Brida start : decrypt after--->"+text);
} catch (error) {
console.log("[!]Exception:" + error.message);
}
});
console.log("Brida start : decrypt after--->"+stringToHex(text));
return stringToHex(text);
}

替换脚本里的contextcustom函数。

这样就能在burpsuite中进行右键菜单转换了。

1580905769487

接下来实现自定义插件实现在reperterscannerintruder模板中自动加密请求包和解密返回包。

为了让处理加解密数据更方便,所以就改变了一下服务端加解密的方式。

1580972196130

这里我用的python编写插件。所以需要安装jython

推荐安装最新版的jython-installer-2.7.2b3.jar

https://repo1.maven.org/maven2/org/python/jython-installer/2.7.2b3/jython-installer-2.7.2b3.jar

同时使用jython自带的pip安装Pyro4

1
pip install pyro4

burp中配置最新安装好pyro4的路径。

1580969785849

然后用python编写burp插件。

申明一个类,继承于IBurpExtenderIHttpListener

1
`class` `BurpExtender(IBurpExtender, IHttpListener)`

重写registerExtenderCallbacksprocessHttpMessage:

1
2
3
4
5
def registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()
self._callbacks.setExtensionName("fuck encrypt by Uknow!")
callbacks.registerHttpListener(self)
1
2
3
4
5
6
7
8
9
10
11
12
13
def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
# tool https://portswigger.net/burp/extender/api/constant-values.html#burp.IBurpExtenderCallbacks
if toolFlag == 64 or toolFlag == 16 or toolFlag == 32: # TOOL_REPEATER TOOL_SCANNER TOOL_INTRUDER
request = messageInfo.getRequest()
analyzedRequest = self._helpers.analyzeRequest(request)
headers = analyzedRequest.getHeaders()
if not messageIsRequest:
response = messageInfo.getResponse()
analyzedResponse = self._helpers.analyzeResponse(response)
messageInfo.setResponse(self.decrypt(analyzedResponse, response))
# elif toolFlag != 4:
else:
messageInfo.setRequest(self.encrypt(analyzedRequest, request))

对着toolFlag一顿if是为了过滤Burp的模块,判断他是从哪过来的,这里是过滤了三个:reperterscannerintruder,抓包过来的无需处理,如果你处理了那APP就不能正常收发数据了。
self.decryptself.encrypt就是去跟Brida开的端口交换数据,处理加解密:

def decrypt(self, RequestOrResponse,raw):
    uri = 'PYRO:BridaServicePyro@localhost:9999'
    pp = Pyro4.Proxy(uri)
    body = raw[RequestOrResponse.getBodyOffset():]
    newbody = body.tostring()
    args = []
    args.append(newbody.encode('hex'))
    ret = pp.callexportfunction('contextcustom4',args)
    ret = self._helpers.bytesToString(ret).decode('hex')
    return self._helpers.buildHttpMessage(RequestOrResponse.getHeaders(), ret)

def encrypt(self, RequestOrResponse,raw):
    uri = 'PYRO:BridaServicePyro@localhost:9999'
    pp = Pyro4.Proxy(uri)
    body = raw[RequestOrResponse.getBodyOffset():]
    newbody = body.tostring()
    args = []
    args.append(newbody.encode('hex'))
    ret = pp.callexportfunction('contextcustom1',args)
    ret = self._helpers.bytesToString(ret).decode('hex')
    return self._helpers.buildHttpMessage(RequestOrResponse.getHeaders(), ret)

callexportfunction来调用你刚才js脚本里rpc.exports里的函数,参数是函数名和参数列表。

完整代码如下:

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
from burp import IBurpExtender
from burp import IHttpListener
from burp import IHttpRequestResponse
from burp import IResponseInfo
from burp import IProxyListener
import Pyro4


print 'uknowsec.cn'

class BurpExtender(IBurpExtender,IHttpListener,IHttpRequestResponse, IProxyListener):
def registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks
self._helpers = callbacks.getHelpers()
self._callbacks.setExtensionName("fuck encrypt by Uknow!")
callbacks.registerHttpListener(self)

def decrypt(self, RequestOrResponse,raw):
uri = 'PYRO:BridaServicePyro@localhost:9999'
pp = Pyro4.Proxy(uri)
body = raw[RequestOrResponse.getBodyOffset():]
newbody = body.tostring()
args = []
args.append(newbody.encode('hex'))
ret = pp.callexportfunction('contextcustom4',args)
ret = self._helpers.bytesToString(ret).decode('hex')
return self._helpers.buildHttpMessage(RequestOrResponse.getHeaders(), ret)

def encrypt(self, RequestOrResponse,raw):
uri = 'PYRO:BridaServicePyro@localhost:9999'
pp = Pyro4.Proxy(uri)
body = raw[RequestOrResponse.getBodyOffset():]
newbody = body.tostring()
args = []
args.append(newbody.encode('hex'))
ret = pp.callexportfunction('contextcustom1',args)
ret = self._helpers.bytesToString(ret).decode('hex')
return self._helpers.buildHttpMessage(RequestOrResponse.getHeaders(), ret)

def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
# tool https://portswigger.net/burp/extender/api/constant-values.html#burp.IBurpExtenderCallbacks
if toolFlag == 64 or toolFlag == 16 or toolFlag == 32: # TOOL_REPEATER TOOL_SCANNER TOOL_INTRUDER
request = messageInfo.getRequest()
analyzedRequest = self._helpers.analyzeRequest(request)
headers = analyzedRequest.getHeaders()
if not messageIsRequest:
response = messageInfo.getResponse()
analyzedResponse = self._helpers.analyzeResponse(response)
messageInfo.setResponse(self.decrypt(analyzedResponse, response))
# elif toolFlag != 4:
else:
messageInfo.setRequest(self.encrypt(analyzedRequest, request))

然后加载就可以在reperterscannerintruder模块中实现自动加密解密了。操作如下图。

1580905769487

比如如上场景可直接进行爆破。

1580905769487

自动以插件也可以使用Java编写,可以根据文章前文给出的官方文档给出的示例进行修改。

lxhToolHTTPDecrypt

https://github.com/lyxhh/lxhToolHTTPDecrypt

HTTP Decrypt 提供了Finds Hooks模块,可以在不逆向不脱壳的情况下快速的找到APP所使用的加解密算法,而toBurp模块提供了直接使用APP内的方法进行加解密,而不需自己动手敲代码,对于整体POST加密更是提供了自动化加解密功能,可以实现Burp一条龙,Burp Scanner ,Intruder自动加解密。

  1. python3 app.py
  2. Android_frida_server 运行
  3. 转发frida端口。
  4. 打开HTTP Decrypt页面,如果在Start界面出现应用包名列表信息则可正常使用其他功能,如果不行,刷新一下看看控制台出现的信息。

1580977948133

Hooks

填写字符串,将类名与你填写的字符串匹配,并Hooks类下的所有方法,hook多个类名,回车换行。

在APP中进行操作,尽量进行多次操作,让脚本hook到所有方法。

如下图,这里找到了加密方法为com.ese.http.encrypt.AesEncryptionBase64.encrypt

1580978070314

Stack

像Brida一样也可以显示Hooks打印的堆栈。

1580978156034

Finds

根据字符串,查找类,匹配到类,将类下的方法都打印出来。多个查找回车换行继续写。提供了过滤机制。

1580979123411

匹配的不是很准确,可能是我姿势有问题,但是我们可以自己去反编译或者通过hook来判断哪个是加解密方法。

toBurp

添入我们得到加解密方法。

1580979268369

然后点击Confirm按钮,之后再点击Add按钮,将方法的信息添加到左边的info里面去,因为加解密方法有两个参数,无法使用HTTPDecrypt自动的生成的脚本(自动生成的脚本只能适应一个参数),所以 我们需要自己写一些代码,接下来我们点击Generate export static script按钮。(因为方法类型是静态的所以选择static按钮,动态的选择instance,如果你很熟悉frida,点哪个都无所谓。)

HTTPDecrypt会将一些代码生成在Custom选项卡中,如下:

1580979877144

像Brida修改代码,添加key值。encrypt方法的第一个参数是一个固定值key,通过反编译和hook分析可以得到。第二个参数为加密内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
'use strict';

var rpc_result = null;
var rpc_result_ios = null;
rpc.exports = {


tag3c57a19c2806a2b4d3f028f23c7d2f4f02: function(arg0, arg1){
Java.perform(function () {
try{
var key = "9876543210123456";
// var context = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
var AesEncryptionBase64bc9333b9adb0ceb7cc6c929d900e3365 = Java.use("com.ese.http.encrypt.AesEncryptionBase64");
rpc_result = AesEncryptionBase64bc9333b9adb0ceb7cc6c929d900e3365.encrypt(key, arg0);
// send(JSON.stringify({"aa":"bb","aa1":"bbb"})+'-cusoto0oom0sc0ri0pt-')
}catch(e){send("tag3c57a19c2806a2b4d3f028f23c7d2f4f02, " + e + "-er00roo000r-")}
});
return rpc_result;
},
// Added Function


}

同样的方法修改解密方法。如图

1580981397792

loadscript加载脚本。

这样我们可以得到两个方法名,和brida中的contextcustom一样。

1580981451928

然后我们将这两个方法名添入burp插件配置项里

1580981619949

同样就可以通过右键菜单栏进行加解密了。

1580905769487

他也具有自动加解密的功能,但是目前只能对整个请求包进行加解密不能匹配到需要加解密的数据。

通过burp发给服务端的数据可以看到,在看起自动加解密后他把整个请求body进行了加密,跟服务端验证不符合,所以存在一定的局限性。

1580982022808

通过修改服务端代码,和请求包格式。

我们改成客户端把整个请求包body进行加密,给后端验证的方法。体验一下自动加解密。

1580905769487

对比总结

  • Brida的插桩功能好像有点不好使,lxhToolHTTPDecrypt的hook功能对APP进行多次操作后,效果还是挺好的。但定位加解密方法还是要人工的去分析,工具只是辅助作用。
  • Brida和lxhToolHTTPDecrypt的右键菜单功能其实是相同的原理,绑定函数去调用函数进行加解密得到返回值
  • Brida灵活性强一点,可以自定义编写插件。但是这也是比较麻烦的一点,对于不熟悉BURP插件编写的使用者是一个难点,lxhToolHTTPDecrypt方便一点,他不需要自己去写插件对数据进行处理,但是他只能对整个body进行自动加解密,存在一定局限性。而在Brida中可以在编写脚本的过程中对需要加解密的内容进行灵活的操作,lxhToolHTTPDecrypt目前还在不断的更新,可能作者后面会解决这个问题。