突破抓包限制:Hook实战解密HTTPS流量与SSL双向认证
传统的抓包工具(如 Fiddler、Wireshark)在面对愈发严格的安全措施时,如 HTTPS 双向认证(mTLS)、内存加载证书、证书绑定等,它们往往显得力不从心。本文旨在带你超越传统抓包的边界,不仅会回顾抓包工具的核心原理,更将深入 Hook 注入技术,实战演示如何通过拦截关键函数,动态获取 SSL 证书、解密 HTTPS 明文流量。
这可能是全网最具深度的关于抓包文章。
抓包工具
Fildder 原理
Fiddler 通过扮演中间人的角色(对客户端扮演服务器,对服务器扮演客户端)来实现 HTTP 抓包和 HTTPS 流量解密的功能。
Fiddler 在启动后,会自动在本地建立一个代理服务器(端口默认为 8888),并通过调用 Windows 的 WinHttpSetDefaultProxyConfiguration 等函数,将自己设置为系统默认的 HTTP/HTTPS 代理。许多应用程序(如浏览器)默认会遵循系统的这个代理设置,因此它们发出的网络请求都会首先被发送到 Fiddler。
可以在系统的“设置” -> “网络和 Internet” -> “代理”中看到“使用代理服务器”的选项被自动开启,并且代理 IP 为:
http=127.0.0.1:8888;https=127.0.0.1:8888;
当 Fiddler 收到 HTTP 请求时,因为不涉及数据包解密,Fiddler 直接进行转发即可。
当需要解密 HTTPS 请求时,Fiddler 会在本地生成一个自签名的根证书(Fiddler Root Certificate),并将这个根证书安装到操作系统的“受信任的根证书颁发机构”存储中。

以请求 https://sample.com 为例,Fiddler 会使用自签名的根证书私钥来为 sample.com 域名动态的伪造一张证书,并返回给浏览器(因为对于浏览器而言,它的服务器是 Fildder),浏览器使用这个假证书来进行 SSL 加密,然后将加密数据包发送给 Fiddler,Fiddler 使用假证书的私钥来解密数据包进行显示、修改等,然后 Fiddler 从 sample.com 服务器请求真正的证书,并使用真证书对数据包再次加密,然后发送给 sample.com 服务器;
sample.com 服务器将响应发送给 Fiddler(因为对 sample.com 服务器而言,发起请求的客户端是 Fildder),Fiddler 使用真证书来解密数据包进行展示,然后使用假证书来再次加密数据包,最后转发给浏览器。

Proxifier 原理
Proxifier 在 Windows 系统中主要使用 LSP(Layered Service Provider,分层服务提供程序) 技术实现对网络流量的拦截,属于应用层的流量拦截,无需安装驱动,大多数游戏加速器也会使用该项目技术。
Proxifier 是透明流量转发,不负责解密 HTTPS 流量,解密操作由后续的软件进行。通常使用 Proxifier 将指定进程的流量转发到 Fildder 或 Charles,然后再使用这些工具进行解包分析。
通常还需要将 Proxifier 的“名称解析”->“DNS 设置”修改为“通过代理解析主机名称”。

下面来解释为什么要这样设置。
1 | 应用程序 -> Proxifier -> Fiddler |
若没有将 DNS 设置为“通过代理解析主机名称”,当本地 DNS 缓存中存在 baidu.com 域名解析条目时,会直接将 baidu.com 域名解析为对应的 IP,从而将 TSL 握手请求包中的域名修改为 IP 后再转发给 Fiddler。Fiddler 在动态伪造证书时是需要知道域名的,如果不知道,Fiddler 就不知道要伪造哪个证书,无法伪造证书。

