简介:这份资源围绕PPTP协议展开,面向网络安全初学者与渗透测试入门者,提供从环境搭建到抓包分析再到口令破解的完整实验链路,适合作为课程实验或自学练手的参考材料。压缩包共12个文件,约3.7MB,以6个Python脚本和2个pcapng抓包文件为主,前者用于搭建与验证实验环境,后者保留真实流量便于对照分析;另配2份docx实验文档与2份txt说明,梳理基础原理与破解思路。已有959人学习下载,说明该实验在同类学习中具有一定参考价值。读者可据此完成PPTP服务端与客户端环境配置,借助抓包文件观察握手与认证过程,理解协议交互细节,并在此基础上尝试基于所获信息进行口令破解,形成从原理认知到动手验证的闭环,同时文档与脚本相互印证,便于复现实验与排查问题。
1. PPTP 的“加密”为什么一抓包就现原形
很多人第一次用抓包工具看 PPTP 流量时都会愣一下:明明配置了用户名密码,怎么认证过程里的哈希值直接躺在包里,连个像样的加密外壳都没有?更反直觉的是,PPTP 的隧道建立、控制连接、数据封装三件事分别跑在 TCP 1723 和 GRE 协议上,认证和加密用的还是两套完全独立的老机制——MS-CHAPv2 做身份校验,MPPE 做数据加密,而 MPPE 的密钥恰恰是从 MS-CHAPv2 的握手材料里推导出来的。这就意味着,只要把认证阶段的几个字段抓全,后面的“加密”在攻击者眼里基本等于明文。搞懂这条链路,你才能明白为什么一个看起来“能用”的隧道协议,在抓包分析面前会翻车得如此彻底,也才能理解实验报告里那些哈希、挑战值、会话密钥到底在传递什么。
2. 控制连接与隧道:PPTP 到底在哪一层干活
2.1 TCP 1723 只负责“谈”,GRE 才负责“搬”
PPTP 最容易被误解的地方,是把它当成一个“完整的加密隧道协议”。实际上它把职责拆得很干净:TCP 1723 上跑的是控制连接(Control Connection),负责建立、维护、拆除隧道,以及承载呼叫相关的管理消息;真正搬运用户数据的是 GRE 协议(IP 协议号 47),PPTP 在 GRE 头里塞了一个增强型 GRE 头,用 Payload Length、Call ID 等字段把多条隧道复用在同一对 IP 之间。
常见的实现思路是:客户端先和服务器在 TCP 1723 上完成一次 Start-Control-Connection-Request/Reply 握手,协商协议版本和帧能力;随后发起 Outgoing-Call-Request,服务器回 Outgoing-Call-Reply 并分配 Call ID;隧道建立后,数据面走 GRE,每个 GRE 包里带 Call ID 来区分不同的 PPP 会话。这个设计动机很直接——控制面和数据面分离,控制连接是有状态、可靠、低频的,GRE 是无连接、高频的,各取所需。
但代价也很明显:GRE 是裸 IP 协议,没有端口概念,很多网络设备对它的放行策略和 TCP/UDP 完全不同。你在实验环境里搭好服务端、客户端能 ping 通,换到另一台机器上却连不上,八成不是配置写错,而是 GRE 被中间设备拦了。这也是后面排查章节要重点讲的一类坑。
2.2 PPP 会话才是认证与加密的真正载体
隧道建好之后,PPTP 里跑的其实是 PPP 会话。也就是说,PPTP 本身不定义认证和加密,它只是把 PPP 帧封装进 GRE 里搬运。认证走的是 PPP 的 LCP 协商 → 认证阶段(PAP 或 MS-CHAPv2)→ NCP 阶段(IPCP 分配地址),加密则是在认证完成后由 MPPE 接管。
这个分层关系决定了抓包分析的正确姿势:你不能只盯着 TCP 1723 看,认证报文是封装在 PPP 帧里、再套进 GRE 的。用抓包工具时如果只过滤 tcp port 1723,你会看到控制消息,但看不到 Challenge、Response 这些关键字段;必须同时抓 GRE,或者直接对 PPP 层做解析,才能把 MS-CHAPv2 的握手材料完整还原出来。
我一般会这么理解:PPTP 像一辆货车,TCP 1723 是调度中心负责安排路线,GRE 是车厢负责装货,PPP 是货物本身的包装规范,MS-CHAPv2 和 MPPE 是包装上的封条。封条的安全性,取决于包装规范本身有多老。
2.3 一个最小可复现的实验拓扑
要讲清楚后面的机制,得先有一个能跑起来的环境。实验拓扑不需要复杂:一台服务端(可以是某开源系统上自带的 PPTP 服务组件),一台客户端,中间用一台可抓包的机器或直接在客户端上抓。服务端需要开启 GRE 转发、配置地址池、指定认证方式为 MS-CHAPv2 并启用 MPPE。
关键配置点在于:认证方式必须显式指定,不能留 auto,否则协商可能退化到 PAP,那样连挑战值都没有,后面的分析就失去意义;MPPE 要选 128 位,40 位和 56 位在密钥推导上的差异会影响你对抓包结果的判断。客户端侧则要确保使用 MS-CHAPv2,并且允许 MPPE。
环境搭好后,先用最朴素的方式验证连通性:建立隧道、拿到地址、ping 通服务端内网地址。这一步过了,才说明控制连接、GRE、PPP 三层都通了。很多实验报告写不下去,不是分析能力问题,而是环境本身没跑通,抓到的包是残缺的。
提示:实验环境务必隔离,抓包分析涉及认证材料,只在自建、无外网暴露的网段里做。
3. MS-CHAPv2 握手:挑战值、响应值与那串“派生密钥”
3.1 认证流程的时序与字段含义
MS-CHAPv2 的握手可以拆成四步:服务器发 Challenge(16 字节随机数 + 16 字节对端挑战值),客户端回 Response(24 字节的 NT-Response + 24 字节的 Authenticator-Response + 用户名),服务器校验后回 Success 并附带一个 Authenticator-Response,客户端再验证服务器身份。
这里的关键在于 NT-Response 的生成方式:它是对“挑战值 + 密码哈希”做一次单向运算得到的,具体是先用密码的 NT-Hash 做填充,再对挑战值做 DES 加密,得到 24 字节结果。也就是说,Response 里不包含密码,但它是密码和挑战值的确定性函数。抓包能拿到挑战值和 Response,就拿到了一个可以离线验证密码猜想的“方程”。
这就是为什么第 1 章说“一抓包就现原形”——认证阶段的设计目标从来不是抗离线分析,它假设的是挑战值一次性、通道可信。一旦攻击者能同时拿到挑战值和响应,这个假设就破了。
3.2 从握手材料到 MPPE 密钥的推导链
MPPE 的会话密钥不是独立协商的,而是从 MS-CHAPv2 的握手材料里“长”出来的。推导链大致是:把密码哈希、挑战值、响应值按固定顺序拼接,做一次哈希得到 Master Key,再从中截取 Asymmetric Start Key,然后为收发两个方向分别派生出会话密钥。
用伪代码表达这个推导过程,能更清楚地看到哪些字段是“输入”:
# 伪代码:MPPE 密钥推导的输入关系(仅示意字段依赖,非完整实现) def derive_mppe_keys(password_hash, challenge, nt_response, auth_response): # 1. 拼接握手材料,顺序固定,错一位结果全错 material = password_hash + challenge + nt_response + auth_response # 2. 哈希得到主密钥材料 master = hash_nt(material) # 3. 截取非对称起始密钥 start_key = master[:16] # 4. 按方向派生收发会话密钥 send_key = derive_direction_key(start_key, direction="send") recv_key = derive_direction_key(start_key, direction="recv") return send_key, recv_key这段伪代码的重点不在具体哈希函数,而在依赖关系:MPPE 密钥的每一个输入都来自认证握手。所以抓包分析时,只要把 Challenge、NT-Response、Authenticator-Response 三个字段抓全,再配合已知或猜想的密码,就能完整复现密钥推导,进而解密后续 GRE 里的数据。这也是为什么实验报告里必须把这三个字段单独列出来——它们是整条链路的“根”。
参数说明上要注意:challenge 是服务器发的 16 字节,nt_response 是客户端回的 24 字节,auth_response 是服务器在 Success 消息里回的 24 字节。三者缺一,推导链就断。很多抓包结果看起来“字段都在”,实际是解析层级不对,把 PPP 帧当成了 GRE 载荷,字段偏移全错。
3.3 为什么这套机制在设计上就注定被离线分析
站在设计动机上看,MS-CHAPv2 诞生时面对的主要威胁是“通道内窃听密码明文”,它的目标是让密码不以明文出现,而不是抵抗“拿到握手材料后的离线穷举”。挑战值虽然是随机的,但它和响应值一起暴露在同一个通道里,等于把方程和答案同时给了观察者。
更关键的是,NT-Hash 的计算不涉及任何慢哈希或加盐,密码空间稍小一点,离线穷举的成本就很低。这不是实现 bug,而是时代局限:当年的算力和威胁模型,和今天完全不是一回事。理解这一点,你才不会把“能抓到哈希”当成某个产品的配置失误,而是协议层的结构性取舍。
4. 抓包分析实战:从 GRE 里把 PPP 帧“剥”出来
4.1 过滤表达式与解析层级的对应关系
抓包分析 PPTP,过滤表达式写错是最常见的翻车点。只写tcp port 1723只能看到控制连接;只写gre能看到数据面但看不到认证协商的上下文;正确的做法是分两段抓:先抓控制连接确认隧道建立时序,再抓 GRE 并让解析器按 PPP 层展开。
在常见抓包工具里,可以先用pptp作为显示过滤,它会同时匹配控制连接和 GRE 里的 PPTP 封装;如果工具版本对 PPTP 解析支持不全,就退回到tcp port 1723 or proto 47,然后手动在 GRE 载荷里定位 PPP 帧头。PPP 帧头有固定的协议字段,0xC223 表示 CHAP,0x0021 表示 IP,0x0057 表示 MPPE 加密数据,靠这个字段判断当前帧处于哪个阶段。
这一步的机制意义在于:认证报文和加密数据在同一个 GRE 通道里交替出现,你必须靠 PPP 协议字段来区分“这是握手”还是“这是密文”。分不清这两者,后面的字段提取就无从谈起。
4.2 提取 Challenge 与 Response 的字段偏移
定位到 CHAP 报文后,字段是按固定结构排列的:Code、Identifier、Length、Value-Size、Value、Name。MS-CHAPv2 的 Challenge 在 Value 里,前 16 字节是服务器挑战值,后 16 字节是对端挑战值;Response 的 Value 前 24 字节是 NT-Response,接着 1 字节标志位,再 24 字节是 Authenticator-Response,最后是用户名。
用一段解析示意代码说明这个偏移关系:
# 伪代码:从 CHAP 报文中提取 MS-CHAPv2 关键字段 def parse_mschapv2(chap_payload): code = chap_payload[0] # 1=Challenge, 2=Response, 3=Success value_size = chap_payload[4] value = chap_payload[5:5 + value_size] if code == 1: server_challenge = value[0:16] # 服务器挑战值 peer_challenge = value[16:32] # 对端挑战值 username = value[32:].split(b'\x00')[0] return {"type": "challenge", "server": server_challenge, "peer": peer_challenge, "user": username} if code == 2: nt_response = value[0:24] # 核心:NT-Response flags = value[24] auth_response = value[25:49] # 服务器身份响应 return {"type": "response", "nt": nt_response, "auth": auth_response}代码后的机制说明:value_size决定了后续字段的边界,如果抓包工具把 PPP 帧头也算进了 payload,偏移会整体错位,提取出来的 challenge 就是垃圾数据。实际排查时,先打印原始十六进制,对照协议字段长度逐段核对,比直接套偏移可靠得多。参数上,NT-Response 固定 24 字节,这是 DES 分块的结果,不会变;用户名长度可变,所以放在最后。
4.3 用抓包结果验证密钥推导是否成立
拿到三个关键字段后,验证推导链是否成立的方法很朴素:用已知密码走一遍推导,得到会话密钥,再去解一条 MPPE 加密的 GRE 数据帧,看解出来的 PPP 载荷是不是合法的 IP 包(协议字段 0x0021,IP 头版本 4)。如果解出来是乱码,说明字段提取或推导顺序有问题;如果解出来是合法 IP 包,整条链路就闭环了。
这个验证过程本身就是实验报告最有价值的部分——它把“抓到了什么”变成“证明了什么”。很多人写报告只贴抓包截图,缺的就是这一步闭环验证,读者无法判断那些字段到底有没有被正确使用。
注意:验证只在自建实验环境内进行,密钥推导代码不要用于任何非授权网络。
5. 避坑与排查:实验里最容易翻车的五件事
5.1 隧道显示已连接但 ping 不通
现象:客户端显示隧道建立成功、拿到地址,但 ping 服务端内网地址全部超时。原因通常是 GRE 被中间设备拦截,或者服务端没有开启 IP 转发、没有配置 NAT。控制连接走 TCP 1723 能通,不代表 GRE 能通,两者是独立放行的。解决:在服务端确认转发开关已开,检查防火墙对协议号 47 的策略,必要时在两端同时抓包确认 GRE 包是否真的发出和到达。
5.2 抓包只看到控制消息,看不到认证字段
现象:过滤tcp port 1723后只有 Start-Control-Connection 之类的消息,找不到 Challenge 和 Response。原因是认证报文封装在 GRE 的 PPP 帧里,不在 TCP 1723 上。解决:改用pptp显示过滤或proto 47,并确保抓包点覆盖 GRE 路径;如果抓包工具不解析 PPP,手动按协议字段定位。
5.3 认证协商退化到 PAP
现象:抓包里完全没有挑战值,只有明文用户名密码字段。原因是服务端认证方式配置成了 auto 或允许 PAP,客户端协商时选了最弱的方式。解决:服务端显式指定只允许 MS-CHAPv2,客户端侧同样锁定,重新建立隧道后确认 CHAP 报文出现。
5.4 字段偏移全错,提取出乱码
现象:按固定偏移取 challenge,得到的十六进制明显不符合随机数特征,或者长度对不上。原因是解析层级错了,把 GRE 头或 PPP 头算进了 payload,或者把 CHAP 的 Length 字段当成了 Value-Size。解决:先打印完整十六进制,逐层剥离 GRE 头、PPP 头,再按 CHAP 结构解析,用 Length 字段做边界校验。
5.5 密钥推导结果解不开数据帧
现象:三个字段都提取到了,推导出的密钥却解不出合法 IP 包。常见原因是拼接顺序错、把对端挑战值和服务器挑战值弄反、或者 MPPE 位长选错(40/56/128 位对应不同截取长度)。解决:对照协议规定的拼接顺序逐项核对,先用一条已知明文的测试流量做校准,确认推导链无误后再解真实数据。
6. 设计取舍:为什么老协议值得被拆开看
把 PPTP 拆到这个程度,会发现它的每一个“弱点”背后都有一个当年的合理取舍。控制面用 TCP 是为了可靠和简单,数据面用 GRE 是为了低开销和多路复用,认证用 MS-CHAPv2 是为了不让密码明文过网,加密用 MPPE 是为了在 PPP 框架内快速加上 confidentiality。单看每一层,都是当时条件下成本与收益的平衡;问题出在层与层之间的耦合——MPPE 的密钥完全寄生在认证握手上,而认证握手又把推导所需的全部材料暴露在同一通道里。这种“密钥来源单一且可观测”的结构,才是它经不起抓包分析的根本原因。
对比后来者的设计思路,差异不在某个算法更强,而在密钥协商被独立出来、握手材料不再同时暴露给观察者、以及认证与加密的信任边界被重新划分。理解这个演进逻辑,比记住“PPTP 不安全”这句结论有用得多,因为你在评估任何隧道方案时,都可以问同样三个问题:密钥从哪来、握手材料暴露了什么、控制面和数据面的信任假设是否一致。
验证方法上,我习惯用“闭环”标准来判断自己是否真的搞懂了:能不能从抓包字段一路推到解密成功,能不能在推导失败时定位到具体是哪一层偏移错了。这个习惯来自一次血泪经验——早期做实验时字段全抓到了,推导却一直失败,最后发现是把对端挑战值和服务器挑战值的位置弄反了,一个字节的顺序问题让整份报告卡了两天。从那以后我每次做协议分析,都强制先把字段偏移和拼接顺序写成可执行的校验代码,跑通了再往下写结论。希望帮到你。
本文还有配套的精品资源,点击获取