攔截 Flutter App 的 HTTPS 要求
最近在做一些 Flutter App 的 PT,需要攔截 App 到 Backend 的要求,取得 API List或修改 Request / Response。
在 Android 6.0 以前,要攔截 App 的 HTTP 要求,只需要於Wi-Fi 設定中,將 Proxy 指向 BurpSuite / Fiddler / Charles,安裝 Debugging Proxy 的 CA 憑證,就能成功抓包了。
但是在 Android 7.0 之後,增加了 App Network Security,就像 Certificate Pinning 一樣,如果沒有指定允許的 CA 在 network_security_config.xml
中,Android 就不會讓網路流量經過 Proxy。
有位大神寫了一個自動化工具放在 Github 上,解包 APK 後,塞入允許所有 CA 的 network_security_config.xml
,流量就能被 Proxy 接收了。
Flutter 的 App 就不一樣了,Flutter 用了自己的 Runtime Engine 去執行 dart bytecode ,所以在 apk 裡的 lib
資料夾中,每個 arch 都會各自平台的 libfultter.so
去執行 kernel_blob.bin
(Non-Release Mode) / libapp.so
(Release Mode)。
如果 apk 是 Debug Mode ,可以直接從 kernel_blob.bin
裡去還原程式碼,Release Mode 就沒辦法了。
Flutter 使用了 BoringSSL 套件去建立 TLS 連線,原始碼可以在 Flutter 官方的 repo 裡面找到。
所以為了攔截 Release App 的 HTTP Request,有幾件事情要完成:
- 攔截 App 的流量,導向到 Debugging Proxy
- Bypass Flutter 的 CA 憑證檢查
- Resquest / Response 改起來
1. 攔截 App 的流量,導向到 Debugging Proxy
有分為 Rooted 跟 Non-Rooted 的方法:
- Rooted
iptables
- 太麻煩了
- ProxyDroid
- 自動產生 iptables 指令套用,簡單
- Non-Rooted
這部分適合 Android 版本比較高的狀況,畢竟現在手機 Root 之後少了一堆功能。- VPN2SOCK
- 用
tun2sock
+ VPN 攔截流量 - 自己寫的 PoC,還有滿滿的 Bug
- 用
- VPN2SOCK
- Android 模擬器
- LDPlayer
- 有內建 Root / ADB
- 有 Android 5.1 / 7.1 可選
- 我是用這個啦,用 Android 5.1 版的,直接開 ProxyDroid就好
- LDPlayer
2. Bypass Flutter 的 CA 憑證檢查
根據這篇的錯誤訊息(不知道為什麼我的 App 沒出現 Exception),Flutter 的 TLS handshake failed 出現在 handshake.cc 的 352 行,原因是 CERTIFICATE_VERIFY_FAILED
。
查看 handshake.cc
的原始碼,發現 flutter 取得 session_verify_cert_chain
回傳的 boolean,來判斷檢查憑證是否合法。
那就讓 session_verify_cert_chain
永遠回傳 true
就好了。
找了一下,發現 session_verify_cert_chain
位在 ssl_x509.cc:362 ,在 ssl_x509.cc:391 跟 ssl_x509.cc:411 有 2 處 return false
,把這兩個地方 patch 成 return true
就好了!!
libfultter.so @ armeabi-v7a
- 就是 32 bits 的 ARM
- 如果懶惰的話,只要 patch 這個 Arch 就好,大多數的手機(
arm64
,x86
,x86_64
)為了相容性都有放 ARM 32 bits 的 runtime,LDPlayer 跟我的 S10 也有,x86 的就當作練習吧。
先用 Ghidra 打開 lib/armeabi-v7a/libflutter.so
。
為了找到 session_verify_cert_chain
,看了一下 Source,這個方法有 3 個 input,也有個 macro OPENSSL_PUT_ERROR
用來印出錯誤訊息,定義在 err.h:422,會讓錯誤訊息顯示出檔名跟行數,所以就在 Ghidra 中尋找 ssl_x509.cc
這個字串,找 xrefs 的 function 並且是 3 個 input 的就是了。
Ghidra -> Search -> Program Text,搜尋 ssl_x509.cc
。
有 7 個 xrefs
在 FUN_012c68b4
中,看到有3個 input,一個 FUN_0127d008
(OPENSSL_PUT_ERROR
) ,OPENSSL_PUT_ERROR
,Line Number 0x186
= 390
也是在 session_verify_cert_chain
裡面。
點一下第 14 行的 return 0
就會定位到 function 的結尾。
LAB_012c696c
012c696c 00 24 mov r4,#0x0 // Line #18 return 0;
012c696e 0d e0 b LAB_012c698c
LAB_012c6970
012c6970 4f f4 c3 70 mov.w r0,#0x186
012c6974 00 21 mov r1,#0x0
012c6976 00 90 str r0,[sp,#0x0 ]
012c6978 10 20 mov r0,#0x10
012c697a 15 4b ldr r3,[DAT_012c69d0 ] = FEE39C79h
012c697c 0b 22 mov r2,#0xb
012c697e 00 24 mov r4,#0x0 // Line #62 uVar4 = 0;
012c6980 7b 44 add r3=>s_...
012c6982 b6 f7 41 fb bl FUN_0127d008 // OPENSSL_PUT_ERROR
LAB_012c6986
012c6986 03 a8 add r0,sp,#0xc
012c6988 d8 f7 ce fc bl FUN_0129f328
LAB_012c698c // return
012c698c 20 46 mov r0,r4
012c698e 23 b0 add sp,#0x8c
012c6990 bd e8 f0 8f pop.w { r4, r5, r6, r7, r8, r9, r10 , r11 , pc }
只要將 L18 的 return 0
跟 L62 的 uVar4 = 0
換成 1 就可以了。
從 Online ARM Assambler 取得 movs r4, 1
的 OpCode 0124
用 Hex Editor 改,或是直接在 Ghidra 中,在movs r4, 0x0
右鍵選 Patch Instruction,把 0x0
改成 0x1
,存檔。
把 libflutter.so
塞回 apk 裡面,重新用 apksigner
簽章,丟進手機安裝。