移動安全-Brida加解密數據

前言

在測試一些應用的時候(以移動端爲例),會經常發現客戶端和服務端的通訊數據是加密過的(如下圖),在這種情況下,我們如果想繼續測試下去,就得去逆向程序中使用的加密算法,然後寫程序實現它,然後在後續測試中使用它,這種方式需要耗費大量的時間和精力。而 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由以下三部分組成:

  1. Brida.jar爲Burpsuite插件;
  2. bridaServicePyro是用於Frida適配到burpsuite上的python腳本,這一部分存儲在插件中,在執行brida過程中複製到緩存文件夾中;
  3. 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的流程已經分析清楚了。接着可以有兩種思路:

  1. 將Java代碼複製出來,在eclipse實現加解密流程,就可以對傳輸的數據進行解密加密了;
  2. 利用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中登錄成功的界面爲:
在這裏插入圖片描述

最後,嘗試一下將請求包解密後發送是否能得到正常響應:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章