前言
在測試一些應用的時候(以移動端爲例),會經常發現客戶端和服務端的通訊數據是加密過的(如下圖),在這種情況下,我們如果想繼續測試下去,就得去逆向程序中使用的加密算法,然後寫程序實現它,然後在後續測試中使用它,這種方式需要耗費大量的時間和精力。而 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中登錄成功的界面爲:
最後,嘗試一下將請求包解密後發送是否能得到正常響應: