news 2026/10/17 14:25:50

Charles抓包HTTPS显示unknown?证书与SSL Pinning排查指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Charles抓包HTTPS显示unknown?证书与SSL Pinning排查指南

搞移动端开发的人,几乎都绕不过抓包这一步。前阵一个朋友发消息问我:用 Charles 抓手机包的时候,为什么有的包能正常抓到,有的却显示 unknown?他说自己对着同一个 App 翻来覆去刷,大部分接口都能看到 JSON 响应,可个别接口在列表里只剩一个带 unknown 标识的深色条目,点开以后没有 URL、没有请求头、没有响应体。这个问题我刚上手 Charles 时也撞见过,而且当时还以为是手机代理没配好,折腾了大半宿。后来把原理弄清楚了,才发现答案一直都很明确:网上的说法是“HTTPS 要装证书”,但真正的原因往往比这一句话更细。

这篇文章不打算从零教你怎么配置代理,而是把所有可能让某个包显示成 unknown 的原因一条条捋清楚,再给你一套可以直接照着做的排查步骤。不管你是刚入门的 Android 开发、正在做前后端联调,还是偶尔要看看线上接口的测试同学,后面这些内容应该都能帮你省下不少时间。

1. 先搞清楚 Charles 的工作原理,再看 unknown 从哪来

1.1 HTTP 是明文,Charles 当然看得懂

抓包之所以能成功,本质上是手机把流量交到了 Charles 手里。你在 Wi-Fi 设置里填代理 IP 和端口之后,App 发出的每一个 HTTP 请求都会先到电脑上的 Charles 进程,然后由 Charles 转发给真正的服务器。

这个“转发”的动作非常关键。HTTP 协议是明文传输的,请求行、请求头、请求体都没有加密,Charles 作为一个中间代理,完全可以在这个转发过程中把完整报文复制一份给你看。所以你抓到的 HTTP 接口,URL、Headers、参数、JSON 响应体都能清清楚楚展示出来,这也是很多人对抓包的第一印象:好像只要代理配好,什么都能看。

但这里有个容易被忽略的事实:Charles 能解密 HTTP,不是因为什么黑魔法,而是因为协议本身就没有加密。它只需站在客户端和服务器之间,把流量原样过一遍。到了 HTTPS 这里,情况就完全不同了。

1.2 HTTPS 包:CONNECT 隧道后面的黑盒

HTTPS 不是一个单独的协议,而是 HTTP 跑在 TLS/SSL 加密层之上。手机上的 App 要和服务器建立 HTTPS 连接时,虽然也要先经过 Charles,但事情变得复杂了。

第一步,客户端会发给 Charles 一个 CONNECT 请求,意思是“喂,我要和某个域名加端口的服务器建立一条隧道,你帮我搭个桥,之后的数据你别动”。这个 CONNECT 请求本身是明文,所以 Charles 能看到目标域名和端口,比如api.example.com:443。

关键在第二步。隧道建立之后,客户端和服务器之间开始进行 TLS 握手。如果 Charles 没有在这个环节介入,那么隧道里流动的全是加密后的密文,Charles 作为一个“管道工”,只能看到一大堆看不懂的二进制数据。

这时候 Charles 会怎么标记这一条流量?如果它完全无法解析,就会直接显示为unknown。你点进去,能看到的大概只有目标主机和端口,其它什么都看不了。简单来说,对 Charles 而言,unknown 就是“我确认这条连接是从你这台手机发出去的,但我没能解密它”。

1.3 “unknown” 这个词只代表 Charles 没能解密

很多人看到 unknown 第一反应是网络问题,或者 App 有问题,其实都不对。unknown 不是报错,它只是一个状态标识,代表 Charles 未能成功介入 TLS 握手,自然也就没有拿到可以解密会话的钥匙。

TLS 握手的简要流程是:客户端先发送 ClientHello,里面写明支持的加密套件和希望访问的域名(SNI);之后服务器返回 ServerHello 和自己的证书;客户端验证证书后,双方再通过一系列密钥交换算出同一个会话密钥。

Charles 如果想解密 HTTPS,就必须在这中间扮演“假服务器”的角色:客户端来握手时,Charles 不是把请求转发给真实服务器,而是自己先回应一个证书。这个证书是 Charles 自己生成的,如果客户端信任了它(也就是手机里装了 Charles 的根证书),双方就能继续协商密钥,Charles 在密钥协商结束后便能解密所有往来数据。如果客户端不信任这个证书,握手会立刻失败,客户端通常会表现出请求失败或一直转圈,Charles 这边则留下一个没有解密能力的 unknown 条目。

提示:判断一个包是不是因为“没解密”而变成 unknown,最简单的方法是看 Charles 左边列表里是否只剩下 CONNECT 请求或者一行 host:port。如果连域名都看不到,说明问题大概率出在 TLS 介入失败,而不是域名解析失败。

把原理说清楚以后,下面就可以按“三家分锅”的思路来排查了。

2. 三家分锅:证书、SSL 代理规则、应用校验

2.1 最常见:证书没装或没打开完全信任

第一个锅,也是最常见的锅,是手机根本没有信任 Charles 的根证书。

Charles 自带一个根证书,用来生成每个网站对应的“假证书”。你需要在计算机上先安装 Charles 的根证书,再把它装进手机。很多教程会告诉你“在手机浏览器里打开 chls.pro/ssl 下载安装”,但这只是第一步。重点提醒一下:iOS 用户装完描述文件以后,还必须去“设置 -> 通用 -> 关于本机 -> 证书信任设置”里面把 Charles CA 打开完全信任。这一步很多教程不会写,漏掉之后 Charles 依然会显示 unknown。

Android 的情况更麻烦。Android 7.0 之后,系统默认不信任用户自己安装的 CA 证书,只有目标 SDK 版本较低的 App 才会兼容用户证书。也就是说,你把证书装进手机,浏览器可能能用,但很多正经 App 根本不会用它,结果就是你看到某个域名的包始终 unknown。

2.2 次常见:SSL Proxying 规则没加全

第二个锅,是 Charles 的 SSL Proxying 规则没配置,或者配置得不够全。

Charles 不是装完证书就会自动解密所有 HTTPS 流量,你必须在Proxy -> SSL Proxying Settings里添加需要解密的域名和端口规则。如果你把这个页面完全空着,或者只填了某个具体域名,那么其它域名的 HTTPS 包就会保持 unknown。

很多第一次用 Charles 的人会把 Host 填成www.example.com、端口填 443 这种精确规则,结果同一个 App 里换了一个子域名,就又开始 unknown。在做全面调试的时候,我通常建议直接加一条* : 443,先把最常见的 HTTPS 端口全覆盖。等确认问题后,再逐步收窄到具体域名。注意 Host 和 Port 是分开填的两列,很多人会在这里填错格式。

2.3 最难搞:应用做了证书校验

第三个锅,是 App 自身做了证书校验,行业内通常叫 SSL Pinning。这种情况最棘手,因为它和 Charles、和手机系统都没有直接关系。

SSL Pinning 的意思是,App 在代码里写死了它信任的服务器证书或公钥。即使你往手机里装了 Charles 的根证书,App 依然只认自己内置的那张服务器证书,看到 Charles 发来的“假证书”时,会直接拒绝握手。表现出来就是:你证书装好了、SSL Proxying 也开了,但某个 App 的所有请求全部 unknown,或者整个页面干脆无法加载。

银行类、支付类、大厂的 App 基本都是这种策略。遇到这类 App,首先不要怀疑自己的抓包步骤,这属于 App 安全策略的一部分。如果是你自己开发的 App,可以在 debug 包里程时关闭 Pinning,或者用一个专门的测试包。如果是第三方 App,就需要通过 hook 等方式绕过校验,但这已经超出了“配证书”的范畴,且只建议在你有合法授权的前提下使用。

2.4 容易被忽略:非标准端口与 UDP/QUIC

最后一种情况,很多人栽过跟头但从未想到过:App 根本不走 443 端口,或者压根不是 TCP 协议。

