要分析抓包的技術(shù)首先要介紹數(shù)字證書是什么辽社,一切的抓包手段都是圍繞數(shù)字證書做文章
數(shù)字證書
TLS 握手的作用之一是身份認證,被驗證的一方需要提供一個身份證明钾恢,在 HTTPS 的世界里手素,這個身份證明就是 TLS 證書鸳址,或者稱為 數(shù)字證書
JDK 中用 java.security.cert.X509Certificate 來表示一個證書,它繼承自抽象類 java.scurity.cert.Certificate泉懦,通過 X509Certificate 我們可以獲取證書的信息,例如x509Certificate.getSubjectDN().getName()
x509Certificate.getSubjectDN().getName()獲取到的是證書的Subject's Name的信息即證書擁有者稿黍,內(nèi)容是一組符合 X.500 規(guī)范的 DN(Distinguished Name)
CN=*,OU=IT Department,O=China Merchants Bank Co., Ltd,L=Shenzhen,C=CN
這是從我們證書中獲取到的信息,DN的屬性含義如圖所示
通過命令openssl x509 -in base64.cer –text 查看證書信息崩哩,輸出如圖所示
可以看出巡球,一個 Certificate 由 Data 和 Signature 兩部分組成。其中 Data 包含的內(nèi)容有:
證書版本號:X.509v3
序列號:一個 CA 機構(gòu)內(nèi)是唯一的邓嘹,但不是全局唯一
簽名算法:簽名的計算公式為RSA(sha256(Data), IssuerPrivateKey)
簽發(fā)者:DN(Distinguished Name)
有效期:證書的有效期間 [Not Before, Not After]
證書擁有者:也是一個 DN公鑰長度一般是 2048bit酣栈,1024bit已經(jīng)被證明不安全
擴展字段:證書所攜帶的域名信息會配置在 SAN 中(X509v3 Subject Alternative Name)
Signature 位于證書最末尾,簽名算法 sha256WithRSAEncryption 在 Data 域內(nèi)已經(jīng)指明 汹押,而 RSA 進行非對稱加密所需的私鑰(Private Key)則是由 Issuer 提供矿筝,Issuer 是一個可以簽發(fā)證書的證書,由證書權(quán)威 CA 提供棚贾,CA 需要保證證書的有效性窖维。
因為 Signature 是 RSA 算法生成的,那么客戶端拿到 TLS 證書之后妙痹,需要 Issuer 的公鑰(Public Key)才能解碼出 Data 的摘要铸史。
然而證書只攜帶了 Issuer 的 DN,并沒有公鑰细诸,為了弄清楚客戶端如何獲取公鑰沛贪,我們需要先搞明白 Certificate Chain(證書鏈)。
證書鏈
一個完整的證書鏈一般由三種類型的證書組成
- Root Certificate (DigiCert Global Root CA) :
根證書
Root Certificate是由Root CAs 發(fā)布震贵,一個Root CAs 下包含多個Intermediate CAs 利赋,同理一個Root Certificate下可以包含多個Intermediate Certificate。根證書是CA自己的證書猩系,是證書驗證鏈的開頭 - Intermediate Certificate (DigiCert SHA2 Secure Server CA):
中間證書或者叫做中介證書
Intermediate Certificate是由Intermediate CAs 發(fā)布媚送,一個Intermediate CAs 下可以包含多個Intermediate CAs ,同理一個Intermediate Certificate下可以包含多個Intermediate Certificate也可以包含End-entity Certificate寇甸。CA會使用Intermediate Certificate 替代Root Certificate去做服務(wù)器端的證書簽名塘偎,確保根證書密鑰絕對不可訪問 -
End-entity Certificate (user.cmbchina.com):
終端證書即用戶證書
包含用來加密傳輸數(shù)據(jù)的公鑰的證書,是HTTPS中使用的證書拿霉。 End-entity Certificate上面幾級證書都是為了保證End-entity Certificate未被篡改吟秩,保證是CA簽發(fā)的合法證書,進而保證End-entity Certificate中的公鑰未被篡改
證書鏈驗證
1.客戶端得到服務(wù)端返回的證書绽淘,通過讀取得到 服務(wù)端證書的發(fā)布機構(gòu)(Issuer)
2.客戶端去操作系統(tǒng)查找這個發(fā)布機構(gòu)的的證書涵防,如果是不是根證書就繼續(xù)遞歸下去直到拿到根證書。
3.匹配客戶端保存的可信任CA與根證書的CA沪铭,確保根證書的合法性
4.用 根證書的公鑰 去 解密驗證 上一層證書的合法性壮池,再拿上一層證書的公鑰去驗證更上層證書的合法性偏瓤;遞歸回溯。
5.最后驗證服務(wù)器端的證書是 可信任 的
關(guān)于證書鏈信任認證過程可以參考我的另一篇文檔
抓包原理-中間人攻擊
前面基礎(chǔ)性內(nèi)容已經(jīng)鋪墊完畢椰憋,現(xiàn)在正式分析抓包技術(shù)
根據(jù)流程可以得出需要讓中間人攻擊生效厅克,必須讓客戶端的證書連驗證能夠通過,并且現(xiàn)在幾乎所有攻擊手段都是從這入手橙依,實現(xiàn)原理上可以分為兩大類:
1.攻擊客戶端CA信任庫证舟,讓系統(tǒng)在誤認為偽證書是可以信任的,從而向中間人發(fā)送信息票编。手段包括
手機系統(tǒng)中安裝偽證書
root手段導(dǎo)入偽證書至證書庫
修改rom中系統(tǒng)證書庫并燒錄系統(tǒng)
例如Charles褪储,Burp卵渴,Magisk慧域,Packet Capture(VPN)都是使用這種方法
SSL PINING正是對抗這種技術(shù)的方法
2.通過腳本或者框架hook驗證證書的方法,主要手段有:
修改系統(tǒng)checkServerTrusted或者TrustManager等涉及驗證或者整理證書的相關(guān)代碼浪读,返回他們需要的結(jié)果
針對有自己的證書驗證邏輯的第三方框架例如OKHttp昔榴,則hook修改CertificationPinner等方法
APP使用自己封裝的通信方法,則通過逆向代碼hook相關(guān)方法
Xposed+JustTrustMe或者Frida使用的是這種方法碘橘,針對這種技術(shù)則要避免將涉及通信的代碼暴露互订,可以使用混淆,加殼等多種手段
SSL PINNING與雙向認證
綠色表示ssl pinning在正常HTTPS通信過程中增加的操作
紅色表示雙向認證在正常HTTPS通信過程中增加的操作
SSL PINNING本地證書管理
SSL PINNING唯一缺點即是如何對本地綁定的證書進行更新痘拆,針對該問題研究了一種解決方案方案:
- 證書服務(wù)器:搭建一個CA服務(wù)器專門用于更新證書或者在驗證證書時通過訪問CA服務(wù)器驗證
- 與證書服務(wù)器通信內(nèi)容及證書間比對都采用不可逆加密信息仰禽,防止中間人拿到通信內(nèi)容反推出明文
- 建立客戶端及證書服務(wù)器端黑名單,盡可能減少與通信次數(shù)并增加中間人攻擊成本
-
證書服務(wù)器與客戶端采用相同加密算法纺蛆,保證加密后信息一致
Hook對抗方案
在攻擊手段中吐葵,hook代碼是一個較難防范的點,針對常用的Xposed框架可以做一下防范措施:
-
檢測進程中使用so名中包含關(guān)鍵"hack|inject|hook|call" 的信息
-
檢測手機是否被root:含有su程序和ro.secure是否為1
防止被Hook的方式就是可以查看XposedBridge這個類桥氏,有一個全局的hook開關(guān)温峭,所有有的應(yīng)用在啟動的時候就用反射把這個值設(shè)置成true,這樣Xposed的hook功能就是失效了
-
Xposed的hook原理的就是在程序啟動都注入jar功能字支,所以安裝hook模塊之后凤藏,每個應(yīng)用內(nèi)部都包含了這個Xposed功能jar,就相當(dāng)于你的應(yīng)用中有了Xposed的所有功能類堕伪,所以在應(yīng)用中反射Xposed的類是可以成功的
-
如果應(yīng)用被Xposed進行hook操作之后揖庄,拋出的異常堆棧信息中就會包含Xposed字樣,所以可以通過應(yīng)用自身內(nèi)部拋出異常來檢測是否包含Xposed字段來進行防護
如何在代碼中執(zhí)行shell命令并返回結(jié)果
public static ArrayList<String> executeCommand(String[] shellCmd){
String line = null;
ArrayList<String> fullResponse = new ArrayList<String>();
Process localProcess = null;
try {
Log.d("gscgsc","excute cmd");
localProcess = Runtime.getRuntime().exec(shellCmd);
} catch (Exception e) {
return null;
}
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(localProcess.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(localProcess.getInputStream()));
try {
while ((line = in.readLine()) != null) {
Log.d("gscgsc","Line received: " + line);
fullResponse.add(line);
}
} catch (Exception e) {
e.printStackTrace();
}
Log.d("gscgsc","–> response was: " + fullResponse);
return fullResponse
}
SSL PINING實現(xiàn)
String hostname = "xxx.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
.add(hostname, "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")
.add(hostname, "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")
.build();
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
Request request = new Request.Builder()
.url("https://" + hostname)
.build();
client.newCall(request).execute();
SSL PINING實現(xiàn)原理
證書校驗時機
private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
//包裝socket
sslSocket = (SSLSocket) sslSocketFactory.createSocket(
rawSocket, address.url().host(), address.url().port(), true /* autoClose */);
//配置socket參數(shù)
ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
//建立握手
sslSocket.startHandshake();
Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
//證書校驗
address.certificatePinner().check(address.url().host(),
unverifiedHandshake.peerCertificates());
//檢驗無異常則保存連接
String maybeProtocol = connectionSpec.supportsTlsExtensions()
? Platform.get().getSelectedProtocol(sslSocket)
: null;
}
//校驗證書與本地證書
public void check (String hostname, List < Certificate > peerCertificates)
throws SSLPeerUnverifiedException {
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
//加密后本地證書的存儲
Pin pin = pins.get(p);
//sha256加密
if (pin.hashAlgorithm.equals("sha256/")) {
if (sha256 == null) sha256 = sha256(x509Certificate);
if (pin.hash.equals(sha256)) return; // Success!
}
//sha1加密
else if (pin.hashAlgorithm.equals("sha1/")) {
if (sha1 == null) sha1 = sha1(x509Certificate);
if (pin.hash.equals(sha1)) return; // Success!
}
}
//校驗失敗拋出異常
throw new SSLPeerUnverifiedException(message.toString());
}
結(jié)果如下