
搞移動端開發(fā)的人幾乎都繞不過抓包這一步。前陣一個朋友發(fā)消息問我用 Charles 抓手機包的時候為什么有的包能正常抓到有的卻顯示 unknown他說自己對著同一個 App 翻來覆去刷大部分接口都能看到 JSON 響應(yīng)可個別接口在列表里只剩一個帶 unknown 標識的深色條目點開以后沒有 URL、沒有請求頭、沒有響應(yīng)體。這個問題我剛上手 Charles 時也撞見過而且當時還以為是手機代理沒配好折騰了大半宿。后來把原理弄清楚了才發(fā)現(xiàn)答案一直都很明確網(wǎng)上的說法是“HTTPS 要裝證書”但真正的原因往往比這一句話更細。這篇文章不打算從零教你怎么配置代理而是把所有可能讓某個包顯示成 unknown 的原因一條條捋清楚再給你一套可以直接照著做的排查步驟。不管你是剛?cè)腴T的 Android 開發(fā)、正在做前后端聯(lián)調(diào)還是偶爾要看看線上接口的測試同學后面這些內(nèi)容應(yīng)該都能幫你省下不少時間。1. 先搞清楚 Charles 的工作原理再看 unknown 從哪來1.1 HTTP 是明文Charles 當然看得懂抓包之所以能成功本質(zhì)上是手機把流量交到了 Charles 手里。你在 Wi-Fi 設(shè)置里填代理 IP 和端口之后App 發(fā)出的每一個 HTTP 請求都會先到電腦上的 Charles 進程然后由 Charles 轉(zhuǎn)發(fā)給真正的服務(wù)器。這個“轉(zhuǎn)發(fā)”的動作非常關(guān)鍵。HTTP 協(xié)議是明文傳輸?shù)恼埱笮?、請求頭、請求體都沒有加密Charles 作為一個中間代理完全可以在這個轉(zhuǎn)發(fā)過程中把完整報文復(fù)制一份給你看。所以你抓到的 HTTP 接口URL、Headers、參數(shù)、JSON 響應(yīng)體都能清清楚楚展示出來這也是很多人對抓包的第一印象好像只要代理配好什么都能看。但這里有個容易被忽略的事實Charles 能解密 HTTP不是因為什么黑魔法而是因為協(xié)議本身就沒有加密。它只需站在客戶端和服務(wù)器之間把流量原樣過一遍。到了 HTTPS 這里情況就完全不同了。1.2 HTTPS 包CONNECT 隧道后面的黑盒HTTPS 不是一個單獨的協(xié)議而是 HTTP 跑在 TLS/SSL 加密層之上。手機上的 App 要和服務(wù)器建立 HTTPS 連接時雖然也要先經(jīng)過 Charles但事情變得復(fù)雜了。第一步客戶端會發(fā)給 Charles 一個 CONNECT 請求意思是“喂我要和某個域名加端口的服務(wù)器建立一條隧道你幫我搭個橋之后的數(shù)據(jù)你別動”。這個 CONNECT 請求本身是明文所以 Charles 能看到目標域名和端口比如api.example.com:443。關(guān)鍵在第二步。隧道建立之后客戶端和服務(wù)器之間開始進行 TLS 握手。如果 Charles 沒有在這個環(huán)節(jié)介入那么隧道里流動的全是加密后的密文Charles 作為一個“管道工”只能看到一大堆看不懂的二進制數(shù)據(jù)。這時候 Charles 會怎么標記這一條流量如果它完全無法解析就會直接顯示為unknown。你點進去能看到的大概只有目標主機和端口其它什么都看不了。簡單來說對 Charles 而言unknown 就是“我確認這條連接是從你這臺手機發(fā)出去的但我沒能解密它”。1.3 “unknown” 這個詞只代表 Charles 沒能解密很多人看到 unknown 第一反應(yīng)是網(wǎng)絡(luò)問題或者 App 有問題其實都不對。unknown 不是報錯它只是一個狀態(tài)標識代表 Charles 未能成功介入 TLS 握手自然也就沒有拿到可以解密會話的鑰匙。TLS 握手的簡要流程是客戶端先發(fā)送 ClientHello里面寫明支持的加密套件和希望訪問的域名SNI之后服務(wù)器返回 ServerHello 和自己的證書客戶端驗證證書后雙方再通過一系列密鑰交換算出同一個會話密鑰。Charles 如果想解密 HTTPS就必須在這中間扮演“假服務(wù)器”的角色客戶端來握手時Charles 不是把請求轉(zhuǎn)發(fā)給真實服務(wù)器而是自己先回應(yīng)一個證書。這個證書是 Charles 自己生成的如果客戶端信任了它也就是手機里裝了 Charles 的根證書雙方就能繼續(xù)協(xié)商密鑰Charles 在密鑰協(xié)商結(jié)束后便能解密所有往來數(shù)據(jù)。如果客戶端不信任這個證書握手會立刻失敗客戶端通常會表現(xiàn)出請求失敗或一直轉(zhuǎn)圈Charles 這邊則留下一個沒有解密能力的 unknown 條目。提示判斷一個包是不是因為“沒解密”而變成 unknown最簡單的方法是看 Charles 左邊列表里是否只剩下 CONNECT 請求或者一行 host:port。如果連域名都看不到說明問題大概率出在 TLS 介入失敗而不是域名解析失敗。把原理說清楚以后下面就可以按“三家分鍋”的思路來排查了。2. 三家分鍋證書、SSL 代理規(guī)則、應(yīng)用校驗2.1 最常見證書沒裝或沒打開完全信任第一個鍋也是最常見的鍋是手機根本沒有信任 Charles 的根證書。Charles 自帶一個根證書用來生成每個網(wǎng)站對應(yīng)的“假證書”。你需要在計算機上先安裝 Charles 的根證書再把它裝進手機。很多教程會告訴你“在手機瀏覽器里打開 chls.pro/ssl 下載安裝”但這只是第一步。重點提醒一下iOS 用戶裝完描述文件以后還必須去“設(shè)置 - 通用 - 關(guān)于本機 - 證書信任設(shè)置”里面把 Charles CA 打開完全信任。這一步很多教程不會寫漏掉之后 Charles 依然會顯示 unknown。Android 的情況更麻煩。Android 7.0 之后系統(tǒng)默認不信任用戶自己安裝的 CA 證書只有目標 SDK 版本較低的 App 才會兼容用戶證書。也就是說你把證書裝進手機瀏覽器可能能用但很多正經(jīng) App 根本不會用它結(jié)果就是你看到某個域名的包始終 unknown。2.2 次常見SSL Proxying 規(guī)則沒加全第二個鍋是 Charles 的 SSL Proxying 規(guī)則沒配置或者配置得不夠全。Charles 不是裝完證書就會自動解密所有 HTTPS 流量你必須在Proxy - SSL Proxying Settings里添加需要解密的域名和端口規(guī)則。如果你把這個頁面完全空著或者只填了某個具體域名那么其它域名的 HTTPS 包就會保持 unknown。很多第一次用 Charles 的人會把 Host 填成www.example.com、端口填 443 這種精確規(guī)則結(jié)果同一個 App 里換了一個子域名就又開始 unknown。在做全面調(diào)試的時候我通常建議直接加一條* : 443先把最常見的 HTTPS 端口全覆蓋。等確認問題后再逐步收窄到具體域名。注意 Host 和 Port 是分開填的兩列很多人會在這里填錯格式。2.3 最難搞應(yīng)用做了證書校驗第三個鍋是 App 自身做了證書校驗行業(yè)內(nèi)通常叫 SSL Pinning。這種情況最棘手因為它和 Charles、和手機系統(tǒng)都沒有直接關(guān)系。SSL Pinning 的意思是App 在代碼里寫死了它信任的服務(wù)器證書或公鑰。即使你往手機里裝了 Charles 的根證書App 依然只認自己內(nèi)置的那張服務(wù)器證書看到 Charles 發(fā)來的“假證書”時會直接拒絕握手。表現(xiàn)出來就是你證書裝好了、SSL Proxying 也開了但某個 App 的所有請求全部 unknown或者整個頁面干脆無法加載。銀行類、支付類、大廠的 App 基本都是這種策略。遇到這類 App首先不要懷疑自己的抓包步驟這屬于 App 安全策略的一部分。如果是你自己開發(fā)的 App可以在 debug 包里程時關(guān)閉 Pinning或者用一個專門的測試包。如果是第三方 App就需要通過 hook 等方式繞過校驗但這已經(jīng)超出了“配證書”的范疇且只建議在你有合法授權(quán)的前提下使用。2.4 容易被忽略非標準端口與 UDP/QUIC最后一種情況很多人栽過跟頭但從未想到過App 根本不走 443 端口或者壓根不是 TCP 協(xié)議。有些 App 為了防抓包或者因為歷史原因HTTPS 服務(wù)跑在 8080、8443、9090 這種自定義端口上。你的 SSL Proxying 規(guī)則只加了*:443它自然不在覆蓋范圍內(nèi)Charles 就顯示 unknown。排查辦法很簡單看看列表里 unknown 條目后面的端口號再把 SSL Proxying 規(guī)則加上那個端口即可。至于 UDP 上的 QUIC/HTTP3那是另一個層面的問題。Charles 默認的抓包機制建立在 TCP 代理之上UDP 流量它根本不會完整接手。如果你發(fā)現(xiàn)某個 App 的部分流量在 Charles 里徹底消失而系統(tǒng)網(wǎng)絡(luò)層面又能看到 UDP 報文那基本可以判斷是 QUIC 或自定義 UDP 協(xié)議。這種流量不是 unknown而是“不可見”。3. 完整實操把 unknown 變成可讀包3.1 確認代理鏈路是否通先看這個淺淺的連接很多人在調(diào)整證書之前應(yīng)該先確認手機流量到底有沒有經(jīng)過 Charles。我見過太多案例折騰半天發(fā)現(xiàn)手機走的是 4G/5G 網(wǎng)絡(luò)跟電腦根本沒處在同一個局域網(wǎng)Charles 當然什么都看不到。先做三件事。第一手機和電腦連到同一個 Wi-Fi。第二在手機 Wi-Fi 的代理設(shè)置里填寫電腦的局域網(wǎng) IP 和端口 8888。第三在電腦上打開 Charles 的Proxy - Proxy Settings確認勾選了 HTTP Proxy。之后隨便打開一個網(wǎng)頁看 Charles 里會不會立刻出現(xiàn)流量條目。如果你看到大量流量進來但有些顯示 unknown那說明鏈路是通的問題在解密層面。如果連一條流量都沒有優(yōu)先檢查電腦防火墻是不是把 8888 端口堵了或者手機代理 IP 填錯了。3.2 在兩套系統(tǒng)上正確安裝證書確認鏈路通之后再處理證書。電腦端先裝一份打開 Charles菜單欄選擇Help - SSL Proxying - Install Charles Root Certificate然后到系統(tǒng)鑰匙串里把證書設(shè)為始終信任。電腦端證書主要是為了讓 Charles 代理桌面端瀏覽器流量時不會被瀏覽器攔下來抓手機包時其實用不到但建議還是裝上后面做題省事。手機端分系統(tǒng)操作。iOS 的流程先是手機瀏覽器訪問chls.pro/ssl下載描述文件然后去“設(shè)置 - 已下載描述文件”安裝裝完以后一定不要忘了去“設(shè)置 - 通用 - 關(guān)于本機 - 證書信任設(shè)置”把 Charles CA 底下的開關(guān)打開。iOS 如果不做這最后一步系統(tǒng)并不認為這個證書是受信任的TLS 握手照樣失敗。Android 的流程手機瀏覽器訪問同樣的地址下載證書然后到“設(shè)置 - 安全 - 加密與憑據(jù) - 安裝證書”中選擇 CA 證書選擇用戶憑據(jù)完成安裝。裝完之后最好到“信任的憑據(jù) - 用戶”里確認能看到 Charles CA。Android 7.0 以上的目標 App 默認不信任用戶證書這一步屬于已知限制后面單獨說怎么繞。3.3 配置 SSL Proxying 并添加精準匹配證書裝好、信任開關(guān)打開接下來要告訴 Charles 它可以去解密哪些域名。打開Proxy - SSL Proxying Settings先把Enable SSL Proxying勾上。然后在 Locations 區(qū)域點擊 Add按你的目標添加規(guī)則。命令格式是 Host 和 Port 兩列Host 填*Port 填443就能覆蓋絕大多數(shù) HTTPS 流量。如果 App 用了自定義端口就把那一行再復(fù)制一條Port 改成實際端口。如果 App 的所有請求都直連某個 IP而不是走域名那么 Host 也可以直接填那個 IP 地址。我習慣在初次調(diào)試項目時直接加*:443看到流量后再針對重點域名做精細化配置。因為你寫太多精確規(guī)則漏掉任何一個子域都會讓人誤以為抓包失敗。注意Host 里的*是通配符不是讓你隨手填一個假的域名。很多人把 host 填成*:443一整串這就是完全搞錯了列的含義規(guī)則不會生效。3.4 右鍵 unknown 項臨時開啟解密有一種操作很多人不知道在 Charles 列表里選中一個 unknown 條目然后右鍵選擇Enable SSL Proxying。Charles 會自動幫你把這條流量的 Host 和 Port 加入 SSL Proxying 規(guī)則。然后你回到 App 里重新觸發(fā)一次請求就能看到原本 unknown 的條目被解密變成完整的 HTTPS 請求。這個操作的好處是精確、直接適合目標域名不明確或者不確定端口的場景。我當時處理那個朋友的 unknown 問題用的就是這一招。他在 Charles 里右鍵某個 unknown 條目Charles 自動加了規(guī)則刷新頁面后綠色的 JSON 包立刻出現(xiàn)。需要提一句右鍵開啟解密只對“Charles 能感知到但沒解密”的連接有效。如果 App 根本沒走 Charles 代理或者握手已經(jīng)被 App 自己攔截右鍵也不會生效。3.5 遇到校驗型 App 該怎么辦如果你把證書、SSL 規(guī)則都配好了某個 App 的請求還是 unknown那基本可以確認它是校驗型應(yīng)用。這種情況下正路有兩條。第一條針對你自研的 App在 build.gradle 或者對應(yīng)的網(wǎng)絡(luò)層配置里為 debug 版本關(guān)閉證書校驗或者放寬網(wǎng)絡(luò)安全策略。具體做法是在 Android 的network_security_config.xml里給 debug 構(gòu)建加一個信任用戶證書的配置開發(fā)階段可以臨時信任 Charlesrelease 版本恢復(fù)嚴格校驗。iOS 同理用 NSURLSession 的 delegate 在開發(fā)階段繞過攔截校驗。第二條針對第三方 App只能借助 hook 工具繞過其 Pinning 邏輯。這類工具本身是合法的安全研究工具但請不要用它去干任何越界的事情。如果你只是調(diào)試自己手機、自己設(shè)備范圍內(nèi)的應(yīng)用社區(qū)里有很多資料可以參考如果是商業(yè)軟件的逆向注意遵守相關(guān)規(guī)則和授權(quán)邊界。4. 排查實錄從數(shù)據(jù)看到的現(xiàn)象倒推原因4.1 只看到 IP 和端口域名全沒了一個很典型的現(xiàn)象Charles 左邊明明有流量但顯示的是111.222.333.444:443這種格式而不是api.example.com:443。你以為是 DNS 解析問題其實不是。產(chǎn)生這個現(xiàn)象的原因通常是兩類。一類是 App 直接寫死了服務(wù)器 IP不走域名這種情況下 TLS 握手里沒有 SNICharles 自然不知道域名只能顯示 IP。另一類是 App 使用自定義 DNS 或者內(nèi)部網(wǎng)絡(luò)代理獲得的域名解析結(jié)果只能拿到 IP。處理辦法把這個 IP 填到 SSL Proxying 規(guī)則里而不是填域名。如果你確定請求走的是域名但 Charles 只顯示 IP可以檢查一下是不是開了 Charles 里的某個域名映射功能或者手機的 DNS 設(shè)置了特殊解析。4.2 證書信任了但部分頁面仍失敗這種情況最多見于混合內(nèi)容頁面App 一部分接口走正常的 HTTPS 443 端口另一部分走 WebSocket 或者流量被換成 QUIC。所以你看到“有的包能抓到有的 unknown”而且各自還很有規(guī)律。先說 WebSocket。App 先發(fā)起一個普通的 HTTP GET 請求帶有Upgrade: websocket頭服務(wù)器返回 101 狀態(tài)碼表示切換協(xié)議。之后雙方就在這個 TCP 連接上持續(xù)收發(fā)數(shù)據(jù)幀Charles 對這個通道默認只能看到握手之前的請求后續(xù)幀不會以 HTTP 請求的形式呈現(xiàn)很多就直接歸類到 unknown 或 WebSocket 分類下。再看端口規(guī)律。如果 unknown 條目清一色集中在某個端口比如 8443那就是 SSL Proxying 規(guī)則漏了端口。直接在設(shè)置里補一條*:8443就能解決。4.3 解出來的響應(yīng)全是亂碼和二進制還有一種讓人摸不著頭腦的情況unknown 的問題解決了包也確實被解密了但是響應(yīng)體看過去全是亂碼。先別急著懷疑解密失敗。很多后端接口為了節(jié)省流量會用 gzip、brotli 壓縮響應(yīng)體或者用 protobuf 這類二進制序列化格式。Charles 原樣顯示這些壓縮后的字節(jié)自然就是亂碼。你可以把響應(yīng)切換到 Hex 視圖看是否真是一堆字節(jié)也可以嘗試在 Charles 的 View 菜單里手動選擇 JSON 來查看。如果是 protobuf還得配合對應(yīng)的 schema 才能解析。這類包其實不算 unknown但容易讓人誤以為“還是沒有解密成功”。我的判斷習慣是如果 Charles 里能看到完整的請求頭和狀態(tài)碼說明解密已成功展示層的問題不影響調(diào)試。4.4 處理 HTTP/2、WebSocket 與 QUIC最后把新協(xié)議單獨說一下。HTTP/2 基于 TCPCharles 是可以解密的。不過由于 HTTP/2 的二進制分幀特性Charles 展示內(nèi)容和 HTTP/1.x 有些差別響應(yīng)頭分布在 Decoded 頁簽下很多人不熟悉會誤以為抓到的包不完整。其實該有的細節(jié)都在。WebSocket 和 HTTP/2/3 不一樣它一旦切換協(xié)議就是純二進制幀。Charles 新版本支持顯示 WebSocket 的收發(fā)明文幀但如果 App 發(fā)送的是經(jīng)過壓縮或者自定義編碼的數(shù)據(jù)表現(xiàn)依然是無法閱讀。這種請配合其它客戶端工具做協(xié)議分析。QUIC/HTTP3 走 UDPCharles 默認的 TCP 代理機制對它無效。如果你確定 App 在手機上啟用了 HTTP/3一個臨時餿主意是想辦法讓服務(wù)器端返回的 Alt-Svc 頭降級為 HTTP/2或者干脆在調(diào)試設(shè)備上禁用 UDP 443 端口讓流量強制退回 TCP。這個思路在不少調(diào)試場景里都有用。最后分享一點個人經(jīng)驗。我現(xiàn)在每次抓包都按固定順序走先看手機代理有沒有生效再看電腦和手機兩端的證書是否完整信任接著確認 SSL Proxying 規(guī)則覆蓋了目標域名和端口最后才去懷疑 App 是不是做了 Pinning。這個順序看上去簡單但能把最容易踩的坑提前排掉。尤其是 iOS 的“證書信任設(shè)置”開關(guān)很多人裝完描述文件就以為完事了其實還差最后一下Android 那邊則最容易卡在用戶證書不被高級別 App 信任。如果你把這些步驟全走了一遍某個包依舊 unknown那大概率不是 Charles 壞了而是這個 App 在傳輸層做了文章。與其半夜刪了重裝再試不如按上面幾條逐項自查基本十分鐘內(nèi)能把問題定位到具體環(huán)節(jié)。