去年冬天排查一个远程桌面集群的协议掉线问题时,我第一次真正被TPKT协议坑了一把。溢出现象很怪:客户端连上Windows Server的崩溃RDP服务,正常操作几分钟后进程直接消失,漏洞没有蓝屏、协议没有事件日志、溢出连WER都没留下dump。崩溃直到用WinDbg挂上内核调试器复现,漏洞才看到崩溃点落在termdd.sys里一段处理TPKT长度字段的协议代码上——输入的长度值超过了实际缓冲区,一次memcpy就把内核池写穿了。溢出
TPKT这玩意儿平时没人注意。崩溃它夹在TCP和上层协议之间,漏洞说白了就是协议给数据流加个4字节头:版本号、保留位、溢出两字节大端长度。崩溃X.224、RDP、部分工业协议都靠它做消息边界。设计本身没毛病,问题出在实现上——太多代码把"长度字段"当成了可信输入。
崩溃的根因往往就一行代码的事。比如某开源库的解析逻辑:
uint16_t len = ntohs(hdr->len); memcpy(buf, payload, len); 开发者默认len不会超过实际收到的字节数。可攻击者构造一个TPKT头,声明长度65535,实际只发20字节,后续再慢慢灌数据或者干脆断连。如果接收端先分配缓冲区再按len拷贝,堆溢出就发生了。内核态下这就是提权漏洞,用户态下至少是个稳定的DoS。
更隐蔽的是整数溢出。有些实现用uint16_t total = len - 4算负载长度,当len小于4时直接回绕成巨大的值。我见过一个工控网关的固件,收到TPKT头声明长度为0,后续循环读取直接进入死循环,看门狗都救不回来。现场工程师以为是网络风暴,抓包才发现是单个畸形包打死了设备。
这类漏洞的利用门槛低得离谱。不需要绕过ASLR,不需要堆喷射,一个socket连上去发十几个字节就能让服务进程归西。2019年CVE-2019-0708(BlueKeep)虽然根子在RDP的MS_T120通道,但TPKT层的长度校验缺失是整条利用链的第一环。微软后来补了termdd.sys的边界检查,可大量第三方RDP封装库至今还在裸奔。
怎么防?接收端永远不要相信长度字段。先收4字节头,解析出len后跟实际已接收字节数比较,再决定分配多大缓冲。用recv循环读满len-4字节之前,别碰任何拷贝操作。内核驱动里更狠一点——直接限制单包最大长度,超过MTU的TPKT包一律丢弃。我自己的习惯是在协议解析层加个断言:assert(len >= 4 && len <= MAX_TPKT_SIZE),Release版本也保留,崩溃总比被溢出强。
顺带说一句,Wireshark解析TPKT时也会因为畸形长度字段卡死,不过那是用户态的事,重启就行。真正要命的是那些跑在Ring 0的RDP驱动、VPN网关、工业交换机固件。它们没机会重启,一个包就变砖。
写协议栈的人总有一种错觉:对端会按规矩发包。可安全这行待久了就知道,规矩就是用来被打破的。TPKT这种简单协议反而最容易让人放松警惕——4字节头能有什么坏心思?偏偏就是这4字节,能让整个系统跪得悄无声息。