有些 App 为了防抓包或者因为历史原因,HTTPS 服务跑在 8080、8443、9090 这种自定义端口上。你的 SSL Proxying 规则只加了*:443,它自然不在覆盖范围内,Charles 就显示 unknown。排查办法很简单:看看列表里 unknown 条目后面的端口号,再把 SSL Proxying 规则加上那个端口即可。

至于 UDP 上的 QUIC/HTTP3,那是另一个层面的问题。Charles 默认的抓包机制建立在 TCP 代理之上,UDP 流量它根本不会完整接手。如果你发现某个 App 的部分流量在 Charles 里彻底消失,而系统网络层面又能看到 UDP 报文,那基本可以判断是 QUIC 或自定义 UDP 协议。这种流量不是 unknown,而是“不可见”。

3. 完整实操:把 unknown 变成可读包

3.1 确认代理链路是否通:先看这个浅浅的连接

很多人在调整证书之前,应该先确认手机流量到底有没有经过 Charles。我见过太多案例,折腾半天发现手机走的是 4G/5G 网络,跟电脑根本没处在同一个局域网,Charles 当然什么都看不到。

先做三件事。第一,手机和电脑连到同一个 Wi-Fi。第二,在手机 Wi-Fi 的代理设置里填写电脑的局域网 IP 和端口 8888。第三,在电脑上打开 Charles 的Proxy -> Proxy Settings,确认勾选了 HTTP Proxy。之后随便打开一个网页,看 Charles 里会不会立刻出现流量条目。

如果你看到大量流量进来,但有些显示 unknown,那说明链路是通的,问题在解密层面。如果连一条流量都没有,优先检查电脑防火墙是不是把 8888 端口堵了,或者手机代理 IP 填错了。

3.2 在两套系统上正确安装证书

确认链路通之后再处理证书。电脑端先装一份:打开 Charles,菜单栏选择Help -> SSL Proxying -> Install Charles Root Certificate,然后到系统钥匙串里把证书设为始终信任。电脑端证书主要是为了让 Charles 代理桌面端浏览器流量时不会被浏览器拦下来,抓手机包时其实用不到,但建议还是装上,后面做题省事。

手机端分系统操作。

iOS 的流程:先是手机浏览器访问chls.pro/ssl,下载描述文件;然后去“设置 -> 已下载描述文件”安装;装完以后一定不要忘了去“设置 -> 通用 -> 关于本机 -> 证书信任设置”,把 Charles CA 底下的开关打开。iOS 如果不做这最后一步,系统并不认为这个证书是受信任的,TLS 握手照样失败。

Android 的流程:手机浏览器访问同样的地址,下载证书;然后到“设置 -> 安全 -> 加密与凭据 -> 安装证书”中选择 CA 证书;选择用户凭据完成安装。装完之后最好到“信任的凭据 -> 用户”里确认能看到 Charles CA。Android 7.0 以上的目标 App 默认不信任用户证书,这一步属于已知限制,后面单独说怎么绕。

3.3 配置 SSL Proxying 并添加精准匹配

证书装好、信任开关打开,接下来要告诉 Charles 它可以去解密哪些域名。

打开Proxy -> SSL Proxying Settings,先把Enable SSL Proxying勾上。然后在 Locations 区域点击 Add,按你的目标添加规则。命令格式是 Host 和 Port 两列:Host 填*,Port 填443,就能覆盖绝大多数 HTTPS 流量。

如果 App 用了自定义端口,就把那一行再复制一条,Port 改成实际端口。如果 App 的所有请求都直连某个 IP,而不是走域名,那么 Host 也可以直接填那个 IP 地址。我习惯在初次调试项目时直接加*:443,看到流量后再针对重点域名做精细化配置。因为你写太多精确规则,漏掉任何一个子域都会让人误以为抓包失败。

注意:Host 里的*是通配符,不是让你随手填一个假的域名。很多人把 host 填成*:443一整串,这就是完全搞错了列的含义,规则不会生效。

3.4 右键 unknown 项临时开启解密

有一种操作很多人不知道:在 Charles 列表里,选中一个 unknown 条目,然后右键,选择Enable SSL Proxying。Charles 会自动帮你把这条流量的 Host 和 Port 加入 SSL Proxying 规则。然后你回到 App 里重新触发一次请求,就能看到原本 unknown 的条目被解密,变成完整的 HTTPS 请求。

这个操作的好处是精确、直接,适合目标域名不明确或者不确定端口的场景。我当时处理那个朋友的 unknown 问题,用的就是这一招。他在 Charles 里右键某个 unknown 条目,Charles 自动加了规则,刷新页面后绿色的 JSON 包立刻出现。

需要提一句:右键开启解密只对“Charles 能感知到但没解密”的连接有效。如果 App 根本没走 Charles 代理,或者握手已经被 App 自己拦截,右键也不会生效。

3.5 遇到校验型 App 该怎么办

如果你把证书、SSL 规则都配好了,某个 App 的请求还是 unknown,那基本可以确认它是校验型应用。这种情况下,正路有两条。

第一条,针对你自研的 App:在 build.gradle 或者对应的网络层配置里,为 debug 版本关闭证书校验,或者放宽网络安全策略。具体做法是在 Android 的network_security_config.xml里给 debug 构建加一个信任用户证书的配置,开发阶段可以临时信任 Charles,release 版本恢复严格校验。iOS 同理,用 NSURLSession 的 delegate 在开发阶段绕过拦截校验。

第二条,针对第三方 App,只能借助 hook 工具绕过其 Pinning 逻辑。这类工具本身是合法的安全研究工具,但请不要用它去干任何越界的事情。如果你只是调试自己手机、自己设备范围内的应用,社区里有很多资料可以参考;如果是商业软件的逆向,注意遵守相关规则和授权边界。

4. 排查实录:从数据看到的现象倒推原因

4.1 只看到 IP 和端口,域名全没了

一个很典型的现象:Charles 左边明明有流量,但显示的是111.222.333.444:443这种格式,而不是api.example.com:443。你以为是 DNS 解析问题,其实不是。

产生这个现象的原因通常是两类。一类是 App 直接写死了服务器 IP,不走域名,这种情况下 TLS 握手里没有 SNI,Charles 自然不知道域名,只能显示 IP。另一类是 App 使用自定义 DNS 或者内部网络,代理获得的域名解析结果只能拿到 IP。

处理办法:把这个 IP 填到 SSL Proxying 规则里,而不是填域名。如果你确定请求走的是域名,但 Charles 只显示 IP,可以检查一下是不是开了 Charles 里的某个域名映射功能,或者手机的 DNS 设置了特殊解析。

4.2 证书信任了,但部分页面仍失败

这种情况最多见于混合内容页面:App 一部分接口走正常的 HTTPS 443 端口,另一部分走 WebSocket 或者流量被换成 QUIC。所以你看到“有的包能抓到,有的 unknown”,而且各自还很有规律。

先说 WebSocket。App 先发起一个普通的 HTTP GET 请求,带有Upgrade: websocket头,服务器返回 101 状态码表示切换协议。之后双方就在这个 TCP 连接上持续收发数据帧,Charles 对这个通道默认只能看到握手之前的请求,后续帧不会以 HTTP 请求的形式呈现,很多就直接归类到 unknown 或 WebSocket 分类下。

再看端口规律。如果 unknown 条目清一色集中在某个端口,比如 8443,那就是 SSL Proxying 规则漏了端口。直接在设置里补一条*:8443就能解决。

4.3 解出来的响应全是乱码和二进制

还有一种让人摸不着头脑的情况:unknown 的问题解决了,包也确实被解密了,但是响应体看过去全是乱码。

先别急着怀疑解密失败。很多后端接口为了节省流量,会用 gzip、brotli 压缩响应体,或者用 protobuf 这类二进制序列化格式。Charles 原样显示这些压缩后的字节,自然就是乱码。你可以把响应切换到 Hex 视图看是否真是一堆字节,也可以尝试在 Charles 的 View 菜单里手动选择 JSON 来查看。如果是 protobuf,还得配合对应的 schema 才能解析。

这类包其实不算 unknown,但容易让人误以为“还是没有解密成功”。我的判断习惯是:如果 Charles 里能看到完整的请求头和状态码,说明解密已成功,展示层的问题不影响调试。

