自己动手写代理:一个能“偷改“网页的底层HTTP/HTTPS中间人工具
一、先说说这玩意儿到底是啥
想象一下快递中转站:你买的东西从商家发出,先到中转站,再送到你手里。正常情况下,中转站只是转手。但如果这个中转站"不正经"——它不仅能偷看你的包裹,还能把里面的说明书换成别的内容,甚至往包裹里塞小广告——这就是这个工具干的事。
这是一个用C语言写的底层网络代理程序。它站在你的浏览器和目标网站之间,所有进出的流量都要经过它手。它的本事主要有三块:
第一块:正经代理。 你访问啥网站,它帮你转发请求,再把网站返回的内容传回给你。支持普通的HTTP,也支持加密的HTTPS。
第二块:内容篡改。 这是它的核心花活。它能在网页传输过程中,按照你设定的规则修改网页内容。比如把所有<h1>标题后面插一段广告,或者把网页里所有的链接都替换成某个视频地址(对,就是那个著名的Rickroll)。
第三块:SSL中间人。 对于HTTPS加密流量,它自带了一套SSL证书体系。浏览器以为在和真网站加密通信,实际上是在和这个代理加密通信;代理解密内容改完以后,再和真网站加密通信。这样一来,加密流量也能被"动手脚"。
项目还自带了两个"恶作剧模式":一个是-gravity,会自动往网页里注入一段JavaScript,让网页上的元素像有了重力一样往下掉;另一个是-rickroll,会把网页里所有的超链接都换成Rick Astley那首《Never Gonna Give You Up》的YouTube链接。
二、这背后涉及了哪些技术领域
别看代码量不大,它横跨了好几个技术方向:
| 领域 | 具体涉及内容 |
|---|---|
| 网络编程 | Socket编程、TCP连接管理、端口监听、非阻塞/超时处理 |
| HTTP协议 | 请求/响应格式解析、Header处理、Content-Length计算、Transfer-Encoding、Content-Type判断 |
| SSL/TLS安全 | OpenSSL库使用、证书链、SSL握手(双向)、X.509证书伪造/自签名 |
| 数据压缩 | Zlib/gzip解压与压缩(处理网页传输中的压缩内容) |
| 正则表达式 | POSIX regex编译与匹配、HTML标签/属性的特殊正则构造 |
| 进程管理 | Fork多进程模型、僵尸进程处理(SIGCHLD信号)、父子进程资源回收 |
| 字符串处理 | C语言内存管理、字符串替换、缓冲区动态扩容 |
三、整体设计思路:简单直接,不整花活
在这里插入代码片
这个程序的设计哲学就一个字:直。没有线程池、没有事件循环、没有复杂的异步框架,就是最经典的Unix网络编程三板斧:
1. 多进程模型:来一个连,fork一个娃
主进程只管一件事:在端口上监听(默认9999)。每当有浏览器连上来,accept之后立刻fork()一个子进程。子进程负责处理这个连接的全部生命周期——从读请求、连目标网站、改内容、到返回响应。处理完就退出。主进程继续accept下一个。
while(1){
newfd = accept(sockfd, ...);
if (fork() == 0){ // 子进程
close(sockfd); // 子进程不需要监听套接字
proxyHttp(newfd, userEditPage);
close(newfd);
exit(0);
} else { // 父进程
close(newfd); // 父进程不需要连接套接字
}
}
这种设计简单粗暴,但非常符合Unix哲学:一个连接一个进程,崩溃也不影响主进程。代码里还注册了SIGCHLD信号处理函数来回收僵尸进程,防止子进程退出后变成"孤魂野鬼"占用系统资源。
2. 模块化回调:解析和篡改分离
程序把"网络转发"和"内容篡改"解耦了。proxyHttp函数只管网络层面的转发,当它发现返回的是HTML内容时,调用一个回调函数editCallback(也就是userEditPage)来处理内容。改完后再继续发送。
这种设计的好处是:如果你想加新的恶作剧功能,不需要动网络转发的代码,只需要写一个新的回调函数塞进去就行。
3. 命令行驱动:所有配置靠参数
程序没有配置文件,所有功能都通过命令行参数开启。参数解析用了一个简单的循环匹配:
while( (o=parseArgs(argc, argv, &cur)) != -1){
switch(o){
case CL_REGEX: // 设置正则
case CL_STRING: // 设置插入字符串
case CL_FILES: // 设置插入文件
case CL_AFTER: // 设置插入位置
// ... 等等
}
}
四、核心流程图解:一个请求是怎么被"动手脚"的
整体数据流
用户请求
│
▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│ socket │───►│ HTTP解析 │───►│ 提取Host │
│ 监听 │ │ 请求头 │ │ 和Path │
└─────────┘ └──────────┘ └────┬─────┘
│
▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│ socket │◄───│ HTTP响应 │◄───│ 连接目标 │
│ 返回 │ │ 组装返回 │ │ 服务器 │
└─────────┘ └──────────┘ └────┬─────┘
│
┌───────────────┘
│
▼
┌──────────────┐
│ 判断是HTML? │──否──► 直接转发
└──────┬───────┘
│是
▼
┌──────────────┐
│ gzip解压 │
│ 正则匹配替换 │
│ 更新Content-Length
└──────────────┘
对于HTTPS流量的特殊处理
浏览器访问 https://example.com 时,流程会更复杂一些:
浏览器访问 https://example.com
│
▼
┌─────────────────────────────────────┐
│ 1. 浏览器 ──CONNECT──► 代理 │
│ (以为代理是目标服务器) │
│ 2. 代理 ──SSL握手──► 浏览器 │
│ (出示伪造证书,证书由自带CA签名) │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 3. 代理 ──TCP连接──► 真实服务器 │
│ 4. 代理 ──SSL握手──► 真实服务器 │
│ (代理此时是"客户端"身份) │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 5. 真实服务器返回加密网页 │
│ 6. 代理解密 ──篡改内容 ──重新加密 │
│ 7. 浏览器收到"合法"的加密响应 │
│ (其实内容已被偷偷修改) │
└─────────────────────────────────────┘
这里的关键是代理需要两套SSL身份:
- 面对浏览器时,它是"服务器",需要用自己的CA证书签发一个伪造的
example.com证书 - 面对真实网站时,它是"客户端",验证真实网站的证书
代码里用SSLWrap(&req, SSL_ACCEPT | HTTP_REQ)和SSLWrap(&res, SSL_CONNECT | HTTP_RES)分别处理这两侧。
五、代码里的几个关键"机关"
1. 内容篡改的核心:replaceFunc
这是整个程序最精妙的函数之一。它不负责真正的替换,而是负责告诉程序在哪里切一刀。
int replaceFunc(const char* string, Range* r){
static int limit = -2;
if (limit == -2) limit = Prox.options.count; // 初始化匹配次数限制
// 用正则找到匹配位置
if (Prox.match(string, r, Prox.regex) != 0) return -1;
// 根据用户指定的位置(before/after/replace/append/prepend)调整Range
switch (Prox.options.position){
case CL_BEFORE: // 在匹配内容之前插入
r->end = r->start;
break;
case CL_AFTER: // 在匹配内容之后插入
r->start = r->end;
break;
case CL_REPLACE: // 替换匹配内容本身
break;
case CL_APPEND: // 在匹配标签结束前插入
r->start = (r->end -= Prox.options.offset);
break;
case CL_PREPEND: // 在匹配标签开始后插入
// 找到结束标记的位置...
break;
}
// 返回下次搜索的偏移量
return offset;
}
这个函数返回的是一个Range(起始位置和结束位置),真正的字符串替换由replaceAll和insertFiles完成。这种"找位置+切分"的设计让同一段逻辑能同时支持"插入字符串"和"插入文件"两种操作。
2. HTML特殊匹配:不用写正则,直接指定标签
程序很贴心地提供了-matchtag和-matchattr参数。你不需要写复杂的正则去匹配<a href="...">,直接说"我要匹配所有href属性"就行。
代码里会根据你的输入,自动编译出对应的正则:
- 匹配HTML标签:
compileRegexTag(tag),比如匹配<h1>...</h1> - 匹配HTML属性:
compileRegexAttr(attr, tag),比如匹配href="..."
3. HTTP头的"外科手术"
程序不仅能改网页内容,还能改HTTP头。支持三种操作:
--add-headers:新增头,比如加个Set-Cookie--replace-headers:替换头,比如把Last-Modified改掉--block-headers:删除头,比如干掉X-XSS-Protection和Content-Security-Policy
这最后一条很有意思——删掉安全相关的HTTP头,本质上是在"削弱"目标网站的安全防护,为后续的脚本注入铺路。
4. Gzip的"剥洋葱"处理
现代网站为了省流量,响应内容通常是gzip压缩的。程序的处理很直接:
if (strstr(H->data, "gzip") != (char*)0){
decodeGzip(&res.store->content, &res.store->contentLength);
deleteHttpHeader(&res.header, (char*) 0, HTTPH_C_ENCODING); // 删掉Content-Encoding头
}
先解压,篡改完以后(理论上应该重新压缩,但代码里似乎依赖后续处理),再更新Content-Length。如果篡改后内容变长了,这个长度必须重新计算,否则浏览器会截断或等待超时。
六、那两个"不正经"的恶作剧是怎么实现的
Rickroll模式
代码里有一行很骚的操作:
if(scenarios & CL_RICKROLL)
return setupRickRoll();
这个模式会把网页里所有的<a href="...">链接,全部替换成Rick Astley的YouTube视频链接。实现原理就是在网页HTML内容上做全局字符串替换,把href="原链接"改成href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"。
Gravity模式(重力模式)
这个模式会往网页里注入一段JavaScript代码,让页面上的DOM元素受到"重力"影响往下掉。实现方式是在HTML的<head>或<body>里插入一个<script>标签,标签内是一段操作CSS transform或position的JS代码。
这两个模式本质上都是预设的篡改规则,只是比手动指定正则更方便,属于"一键搞事情"。
七、准备SSL证书 和 运行测试
.
├── commandline.c # 命令行解析
├── commandline.h
├── http.c / http.h # HTTP协议解析
├── ssl.c / ssl.h # OpenSSL封装
├── regex.c / regex.h # 正则匹配
├── utils.c / utils.h # 工具函数
├── scenarios.c / scenarios.h # 恶作剧功能
├── proxy.c / proxy.h # 代理核心逻辑
└── data/ # 放证书的地方
If you need the complete source code, please add the WeChat number (c17865354792)
准备SSL证书
代码里默认会找这两个文件:
data/localhost.pem(CA证书)data/privkey.pem(私钥)
如果目录里没有,自己生成一套自签名的:
mkdir -p data
cd data
# 生成私钥
openssl genrsa -out privkey.pem 2048
# 生成自签名证书(Common Name可以填localhost)
openssl req -new -x509 -key privkey.pem -out localhost.pem -days 365 \
-subj "/C=US/ST=Test/L=Test/O=Test/CN=localhost"
cd ..
注意: 这套证书是"伪造"的。现代浏览器访问HTTPS网站时,会弹出大红警告(证书不受信任),这是正常的。如果你想让浏览器不报警,需要把localhost.pem安装到系统的"受信任的根证书颁发机构"里(后面会说)。
运行测试
1. 先看帮助
./prox
如果参数不够,程序会打印出你贴的那段帮助信息,告诉你怎么用。
2. 最简单的HTTP测试
先别急着搞HTTPS,先用HTTP试一把,确认代理能跑通。
启动代理(监听9999端口,目标是example.com):
./prox -p 9999 -r "Example Domain" -string "【你被我改了】" example.com
这个命令的意思是:
-p 9999:代理监听本地9999端口-r "Example Domain":正则匹配网页里的"Example Domain"这串字-string "【你被我改了】 ":把匹配到的内容替换成这串字example.com:目标主机
测试方法A:用curl(最方便)
# 通过代理访问
http_proxy=http://127.0.0.1:9999 curl -v http://example.com
如果成功,你会在返回的HTML里看到 【你被我改了】 而不是原来的 “Example Domain”。
测试方法B:用浏览器
打开浏览器(建议先用命令行浏览器如lynx或links测试,避免缓存干扰):
# 安装lynx
sudo apt-get install lynx
# 通过代理访问
lynx -proxy=http://127.0.0.1:9999 http://example.com
或者给Chrome/Firefox设置代理:
- 地址:
127.0.0.1 - 端口:
9999
然后访问 http://example.com,看看标题有没有被改掉。
3. 测试"恶作剧模式"
项目自带两个整活功能,一键启动:
Rickroll模式(把所有链接换成Rick Astley视频):
./prox -p 9999 -rickroll example.com
Gravity模式(给网页加物理重力效果):
./prox -p 9999 -gravity example.com
测试方法和上面一样,通过代理访问看效果。
HTTPS测试
HTTPS的问题是证书信任。因为代理会用自己生成的CA证书去冒充目标网站,浏览器会报警。
测试步骤:
1. 启动代理
./prox -p 9999 -r "<title>" -string "<title>【HTTPS也被改了】" -ca data/localhost.pem -pk data/privkey.pem example.com
2. 把证书导入浏览器/系统
不同系统方式不一样:
Linux(以Firefox为例):
- 打开Firefox设置 → 隐私与安全 → 证书 → 查看证书
- 导入
data/localhost.pem,勾选"信任此证书来标识网站"
Windows:
- 双击
localhost.pem→ 安装证书 → 选择"本地计算机" → 放进"受信任的根证书颁发机构"
macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain data/localhost.pem
3. 浏览器设置代理后访问 https://example.com
如果证书导入成功,浏览器不会报红字,并且网页标题会被改成 【HTTPS也被改了】。
如果没导入证书,浏览器会显示"您的连接不是私密连接",点高级→继续前往(不同浏览器叫法不一样)也能看到效果,只是每次都有警告。
八、知识要点总结:从这段代码能学到什么
如果你是想学习网络编程和安全攻防,这个项目虽然不大,但浓缩了很多实用知识点:
1. HTTP代理的本质是"双向搬运工"
代理不是简单的转发字节流,它必须完整解析HTTP请求头(特别是Host头),因为目标地址在请求头里,不在TCP层。对于HTTPS,还需要处理CONNECT方法建立隧道。
2. SSL中间人的核心是"证书信任链"
为什么浏览器会信任代理伪造的证书?因为代理自带了一个自签名CA,你需要事先把这个CA的根证书安装到浏览器/系统的信任列表里。一旦信任,代理就能为任意域名签发"合法"证书。这也解释了为什么企业内网能监控HTTPS流量——公司电脑预装了企业CA证书。
3. 内容篡改的三个前提
- 必须能解密(对于HTTPS需要SSL中间人)
- 必须能识别内容类型(通过
Content-Type: text/html判断) - 必须能处理压缩(先解压再改,改完更新长度)
4. 多进程模型的利弊
利:代码简单,一个连接一个进程,天然隔离,崩溃安全。
弊:进程创建开销大,不适合高并发。现代高性能代理一般用事件驱动(epoll/kqueue)或线程池。但对于一个教学/演示性质的工具,多进程足够用了。
5. 正则表达式是文本篡改的瑞士军刀
程序不仅支持用户自定义POSIX正则,还内置了HTML标签和属性的正则生成器。理解regcomp和regexec在C中的用法,是学习底层文本处理的好例子。
6. 安全与攻击是一体两面
程序里删除Content-Security-Policy头的操作,是典型的"先拆盾再攻击"思路。Content-Security-Policy(CSP)是现代浏览器防止XSS和注入攻击的重要机制,删掉它,后续注入的脚本才能顺利执行。
Welcome to follow WeChat official account【程序猿编码】
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)