简介面向C#开发者的FiddlerCore抓包示例可解决HTTPS流量捕获、证书安装与代理切换等常见需求。代码基于社区FiddlerCore Demo改造提供两种抓包模式通过系统代理全局捕获或使用WebProxy.Start(8877)自定义代理精准监听并附带证书生成、事件处理、自动点击等辅助类便于直接编译运行或集成到项目。压缩包共33个文件涵盖cs源码、config配置、dll依赖、exe可执行文件及pdb调试文件等整体仅717KB轻量易部署。其中包含关键源码说明文档和调试输出可帮助理解FiddlerCore的工作流程。资源已有1577人学习适合初学抓包或需要快速搭建HTTPS抓包工具的开发者参考借鉴。1. FiddlerCore 抓包不装 Fiddler 界面直接在代码里接管 HTTP/HTTPS 流量FiddlerCore 是 Fiddler 的抓包引擎类库以 NuGet 包的形式把整套代理转发、HTTPS 解密和会话记录能力塞进 .NET 程序里。和 Wireshark 那种被动嗅探不同FiddlerCore 走的是主动接管程序启动时监听一个本地端口流量经过你的回调代码你可以在请求发出前改头、在响应返回后换 body。它的典型受众是写接口自动化测试、调试 App 与小程序、在 CI 里验证客户端请求完整性的人。跟 Charles、Fiddler 图形界面比它的优势是没有界面适合做后台服务和自动化流水线代价是所有证书、代理、回调、线程问题都得自己扛这也是本文想讲透的部分。2. 证书与代理前置FiddlerCore 能解密 HTTPS 的两个前提条件用 FiddlerCore 抓 HTTPS 不是 StartCapture 一下就完事。两个前置条件必须成立一是根证书生成并被目标客户端信任二是代理端口与代理注册方式选对。前者决定你能不能看到明文后者决定流量会不会经过你。很多人装上包跑起来发现全是 CONNECT 隧道、看不到 URL多半就是这两件事没做扎实。下面把这两件事拆开讲。2.1 为什么 HTTPS 抓包必须装根证书中间人代理的信任链模型FiddlerCore 解密 HTTPS 的原理是中间人代理客户端连接它时它扮演目标服务器向客户端出示一张由本地根证书签发的、域名匹配的动态证书同时它作为客户端去连接真实服务器完成真正的 TLS 握手。两张证书一段链路客户端那边只认“根证书是否被信任”。如果根证书没被信任客户端会立刻报证书错误如果根证书被信任客户端就会接受动态证书流量在 FiddlerCore 里就是明文回调里能直接拿到 URL、请求头和响应体。这里有一个新手最容易误解的地方DecryptSSL 标志只是让 FiddlerCore 去解密但不负责“让客户端信任”。证书信任是操作系统或 App 层面的决定。PC 上装进“受信任的根证书颁发机构”就好Android 7 及以上App 默认不信任用户安装的 CA这也是“安卓模拟器抓包、小程序抓包失败”的高频原因——证书装了但装错了位置或者 App 根本不看用户证书库。2.2 生成与信任根证书CertMaker 的代码路径与手工导入FiddlerCore 用 CertMaker 管理根证书。常见做法是程序启动时检查、生成并尝试信任if (!CertMaker.rootCertExists()) { CertMaker.createRootCert(); } bool trusted CertMaker.trustRootCert(); if (!trusted) { Console.WriteLine(根证书已生成但写入系统信任库失败请检查权限); }rootCertExists()检查当前用户证书库里是否已有 Fiddler 根证书避免每次启动都重新生成——重新生成会导致旧证书失效之前信任过的客户端全部报错。createRootCert()在本地生成一对密钥和自签名根证书保存在用户配置目录trustRootCert()把它写入 Windows 当前用户的受信任根证书存储。返回 false 多半是权限不足用管理员身份跑一次或者手工导入在 certmgr.msc 里找到 Fiddler 开头的根证书导出为 .cer再放到测试机的受信任根证书存储。在 CI 或 Linux 上我更习惯把“生成证书”和“信任证书”拆开生成一次把 .cer 当测试资产入库目标环境只导入、不生成。这样所有测试机的根证书一致也避免每台机器各自生成、互相不认。Android 模拟器要抓 HTTPS把同一个 .cer 装进系统证书目录比在代码里 trustRootCert 更可靠因为后者只影响 Windows 当前用户。2.3 代理端口与系统代理注册StartCapture 参数怎么选启动代理的惯用写法是给 StartCapture 传端口和一组标志var flags FiddlerCoreStartupFlags.DecryptSSL | FiddlerCoreStartupFlags.AllowRemoteClients | FiddlerCoreStartupFlags.RegisterAsWinINET; FiddlerApplication.StartCapture(8888, flags);端口号避开 80、443 这种被业务占用的端口也避开 8080 这种容易冲突的调试端口我一般选 8877 或 8888冲突了再换。几个常用标志的作用标志作用什么时候用DecryptSSL对 HTTPS 流量做解密要抓 HTTPS 必开AllowRemoteClients允许非本机客户端连入代理手机、模拟器、别的机器抓包RegisterAsWinINET把系统 WinINET 代理指到本地端口抓 IE/Chrome/.NET 桌面程序MonitorAllConnections监控本机所有连接而不是只看 WinINET抓不读系统代理的进程第三行有个坑RegisterAsWinINET 只对走 WinINET 的程序生效。Chrome、IE、.NET 的 HttpWebRequest 会读这个设置但很多 Go、Node、Python 写的客户端自己管代理根本不看系统设置。所以“注册了系统代理还是抓不到某个程序”先别怀疑抓包引擎先确认目标程序用没用系统代理它不认就在它的启动脚本里设 HTTP_PROXY、HTTPS_PROXY 环境变量指到 127.0.0.1:8888。只抓特定 App 时我一般不注册系统代理而是手动在 App 或模拟器里指代理Android 模拟器用adb shell settings put global http_proxy 10.0.2.2:8888指向宿主机真机用电脑的局域网 IP前提是手机和电脑同网段。结束抓包后记得adb shell settings delete global http_proxy清掉设置否则模拟器所有联网请求都会卡在代理上。不同小版本的 FiddlerCore 对 StartCapture 的重载有差异有的版本只接受 bool 参数编辑器提示找不到对应重载时先查包版本换成你那个版本支持的签名。3. 把抓包引擎跑起来会话回调、URL 过滤与响应改写的完整流程前置条件就绪后核心编程模型是事件回调。FiddlerCore 把每个请求封装成 Session 对象在请求发出前、响应返回时、会话结束时分别触发事件。理解这三个时机就理解了 90% 的用法。3.1 最小可运行示例初始化、启动、两个核心回调using System; using Fiddler; class Sniffer { static void Main(string[] args) { // 1. 根证书不存在就生成信任交给部署环境处理 if (!CertMaker.rootCertExists()) { CertMaker.createRootCert(); } // 2. 启动代理解密 HTTPS允许远程设备接入 var flags FiddlerCoreStartupFlags.DecryptSSL | FiddlerCoreStartupFlags.AllowRemoteClients; FiddlerApplication.StartCapture(8888, flags); // 3. 请求阶段看见进来的请求 FiddlerApplication.BeforeRequest (Session oSession) { if (oSession.HTTPMethodIs(CONNECT)) return; // HTTPS 隧道解密后会再次触发本回调 Console.WriteLine( {0} {1}, oSession.oRequest.headers.HTTPMethod, oSession.fullUrl); }; // 4. 响应阶段看见返回的响应 FiddlerApplication.BeforeResponse (Session oSession) { Console.WriteLine( {0} - {1}, oSession.fullUrl, oSession.responseCode); }; Console.WriteLine(按回车停止抓包...); Console.ReadLine(); FiddlerApplication.Shutdown(); } }BeforeRequest 在请求发往服务器之前触发HTTPS 场景下同一个会话会触发两次第一次是 CONNECT 隧道请求此时还没有可读的 URL 细节HTTPMethodIs(CONNECT)判断后直接 returnFiddlerCore 完成 TLS 解密后会以真实请求再触发一次这时 fullUrl、请求头都是明文。BeforeResponse 在响应到达时触发responseCode 就是状态码。这个最小示例不做任何修改只做记录作用是验证“流量确实经过了你的代码”。3.2 过滤规则先做 URL 白名单别让大流量淹没日志把抓包引擎接进自动化之前第一件事是过滤。无差别打印所有流量图片、视频、埋点日志几秒钟就能让日志失去可读性还会拖慢程序。常见做法是维护一个白名单只关心特定域名和接口FiddlerApplication.BeforeRequest (Session oSession) { if (oSession.HTTPMethodIs(CONNECT)) return; string url oSession.fullUrl; if (!url.Contains(api.example.com) !url.Contains(log.example.com)) return; // 要读响应体时必须提前把响应缓冲下来 oSession.bBufferResponse true; oSession[captureTag] needed; }; FiddlerApplication.BeforeResponse (Session oSession) { if (oSession[captureTag] ! needed) return; string body oSession.GetResponseBodyAsString(); Console.WriteLine(resp: {0}, body.Substring(0, Math.Min(body.Length, 300))); };这里的关键是 bBufferResponseFiddlerCore 默认流式转发响应BeforeResponse 触发时 body 可能还没读完直接调 GetResponseBodyAsString 会拿到空串或截断数据。必须在请求阶段对命中白名单的会话置位响应阶段才能安全地整读。提示bBufferResponse 一定要在请求阶段置位放到 BeforeResponse 里再设就晚了。oSession[captureTag] 是会话自带的粘滞标记跟着这个 Session 对象走完整个生命周期跨回调传状态时比静态变量安全得多——并发请求多时静态变量会互相覆盖这个不会。3.3 修改请求与伪造响应一个能直接用的改写示例抓包不只是看FiddlerCore 最常用在“改”。改请求头、改请求体、改响应体都是实测联调里的高频操作。下面这个示例针对用户接口做三件事给请求换 token、改 POST 参数、把响应里的用户等级从 0 改成 3FiddlerApplication.BeforeRequest (Session oSession) { if (oSession.HTTPMethodIs(CONNECT)) return; if (!oSession.fullUrl.Contains(/api/user/profile)) return; // 改请求头 oSession.oRequest.headers[Authorization] Bearer test-token; oSession.bBufferResponse true; // 改请求体POST 场景 if (oSession.oRequest.headers.HTTPMethod POST) { oSession.utilReplaceInRequest(version1, version2); } }; FiddlerApplication.BeforeResponse (Session oSession) { if (!oSession.fullUrl.Contains(/api/user/profile)) return; string body oSession.GetResponseBodyAsString(); string newBody body.Replace(\level\:0, \level\:3); oSession.utilSetResponseBody(newBody); };BeforeRequest 里用索引器改请求头oSession.oRequest.headers[Authorization] ...是增改请求头的标准写法utilReplaceInRequest 是 Fiddler 封装好的字符串替换适合改 keyvalue 这种简单结构复杂结构建议先 GetRequestBodyAsString 再整体替换。响应侧先整读 body字符串替换后 utilSetResponseBody 写回。多数版本里 utilSetResponseBody 会自动修正 Content-Length如果发现对端读响应被截断就手动把 Content-Length 改回新 body 的字节数。改成什么样、影响哪些字段要对着接口文档逐条核对别随手把线上字段乱换。4. 抓包失败排查手册证书信任、远程设备与回调重复的五个坑这一章收集的是我用 FiddlerCore 过程中真正摔过的坑每条都按现象、原因、解决写。遇到“App 抓包失败”“小程序抓不到包”这类问题先从这五条里找共性。4.1 只能看到 CONNECT 隧道看不到任何解密后的 URL现象日志里清一色CONNECT api.example.com:443后面的真实请求一个都没有。 原因要么 StartCapture 没带 DecryptSSL 标志要么根证书没被目标客户端信任FiddlerCore 解密失败后只能转发隧道读不到明文。这两个原因的症状完全一样先查代码再查证书。 解决确认启动标志里有 DecryptSSL再调用CertMaker.rootCertExists()和CertMaker.trustRootCert()检查根证书状态如果是非 Windows 目标按 2.2 的方式把根证书导入目标设备。4.2 App 报证书错误或直接断连浏览器却正常现象同一台电脑上浏览器能抓手机上的 App、小程序一开代理就提示“连接不安全”或直接超时。 原因两种常见情况。一是 App 做了证书绑定SSL Pinning它校验的不是系统信任链而是内置的服务器证书指纹任何中间人证书都会失败二是 Android 7 及以上App 默认只信任系统证书不信任用户安装的 CA。 解决证书绑定只能在 App 侧解——调试包关闭绑定校验或者用 Hook 工具处理绑定逻辑正规做法是要求客户端团队出测试包。Android 用户 CA 的问题把抓包根证书装进系统证书目录/system/etc/security/cacerts模拟器直接改镜像真机需要 rootiOS 则要在“设置 - 通用 - 关于本机 - 证书信任设置”里把抓包根证书打开为完全信任。这也是“小程序抓包、安卓模拟器抓包失败”搜出来最高频的原因。注意证书绑定只能在 App 侧解抓包工具层面没有通用开关。4.3 手机连不上代理PC 上一切正常现象PC 上抓包正常手机 Wi-Fi 代理指向电脑 IP 后完全没有流量进来。 原因两层。启动时没带 AllowRemoteClients代理只监听回环地址外部设备根本连不到或者 Windows 防火墙拦了入站端口。 解决启动标志加上 AllowRemoteClients让代理监听 0.0.0.0在防火墙入站规则里放行抓包端口模拟器场景用 10.0.2.2 指向宿主机而不是电脑的局域网 IP真机确认手机和电脑在同一网段。有些 App 不读系统代理需要配合把进程流量强制转发到指定代理的工具比如带规则转发的代理工具拉进代理通道否则代理设了也白设。4.4 停掉程序后本机上网全挂现象程序跑着没事CtrlC 强杀或崩溃退出后浏览器所有请求都走代理连不上网。 原因RegisterAsWinINET 在启动时改了系统代理设置正常 Shutdown 会还原但强杀进程时没人还原注册表里就残留了代理配置。这属于自己给自己挖的坑。 解决启动前备份HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings下的 ProxyEnable 和 ProxyServer 值退出时在 finally 里调用FiddlerApplication.Shutdown()实在被强杀了用netsh winhttp reset proxy重置或者去系统代理设置里手动关掉。4.5 抓包程序内存持续上涨最后卡死现象长时间挂着抓包内存一直涨跑几个小时就 OOM 或卡死。 原因最常见的是无差别读 body——BeforeResponse 里对每个请求都 GetResponseBodyAsString图片、视频、分块流全被读进内存其次是同名事件重复订阅每次初始化都 一次回调翻倍执行。 解决先做 URL 过滤非目标请求直接 return需要统计信息的只在 AfterSessionComplete 里读摘要字段不整读 body事件订阅做成“只订一次”按 5.3 的 EnsureSubscribed 模式防止重复累加。5. 多线程回调与粘滞状态FiddlerCore 并发场景的稳定性写法FiddlerCore 的单机抓包很简单一旦接入自动化测试或长期服务并发问题就浮出来。它的回调跑在多个抓包线程上不是你的主线程Session 对象是粘滞的但共享集合不是。这一章讲的写法我是在一次并发压测翻车后总结出来的。5.1 回调跑在哪个线程并发集合与 UI 更新的正确姿势FiddlerCore 内部按连接分配工作线程BeforeRequest、BeforeResponse、AfterSessionComplete 都可能在不同线程上触发。所以回调里直接操作 List 会在遍历时报“集合已修改”直接更新 WinForms/WPF 控件会报跨线程错误。同步在途状态我一般用 ConcurrentDictionary以会话 id 为键using System.Collections.Concurrent; private static readonly ConcurrentDictionarystring, Session _inFlight new ConcurrentDictionarystring, Session(); FiddlerApplication.BeforeRequest (Session oSession) { if (oSession.HTTPMethodIs(CONNECT)) return; _inFlight[oSession.id] oSession; }; FiddlerApplication.AfterSessionComplete (Session oSession) { _inFlight.TryRemove(oSession.id, out _); Console.WriteLine(done: {0} - {1}, oSession.fullUrl, oSession.responseCode); };oSession.id 是 FiddlerCore 给每个会话分配的唯一标识用它做键可以安全跟踪在途请求不会跟别的会话串。需要弹日志到界面时在回调里只做“塞队列”由一个 UI 定时器去消费队列刷新界面避免在抓包线程里直接碰控件。这两个习惯能避开大部分并发异常。5.2 粘滞标记与并发改写为什么别用静态变量跨回调传状态BeforeRequest 和 BeforeResponse 收到的是同一个 Session 对象所以可以用 oSession[标记] 存中间状态——这是 Fiddler 官方的粘滞会话写法。反过来千万别用类的静态字段传请求间的状态两个并发请求同时进 BeforeRequest后写的会覆盖先写的BeforeResponse 里拿到的就是错的状态。FiddlerApplication.BeforeRequest (Session oSession) { if (oSession.oRequest.headers.HTTPMethod POST oSession.fullUrl.Contains(/api/order)) { oSession[wantModify] 1; oSession.bBufferResponse true; } }; FiddlerApplication.BeforeResponse (Session oSession) { if (oSession[wantModify] ! 1) return; string body oSession.GetResponseBodyAsString(); string newBody body.Replace(\stock\:0, \stock\:10); oSession.utilSetResponseBody(newBody); };如果确实要跟踪一个跨多次请求的业务流比如登录后拿 token、再带 token 下单用“首包响应里解析出值写入按会话键隔离的字典”不要写成全局单例。并发量上来以后这条规则直接决定抓包逻辑是对是错。我在压测里就吃过亏多个下单请求同时进来静态字段里存的 token 互相覆盖响应全改错了。5.3 清理与防重复订阅长期挂机服务的三个规范长期运行的服务端抓包最怕两个问题一是事件重复订阅导致回调翻倍二是会话数据只进不出导致内存上涨。事件订阅要封装成幂等操作private static bool _subscribed; private static void EnsureSubscribed() { if (_subscribed) return; FiddlerApplication.BeforeRequest OnBeforeRequest; FiddlerApplication.AfterSessionComplete OnAfterSessionComplete; _subscribed true; } private static void Cleanup() { if (!_subscribed) return; FiddlerApplication.BeforeRequest - OnBeforeRequest; FiddlerApplication.AfterSessionComplete - OnAfterSessionComplete; _subscribed false; }EnsureSubscribed 用布尔位保证同一套事件只注册一次服务重启抓包时先 Cleanup 再重新订阅。会话记录按时间窗口轮转落盘不要在一个 List 里攒满全部会话每处理完一个会话就释放对 Session 的引用避免它连同请求体、响应体一起滞留在内存里。做到这三条FiddlerCore 作为后台服务跑几天是常有的事。6. 收尾技巧把抓包结果沉淀成断言验证自动化里有没有漏请求抓包引擎接进自动化测试之后最有价值的用法不是看日志而是把“必须发起的请求”变成断言。FiddlerCore 的 AfterSessionComplete 在这里正好用每个请求结束都会触发一次把 URL 路径装进集合测试收尾时和期望清单比对。using System; using System.Collections.Generic; using System.Linq; HashSetstring expected new HashSetstring { /api/login, /api/order/create }; HashSetstring seen new HashSetstring(); FiddlerApplication.AfterSessionComplete (Session oSession) { string path new Uri(oSession.fullUrl).AbsolutePath; if (expected.Contains(path)) seen.Add(path); }; // 测试收尾时 foreach (string p in expected.Where(p !seen.Contains(p))) { Console.WriteLine(漏发请求: p); }上面的 Where 来自 System.Linq需要加 using。这个断言专门抓一类问题界面操作没报错但关键的请求压根没发出去或者被中间件拦在半路。比看 UI 状态判断可靠性高得多因为它是从流量链路里直接取证而不是靠人眼猜。从那以后我每次搭 FiddlerCore 抓包服务都会强制走一遍四件事查根证书信任、查远程代理可达、查 bBufferResponse 是否设在请求阶段、查事件有没有重复订阅。确认这四点没问题再往回调里加业务逻辑能少翻一大半车。希望帮到你。本文还有配套的精品资源点击获取