如何在 Android 上優(yōu)雅的進(jìn)行 HTTPS 明文抓包
前言
Android系統(tǒng)上抓包HTTPS是不是越來(lái)越難了?高版本無(wú)法添加CA證書(shū),抓包軟件依賴(lài)太多,VPN模式、或HOOK程序時(shí),會(huì)被APP檢測(cè)到。對(duì)抗成本愈加增高。有什么萬(wàn)能的工具嗎?
是的,eCapture for Android[1]來(lái)了。以后在Android上抓HTTPS通訊包,再也不用安裝CA證書(shū)了,再也不用下載一堆python依賴(lài)環(huán)境了,再也不用重打包ssl類(lèi)庫(kù)了,再也不用改一堆手機(jī)參數(shù)了,一鍵啟用,簡(jiǎn)單明了。
eCapture簡(jiǎn)介
eCapture是一款無(wú)需CA證書(shū)即可抓獲HTTPS明文的軟件。支持pcapng格式,支持Wireshark直接查看?;趀BPF技術(shù),僅需root權(quán)限,即可一鍵抓包。eCapture中文名旁觀者,即 當(dāng)局者迷,旁觀者清。
2022年年初上海疫情期間,筆者開(kāi)始編寫(xiě)并開(kāi)源[2],至今已經(jīng)半年,GitHub上已經(jīng) 4200個(gè)星星。
eCapture是基于eBPF技術(shù)實(shí)現(xiàn)的抓包軟件,依賴(lài)系統(tǒng)內(nèi)核是否支持eBPF。目前支持在操作系統(tǒng)上,支持了X86_64\ARM64的Linux kernel 4.18以上內(nèi)核,支持了ARM64 Android(Linux) kernel 5.4以上版本。最新版是在2022年9月9日發(fā)布的v0.4.3版本。
演示視頻
下載后,一條命令啟動(dòng),干凈利索。./ecapture tls -w ecapture.pcapng
先看演示視頻,演示環(huán)境為Ubuntu 21.04、Android 5.4 (Pixel 6)。
模塊功能
eCapture支持tls、bash、mysqld、postgres等模塊的信息提取與捕獲。本文僅討論tls這個(gè)HTTPS/TLS明文捕獲模塊。
加密通訊明文捕獲--tls模塊
tls模塊在加密通訊類(lèi)庫(kù)上,支持了openssl、gnutls、nspr/nss、boringssl等類(lèi)庫(kù)。但在Android上,pcapng模式只支持boringssl,文本模式則都支持。
如何使用eCapture
環(huán)境依賴(lài)
- 操作系統(tǒng) Linux kernel 4.18以上,Android kernel 5.4以上。
- 支持BPF,可選支持BTF(eCapture版本不同)
- root權(quán)限
版本選擇
BPF CO-RE[3]特性為BTF通用的格式,用于做跨內(nèi)核版本兼容。有的Android手機(jī)是沒(méi)開(kāi)啟BTF??梢圆榭聪到y(tǒng)配置確認(rèn),CONFIG_DEBUG_INFO_BTF=y為開(kāi)啟;CONFIG_DEBUG_INFO_BTF=n為關(guān)閉;其他為不支持BPF,無(wú)法使用eCapture。
cfc4n@vm-server:~$# cat /boot/config-`uname -r` | grep CONFIG_DEBUG_INFO_BTF
CONFIG_DEBUG_INFO_BTF=y
Android系統(tǒng)上,config是gzip壓縮的,且配置文件目錄也變了??墒褂脄cat /proc/config.gz命令代替。
eCapture默認(rèn)發(fā)行了支持CO-RE的ELF程序。Android版會(huì)發(fā)行一個(gè)5.4內(nèi)核不支持BTF(即沒(méi)有CO-RE)的版本。下載后,可以通過(guò)./ecapture -v確認(rèn)。非CO-RE版本的version信息中包含編譯時(shí)的內(nèi)核版本。
# no CO-RE
eCapture version: linux_aarch64:0.4.2-20220906-fb34467:5.4.0-104-generic
# CO-RE
eCapture version: linux_aarch64:0.4.2-20220906-fb34467:[CORE]
若版本不符合自己需求,可以自行編譯,步驟見(jiàn)文末。
全局參數(shù)介紹
全局參數(shù)重點(diǎn)看如下幾個(gè):
root@vm-server:/home/cfc4n/# ecapture -h
--hex[=false] print byte strings as hex encoded strings
-l, --log-file="" -l save the packets to file
-p, --pid=0 if pid is 0 then we target all pids
-u, --uid=0 if uid is 0 then we target all users
- --hex 用于stdout輸出場(chǎng)景,展示結(jié)果的十六進(jìn)制,用于查看非ASCII字符,在內(nèi)容加密、編碼的場(chǎng)景特別有必要。
- -l, --log-file= 保存結(jié)果的文件路徑。
- -p, --pid=0 捕獲的目標(biāo)進(jìn)程,默認(rèn)為0,則捕獲所有進(jìn)程。
- -u, --uid=0 捕獲的目標(biāo)用戶(hù),默認(rèn)為0,則捕獲所有用戶(hù),對(duì)Android來(lái)說(shuō),是很需要的參數(shù)。
模塊參數(shù)
root@vm-server:/home/cfc4n/project/ssldump# bin/ecapture tls -h
OPTIONS:
--curl="" curl or wget file path, use to dectet openssl.so path, default:/usr/bin/curl
--firefox="" firefox file path, default: /usr/lib/firefox/firefox.
--gnutls="" libgnutls.so file path, will automatically find it from curl default.
--gobin="" path to binary built with Go toolchain.
-h, --help[=false] help for tls
-i, --ifname="" (TC Classifier) Interface name on which the probe will be attached.
--libssl="" libssl.so file path, will automatically find it from curl default.
--nspr="" libnspr44.so file path, will automatically find it from curl default.
--port=443 port number to capture, default:443.
--pthread="" libpthread.so file path, use to hook connect to capture socket FD.will automatically find it from curl.
--wget="" wget file path, default: /usr/bin/wget.
-w, --write="" write the raw packets to file as pcapng format.
-i參數(shù)
-i參數(shù)為網(wǎng)卡的名字,Linux上默認(rèn)為eth0,Android上默認(rèn)為wlan0,你可以用這個(gè)參數(shù)自行指定。
輸出模式
輸出格式支持兩種格式,文本跟pcapng文件。有三個(gè)參數(shù),
默認(rèn),全局參數(shù),輸出文本結(jié)果到stdout
-l 全局參數(shù),保存文本結(jié)果的文件路徑
-w 僅tls模塊參數(shù),保存pcapng結(jié)果的文件路徑
類(lèi)庫(kù)路徑
Linux上支持多種類(lèi)庫(kù),不同類(lèi)庫(kù)的路徑也不一樣。
類(lèi)庫(kù) | 參數(shù)路徑 | 默認(rèn)值 |
openssl/boringssl | --libssl | Linux自動(dòng)查找,Android為/apex/com.android.conscrypt/lib64/libssl.so |
gnutls | --gnutls | Linux自動(dòng)查找,Android pcapng模式暫未支持 |
nspr/nss | --nspr | Linux自動(dòng)查找,Android pcapng模式暫未支持 |
文本模式
-l 或者不加 -w 參數(shù)將啟用該模式。
支持openssl、boringssl、gnutls、nspr/nss等多種TLS加密類(lèi)庫(kù)。支持DTLS、TLS1.0至TLS1.3等所有版本的加密協(xié)議。支持-p、-u等所有全局過(guò)濾參數(shù)。
pcapng模式
-w 參數(shù)啟用該模式,并用-i選擇網(wǎng)卡名,Linux系統(tǒng)默認(rèn)為eth0,Android系統(tǒng)默認(rèn)為wlan0,
僅支持openssl、boringssl兩個(gè)類(lèi)庫(kù)的數(shù)據(jù)捕獲。暫不支持TLS 1.3協(xié)議。
類(lèi)庫(kù)與參數(shù)支持
在Linux系統(tǒng)上,大部分類(lèi)庫(kù)與參數(shù)都是可以支持的。但在Android系統(tǒng)上,因?yàn)閮?nèi)核與ARM架構(gòu)的原因,支持的參數(shù)上,有一定的差異。
不同模式的參數(shù)支持
-p、-u兩個(gè)全局參數(shù),支持文本模式,不支持pcapng模式。這是因?yàn)閜capng模式是使用eBPF TC技術(shù)實(shí)現(xiàn)。
模式 | -p | -u | --libssl | --port |
文本 | ? | ? | ? | ? |
pcapng | ? | ? | ? | ? |
不同模式的類(lèi)庫(kù)以協(xié)議支持
模式 | openssl(類(lèi)庫(kù)) | boringssl(類(lèi)庫(kù)) | TLS 1.0/1.1/1.2(協(xié)議) | TLS 1.3(協(xié)議) |
文本 | ? | ? | ? | ? |
pcapng | ? | ? | ? | ? |
pcapng模式暫時(shí)不支持TLS 1.3,TLS 1.3密鑰捕獲功能[4]已經(jīng)開(kāi)發(fā)完成,只是遇到一些BUG,還在解決中。筆者不是openssl的專(zhuān)家,對(duì)TLS 協(xié)議也不太熟。需要補(bǔ)充這兩塊的知識(shí),解決起來(lái)成本比較高,也歡迎對(duì)這塊擅長(zhǎng)的朋友一起來(lái)解決。
與tcpdump聯(lián)合使用
eCapture基于eBPF TC,實(shí)現(xiàn)了流量捕獲,并保存到pcapng文件中?;趀BPF Uprobe實(shí)現(xiàn)了TLS Master Secret的捕獲。并基于Wireshark的Decryption Secrets Block (DSB)[5]標(biāo)準(zhǔn),實(shí)現(xiàn)了gopacket的DSB功能[6],合并網(wǎng)絡(luò)包與密鑰,保存到pcapng中。
eCapture在網(wǎng)絡(luò)包捕獲上,沒(méi)有tcpdump強(qiáng)大,不支持豐富的參數(shù)。你可以用eCapture捕獲master secrets,用tcpdump捕獲網(wǎng)絡(luò)包,然后使用wiresahrk自定義設(shè)置密鑰文件,配合使用。
網(wǎng)絡(luò)包捕獲
tcpdump 的常規(guī)用法,不再贅述。
密鑰捕獲
同時(shí)啟用ecapture ,模式可以選文本或者pcapng,都會(huì)保存TLS的master secrets密鑰數(shù)據(jù)到ecapture_masterkey.log中。
網(wǎng)絡(luò)包查看
用Wireshark打開(kāi)網(wǎng)絡(luò)包文件,設(shè)置這個(gè)master key文件,之后就可以看到TLS解密后的明文了。
配置路徑:Wireshark --> Preferences --> Protocols --> TLS --> (Pre)-Master-Secret log filename
參數(shù)
指定路徑
默認(rèn)路徑
在Android上,Google使用了boring ssl類(lèi)庫(kù),也就是C++語(yǔ)言在libssl基礎(chǔ)上的包裝。默認(rèn)情況下,會(huì)使用/apex/com.android.conscrypt/lib64/libssl.so路徑。
APP的類(lèi)庫(kù)確認(rèn)
你可以使用lsof -p {APP PID}|grep libssl來(lái)確認(rèn)。若不是默認(rèn)路徑,則可以使用--libssl參數(shù)來(lái)指定。
高級(jí)用法
如果你需要查看的APP是自定義SSL類(lèi)庫(kù),那么你可以自助修改eCapture來(lái)實(shí)現(xiàn)。
自定義函數(shù)名與offset
首先,需要確定HOOK函數(shù)的函數(shù)名或者符號(hào)表地址。
沒(méi)有源碼
如果你沒(méi)有目標(biāo)類(lèi)庫(kù)的源碼,可以通過(guò)IDA等軟件靜態(tài)分析、動(dòng)態(tài)調(diào)試,確定SSL Write的地址offset。在配置填寫(xiě)在user/module/probe_openssl.go文件中,對(duì)應(yīng)的probe配置部分。
{
Section: "uprobe/SSL_write",
EbpfFuncName: "probe_entry_SSL_write",
AttachToFuncName: "SSL_write",
UprobeOffset: 0xFFFF00, // TODO
BinaryPath: binaryPath,
},
offset自動(dòng)計(jì)算
如果你有源碼,則可以自行閱讀源碼確定函數(shù)名或者符號(hào)表的地址。對(duì)于結(jié)構(gòu)體的成員屬性讀取,則可以通過(guò)offsetof宏來(lái)自動(dòng)計(jì)算。通過(guò)偏移量的方式,讀取內(nèi)容。
// g++ -I include/ -I src/ ./src/offset.c -o off
#include <stdio.h>
#include <stddef.h>
#include <ssl/internal.h>
#include <openssl/base.h>
#include <openssl/crypto.h>
#define SSL_STRUCT_OFFSETS \
X(ssl_st, session) \
X(ssl_st, s3) \
X(ssl_session_st, secret) \
X(ssl_session_st, secret_length) \
X(bssl::SSL3_STATE, client_random) \
X(bssl::SSL_HANDSHAKE, new_session) \
X(bssl::SSL_HANDSHAKE, early_session) \
X(bssl::SSL3_STATE, hs) \
X(bssl::SSL3_STATE, established_session) \
X(bssl::SSL_HANDSHAKE, expected_client_finished_)
struct offset_test
{
/* data */
int t1;
bssl::UniquePtr<SSL_SESSION> session;
};
int main() {
printf("typedef struct ssl_offsets { // DEF \n");
#define X(struct_name, field_name) \
printf(" int " #struct_name "_" #field_name "; // DEF\n");
SSL_STRUCT_OFFSETS
#undef X
printf("} ssl_offsets; // DEF\n\n");
printf("/* %s */\nssl_offsets openssl_offset_%d = { \n",
OPENSSL_VERSION_TEXT, OPENSSL_VERSION_NUMBER);
#define X(struct_name, field_name) \
printf(" ." #struct_name "_" #field_name " = %ld,\n", \
offsetof(struct struct_name, field_name));
SSL_STRUCT_OFFSETS
#undef X
printf("};\n");
return 0;
}
參數(shù)提取
對(duì)于參數(shù),你需要確認(rèn)被HOOK函數(shù)的參數(shù)類(lèi)型,以便確認(rèn)讀取方式,可以參考kern/openssl_kern.c內(nèi)的SSL_write函數(shù)實(shí)現(xiàn)。
編譯
ARM Linux 編譯
公有云廠商大部分都提供了ARM64 CPU服務(wù)器,筆者選擇了騰訊云的。在廣州六區(qū)中,名字叫標(biāo)準(zhǔn)型SR1(SR1即ARM 64CPU),最低配的SR1.MEDIUM2 2核2G即滿(mǎn)足編譯環(huán)境。可以按照按量計(jì)費(fèi)方式購(gòu)買(mǎi),隨時(shí)釋放,比較劃算。
操作系統(tǒng)選擇ubuntu 20.04 arm64。
ubuntu@VM-0-5-ubuntu:~$sudo apt-get update
ubuntu@VM-0-5-ubuntu:~$sudo apt-get install --yes wget git golang build-essential pkgconf libelf-dev llvm-12 clang-12 linux-tools-generic linux-tools-common
ubuntu@VM-0-5-ubuntu:~$wget https://golang.google.cn/dl/go1.18.linux-arm64.tar.gz
ubuntu@VM-0-5-ubuntu:~$sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.18.linux-arm64.tar.gz
ubuntu@VM-0-5-ubuntu:~$for tool in "clang" "llc" "llvm-strip"
do
sudo rm -f /usr/bin/$tool
sudo ln -s /usr/bin/$tool-12 /usr/bin/$tool
done
ubuntu@VM-0-5-ubuntu:~$export GOPROXY=https://goproxy.cn
ubuntu@VM-0-5-ubuntu:~$export PATH=$PATH:/usr/local/go/bin
編譯方法
- ANDROID=1 make 命令編譯支持core版本的二進(jìn)制程序。
- ANDROID=1 make nocore命令編譯僅支持當(dāng)前內(nèi)核版本的二進(jìn)制程序。