移动安全-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中登录成功的界面为:
在这里插入图片描述

最后,尝试一下将请求包解密后发送是否能得到正常响应:
在这里插入图片描述

发布了117 篇原创文章 · 获赞 84 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/weixin_39190897/article/details/102691898