追捕 EtherSlip(DOS 网络)中潜伏 34 年的指针 Bug

Hacker News Top 工具

摘要

一位开发者讲述如何利用 Open Watcom 的堆损坏哨兵,追踪并修复 EtherSlip DOS 包驱动里一个存在了 34 年的 NULL 指针错误。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/04/22 03:34

# 追捕 EtherSlip 中潜伏 34 年的指针 Bug 来源:https://www.brutman.com/Adventures_In_Code/EtherSlip_ARP/EtherSlip_ARP.html mbbrutman@gmail\.com 发布时间:2026-04-19 标签:DOS、网络、分段内存真难 几周前,我重新整理了一份“在 DOS PC 与 Linux 之间跑 SLIP 连接”的教程。如果你不熟 SLIP,它全称 Serial Line Internet Protocol,能让你通过串口跑 TCP/IP。TCP/IP 走以太网当然更快,但串口也能凑合。 DOS 下有好几个 packet driver 支持 SLIP,我常用的是“EtherSLIP”——它伪装成以太网 packet driver,底层其实只是串口 SLIP。这样,所有给以太网写的程序都能直接跑,不用改;否则就得找专为 SLIP 写的版本。mTCP 全家桶都认以太网,它们才不管底下是真网卡还是 EtherSLIP 冒充的。(除了 EtherSLIP,Crynwr 包里还有个 Token Ring driver 也冒充以太网。) 我用 Telnet 测试,结果线缆有问题,速度慢得发指还疯狂丢包(最后证实是硬件毛病)。Telnet 退出时突然蹦出一条: `` *** NULL assignment detected `` 这可不是好事。我用的 Open Watcom 1.9 会在程序结束时检查堆是否被踩,但这条提示是另一码事。翻遍 PDF,在《Watcom C/C++ Programmer's Guide》里找到解释: - 正常 OS 里解引用 NULL 会触发信号/异常;16 位 DOS 没这能力,写了就写了,不会当场爆炸。 - 编译器耍了个花招:在数据段开头留 32 字节,填好固定魔数;程序退出时再看这 32 字节有没有被改。如果被改,大概率是有人通过 NULL 指针写了东西。(别的指针不该指向这片区域。) 收到这条警告,就说明前 32 字节被踩了,铁定有 Bug。即使没警告也不代表没 Bug——只要写歪一点,超出这 32 字节,机制就抓不到。 现状: - 只有 EtherSLIP 会触发;这编译器我用了 15 年头回见。 - 只有丢包重传时才爆。 - 机器是 8088,Open Watcom 调试器挂不上,只能硬看。 ## 第一次:到处插 if 编译器告诉我“有人拿 NULL 写内存”,那我就在可疑路径插桩,看到 NULL 就报警。思路简单,活儿枯燥,要插就得把相关指针全查一遍。好在重传代码不算复杂,开干: `` void near TcpSocket::resendPacket( TcpBuffer *buf ) { if ( buf == NULL ) { TRACE_WARN(("Whoops: resendPacket tried to reference a NULL pointer.")); return; } TcpPacket_t* packetPtr = &buf->headers; ... `` 跑一趟、复现一次,没触发;继续加桩,直到把整个 Telnet 插成刺猬,依旧没影儿——此路不通,换招。 ## 第二次:提前验尸 编译器只在程序收尾才验那 32 字节;我把它提前,边走边验,总能逮到 closer 的现场。 先扒编译器源码,目标文件 bld/clib/startup/a/cstrt086.asm,核心摘抄如下: `` assume ds:DGROUP INIT_VAL equ 0101h NUM_VAL equ 16 _NULL segment para public 'BEGDATA' __nullarea label word dw NUM_VAL dup(INIT_VAL) public __nullarea _NULL ends `` 出错字符串: `` NullAssign db '*** NULL assignment detected',0 `` 退出时的检查: `` __exit proc near ... lea di,__nullarea repe scasw je ok ... `` 简化:在数据段最前面放 16 个 0x0101,程序结束时扫一遍,谁改谁背锅。 我用 C 抄一份运行时检查: `` extern "C" uint8_t _nullarea; uint8_t *_nullareap = &_nullarea; bool failed = false; extern "C" void nullCheck( const char *loc ) { if (failed) return; for (int i=0;i<32;i++) if (_nullareap[i]!=0x01) { TRACE_WARN(("Null check failed at %s\n", loc)); Utils::dumpBytes(Trace_Stream, _nullareap, 32); failed=true; break; } } `` 然后在 Telnet 里到处插 `nullCheck("xxx");`。最终锁定: `` nullCheck("Packet_send_pkt sendattempt"); int86x(Packet_int, &inregs, &outregs, &segregs); nullCheck("Packet_send_pkt after soft int"); `` 第二次调用炸了。说明不是我的 Telnet 代码,而是 packet driver 里踩了内存,前面插 if 当然抓不到。 trace 如下: `` 2026-04-17 16:41:28.59 W Null check failed at Packet_send_pkt after soft int Buffer address: 318a:0000 01 01 00 02 12 00 56 34 01 01 01 01 01 01 01 01 ......V4........ 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ................ `` 只有 6 字节被改,内容固定:`00 02 12 00 56 34`。抬头看启动日志: `` MAC=00.02.12.00.56.34 `` 好家伙,被踩的正是 EtherSLIP 伪造的 MAC 地址!范围瞬间缩小:send 路径里谁动 MAC 谁就脱不了干系。 ## 先补点 x86 分段常识 真·x86 16 位是分段内存模型:段寄存器×16+偏移,可寻 1 MB。段寄存器有 CS/IP、SS/SP、DS/SI、ES/DI 等。同一物理地址可被多组“段:偏移”指到,例如: `` 003F:0032 ≡ 0040:0022 ≡ 0041:0012 → 物理 0x00422 `` 因此指针比较得先归一化,否则看着不同,其实指同一字节。C 语言把这些破事藏起来了,汇编里得自己盯。 ## 顺便看一眼 ARP 包格式 ARP 用来在同网段查 MAC。SLIP 本身是点对点,不需要 ARP;但 EtherSLIP 要“演”以太网,就得陪玩 ARP:收到 ARP 请求就回一个假的 ARP 应答,让上层以为自己在以太网上。 ## 现在进 EtherSLIP 源码找 Bug EtherSLIP 源码随 Crynwr 包放出,开源就是爽——30 年后还能翻旧账。可惜 1991 年加的那堆以太网仿真注释少得可怜,全是跳来跳去的汇编。 send_pkt 入口:上层把要发的包指针和长度递进来,驱动拆掉以太网头,按 SLIP 格式封装后扔串口。ARP 包会被截胡:如果是 ARP 请求,就不真发,直接内部回一个伪造应答。 mTCP 其实有点 SLIP 意识,启动时会给自己 IP 塞一条假 ARP 记录,省得平时再查。但有俩例外仍会发 ARP: 1. 启动时自检地址冲突,会 ARP 自己。 2. 重传前,会再 ARP 下一跳。 情况 1 EtherSLIP 特意放过,不回;情况 2 会触发 EtherSLIP 的伪造 ARP 应答流程——Bug 就藏在这里。

相似文章

大鱼:利用 OpenBSD 中一个存在了 37 年的内核漏洞

Hacker News Top

安全研究公司 Calif 披露了一个存在于 OpenBSD 中、可追溯至 37 年前原始 BSD 源码的内核漏洞:共享内存附加计数器(shm_nattch)为有符号 16 位 short,通过反复 fork 可导致整数溢出,结合竞争条件引发的 use-after-free 和一处越界写,最终在启用 KARL/SMAP/SMEP 的情况下实现本地提权至 root。相关补丁已通过 OpenBSD errata 026 和 018 修复。

OpenBSD的PPP协议栈中存在一个存在27年的认证绕过漏洞

Lobsters Hottest

OpenBSD的PPP协议栈中存在一个存在27年的认证绕过漏洞,攻击者通过发送长度为零的用户名字段和密码字段,利用PAP处理器中缺失的边界检查,无需凭证即可获得完整的PPPoE访问权限。同样的代码还允许内核堆内存越界读取。

核心转储流行病学:修复一个18年的旧bug

OpenAI Blog

OpenAI工程师详细描述了Rockset的C++数据基础设施中看似不可能的崩溃的诊断过程,揭示了一个Azure上的静默硬件损坏bug以及GNU libunwind中存在18年的竞态条件,最终通过崩溃数据的流行病学分析得以解决。