Wireshark 原理
Wireshark 的工作流程始于捕获,当选择一个网络接口(如 Wi-Fi 或以太网网卡)开始抓包时,Wireshark 会通过底层驱动(如 Npcap 或 WinPcap)将网卡设置为混杂模式。在此模式下,网卡会捕获所有流经它的网络数据包。
捕获到的原始数据是二进制比特流,Wireshark 的内置的协议解析器会按照网络协议栈(如 Ethernet → IP → TCP → HTTP)逐层解码这些二进制数据,将晦涩的代码转换为人类可读的协议字段和含义。因此要熟练掌握 Wireshark 的使用,需要先了解网络协议栈,可以参考: 网络编程/网络协议-1-基础概念 。
Wireshark 还提供了一个分为三部分的界面供我们深入检查数据包:数据包列表、数据包详情和数据包字节流。通常会需要结合显示过滤器(如 http.request.method == “GET”)来精准筛选流量,或通过“Follow TCP Stream”功能重构完整的会话内容。
虽然 Wireshark 也可以捕获和分析 HTTP(s)流量,但在这方面还是不如 Fiddler 等工具专业,所以我们通常只使用其来捕获除 HTTP(s)以外的流量。
浏览器 HTTP 抓包
先来一个开胃前菜。网页抓包是调试前端 API 接口最基础的技能,浏览器内置的开发者工具是完成这项任务的首选利器。
以主流的 Chrome 浏览器为例,按 F12 或 Ctrl+Shift+I 就可以打开开发者工具,其“Network(网络)面板”中记录了所有由浏览器发起的网络请求,包括 HTML、CSS、JS、图片、XHR/Fetch(API 接口)等。
但有些网页会禁用浏览器的调试工具,防止用户进行抓包和调试,它们通常采用包括但不限于下面的方式:
- 使用 addEventListener 来拦截 F12、 Ctrl+Shift+I 等快捷键以及右键菜单事件
- 因为只有在打开调试工具的情况下,
debugger语句才会生效。根据这个机制,可以通过 debugger 上下语句执行的时间差来判断调试器是否打开。 - 也可以通过无限执行
debugger语句来干扰调试。
目前已经有开源组件 disable-devtool 可以快速搞定禁用调试工具,这个组件使用的检测/禁用手段也更加多样化。
遇到这种禁用调试工具的网站时,如果只需要抓包,使用 Fildder 或 Charles 无意是最简单的方法。
基于 libcurl 的 HTTP 抓包
许多命令行工具和应用程序使用 libcurl 库进行网络通信。默认情况下,libcurl 并不会自动遵循系统代理设置,除非在代码中显式指定。
1 | curl_easy_setopt(pCURL, CURLOPT_PROXY, "127.0.0.1:8888"); |
对于一些支持设置 HTTP 代理的软件,我们可以尝试将代理设置为本地的 Fildder 代理端口(如 127.0.0.1:8888),然后使用 Fiddler 进行抓包分析。
而对于大多数不支持代理设置的软件,通常需要使用 Proxifier 将该软件进程的流量转发到 Fiddler 代理端口,然后使用 Fildder 进行抓包分析。
HTTPS 双向认证
根据之前的文章 网络编程/网络协议-7-HTTP与HTTPS协议 《网络协议(7)–HTTP与HTTPS协议》 所介绍的 SSL/TSL 握手的过程可以知道,在握手过程中,不仅客户端可以校验服务器的证书,服务器也可以校验客户端的证书,这个叫 HTTPS 双向认证(mutual TLS),简称 mTLS。libcurl 作为一个成熟的网络库,对 mTLS 提供了很好的支持。
客户端校验服务器证书
通过如下代码可以开启 libcurl 客户端对服务器证书的校验:
1 | curl_easy_setopt(pCURL, CURLOPT_SSL_VERIFYPEER, 1L); |
在开启服务器证书校验后,还需要指定 CA 包(Certificate Authority bundle,包含多个信任的根证书,用于验证服务器证书链)才能正常请求。有两种方式指定 CA 包,一种是通过 CURLOPT_CAINFO 选项来从本地文件路径加载:
1 | curl_easy_setopt(pCURL, CURLOPT_CAINFO, "D:\\certs\\ca.crt"); |
另一种是通过 CURLOPT_CAINFO_BLOB 选项来从内存加载:
1 | const char* strCA = R"( |
服务器校验客户端证书
像网银、企业内部服务等这些对安全级别要求较高的应用,服务器通常需要校验客户端的证书,即 TLS 握手的 ServerHello 响应中包含“客户端证书请求”,此时客户端就需要发送相应的证书给服务器。
libcurl 通过 CURLOPT_SSLCERT 和 CURLOPT_SSLKEY 选项指定客户端证书和私钥的本地路径(如果通过内存加载则分别对应 CURLOPT_SSLCERT_BLOB 和 CURLOPT_SSLKEY_BLOB 选项),还是可以使用 CURLOPT_SSLCERTTYPE 选项指定证书类型(默认为 PEM 类型)以及 CURLOPT_KEYPASSWD 选项指定私钥的密码(可选)。
libcurl 在收到服务器的“客户端证书请求”后,会自动将相应的信息发送给服务器。
创建证书及私钥
如何来创建双向证书所需的证书呢?在创建证书及私钥之前,需要先弄清除,公钥、私钥、CA 和证书的关系(详见 网络编程/网络协议-7-HTTP与HTTPS协议 《网络协议(7)–HTTP与HTTPS协议》 中的“证书的申请”章节)。
- 公钥和私钥是非对称加密中的概念,私钥保密,公钥公开。
- 证书里面会包含公钥,证书也是公开的,但证书对应的私钥需要保密。所以在我的文章中一般将证书等同于公钥。
- CA = Certificate Authority,直译为证书机构。使用 CA 证书的私钥来签发证书,然后用 CA 证书来验证所签发的证书。
回到本节的双向校验过程中来,客户端验证服务器的证书也就是使用 CA 证书来验证服务器的证书,服务器验证客户端证书也就是使用 CA 证书来验证客户端的证书,因为客户端和服务器的证书都是通过 CA 证书私钥来签发的。
1 | CA私钥 ---签发--> (服务器证书 + 私钥) |
我们可以使用 openssl 来生成上述证书,大致步骤如下。
- 创建 CA 的私钥和证书。
1 | # CA私钥 |
- 创建服务器的私钥和证书。
1 | # 私钥 |
- 创建客户端的私钥和证书。
1 | openssl genrsa -out client.key 2048 |
绕过对服务器证书的校验
在抓取 libcurl 的 HTTPS 包时,如果提示如下的错误,说明 libcurl 开启了对服务器证书的校验,并且证书校验失败。

要解决 Fiddler 解密失败的问题,我们需要理解两种场景下证书校验的差异:
没有 Fiddler 的情况。
客户端(libcurl)向服务器发起 TLS 握手请求,其中包含 ClientHello 消息。服务器返回 ServerHello、证书(以及其它消息)。客户端收到证书后,会使用自定义的 CA 证书包来验证服务器证书。如果验证通过(即服务器证书是由自定义 CA 证书包中的某个根证书签名的),则握手继续;否则,握手失败。
有 Fiddler 的情况(且 Fiddler 作为中间人代理)。
客户端实际上是与 Fiddler 建立 TLS 连接,而不是直接与目标服务器建立连接。因此,客户端会向 Fiddler 请求证书(因为 Fiddler 此时扮演服务器的角色)。Fiddler 会动态生成一个目标服务器域名的证书,并用 Fiddler 自己的根证书签名。客户端收到这个证书后,同样会用自定义的 CA 包来验证。如果自定义 CA 包中包含了 Fiddler 的根证书,则验证通过;否则,验证失败。
如果 libcurl 是通过文件路径方式来指定的 CA 包,我们可以找到该文件,通过将 Fiddler 根证书添加到该文件中的方式,来使程序再次信任 Fiddler 的根证书。大致步骤如下:
导出 Fiddler 的根证书(打开 Fiddler → Tools → Options → HTTPS → Actions → Export Root Certificate to Desktop)
把证书转成与 libcurl 程序所指定证书一样的格式,如转成 pem 格式。
1
openssl x509 -in FiddlerRoot.cer -out FiddlerRoot.pem -outform PEM
将 pem 内容添加到原有的 CA 包尾部(或者完全替换原有 CA 包的内容)。
如果在 Fiddler 之前使用了 Proxifier 转发流量,不要忘记在 Proxifier 中将 DNS 设置“通过代理解析主机名称”。
如果 libcurl 是通过内存加载的 CA 包,可以采用注入 + Hook 的方式将 CA 包重定向到我们指定的文件,见下面的章节。
libcurl 与 Schannel
libcurl 中的 Schannel 和 OpenSSL 都是用于处理 HTTPS (SSL/TLS) 连接的底层安全库,它们的主要区别在于:OpenSSL 跨平台的第三方加密库;而 Schannel (Security Support Provider Interface) 是 Windows 平台自带的微软实现,原生集成。可以在编译 libcurl 时选择以何种方式支持 HTTPS。
当使用 OpenSSL 作为底层安全库时,可以使用上面介绍的方法来抓取 HTTPS 流量。
但是,当使用 Schannel 作为底层安全库时,默认会验证证书的吊销状态,而 Fiddler 生成的证书没有有效的吊销信息,会导致返回CERT_TRUST_REVOCATION_STATUS_UNKNOWN错误。
使用如下命令查看 Fiddler 导出的证书的吊销状态:
1 | certutil -verify FiddlerRoot.cer |
通常会输出如下结果:
1 | 证书是一个 CA 证书 |
如果能修改 libcurl 程序的代码,可以禁用吊销状态检查:
1 | curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE); |
当然大多数情况下,都无法修改程序的代码,那么如何跳过吊销状态检查呢?嘿嘿
绕过对客户端证书的校验
找到客户端使用的证书和私钥文件(如果私钥有密码,还要先想办法获取到密码),通过如下命令来生成 pfx 文件:
1 | openssl pkcs12 -export -out client.pfx -inkey client.key -in client.crt |
双击 pfx 文件,导入到系统中(理论上是不需要该步骤了,但可能是因为 Schannel 存在 bug 的缘故,如果不将 pfx 导入到系统,fiddler 会解析证书失败)。
编辑 Fiddler 的 Rules,在 OnBeforeRequest 函数中添加类似如下内容:
1 | if (oSession.uriContains("localhost")) { |
如果 libcurl 是通过内存加载的客户端证书、私钥,可以采用注入 + Hook 的方式将获取到这些信息,见下面的章节。
Hook libcurl 获取证书信息
当目标程序通过内存(BLOB)方式加载证书和私钥时,我们无法直接修改文件。此时,Hook(钩子) 技术便成为关键手段。通过拦截程序对 libcurl API 的调用,我们可以动态读取、修改甚至替换这些敏感信息。
涉及到的 libcurl 选项如下:
1 | CURLOPT_CAINFO_BLOB |
通过分析 libcurl(8.13.0 版本)的代码,发现 curl_easy_setopt 调用路径如下:
1 | CURLcode curl_easy_setopt(CURL *d, CURLoption tag, ...) |
Hook Curl_vsetopt 一个函数就可以截获和修改我们所需要的信息。
libcurl 没有导出 Curl_vsetopt 函数,所以需要通过特征码来搜索函数地址。使用 IDA Pro 分析 32 位 libcurl.dll 发现,可以通过函数的前几个指令作为特征码来搜索函数地址,因此 函数特征码为 0x8B, 0x4C, 0x24, 0x08, 0x81, 0xF9, 0x10, 0x27, 0x00, 0x00。
1 | 8B4C24 08 | mov ecx,dword ptr ss:[esp+8] | _Curl_vsetopt |
编写 DLL,在 DLL 中通过特征码查找函数地址,然后使用 MinHook 库来 Hook 该函数。
在查找函数特征码时,需要区分动态链接和静态链接 libcurl 库的情况。动态链接时,直接从 libcurl.dll 模块查找;静态链接时,从主模块中查找。
查找 Curl_vsetopt 函数地址的代码如下:
1 | PVOID dllBase = NULL; |
使用 MinHook 库 Hook 查找到的函数,代码如下:
1 | MH_STATUS mhStatus = MH_Initialize(); |
最后,在我们的 detour 函数中根据 option 来做相应的处理:
1 | CURLcode My_Curl_vsetopt(struct Curl_easy* data, int option, va_list param) { |
Hook OpenSSL 获取明文
libcurl 通过 OpenSSL 提供对 SSL 和 TLS 协议的支持,因此我们可以 Hook OpenSSL 库的函数来截获加密前的请求明文以及解密后的响应明文。
写一个简单的 OpenSSL 示例程序,单步调试后可以发现,OpenSSL 内部会分别调用 SSL_write 函数写入待加密的报文数据,SSL_read 函数读取解密后的报文数据。
因此我们只需要 Hook 这两个函数就可以截获请求和响应的明文,而且这两个函数都是 OpenSSL 的导出函数,在程序使用动态链接 OpenSSL 库的情况下,我们只需要查找 IAT 就可以获取函数地址。
本节以静态链接 OpenSSL 库为例,通过特征码来查找这两个函数的地址。
从 OpenSSL 3.5.0 版本中查看 SSL_read 和 SSL_write 函数的定义如下:
1 | int SSL_read(SSL *s, void *buf, int num) |
SSL_read 与 SSL_write 函数的定义非常类似,而且在 OpenSSL 源码中还有很多类似的函数,因此不能简单的使用前几个指令作为特征码进行搜索。
进一步分析 ERR_raise 宏,其定义如下:
1 |
|
其中,ERR_set_debug 函数的参数分别是源文件路径、代码所在行、函数名称,代码行是一个整数常量,可以很方便地作为特征码来使用,也就是下面汇编代码中的 push 94Bh。

因此 SSL_read 与 SSL_write 函数的特征码分别如下:
1 |
|
在找到函数地址之后,同样使用 MinHook 库进行 Hook,将截获到的请求和响应内容写入到日志文件:
1 | int MySSLRead(void* s, void* buf, int num) { |
WinHTTP
Fiddler 可以抓取使用 WinINET API 接口发送的流量,但是无法抓 WinHTTP API 接口发送流量。尝试了各种办法依然无法抓取,最后只好使用 Proxifier 来将流量转发到 Fiddler。
同样别忘记在 Proxifier 中将 DNS 设置“通过代理解析主机名称”。
Electron HTTP 抓包
Electron 应用默认不走系统代理,所以使用 Fiddler 无法直接抓包。使用 Electron 程序内置的调试工具进行抓包非常直观,而且不用考虑 HTTPS 双向认证的问题,可以优先尝试打开调试工具。
方法 1
如果能直接打开调试工具(如快捷键 Ctrl+Shift+I 或 F12 等),这是最简单的方式。
方法 2
如需抓取渲染进程的包,则使用 --remote-debugging-port=9222 --remote-allow-origins=* 命令行启动 Electron 程序,然后在浏览器中通过 http://localhost:9222 访问调试工具。
如需抓取主进程的包,则使用--inspect=9222命令行启动 Electron 程序,并进行相应配置,详见 Web与Electron/Electron启动和禁用调试工具的方法
方法 3
使用 asar 命令解压 app.asar 资源包,并在 js 代码中查找 new BrowserWindow,针对需要抓包的 BrowserWindow 对象启动 devTools 并调用 openDevTools 函数打开调试工具(也粗暴一点就把所有的都加上),然后再使用 asar 命令重新打包资源包。
1 | mainWindow = new BrowserWindow({ |
有的 Electron 应用会校验 asar 资源包的哈希值(如 WeMod),防止被修改之后重新打包,此时需要使用 IDA Pro 屏蔽掉该逻辑。
方法 4
虽然大多数情况下都可以打开调试工具的,除非方法不到位,但如果实在无法打开,还可以使用 Proxifier 将流量转发到 Filddler,然后使用 Fiddler 抓包分析。按照这种方法,如果遇到了 HTTPS 双向认证,则需要从资源文件和源码中获取到证书、私钥等信息。
CEF HTTP 抓包
CEF 应用可以通过设置远程调试端口来进行调试,也就是使用下面的命令行来启动 CEF 应用(指定一个 1024~65535 之间的端口号),然后在浏览器中通过 http://localhost:34444 访问调试工具。
1 | cef_app.exe -remote-debugging-port=34444 |
调试端口屏蔽与反屏蔽
但开发者也可以在代码中屏蔽远程调试命令行参数,下面介绍几种屏蔽调试参数的方法,同时也介绍了对于逆向分析人员如何绕过该屏蔽方法的手段。
方法 1:不提供 devtools_resources.pak 文件。
这种情况只需要从 cef 官网下载对应版本的 devtools_resources.pak 文件放到 libcef.dll 同级目录即可。
方法 2:禁用命令行参数解析。
1 | CefSettings settings; |
CefInitialize 函数的调用路径如下:
1 | libcef_dll_wrapper.dll!bool CefInitialize(const CefMainArgs& args, const CefSettings& settings, CefRefPtr<CefApp> application, void* windows_sandbox_info) |
cef_initialize 函数是 libcef.dll 的导出函数,所以我们只需要 Hook 该函数,并将 CefSettings.command_line_args_disabled 修改为 0 即可绕过该禁用。
方法 3:在 OnBeforeCommandLineProcessing 回调函数移除命令行参数中的 remote-debugging-port。
1 | // 移除remote-debugging-port参数 |
OnBeforeCommandLineProcessing 函数由 libcef_dll 项目中的 app_on_before_command_line_processing 函数所调用,但由于 app_on_before_command_line_processing 函数不是导出函数,因此查找该函数的地址可能会费点功夫。
Hook BoringSSL
上述方法虽然可以在大部分情况下打开调试工具,但有些情况下还是可能无法打开调试功能,比如开发者对 CEF 进行魔改。在无法打开调试工具时,使用 Proxifier + Fiddler 的组合就可以轻易的抓取 HTTP 数据包。
如果需要抓取 HTTPS 数据包,可以使用 Hook BoringSSL 的方式。
因为 CEF 使用了 OpenSSL 的 BoringSSL 分支,因此在抓取 HTTPS 数据包时,可以通过 Hook BoringSSL 的 SSL_read 和 SSL_write 函数来截获发送和接收的数据包,具体方法与上面小节介绍的 Hook OpenSSL 的 SSL_read 和 SSL_write 函数的方法类似。