4.4 处理 HTTP/2、WebSocket 与 QUIC

最后把新协议单独说一下。

HTTP/2 基于 TCP,Charles 是可以解密的。不过由于 HTTP/2 的二进制分帧特性,Charles 展示内容和 HTTP/1.x 有些差别,响应头分布在 Decoded 页签下,很多人不熟悉,会误以为抓到的包不完整。其实该有的细节都在。

WebSocket 和 HTTP/2/3 不一样,它一旦切换协议就是纯二进制帧。Charles 新版本支持显示 WebSocket 的收发明文帧,但如果 App 发送的是经过压缩或者自定义编码的数据,表现依然是无法阅读。这种请配合其它客户端工具做协议分析。

QUIC/HTTP3 走 UDP,Charles 默认的 TCP 代理机制对它无效。如果你确定 App 在手机上启用了 HTTP/3,一个临时馊主意是想办法让服务器端返回的 Alt-Svc 头降级为 HTTP/2,或者干脆在调试设备上禁用 UDP 443 端口,让流量强制退回 TCP。这个思路在不少调试场景里都有用。

最后分享一点个人经验。我现在每次抓包,都按固定顺序走:先看手机代理有没有生效,再看电脑和手机两端的证书是否完整信任,接着确认 SSL Proxying 规则覆盖了目标域名和端口,最后才去怀疑 App 是不是做了 Pinning。这个顺序看上去简单,但能把最容易踩的坑提前排掉。尤其是 iOS 的“证书信任设置”开关,很多人装完描述文件就以为完事了,其实还差最后一下;Android 那边则最容易卡在用户证书不被高级别 App 信任。如果你把这些步骤全走了一遍,某个包依旧 unknown,那大概率不是 Charles 坏了,而是这个 App 在传输层做了文章。与其半夜删了重装再试,不如按上面几条逐项自查,基本十分钟内能把问题定位到具体环节。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 14:25:05

红外光人脸表情识别实战:VOC+YOLO双格式数据集与少样本训练策略

最近在做夜间环境下的表情识别,第一个卡住我的不是模型,而是数据。日常用惯的表情数据集基本都来自可见光RGB摄像头,一到暗光环境,脸都看不清,更别说识别“是不是在笑”。后来换到一份红外光面部表情人脸表情检测数据集…

作者头像 李华
网站建设 2026/10/17 14:25:01

React+TS+Vite后台环境搭建:Vite 4.5与TS 5.2稳定组合实践

简介:这是一套基于 React TypeScript Vite 构建的轻量级后台管理系统模板,面向前端初学者与中级开发者,适用于快速搭建企业级管理后台原型或学习现代前端工程化实践。资源共76个文件,涵盖30个tsx(核心页面与组件&…

作者头像 李华
网站建设 2026/10/17 14:23:48

写好“已实现功能”清单:需求分层、技术取舍与踩坑实录

"已实现功能"这四个字,可能是我在项目文档里见过最容易被低估的小标题。很多人写到这一节,就是一行行"支持XXX、新增XXX、优化XXX",像超市小票一样往那一摆就完事。但我自己在一个跨平台效率工具(下面就叫它项…

作者头像 李华
网站建设 2026/10/17 14:23:30

Agent工作流设计:从技能拆解到工程化落地实践

1. 为什么"给 Agent 配工作流"这件事值得单独拎出来讲很多人第一次接触 AI Agent,注意力都放在模型能力上——参数多大、上下文多长、推理多强。但真正把 Agent 用进日常工程的人会发现,决定成败的往往不是模型本身,而是它外面那层…

作者头像 李华
网站建设 2026/10/17 14:23:07

从安全到性能:用XinServer重构App接口网关的实战记录

刚接手公司某个核心App的后端接口时,我其实有点打心底里抗拒。原因很简单:之前那套接口层已经跑了三年,代码里塞满了各种过滤逻辑、权限校验和限流补丁,谁动谁炸。而让人彻底下决心的,是一次线上日志泄露排查——几百万…

作者头像 李华