Windows 7 TCP/IP劫持
盲TCP/IP劫持在Windows 7上仍然可行……且不仅限于此。尽管2020年1月14日是Windows 7的官方终止支持(EOL)日期,但该版本Windows仍然是“多汁”目标之一。根据各种数据,Windows 7占据操作系统(OS)市场约25%的份额,仍然是全球第二受欢迎的桌面操作系统。
历史背景
2012年加入微软担任安全软件工程师前几个月,我向微软提交了一份报告,其中包含所有Microsoft Windows版本(包括当时最新的Windows 7)中的一个有趣漏洞。这是TCP/IP栈实现中的一个问题,允许攻击者执行盲TCP/IP劫持攻击。在与MSRC(微软安全响应中心)的讨论中,他们承认该漏洞存在,但对问题的影响表示怀疑,声称利用“非常困难且非常不可靠”。因此,他们不打算在当前操作系统中修复它,但会在即将发布的Windows 8中解决。
我不同意MSRC的评估。2008年,我开发了一个完全可用的PoC工具,可自动查找执行盲TCP/IP劫持攻击所需的所有基本要素(客户端端口、SQN和ACK)。该工具利用的正是我报告的TCP/IP栈中的相同弱点。微软表示,如果我分享我的工具(我不愿意这样做),他们会重新考虑决定。但当时不会分配CVE,此问题预计在Windows 8中解决。
随后几个月,我作为全职员工(FTE)加入微软,并验证了此问题在Windows 8中已修复。多年来,我完全忘记了此事。然而,离开微软后,我在旧笔记本电脑上清理时找到了我的旧工具。我复制了它,并决定在有时间时重新审视。我找到了一些时间,认为我的工具值得发布和适当描述。
什么是TCP/IP劫持?
很可能大多数读者都知道这是什么。对于不知道的人,我鼓励您阅读互联网上许多关于此的优秀文章。
值得一提的是,最著名的盲TCP/IP劫持攻击可能是Kevin Mitnick于1994年圣诞节对San Diego Supercomputer Center的Tsutomu Shimomura计算机进行的攻击。
这是一种非常古老的技术,没人期望它在2021年仍然存在……然而,今天仍然可以在不攻击负责生成初始TCP序列号(ISN)的PRNG的情况下执行TCP/IP会话劫持。
TCP/IP劫持的当前影响
(不)幸的是,它不再像过去那样灾难性。主要原因是大多数现代协议都实现了加密。当然,如果攻击者可以劫持任何已建立的TCP/IP会话,那是非常糟糕的。但是,如果上层协议正确实现加密,攻击者在此方面的能力将受到限制,除非他们能够正确生成加密消息。
也就是说,我们仍然广泛部署不加密流量的协议,例如FTP、SMTP、HTTP、DNS、IMAP等。幸运的是,像Telnet或Rlogin这样的协议(希望?)只能在博物馆中看到。
漏洞在哪里?
TL;DR:在Windows 7的TCP/IP栈实现中,IP_ID是一个全局计数器。
详情
我2008年开发的工具实现了一种已知攻击,由‘lkm’(有拼写错误,作者真实昵称是‘klm’)在Phrack 64杂志中描述,可在此处阅读:
这是一篇惊人的文章(研究),我鼓励每个人仔细研究所有细节。
早在2007年(和2008年),此攻击可以成功在许多现代OS(当时现代)上执行,包括Windows 2K/XP或FreeBSD 4。我在波兰的一个本地会议(SysDay 2009)上对Windows XP进行了此攻击的现场演示。
在我们深入如何执行所述攻击的细节之前,有必要刷新TCP如何处理通信的更多细节。引用phrack论文:
连接中涉及的两个主机在连接建立时随机计算一个32位SEQ号。此初始SEQ号称为ISN。然后,每次主机发送带有N字节数据的包时,它将N添加到SEQ号。发送方在每个传出TCP包的SEQ字段中填入其当前SEQ。ACK字段填入来自另一主机的下一个预期SEQ号。每个主机将维护自己的下一个序列号(称为SND.NEXT),和来自另一主机的下一个预期SEQ号(称为RCV.NEXT)。TCP通过定义“窗口”的概念实现流控制机制。每个主机有一个TCP窗口大小(动态,特定于每个TCP连接,并在TCP包中宣布),我们称之为RCV.WND。在任何给定时间,主机将接受序列号在RCV.NXT和(RCV.NXT+RCV.WND-1)之间的字节。此机制确保在任何时间,不会有超过RCV.WND字节“在传输中”到主机。
简而言之,为了执行TCP/IP劫持攻击,我们必须知道:
- 客户端IP
- 服务器IP(通常已知)
- 客户端端口
- 服务器端口(通常已知)
- 客户端的序列号
- 服务器的序列号
好的,但这与IP ID有什么关系?
1998年(!),Salvatore Sanfilippo(又名antirez)在Bugtraq邮件列表中发布了一种新的端口扫描技术描述,今天称为“空闲扫描”。原始帖子可在此处找到: https://seclists.org/bugtraq/1998/Dec/79
关于空闲扫描的更多信息可在此处阅读: https://nmap.org/book/idlescan.html
简而言之,如果IP_ID实现为全局计数器(例如在Windows 7中),它只是随每个发送的IP包递增。通过“探测”受害者的IP_ID,我们知道每个“探测”之间发送了多少包。这种“探测”可以通过发送任何导致攻击者回复的包到受害者来执行。‘lkm’建议使用ICMP包,但可以是任何带有IP头的包:
|
|
这本质上创建了一种“隐蔽通道”,远程攻击者可以利用它来“发现”执行TCP/IP劫持攻击所需的所有信息。如何?让我们引用原始phrack文章:
发现客户端端口
假设我们已经知道客户端/服务器IP和服务器端口,有一个众所周知的方法来测试给定端口是否是正确的客户端端口。为此,我们可以从客户端IP:猜测的客户端端口向服务器-IP:服务器端口发送一个设置了SYN标志的TCP包(我们需要能够发送欺骗IP包才能使此技术工作)。
当攻击者猜中有效的客户端端口时,服务器向真实客户端(不是攻击者)回复ACK。如果端口不正确,服务器向真实客户端回复SYN+ACK。真实客户端没有启动新连接,因此它向服务器回复RST。
所以,测试猜测的客户端端口是否正确所需做的就是:
- 发送PING到客户端,记录IP ID
- 发送我们的欺骗SYN包
- 重新发送PING到客户端,记录新的IP ID
- 比较两个IP ID以确定猜测的端口是否正确。
查找服务器的SND.NEXT
这是关键部分,我能做的最好是再次引用phrack文章:
每当主机收到具有良好源/目标端口但不正确seq和/或ack的TCP包时,它会发回一个带有正确SEQ/ACK号的简单ACK。在我们研究此事之前,让我们准确定义什么是正确的seq/ack组合,如RFC793 [2]所定义:正确的SEQ是介于主机的RCV.NEXT和(RCV.NEXT+RCV.WND-1)之间的SEQ。通常,RCV.WND是一个相当大的数字(至少几十KB)。正确的ACK是对应于主机已发送内容的序列号的ACK。也就是说,主机收到的包的ACK字段必须小于或等于主机自己的当前SND.SEQ,否则ACK无效(你不能确认从未发送的数据!)。重要的是注意序列号空间是“循环的”。例如,接收主机用于检查ACK有效性的条件不是简单的无符号比较“ACK <= 接收者的SND.NEXT”,而是有符号比较“(ACK – 接收者的SND.NEXT)<= 0”。现在,让我们回到我们的原始问题:我们想猜测服务器的SND.NEXT。我们知道如果我们从服务器向客户端发送错误的SEQ或ACK,客户端将发回ACK,而如果我们猜对了,客户端将不发送任何东西。与客户端端口检测一样,这可以通过IP ID测试。如果我们查看ACK检查公式,我们注意到如果我们随机选择两个ACK值,称它们为ack1和ack2,使得|ack1-ack2| = 2^31,那么恰好其中一个有效。例如,让ack1=0和ack2=2^31。如果真实ACK在1和2^31之间,则ack2将是可接受的ack。如果真实ACK是0,或在(2^32 – 1)和(2^31 + 1)之间,则ack1将是可接受的。考虑到这一点,我们可以更容易地扫描序列号空间以找到服务器的SND.NEXT。每个猜测将涉及发送两个包,每个包的SEQ字段设置为猜测的服务器SND.NEXT。第一个包(resp. 第二个包)将有其ACK字段设置为ack1(resp. ack2),因此我们确信如果猜测的SND.NEXT正确,至少一个包将被接受。序列号空间比客户端端口空间大得多,但两个事实使此扫描更容易:首先,当客户端收到我们的包时,它立即回复。不像客户端端口扫描那样存在客户端和服务器之间的延迟问题。因此,两个IP ID探测之间的时间可以非常短,加速我们的扫描并大大减少客户端在我们的探测之间有IP流量并干扰我们检测的几率。其次,由于接收者的窗口,不需要测试所有可能的序列号。事实上,我们最多只需要做大约(2^32 / 客户端的RCV.WND)次猜测(此事实已在[6]中提到)。当然,我们不知道客户端的RCV.WND。我们可以大胆猜测RCV.WND=64K,执行扫描(尝试每个SEQ的64K倍数)。然后,如果我们没有找到任何东西,我们可以尝试所有SEQ如seq = 32K + i64K对于所有i。然后,所有SEQ如seq=16k + i32k,依此类推……缩小窗口,同时避免重新测试已经尝试过的SEQ。在典型的“现代”连接上,此扫描通常用我们的工具花费不到15分钟。
有了服务器的SND.NEXT已知,以及解决我们对ACK无知的方法,我们可以以“服务器 -> 客户端”的方式劫持连接。这不错,但不是非常有用,我们更希望能够从客户端发送数据到服务器,使客户端执行命令等。为此,我们需要找到客户端的SND.NEXT。
这里是Windows 7的一个小而奇怪的差异。所述场景完美适用于Windows XP,但我在Windows 7中遇到了不同的行为。有两个边缘情况作为ACK值来满足ACK公式并没有真正改变任何东西,并且我通过始终使用一个边缘值作为ACK获得了完全相同的结果(仅在Windows 7中)。最初,我认为我的攻击实现不适用于Windows 7。然而,经过一些测试和调整,事实证明并非如此。我不确定为什么或我错过了什么,但最终,您可以发送更少的包(少一半)并加速整体攻击。
查找客户端的SND.NEXT
引用:
我们可以做什么来找到客户端的SND.NEXT?显然,我们不能使用与服务器SND.NEXT相同的方法,因为服务器的OS可能不易受此攻击,此外,服务器上的繁重网络流量将使IP ID分析不可行。然而,我们知道服务器的SND.NEXT。我们也知道客户端的SND.NEXT用于检查客户端传入包的ACK字段。所以我们可以从服务器向客户端发送包,SEQ字段设置为服务器的SND.NEXT,选择一个ACK,并确定(再次使用IP ID)我们的ACK是否可接受。如果我们检测到我们的ACK可接受,那意味着(guessed_ACK – SND.NEXT)<= 0。否则,它意味着……好吧,你猜对了,那(guessed_ACK – SND_NEXT)> 0。使用此知识,我们可以通过进行二分查找(稍微修改,因为序列空间是循环的)在最多32次尝试中找到确切的SND_NEXT。现在,最后我们拥有所有必需的信息,我们可以从客户端或服务器执行会话劫持。
(不)幸的是,这里Windows 7也不同。这与前一阶段如何处理ACK正确性的差异有关。无论guessed_ACK值如何((guessed_ACK - SND.NEXT)<= 0或(guessed_ACK - SND_NEXT)> 0),Windows 7都不会向服务器发送任何包。本质上,我们在这里是盲目的,不能进行同样非常有效的‘二分查找’来找到正确的ACK。然而,我们并没有完全迷失。如果我们有正确的SQN,我们总是可以暴力破解ACK。同样,我们不需要验证每个可能的ACK值,我们仍然可以使用相同的TCP窗口大小技巧。然而,为了更有效且不错过正确的ACK括号,我选择使用窗口大小值为0x3FF。本质上,我们正在用包含我们注入负载的欺骗包淹没服务器,具有正确的SQN和猜测的ACK。此操作大约需要5分钟并且有效。然而,如果由于任何原因我们的负载未注入,应选择更小的TCP窗口大小(例如0xFF)。
重要说明
这种类型的攻击不限于任何特定OS,而是利用通过将IP_ID实现为全局计数器生成的“隐蔽通道”。简而言之,任何易受“空闲扫描”影响的OS也易受老式盲TCP/IP劫持攻击的影响。
- 我们需要能够发送欺骗IP包来执行此攻击。
- 我们的攻击依赖于“扫描”和持续“探测”IP_ID:
- 受害者和服务器之间的任何延迟都会影响此类逻辑。
- 如果受害者的机器过载(繁重或慢速流量),它显然会影响攻击。采取适当的受害者网络性能措施可能对于正确调整攻击是必要的。
概念验证
最初,我在2008年实现了lkm的攻击,并针对Windows XP进行了测试。当我在现代系统上运行编译的二进制文件时,一切正常。然而,当我获取原始源代码并想在现代Linux环境上重新编译时,我的工具停止工作(!)。新二进制文件无法找到客户端端口和SQN。但是,旧二进制文件仍然完美工作。这对我来说是一个谜。strace工具的输出给了我一些线索:
旧二进制文件生成的包:
|
|
新二进制文件生成的包:
|
|
cmsg_len和msg_controllen有不同的值。然而,我没有修改源代码,所以怎么可能?一些GCC/Glibc更改破坏了发送欺骗包的功能。我在这里找到了答案: https://sourceware.org/pipermail/libc-alpha/2016-May/071274.html
我需要重写欺骗功能以使其在现代Linux环境中再次功能正常。然而,要做到这一点,我需要使用不同的API。我想知道有多少非攻击性工具被此更改破坏。
Windows 7测试
我针对完全更新的Windows 7测试了此工具。令人惊讶的是,重写PoC并不是最困难的任务……设置完全更新的Windows 7要问题得多。许多更新会破坏更新通道/服务本身,您需要手动修复它。通常,这意味着手动下载特定的KB并在“安全模式”下安装它。然后它可以“解锁”更新服务,您可以继续您的工作。最终,我花了大约2-3天时间获得完全更新的Windows 7,它看起来像这样:
- 192.168.1.132 – 攻击者的IP地址
- 192.168.1.238 – 受害者的Windows 7机器IP地址
- 192.168.1.169 – 在Linux上运行的FTP服务器。我测试了在git TOT内核(5.11+)下运行的ProFTPd和vsFTP服务器。
此工具不对每个受害者进行适当的“调整”,这可以显著加速攻击。然而,在我的特定情况下,完整攻击(意味着查找客户端端口地址、查找服务器的SQN和查找客户端的SQN)花费了大约45分钟。
我找到了攻击Windows XP的旧日志(~2009),整个攻击花费了近一个小时:
|
|