前言
在测试一些应用的时候(以移动端为例),会经常发现客户端和服务端的通讯数据是加密过的(如下图),在这种情况下,我们如果想继续测试下去,就得去逆向程序中使用的加密算法,然后写程序实现它,然后在后续测试中使用它,这种方式需要耗费大量的时间和精力。而 Brida 这款插件的出现简直天降神器。
Brida是BurpSuite的一个插件,它可以将Burp和Frida结合起来使用,可以在 BurpSuite中直接调用目标应用程序中的加/解密函数,这样就可以根据你的需求修改移动端APP与服务器的通信流量。而不用去逆向它,从而节省测试人员的精力。
Brida插件
安装Python 2.7
Brida目前只支持python 2.7。
安装Pyro4
Pyro4作为Burp和Frida的接口,作用是使Python和Java能够兼容使用。我们可以使用 pip安装:pip install pyro4
。
安装Frida框架
使用Brida之前,需要安装Frida,参考前面写的文章 移动安全-Hook技术。
安装Brida
安装完Brida插件,点击Start server,如下图说明环境安装完成:
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的包名),比如com.atrue.sbw.Order |
Brida的操作按钮:
按钮 | 作用 |
---|---|
Start server | 在Burp和Frida之间启动桥接服务器(它在后台运行python或者Pyro RPC服务) |
kill server | 关闭桥接服务器 |
Spawn application | 在设备上启动应用程序,并且在其中注入Frida JS脚本 |
Reload JS | 在不重启程序的情况下重新加载js脚本 |
Save settings to file | 将设置保存到文件中 |
Load settings from file | 从指定文件中导入设置 |
Execute Method | 执行“execute method”方法 |
APK实战讲解
测试环境搭建
1、APK客户端环境
本文提供一个自己写的eseBrida.apk,下载的安装包将包含以下内容:
咱们拿到apk文件,使用adb命令安装到手机。因为我这里是测试版本,所以安装需要加-t参数,adb install -t esebrida.apk
,安装运行如下:
应用中有个设置按钮,可设置服务器地址(http://192.168.62.101:8088/AndroidLogin.php
):
2、Web服务器环境
这里利用phpstudy在www目录下存放并运行 AndroidLogin.php,
然后启动 phpStudy 2018:
接着在浏览器访问服务器地址 http://192.168.62.101:8088/AndroidLogin.php
:
3、代理环境设置
手机WIFI代理设置:
在APK中输入用户名和密码,点击登录,并在PC端使用BurpSuite抓取数据包:
这里的数据是密文传输,不利于测试,这里想对算法进行解密,然后在实现加密传输到服务器端。
APK源码分析
在jadx-gui中打开eseBrida.apk文件,分析源码:
上层定位,发现加密算法的秘钥硬编码,如下:
自此,APK的流程已经分析清楚了。接着可以有两种思路:
- 将Java代码复制出来,在eclipse实现加解密流程,就可以对传输的数据进行解密加密了;
- 利用Brida调用APK自身加密解密函数,一键实现加密解密操作。
自然,方法2相对方法1要简单且方便,所有便有这篇文章。
此处咱们顺便看下服务器端的源码AndroidLogin.php:
<?php
//加解密
class Security {
private static function pkcs5_pad ($text, $blocksize) {
$pad = $blocksize - (strlen($text) % $blocksize);
return $text . str_repeat(chr($pad), $pad);
}
public static function encrypt($input, $key) {
$size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
$input = Security::pkcs5_pad($input, $size);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $key, $iv);
$data = mcrypt_generic($td, $input);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$data = base64_encode($data);
return $data;
}
public static function decrypt($sStr, $sKey) {
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128,$sKey,base64_decode($sStr),MCRYPT_MODE_CBC);
$dec_s = strlen($decrypted);
$padding = ord($decrypted[$dec_s-1]);
$decrypted = substr($decrypted, 0, -$padding);
return $decrypted;
}
}
error_reporting(0);//加上error_reporting(0);就不会弹出警告了
//post方式 秘钥与Android端一致
$privateKey = "9876543210123456";
//获取Android 端传输来的json数据
$json = file_get_contents("php://input");
$data = json_decode($json, true);
$resname = $data['username'];
$respasswd = $data['password'];
$name = Security::decrypt($resname, $privateKey);
$passwd = Security::decrypt($respasswd, $privateKey);
//echo "------".$name."--------".$passwd."-------\n";
if($name=="admin" and $passwd =="654321"){
$data = Security::encrypt("login_success", $privateKey);
echo $data;
}else{
$data1 = Security::encrypt("error", $privateKey);
echo $data1;
}
?>
再来看看客户端中发送登录请求的源码:
package com.ese.http.httpUtils;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.alibaba.fastjson.JSON;
import com.ese.http.BuildConfig;
import com.ese.http.config.eseeServer;
import com.ese.http.encrypt.AesEncryptionBase64;
import com.ese.http.entity.UserInfo;
import com.ese.http.ui.welcome;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class SendGETorPOST {
public void executePOST(Context context, UserInfo userInfo, String Url) {
String str = eseeServer.AesKey;
HttpURLConnection httpURLConnection = null;
InputStream inputStream = null;
String allUrl = Url;
try {
Log.e("eseTools", allUrl);
if (allUrl == null || allUrl == BuildConfig.FLAVOR || allUrl.isEmpty()) {
allUrl = eseeServer.urlPOST;
}
HttpURLConnection httpURLConnection2 = (HttpURLConnection) new URL(allUrl).openConnection();
httpURLConnection2.setRequestMethod("POST");
httpURLConnection2.setRequestProperty("Content-Type", "application/json");
httpURLConnection2.getOutputStream();
httpURLConnection2.connect();
OutputStream outputStream = httpURLConnection2.getOutputStream();
String username = AesEncryptionBase64.encrypt(str, userInfo.getUsername());
userInfo.setPassword(AesEncryptionBase64.encrypt(str, userInfo.getPassword()));
userInfo.setUsername(username);
outputStream.write(JSON.toJSONString(userInfo).getBytes());
if (httpURLConnection2.getResponseCode() == 200) {
inputStream = httpURLConnection2.getInputStream();
String res = AesEncryptionBase64.decrypt(str, new ReadInputStream().readInputStream(inputStream));
if (res.contains("success")) {
Intent intent = new Intent(context, welcome.class);
intent.putExtra("server", res);
context.startActivity(intent);
}
}
if (httpURLConnection2 != null) {
httpURLConnection2.disconnect();
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (Exception e2) {
e2.printStackTrace();
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
if (inputStream != null) {
inputStream.close();
}
} catch (Throwable th) {
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e3) {
e3.printStackTrace();
}
}
throw th;
}
}
}
寻找加解密函数
相比较算法还原,相信算法在哪里这个问题对各位大黑阔来说应该不是什么多大的问题。不过,Brida提供了一个很方便的操作:插桩。
在Brida中切换到Analyze Binary,点击Load tree,然后可能会卡一会,因为在加载类列表,加载完点开Java,可以看到这个进程里的所有类,一般我在这就直接搜crypt,然后他就会卡更久,因为这个智障把SO里的导出导入函数也搜了一个遍,最后可以右键->Inspect,把可疑的给hook住。
然后让他发包,如果方法被调用了就会打出日志,运气好的话直接就找到他的加解密函数了,运气不好的话…不存在的,我运气一直很好。(我信你个鬼,你个糟老头子坏的很,大部分情况下还是得自己看代码找算法位置,配合着来。
编写Brida js脚本
1、运行Frida
首先下载脚本文件 startFridaService.py:
# -*- coding: utf-8 -*-
# python2.7
import sys
import subprocess
forward1 = "adb forward tcp:27042 tcp:27042"
forward2 = "adb forward tcp:27043 tcp:27043"
cmd = ["adb shell","su","cd /data/local/tmp","./frida-server-12.6.18"]
def Forward1():
s = subprocess.Popen(str(forward1+"\r\n"), stderr=subprocess.PIPE, stdin=subprocess.PIPE,stdout=subprocess.PIPE, shell=True)
stderrinfo, stdoutinfo = s.communicate()
return s.returncode
def Forward2():
s = subprocess.Popen(str(forward2+"\r\n"), stderr=subprocess.PIPE, stdin=subprocess.PIPE,stdout=subprocess.PIPE, shell=True)
stderrinfo, stdoutinfo = s.communicate()
return s.returncode
def Run():
s = subprocess.Popen(str(cmd[0]+"\r\n"), stderr=subprocess.PIPE, stdin=subprocess.PIPE,stdout=subprocess.PIPE, shell=True)
for i in range(1,len(cmd)):
s.stdin.write(str(cmd[i]+"\r\n"))
s.stdin.flush()
stderrinfo, stdoutinfo = s.communicate()
return s.returncode
if __name__ == "__main__":
Forward1()
print("adb forward tcp:27042 tcp:27042")
Forward2()
print("adb forward tcp:27043 tcp:27043")
print("Android server--->./frida-server64")
print("success-->frida-ps -R")
Run()
然后在CMD中一键启动并运行Frida:
2、先给一个Brida简单的test.js框架:
'use strict';
// 1 - FRIDA EXPORTS
rpc.exports = {
exportedFunction: function() {
},
contextcustom1: function(message) {
console.log("Brida start :--->");
return "Brida test1";
},
getplatform: function () {
if (Java.available) {
return 0;
} else if (ObjC.available) {
return 1;
} else {
return 2;
}
}
}
3、运行Brida,成功运行如下:4、测试方法contextcustom1:
5、编写Brida调用encrypt加密函数:
'use strict';
// 1 - FRIDA EXPORTS
rpc.exports = {
exportedFunction: function() {
},
contextcustom1: function(message) {
console.log("Brida start :--->");
return "Brida test1";
},
contextcustom2: 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;
},
getplatform: function () {
if (Java.available) {
return 0;
} else if (ObjC.available) {
return 1;
} else {
return 2;
}
}
}
6、执行方法contextcustom2
通过签名抓取的数据包, 发现加密数据一致,证实调用APK加密算法。
7、Burpsuite右键菜单
发现4个方法与请求数据包与返回数据包相互一 一对应:
- Brida Custom 1:通过右键菜单进行访问,它会调用contextcustom1 JS脚本;
- Brida Custom 2:通过右键菜单进行访问,它会调用contextcustom2 JS脚本;
- Brida Custom 3:通过右键菜单进行访问,它会调用contextcustom3 JS脚本;
- Brida Custom 4:通过右键菜单进行访问,它会调用contextcustom4 JS脚本。
8、编写对应插件eseScript.js脚本
注意:加载其他脚本,需要重启Burpsuite。
'use strict';
// 1 - FRIDA EXPORTS
rpc.exports = {
exportedFunction: function() {
},
//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);
},
getplatform: function () {
if (Java.available) {
return 0;
} else if (ObjC.available) {
return 1;
} else {
return 2;
}
}
}
// 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;
}
【注意】函数接收的参数和返回的数据都是以 16进制编码的,所以我们使用时要先对他们进行16进制解码,然后返回的时候在进行16进制编码。
rpc.exports的每一项是一个函数, :
前面的为函数名(全部为小写),比如 contextcustom1, 后面为函数的具体内容,rpc.exports中的函数都可以被 Brida调用。该脚本会被Frida注入到我们在 Brida中指定的进程中所以我们可以直接使用 Frida的API。
9、运行效果
(1)请求包解密:
(2)请求包加密:
(3)响应包解密:
在可编辑视图的情况下,将直接替换为JS执行的结果。在不可编辑的视图上,它将生成一个带有结果的消息框。
10、当你输入 账号:admin 密码:654321,登录成功:
APK中登录成功的界面为:
最后,尝试一下将请求包解密后发送是否能得到正常响应: