电线上的沉默-全-

电线上的沉默(全)

原文:Silence on the Wire

译者:飞龙

协议:CC BY-NC-SA 4.0

引言

关于我的一些话

我似乎天生就是计算机极客,但我的网络安全冒险之旅只是偶然开始的。我总是喜欢实验,探索新想法,解决看似明确但仍然难以捉摸的挑战,这些挑战需要创新和创造性的方法——即使只是失败地尝试解决它们。当我年轻的时候,我大部分时间都在追求有时冒险、经常愚蠢的尝试,去探索化学、数学、电子学和最终的计算世界,而不是整天在街区骑自行车。(我可能有点夸张,但我的母亲似乎总是担心。)

在我第一次接触互联网(可能在 90 年代中期,也就是在我第一次在心爱的 8 位机器上编写我的第一个“Hello world”程序后的八年左右)之后不久,我收到了一个不同寻常的请求:一封垃圾邮件,难以置信的是,它要求我(以及两千多人)加入一个据说是恶意、黑帽黑客的地下团队。这并没有让我躲进地下(或许是因为我强烈的自我保护本能,在某些圈子中被认为是懦弱)但不知何故,这给了我一个深入了解计算机安全领域的良好动机。在做了很多业余编程之后,我发现从不同的角度审视代码,并尝试找到一种让算法做比预期更多事情的方法,这让我着迷。互联网似乎是一个巨大的资源,对于我渴望的挑战——一个庞大而复杂的系统,只有一个指导原则:你无法真正信任任何人。于是,一切就这样开始了。

我没有你可能会从通常的计算机安全专家那里期望的背景,这个职业现在正变得司空见惯。我从未接受过任何正式的计算机科学教育,也没有持有令人印象深刻的证书。安全一直是我主要的激情之一(现在是我的生计)。我不是典型的计算机极客——我偶尔也会站起来,从理智的距离审视我的工作,或者完全远离电脑。

无论好坏,所有这些都影响了这本书的形状和其信息。我的目标是向其他人展示我是如何看待计算机安全的,而不是通常是如何教授的。对我来说,安全不是一个需要解决的问题,也不是一个简单的流程。它不是关于特定领域的专业知识。它是观察整个生态系统并理解其每个组成部分的练习。

关于这本书

即使在我们的显示器昏暗的光线下,我们仍然是人类。我们被教导要信任他人,我们不想过于偏执。我们需要在安全和生产力之间找到一个合理的折衷方案,以便舒适地生活。

互联网与现实世界社会不同。遵守规则没有共同的好处,对于虚拟世界的错误行为也鲜有悔恨。我们不能简单地信任系统,我们试图提出一个适用于所有问题的单一规则的努力将注定失败。我们本能地划出一条直线来区分“我们”和“他们”,并称我们的岛屿是安全的。然后,我们警惕地观察地平线上的海盗船。很快,安全问题开始以局部异常的形式出现,这些异常可以轻易地定义、诊断和解决。从那个角度来看,攻击者似乎被明确的目标所驱动,如果我们保持警惕,我们就可以在他们接近时看到他们并阻止他们。

然而,虚拟世界却截然不同:安全不是没有错误;安全不在于超越攻击者的触及范围。几乎任何涉及信息的过程都有固有的安全影响,一旦我们超越该过程试图实现的目标范围,这些影响就会显现出来。理解安全的艺术就是能够跨越界限,从不同的角度看待问题。

这是一本非常规的书,我希望如此。它不是问题汇编或系统安全指南。它从尝试追踪一条信息的故事开始,从你触摸键盘的那一刻起,一直到线那头的远程对方。它涵盖了技术和其安全影响,重点关注那些不能被定性为错误的问题,没有攻击者,没有需要分析和解决的错误,或者没有可检测的攻击(或者至少不是我们可以从合法活动中区分出来的攻击)。本书的目标是证明理解互联网的唯一方法就是有勇气超越规范或读出字里行间的含义。

如副标题所示,本书关注于日常通信和计算中固有的隐私和安全问题。其中一些问题具有深远的影响,而另一些则仅仅是有趣和启发性的。这些问题都不会立即对你的环境造成损害,也不会破坏你的硬盘上的数据。这里的信息对 IT 专业人士和经验丰富的业余爱好者来说非常有用和宝贵,他们希望通过挑战来锻炼自己的思维,并了解设计决策的非显而易见后果。这是一本面向那些想学习如何利用这些微妙之处来掌控自己的环境并在外部世界中取得优势的人的书。

本书分为四个部分。前三个部分涵盖了数据流和部署的技术阶段。最后一部分专注于整个网络。每一章都涵盖了在每个阶段处理数据所使用的技术的相关元素,对安全影响的讨论,展示其副作用,以及如何解决问题的建议(如果可能的话),以及如何进一步探索主题的建议。我尽力避免图表、表格、规格说明书等(尽管你会在脚注中找到很多)。由于你可以在网上轻松找到大量的优质参考资料,我的重点是让这本书简单易懂。

我们开始吧?

第一部分。源头

关于在发送任何网络信息之前就显现出来的问题

第一章。我能听到你在打字

我们调查你的按键如何从远远的地方被监控

从你按下键盘上的第一个键的那一刻起,你发送的信息就开始在虚拟世界中进行漫长的旅程。在数据包通过光纤链路快速传输并从卫星收发器反射之前,几微秒内,一条信息就会通过一个令人惊叹的电路迷宫走很长的路。在你按下的键被操作系统及其可能运行的应用程序接收之前,许多精确和微妙的基础机制都在进行一个对各种黑客都感兴趣的过程,并且已经证明对安全群体也具有重要意义。通往用户空间的道路上潜伏着许多惊喜。

本章重点讨论这些数据传输的早期阶段,以及你的同行(可能还有淘气的用户)有机会在你自己的终端中了解你正在做什么的机会。

一个与计算机处理输入方式相关的潜在信息泄露场景的显著例子,与一个乍一看似乎完全不相关的主题相关联:在一个完全可预测的行为的机器上产生随机数的困难任务。很难想象一个不那么明显的联系,然而我提到的这个问题是非常真实的,可能允许一个狡猾的观察者推断出用户的大部分活动,从他的密码到他正在输入的私人电子邮件。

需要随机性

计算机是完全确定性的。它们以受一组明确定义的法律所控制的方式处理数据。工程师们尽最大努力补偿与制造过程和电子元件本身的特性(干扰、热噪声等)相关的缺陷,所有这些都是为了确保系统始终遵循相同的逻辑并正常工作;当随着时间的推移和压力,组件拒绝按预期行事时,我们认为计算机出现了故障。

机器能够达到这种一致性水平的能力,加上它们惊人的计算能力,这就是为什么计算机对于那些能够掌握和控制它们的人来说是一个如此伟大的工具。自然,有一件事必须说:并非一切尽善尽美,那些抱怨计算机不可靠的人并不完全错。尽管设备运行得完美无缺,但计算机程序本身在许多场合还是会出问题。这是因为尽管计算机硬件可以并且通常是一致和可靠的,但你通常无法对足够复杂的计算机程序的行为做出长期预测,更不用说一个复杂的相互依赖的程序矩阵(如典型的操作系统)了;这使得验证计算机程序变得相当困难,即使我们能够提出一个详细、足够严格且无懈可击的假设模型,来描述程序应该做什么。为什么?好吧,在 1936 年,现代计算机之父艾伦·图灵通过归谬法(reductio ad absurdum)证明,在有限时间内确定任何计算机程序或算法的结果的通用方法是不可行的(尽管对于某些算法可能有特定的方法).^([41])

这在实践中意味着,虽然你不能期望你的操作系统或文本编辑器始终如你或作者所期望的那样表现,但你合理地期望在相同硬件上运行的同一文本编辑器的两个实例在相同的输入下会表现出一致和相同的行为(除非,当然,其中一个实例被一架掉落的钢琴压碎或受到其他讨厌的外部事件的影响)。这对软件公司来说是个好消息,但无论如何,在某些情况下,我们这些安全专家宁愿计算机的确定性稍微低一点。不一定是在它的行为上,而是在它能提出什么上。

以数据加密和特别是那神秘的生物,公钥加密为例。这种新颖而卓越的加密形式(以及更多),最早在 20 世纪 70 年代由惠特菲尔德·迪菲和马丁·赫尔曼提出,不久之后由罗恩·里维斯、阿迪·沙米尔和伦·阿德勒曼将其发展成为完整的加密系统,其基础是一个简单的概念:有些事情比其他事情更难。当然,这似乎很明显,但只要加入几个高等数学概念,你就为突破性发明做好了准备。

传统对称加密要求所有参与秘密通信的各方共享一个相同的“秘密”值(一个密钥)。这个密钥是必需的,也是足够的,用于加密和随后解密传输的信息,使得即使知道加密方法的第三方观察者也无法解读信息。共享秘密的需求使得整个方法在计算机通信方面并不总是实用,主要是因为各方在通信之前必须建立一个安全的交换通道;通过非安全流传输秘密会使该方案容易受到解密攻击。在计算机的世界里,你经常与之前从未见过的人或系统进行通信,而且没有其他负担得起且安全的通信渠道。

另一方面,公钥密码学不是基于共享秘密。每个参与者持有两份信息:一份(公钥)用于创建加密消息,但对于解密几乎毫无用处,另一份(私钥)用于解密之前加密的消息。现在,各方可以通过一个不安全的通道交换他们的公钥,即使有人在监听。他们提供了给对方所需的信息(对观察者来说毫无意义),以在各方之间加密消息,但他们将访问加密数据的部分保持私密。突然之间,完全陌生的人之间的安全通信——比如坐在公寓沙发上的一位顾客和在线购物服务器——变得接近现实。

基本上,原始 RSA(Rivest, Shamir, 和 Adleman)公钥密码系统是基于这样一个观察:任意两个大数相乘的计算复杂度相对较低;它直接与要相乘的数字位数成正比。另一方面,找到大数的因子(分解)的复杂度要高得多,除非你是为国家安全局工作的神话般的密码天才。RSA 算法首先选择两个任意非常大的质数,记为pq,然后将它们相乘。然后它使用这个乘积以及一个互质数,记为(p-1)(q-1),来构造一个公钥。这个密钥可以用来加密信息,但仅凭这个密钥本身不足以解密该信息,除非通过分解。

而且还有一个问题:两个大质数的乘积的分解通常是不切实际的,这阻碍了这种攻击。在传统计算机上,最快的通用整数分解算法,即通用数域筛法(GNFS),需要超过一千年才能找到这样一个 1024 位整数的因子,以每秒一百万次测试的速度。另一方面,找到一个产生如此大乘积的两个质数,对于一台普通的 PC 来说,只需要几秒钟。

如前所述,在 RSA 中,除了你的公钥外,你还会生成一个私钥。私钥携带有关质数的额外信息,可用于解密使用你的公钥加密的任何信息。这种技巧之所以可能,得益于中国剩余定理、欧拉定理以及其他一些可能令人畏惧但非常迷人的数学概念,特别好奇的读者可能希望自行探索.^([42])

之后还设计了一些其他依赖于数学中其他难题的公钥加密系统(包括椭圆曲线加密系统等),但所有这些系统都共享公钥和私钥的基本概念。这种方法已被证明对于保护电子邮件、网络交易等非常实用,即使双方之前从未交流,并且在建立连接之前没有安全的通道来交换任何额外的信息.^([3]) 我们每天使用的几乎所有加密设计,从安全壳(SSH)和安全套接字层(SSL)到数字签名的更新或智能卡,都是由于 Diffie、Hellman、Rivest、Shamir 和 Adleman 的贡献。

自动随机数生成

只有一个问题:在确定性机器上实现 RSA 时,第一步是生成两个非常大的质数,pq。对于计算机来说,找到一个大的质数很简单,但有一个小问题:这些质数还必须是不可能被他人猜到的,并且它们不能在每台机器上都是相同的。(如果它们是相同的,那么对这个算法的攻击就不需要任何分解,而且 pq 将为拥有类似计算机的任何人所知。)

在过去几年中,已经开发出许多算法来快速找到质数候选者(伪质数)并执行快速的初步质数测试(用于验证伪质数).^([43]) 但要生成一个真正不可预测的质数,我们需要使用大量的熵或随机性,以便盲目地选择一个范围内的质数之一,或者从一个随机位置开始,选择遇到的第一个质数。

虽然在密钥生成时需要一些随机性是必不可少的,但需求并不止于此。公钥加密依赖于相当复杂的计算,因此速度相对较慢,尤其是在与传统对称密钥加密相比时,后者使用短共享密钥和一组已知执行非常快的操作机器。

为了实现如 SSH 这样的功能,其中期望有合理的性能,更合理的方法是使用公钥算法建立初始通信和基本验证,从而创建一个安全通道。下一步是交换一个紧凑的、可能是 128 位的对称加密密钥,并通过切换到旧式对称加密继续通信。对称加密的主要问题通过创建一个初始(且缓慢)的安全流来解决,以交换共享秘密,然后切换到更快的算法,从而使用户能够在不牺牲安全性的情况下受益于更高的性能。然而,为了以合理的方式使用对称加密,我们仍然需要使用一定量的熵来为每次安全通信生成一个不可预测的对称会话密钥。


^([1]) 质数是只能被 1 和自身整除的正整数。

^([2]) 与 x * 互质(也称为与 x * 相对质)的数与 x 没有除了 1 和 -1 以外的共同因子。(它们的最大公约数是 1。)

^([3]) 为了完整性起见,应该指出,临时公钥加密,在其他方面,容易受到“中间人”攻击的威胁,攻击者假冒其中一个端点并提供自己的、伪造的公钥,以便能够拦截通信。为了防止此类攻击,必须设计额外的手段来验证密钥的真实性,无论是通过安排安全交换还是建立一个中央机构来颁发或认证密钥(公钥基础设施,PKI)。

随机数生成器的安全性

程序员们为计算机发明了许多生成看似随机数的方法;这些算法的通用名称是伪随机数生成器(PRNGs)。

PRNGs 对于简单的应用足够了,例如为计算机游戏生成“随机”事件或为特别侵扰性的垃圾邮件创建无意义的主题行。例如,考虑线性同余(也称为幂余数)生成器,^([44]) 这是一个此类算法的经典例子。尽管其名称晦涩,但这个随机数生成器每次生成“随机”输出时都会执行一系列简单的操作(乘法、加法和模运算^([4]))。生成器使用其前一个输出 r[t] 来计算下一个输出值,r[t+1](其中 t 表示时间):

r[t] + 1 = (a × r[t] + c) mod M

模运算符控制范围并防止溢出,这是一种当结果在某个点超出预定义值范围时发生的情况。如果 r[0],aMc——生成器的控制变量集——都是正整数,则此方程的所有结果都落在 0 到 M-1 的范围内。

尽管,通过一些微调,这个算法的输出可能表现出适合生成随机数类似物的统计特性,但其操作本身并没有真正不可预测。这正是问题所在:攻击者可以轻松地开发他们自己的生成器副本,并使用它来确定我们的生成器将产生的任何数量的结果。即使我们从一个攻击者不知道的初始生成器状态(r[0])开始,他们通常也可以通过观察受害者的生成器的后续输出,然后利用这些知识来调整他们的版本以模仿我们的版本。事实上,早在十年前就设计了一种通用的方法来重建和预测所有多项式同余生成器,[45],在用于关键任务时忽视这个可能有些不便的小细节是非常不明智的,因为它在这个算法中留下了巨大的漏洞。

随着时间的推移,我们意识到,在计算机没有发生大规模内存故障或处理器崩溃的情况下,产生实际不可预测数据的唯一合理方式,就是尽可能多地从其物理环境中收集实际不可预测的信息,然后将这些信息作为值传递给任何需要良好随机性的应用程序。问题是,一台普通的计算机没有“感官”来探测环境中的看似随机的外部信号。然而,我们知道一种相当好的方法来克服这种不便。


^([4]) 模运算符返回两个数整数除法的余数。例如,7 除以 3 得到整数结果 2 和余数 1(7 = 2 * 3 + 1);因此,7 模 3 等于 1。

I/O Entropy: This Is Your Mouse Speaking

在几乎每个计算机系统中,外部设备通过硬件中断机制通信相关的事件,这种信息可以通过网卡或键盘从网络卡或键盘获取。每个设备都有一个分配的硬件中断(IRQ)号,并通过改变计算机内部指定硬件线上的电压来报告重要的发展情况,对应于这个特定的 IRQ。然后,这种变化被一个称为可编程中断控制器(PIC)的设备解释,它作为主处理器(或处理器)的个人管家。

一旦 CPU 发出指令,PIC(可编程中断控制器)将决定何时、如何以及以何种优先级将外部设备对主单元的请求发送出去,这使得处理器能够以高效和可靠的方式管理事件。当 PIC 接收到信号时,处理器会推迟其当前任务,除非当然 CPU 已经选择在那一刻忽略所有中断请求(如果它真的很忙)。接下来,它会调用操作系统分配的代码来处理来自该设备或设备组的反馈。一旦程序处理了事件,CPU 就会恢复原始进程及其上下文——即中断发生时其环境状态的信息——然后继续就像什么都没发生一样。

发送中断:一个实际例子

在实践中,检测外部条件并生成和接收一个中断请求(IRQ)涉及许多额外的步骤。例如,图 1-1 展示了按下或释放键盘上的键时触发的事件序列。在你甚至触摸单个键之前,你键盘内部的一个微控制器芯片,作为键盘控制器,正忙于扫描键盘的状态变化。

键盘与计算机通信

图 1-1. 键盘与计算机通信

键盘被组织成一个水平和垂直线的阵列。按键(微开关或压力敏感膜开关)安装在每一行和每一列的交叉点。控制器以非常高的速度单独测试每一行和每一列。

例如,如果键盘控制器在测试第 3 行第 5 列时检测到一个闭合电路(当电压施加到这些线时表现为低电阻),它就会得出结论,该特定位置的键(J)被按下。当键盘控制器感知到变化时,它会将行列坐标转换为扫描码,这是一个通过其唯一标识符识别键的值。然后,扫描码信息被排队在芯片的内部缓冲区中,然后告诉 CPU 有新数据,并回到自己的事务中。

输入控制器芯片是主板上的键盘控制器对应的部分。输入控制器通常处理所有基本输入设备,例如鼠标和键盘。它从键盘芯片接收一个单一的扫描码,并向 CPU 的管家,即 PIC 发出适当的中断信号。一旦 PIC 确定它可以传递这个特定的 IRQ,它就会将这个信号传递给处理器,处理器随后通常会中断当前任务并调用操作系统安装的中断处理程序。处理程序预计会读取数据,并告诉芯片它已成功读取扫描码。然后输入控制器继续其正常操作,并最终从键盘读取另一个扫描码,如果缓冲区中有数据的话。⁵

这种方案对于随机数生成非常重要,尽管其重要性是间接的。计算机使用异步事件通知方案(中断)几乎立即且精确地接收关于用户活动的反馈——也许最有趣的是,按键之间的准确延迟测量。尽管信息并不总是不可预测的,但可能是机器能够获得的最好的外部、可测量、某种程度上不可确定性的信号。因此,为了绕过计算机的决定论性质并在计算中插入随机性,安全 PRNG 实现的作者们求助于从某些设备的通常不可预测行为中收集熵,例如鼠标、键盘、网络接口,有时甚至是硬盘驱动器。为此,他们在操作系统的中断处理程序中添加了额外的代码,以记录每个可接受事件的某些参数。

尽管可以争辩说,那些来源中的任何一个都无法始终如一地提供真正的随机反馈——例如,用户输入了aardva之后,接下来的两个字符很可能是rk——但某些行为,比如一开始想到鸵鸟,确实相当不可预测,从实际的角度来看(而不涉及关于自由意志和决定论宇宙的学术讨论)。这种方法增加熵的效果相当不错,因为它包含了几个攻击者难以合理考虑、监控或预测的因素,同时还能保持他们的理智。通过长时间从所有这些来源收集数据,概率定律告诉我们,我们将收集一定量的熵。通过在缓冲区中收集数据,我们构建了一个熵池,它可以充满或耗尽,这取决于不可预测数据的供应和需求。不幸的是,池中的这些随机小片段——我们的打字受到宇宙事件的影响——仍然与大量容易预测的数据混合在一起,因此不能立即用于随机数生成。

为了确保在维护和补充熵池的过程中实际收集到的熵量均匀地分布在所有 PRNG 输出位上(所有不可预测的数据都耗尽),池必须进行哈希;也就是说,必须彻底搅拌和混合,以便没有数据部分比其他部分更容易预测。输出中的每一个比特都必须以非平凡的方式同等依赖于所有输入比特。在没有知道哪些信息是可预测的,哪些是不可预测的(这些信息对于监控按键或鼠标移动的计算机来说并不容易获得)的情况下实现这一点可能是一项艰巨的任务。

单向快捷键函数

幸运的是,安全的单向哈希函数(“消息摘要”),现代密码学的旗舰产品,可以帮助我们混合数据,将最多的熵输入到每个输出比特中,无论输入如何不均匀。这些是生成固定长度快捷键的函数:任意块输入数据的唯一标识符。但这还不是全部。

所有单向哈希函数都有两个重要特性:

  • 计算快捷键很容易,但无法从结果中推断出原始消息或其任何属性。对消息的任何特定更改都有可能影响输出的一切属性,就像任何其他更改一样。

  • 两个不同消息具有相同快捷键的可能性仅由快捷键的大小决定。拥有足够大的快捷键(大到使穷举搜索变得不切实际,如今设定在约 128 到 160 位,或大约 3.4E+38 到 1.46E+48 种组合),就找不到两个具有相同快捷键的消息。

因此,快捷键函数提供了一种方法,将输入数据中存在的熵以均匀的方式分布到输出数据中。这解决了通常随机但局部可预测的熵源的问题:我们从环境中收集大约数量的熵,与可预测的数据混合或不混合,并可以生成一个保证与最初收集的熵一样不可预测的快捷键,无论输入数据中的熵是如何分布的。

快捷键函数是如何工作的?一些又依赖于我们已知非常难以解决的数学问题。事实上,任何安全的对称或公钥加密算法都可以很容易地转换成一个安全的哈希函数。只要人类没有找到解决这些问题的真正巧妙解决方案,依赖这种方法应该是可以的。

然而,通过使用重型武器,我们最终得到了缓慢且过于复杂的生成快捷方式的工具,这在紧凑型实现中通常是不切实际的,尤其是在将此类解决方案与操作系统集成时。另一种选择是处理数据,使得输入和输出所有比特之间的相互依赖关系足够复杂,以便完全混淆输入消息,并希望这“足够好”以阻止已知的密码分析技术。因为“希望足够好”实际上是计算机科学中很大一部分的座右铭,我们欣然接受这作为一个合理的做法。

后者这类算法的优点,包括 MD2、MD4、MD5 和 SHA-1 等流行函数,是它们通常比基于困难数学挑战的对应算法快得多,而且当设计得当,它们不易受到行业中的密码分析技巧的影响。它们的弱点是它们不是可证明安全的,因为它们都没有简化为经典且难以解决的问题。事实上,一些已经被证明具有特定的弱点.^([46])

如前所述,伪随机数生成中的快捷函数的一项重要服务是它们可以在包含 n 个随机比特和任意数量可预测比特的数据段上运行,从而产生一个快捷方式,将 n 比特的熵均匀地分布在快捷方式的全部比特上(这得益于之前讨论的两个基本单向快捷函数属性)。因此,快捷函数成为了一个方便的熵提取器。通过将收集到的足够多的、通常不可预测的中断处理程序数据通过快捷函数处理,我们可以生成随机数,而无需透露用于生成该数的具体信息形状的任何有价值信息,也不会因为输入不完善而影响输出的任何有意义方式。我们所需做的只是确保在一段中断数据中收集到足够的熵并输入到快捷函数中,否则我们可能会危及整个方案。如果攻击者可以预测我们用于随机数生成的大量数据,而剩余的部分只有少数几种可能的组合,他们可以通过尝试并验证所有可能的值来对我们的实现进行成功的暴力攻击。例如,如果我们使用产生 128 位摘要的快捷函数,无论我们实际收集了多少数据,无论是 200 字节还是 2 兆字节的键盘敲击数据,我们都必须确保在哈希之前至少有 128 个输入比特对攻击者来说是不可预测的。

严谨的重要性

作为事物可能出错的一个例子,考虑一个用户在系统熵池为空时决定编写一个 shell 脚本的情况,这可能是由于之前执行的一些需要随机数的操作。攻击者注意到用户正在编写脚本,因为正在执行vi delallusers.sh;他们进一步假设脚本必须以类似#!/bin/sh的内容开始。尽管他们不能确定接下来会发生什么,但他们可以合理地预期脚本将以调用一个 shell 命令开始,并且不太可能接着是一首关于河马的蹩脚诗。

在这个时候,某种加密实用工具突然向系统请求一个 128 位的随机数,用作会话密钥来保护通信。然而,系统未能正确估计记录脚本第一行写入过程的缓冲区中可用的熵量,攻击者现在有一个简单的任务。计算机没有信息表明用户此时所执行的这个特定动作是否对他人来说是可预测的。它只能推测(在程序员的假设帮助下),在几分钟或几小时的过程中,用户的行为将汇总成某种无法精确预测的东西,并且平均而言,这么多的输入确实会依赖于攻击者无法预测的因素。

到目前为止,攻击者已经知道了熵池的大部分内容,在未知部分的选择上只剩下寥寥数千种可能——尽管操作系统确信缓冲区中有更多的熵。对于有计算机辅助的人来说,这数千种选择几乎不是什么大挑战。因此,而不是得到一个有 39 位数字组合的 39 位随机数,一个无知的加密应用程序最终得到了一个由输入生成的数字,这个输入可能只是成千上万个选项中的一个,攻击者可以通过试错法轻松验证,攻击者很快就能解密原本应该保持安全的信息。


^([5]) 在许多架构上,必须手动指示 PIC(中断控制器)中断已被处理,并且它不应再阻止后续的中断。这是通过结束中断(EOI)代码来完成的。

熵是一种可怕的东西,浪费它

由于在短期内准确预测从用户那里收集到的熵量几乎是不可能的,为了防止之前讨论过的可预测的 PRNG 输出问题,所有实现都包括在生成新输出过程中的快捷方式或内部 PRNG 状态。之前的输出成为计算下一个 PRNG 值所使用的方程的一部分。

在这个设计中,一旦系统最初收集了足够的熵,用于补充熵池的最新数据并不需要在任何时候都是完全随机的,以确保基本的安全性。

然而,还有一个问题。如果实现过程在旧的、继承的熵上运行了很长时间,只是反复使用 MD5 或 SHA-1 进行哈希,它就完全依赖于快捷算法的安全性,由于之前讨论的性能和安全权衡,这一点无法完全信赖。此外,哈希函数未必经过了合格密码学家对这一特定用途的适当适用性评估。实现不再仅仅依赖于快捷函数的位哈希属性,现在完全依赖于其免受破解攻击的不可攻破性。如果,在每一步后续操作中,都会泄露一些关于生成器内部状态的信息,并且没有向池中添加新的不可预测数据,从长远来看,这些数据可能足以以合理的确定性重建或猜测内部状态,这使得预测设备未来的行为成为可能。另一方面,如果以至少统计上防止内部状态大量重用的速率添加新的随机数据,即使哈希函数在本质上被破坏,攻击也变得不太可行。

许多专家认为,对于最苛刻的应用,不应进行这种程度的对哈希函数的信任和依赖。因此,对于实现来说,跟踪系统中收集的估计熵量非常重要,即使它不是瞬间的正确,但它反映了我们从使用的源中期望的一般统计趋势。外部熵可用性的短期波动,如之前讨论的脚本编辑示例,可能会发生,并且将通过输出重用算法得到补偿。然而,为了确保频繁地补充内部熵池并最小化哈希函数随着时间的推移可能泄露内部状态的风险,有必要进行准确的长远预测。因此,实现必须考虑提供给用户进程的数据中消耗的所有熵,并在足够的熵可用之前拒绝提供更多的随机数。

一个考虑了上述所有因素的适当 PRNG 实现的良好例子是麻省理工学院 Theodore Ts’o 在 1994 年设计和实施的优秀系统。他的机制,/dev/random,最初在 Linux 中实现,后来被引入到 FreeBSD、NetBSD 和 HP/UX 等系统中。Ts’o 的机制监控多个系统 I/O 事件,测量时间间隔和其他重要的中断特征。它还通过将熵池保存到磁盘来在系统关闭期间保留熵池,从而防止系统启动到完全可预测的状态,这使得攻击更加困难。

攻击:突然范式转变的影响

对于为要求严格的应用程序提供不可预测随机数的看似万无一失的方案,可能存在什么问题?至少在你期望的地方没有问题。生成的数字确实难以预测。

然而,在这个技术的设计师的推理中存在一个微小但灾难性的错误。Ts’o 先生的设计假设攻击者对基于对机器及其环境的了解来预测随机数感兴趣。但如果攻击者想要做的是完全相反的事情呢?

即使攻击者有机器上的账户,尽管他们无法直接访问用户正在输入的信息,但通过清空熵池(可以通过简单地从系统中请求随机数据并将其丢弃来实现)然后监控 PRNG 输出的可用性,他们可以推断出系统输入活动发生的确切时刻。如果没有 I/O 活动,PRNG 将没有新的数据可用,因为熵估计不会改变。如果发生按键或按键释放,攻击者将获得少量信息,然后可以推断出某个键被按下或释放。

其他事件,例如磁盘活动,也会生成一些伪随机数生成器(PRNG)输出,但通过这种方式收集到的熵的数量和时序模式与键盘中断数据的特征不同。因此,根据任何给定时间可用的数据量,可以区分事件。按键数据与磁盘活动数据看起来将不同。

最后,确保安全随机数生成最高可能安全水平的方法实际上会降低用户的隐私:这种机制可以用来估计从外部来源可用的熵的数量,可能会被滥用并用于监控系统输入活动的某些方面。尽管攻击者无法检测到正在输入的确切内容,但键盘上输入不同单词的时序模式非常明显,特别是如果存在精确的按键和释放信息,就像在这个案例中一样。通过检查这些模式,攻击者可以推断出实际的输入,或者至少更容易地猜测它。

输入时序模式的近距离观察

由加州大学的研究团队进行的一项深入分析表明,仅通过观察按键间隔时间,就有可能推断出用户输入的某些属性,甚至可以完全重建数据。研究得出结论,对于无缝打字和键盘熟练的操作者,按键间隔时间可能会有所变化,但每个键到键转换的主导时间模式是明显可见的。

原因在于我们的手以某种方式放在键盘上,而键盘上的键位位置会影响我们用指尖到达键位的速度。例如,按 e 和 n 之间的间隔通常与按 m 和 l 之间的间隔不同。在前一种情况下,因为一只手控制键盘的左侧,另一只手控制右侧(参见图 1-2),同时输入这两个字符几乎不需要移动,并且两个键几乎同时按下,时间间隔小于 100 毫秒。输入 m 和 l 则需要相当不自然的指法,并且需要更长的时间。

每只手通常的领域。深灰色键通常由左手控制,白色区域由右手控制。

图 1-2. 每只手通常的领域。深灰色键通常由左手控制,白色区域由右手控制。

在分析了大量样本之后,这项研究的作者估计,从按键时间数据中可以获得大约每个按键 1.2 比特的信息。通过观察序列延迟,可以确定最有可能产生这种模式的一组键盘输入,从而更容易猜测按键的确切顺序。计算比特分数的想法可能听起来很荒谬,但它的真正含义是,每个键的可能性可以减少到 2^(1.2),或者大约 2.40 倍。对于一个普通的按键,它一开始通常携带不超过 6 比特的随机性,这将从大约 64 个元素减少到 26 个元素。

这种净效应是减少了搜索空间水平;如果我们想猜测正在输入的键,我们可以看到有一种方法可以限制可能性的数量。尽管这种减少本身可能并不特别令人印象深刻,但加上从键盘输入的数据不太可能是随机的垃圾。英语文本的熵估计为每字符 0.6 到 1.3 比特,这意味着平均需要大约 1.5 到 2.5 次尝试才能成功预测下一个字符。有了进一步减少搜索空间的方法,几乎可以找到所有输入数据的非歧义性词典单词匹配。

为了验证他们的估计并在实践中展示问题,研究人员使用了隐马尔可夫模型和维特比算法来猜测按键。马尔可夫模型是一种描述离散系统的方法,其中下一个值仅取决于其当前状态,而不是先前值(马尔可夫链)。隐马尔可夫模型是一种变体,它提供了一种描述系统的方法,其中每个内部状态都产生一个观察结果,但对于实际状态却不知道。该模型在语音识别等应用中常用,其目标是获得纯数据(口头语言的文本表示)。

作者得出结论,隐马尔可夫模型适用于按键分析,他们认为系统的内部状态是按键信息;隐马尔可夫模型中的观察结果是按键间的时序。

可能会有人认为这是一种过度简化,因为在图 1-3 图 1-3. 在前一步中需要将左手移动到不同的位置会影响 P-V 时间。马尔可夫模型无法考虑手切换场景中手的先前位置。所示的情况下,可能存在更深层次的依赖关系。

图 1-3. 在前一步中需要将左手移动到不同的位置会影响 P-V 时间。马尔可夫模型无法考虑手切换场景中手的先前位置。

图 1-3. 在前一步中需要将左手移动到不同的位置会影响 P-V 时间。马尔可夫模型无法考虑手切换场景中手的先前位置。

维特比算法是解决隐马尔可夫模型问题的一种方法。该算法可以根据观察序列找到最可能的内部状态序列。在这种情况下,我们使用它来确定基于时序的最可能的字符序列。

应用维特比算法的最终结果是,将非字典八字符密码的搜索空间减少了 50 倍。对于基于字典的英语文本的重建,这个因素可能要高得多。

现在让我们看看中断监控。我们刚才讨论的研究主要集中在通过监听安全壳(SSH)流量模式可获得的部分信息。在中断监控的情况下,攻击者可以获得的信息要多得多。首先,按键持续时间信息以及按键间的时序信息都是可用的,单个按键的持续时间取决于使用的手指。例如,食指通常与键的接触时间最短,无名指可能是最慢的,依此类推。这是非常有价值的信息,使得定位键盘上按键的大致区域变得容易得多。

其次,这些数据还使攻击者能够监控手部转换,即当左手首先输入第一个字符,右手输入第二个字符,或者反之亦然的时刻。因为每只手都受大脑不同半球的控制,几乎所有熟练的键盘用户在切换手时通常会在释放第一个键之前按下第二个键。尽管按键和释放事件本身是不可区分的,但两个键盘事件之间特别短的时间间隔是这种现象的明显迹象。在某些罕见的情况下,尤其是当打字员匆忙时,第二个按键不仅会在释放之前发生,甚至会在第一个键按下之前发生。这导致了诸如“teh”而不是“the”这样的常见打字错误。

图 1-4 展示了样本键盘时序的捕获。用户输入单词 evil。左手的中指按键 e 持续了中等的时间。然后,由于需要整个手移动以使用食指按键 v,因此在按键 v 之前有一个相当长的间隔。(因为空格键挡住了,所以不能用大拇指。)“v 按键的时间很短,i 也是如此,两者都通过食指访问。还有一个明显的重叠:由于手部转换,i 在 v 释放之前被按下。最后,一段时间后,无名指按下 l(不需要移动手),接触时间相当长。

按键和释放时间的手部转换

图 1-4. 按键和释放时间的手部转换

因此,合理地预期,在这个攻击中可以达到更高的成功率。(上述白皮书中讨论的场景中,大部分这些信息是不可用的。)

立即防御战术

既然我们知道键盘嗅探的潜在可能性,我们该如何阻止它?最好的办法是使用一个合理大小的独立键盘熵缓冲区。只有在缓冲区溢出或经过比通常的按键延迟(即至少几秒钟)大得多的时间间隔之后,缓冲区才会被刷新并传递到核心 PRNG 实现,从而消除攻击者测量时间的能力。

使用这种解决方案,攻击者只能获得两种类型的信息。第一种来自溢出时的刷新程序,向攻击者透露在可测量的时间段内按下了多少个键(取决于缓冲区大小),但不会透露确切的键间隔时间。第二种可能是定时刷新序列的结果,并告知攻击者在一个固定的时间框架内按下了键或几个键,但不会提供有关事件数量及其精确发生时间的任何信息。以这种方式提供的信息对于时间攻击的价值很小,只能用于生成键盘活动的通用统计数据,后者在大多数多用户环境中不构成威胁。

硬件随机数生成器:更好的解决方案?

到目前为止,许多硬件平台实现了物理随机数生成器,通常称为 TRNGs,即真正的随机数生成器。这些设备提供了一种更可靠的方式来生成真正不可预测的数据,而不是收集仅预期难以预测的信息,并且是所有配备此硬件的机器获取熵的推荐方式。目前两种流行的解决方案是英特尔和 VIA 开发的集成电路。

英特尔随机数生成器(Intel RNG)与 i810 等芯片组集成,并采用传统的双振荡器设计。高频振荡器产生一个基本信号,本质上是一种交替逻辑状态的模式(010101010101...)。另一个振荡器是一个低频设备,以高速振荡器频率的 1/100 的标称速率工作,但其实际频率由一个电阻调制,该电阻作为熵的主要来源。

由于热噪声和其他随机材料效应,电阻的一些可测量特性会发生变化。低频振荡器用于在现在随机的频率下驱动采样交替信号(振荡器输出的下降沿)。信号经过一些必要的条件处理和“白化”使用冯·诺伊曼校正后,然后对外界可用。密码学研究公司(Cryptography Research)的本杰明·朱(Benjamin Jun)和保罗·科赫(Paul Kocher)对设计者和实际输出进行了仔细分析,表明输出质量始终很高,并且该生成器每输出一个比特提供大约 0.999 比特的熵。

VIA C3 “Nehemiah” RNG 基于一个稍微不同的设计,它使用一组振荡器,但没有使用如特殊电阻连接等单独的噪声源。相反,它依赖于振荡器的内部抖动,这是一个可以归因于许多内部和外部因素的效果,并且可以通过可配置的“偏差”设置进行控制。

在这种情况下,由密码学研究进行的单独分析表明,生成器显然提供的熵量低于其对应物,范围从每个输出比特 0.855 到 0.95 比特。如果将 RNG 输出视为完全随机的并直接用于密钥生成或其他关键任务,这将是一个危险的结果,因为实际熵量相应减少。为了解决这个问题,我们可以从生成器获取比必要更多的数据,然后通过一个安全的哈希函数,如 SHA-1,来消除任何可能的偏差或熵不足。这个解决方案是防止 TRNG 问题的通用良好实践,只要这些不良影响在合理范围内——也就是说,每个比特仍然携带一些有用的熵。

一些研究人员还建议使用某些非专用输入设备,如网络摄像头或内置麦克风,作为熵的来源:数字相机的电荷耦合器件(CCD)传感器往往会表现出像素噪声,而严重过放的麦克风信号基本上是一个好的随机噪声来源。然而,由于不同制造商的流行媒体设备电路的不同,没有通用的设置此类生成器的方法,因此这种方式生成的“随机”数质量无法保证。实际上,一些设备会接收到看似随机但实际上完全可预测的无线电干扰或某些电路信号。此外,一些设备,特别是 CCD 传感器,表现出静态噪声模式。虽然看似随机,但这种噪声变化不快,可能不可靠。

思考食物

我已经决定省略对一些有趣概念的深入讨论,但这些可能对进一步的探索具有宝贵的启发。

远程定时攻击

理论上,可能通过网络部署 PRNG 定时攻击。某些启用了密码学的服务实现了对称密码学。在通过公钥基础设施建立较慢的非对称流并验证双方后,生成一个对称会话密钥,然后两个端点切换到更快的对称替代方案。

  • 可能通过使应用程序耗尽系统中的现有熵池,直到没有足够的熵来生成新的会话密钥,但仅是微小的一部分。然后应用程序将延迟生成对称密钥,直到有足够的熵来生成密钥的剩余部分,这可能会在下一个按键或释放时发生。

  • 我认为这种攻击更有可能在实验室环境中成功,而不是在任何实际应用中,尽管我的技术审稿人不同意我的怀疑态度,因此,这只是一个观点。弗吉尼亚大学的一项有趣的分析批评了之前在论文中讨论的原始 SSH 时间研究,理由是网络抖动足以使时间数据变得不可用,尽管值得注意的是,如果某个特定活动在一段时间内重复进行(例如,每次登录都输入相同的密码),随机网络性能波动可能会很好地平均化.^([51])

利用系统诊断

一些系统有更好的方法来恢复按键信息和其他时间数据。在我发布 PRNG 时间研究之后,有人指出 Linux 提供了一个/proc/interrupts 接口,该接口显示中断摘要统计信息,目的是提供一些有用的性能数据。通过检查中断计数器变化对于 IRQ 1,可以获得与通过 PRNG 获取的相同的时间信息,已经过滤掉了任何可能的磁盘和网络活动,从而造成与之前讨论过的类似的隐私泄露。

可复现的不确定性

值得考虑的其他问题与 PRNG 实现本身有关。批量购买相同硬件并在每个设备上安装相同的系统是一种常见做法,但对于没有经历大量控制台活动的服务器来说可能会成为问题。使用专门的复制工具镜像安装并随后在多台服务器上传播镜像也存在风险。在所有情况下,系统可能会因为实际熵过低而持续一段时间。

第二章. 付出额外努力永远不会被忽视

在这里我们学习如何构建木制计算机以及如何通过观察真实计算机的运行来获取信息

您输入的数据现在已安全地存储在您选择的运行应用程序的手中。程序将花费时间决定如何处理信息,如何解释它,以及下一步要采取哪些行动。

在本章中,我们详细探讨了数据处理的基础机制,并探讨了可能潜伏在处理器散热片之下的某些陷阱。我们特别关注我们可以通过观察机器执行给定程序以及完成某些任务所需的时间来推断的信息。作为额外奖励,我们还将构建一个完全功能的木制计算机。

布尔的遗产

要理解处理器的结构,我们必须回到处理器尚未被梦想出来的时代。这一切始于 19 世纪,当时自学成才的数学家乔治·布尔(1815–64)设计了一种简单的二进制代数系统,旨在为理解建模形式推理提供一个框架。他的方法将逻辑的基本概念简化为三组简单的代数运算,这些运算可以应用于代表两种相反状态的元素,即真和假。这些运算包括:

  • 析取运算符,OR。当至少有一个操作数([6])为真时,这个运算符才为真.([7])

  • 连接运算符,AND。只有当所有操作数都为真时,这个运算符才为真。

  • 补码(否定)运算符,NOT。当它的唯一操作数为假时,这个运算符才为真。

尽管布尔代数模型在设计上很简单,但它最终成为了解决逻辑问题和某些其他数学挑战的有力工具。最终,它使得许多勇敢的先知能够梦想出聪明的分析机器,这些机器有一天将改变我们的日常生活。

今天,布尔逻辑对于经验丰富的计算机用户来说很少是神秘的,但从这一组简单操作到今天的计算机的道路往往如此。我们将首先通过尝试捕捉这个模型在最简单形式下的本质来开始探索这条道路。


^([6]) 操作数是操作符操作的对象。

^([7]) 逻辑或的意义与英语中对这一术语的常见理解不同:当只有其中一个或所有参数为真时,结果语句仍然为真。在英语中,“或”通常意味着只有一个选项为真。

通向通用运算符之路

简化的道路往往要通过看似不必要的复杂级别——这个例子也不例外。为了开始,我们必须考虑另一位 19 世纪数学家奥古斯都·德摩根(1806–71)的工作。德摩根定律指出,“析取的补码是补码的合取。”这种臭名昭著的混淆简单概念的做法对布尔逻辑以及最终数字电路的设计产生了深远的影响。

用简单的英语来说,德摩根定律解释了当任何(或两个)条件不满足时,声称两个条件都满足的句子(或者说,条件合取发生)也将是错误的——哦,反之亦然。

这条定律得出结论,NOT OR (a, b) 应该在逻辑上等同于 AND (NOT a, NOT b)。考虑一个现实世界的例子,其中 a 和 b 代表以下内容:

a = “Bob 喜欢牛奶”
b = “Bob 喜欢苹果”

现在可以将德摩根方程的两侧写成:

OR (NOT a, NOT b) ⇔ Bob 不喜欢牛奶或不喜欢苹果
NOT AND (a, b) ⇔ 不正确的是 Bob 同时喜欢牛奶和苹果

两个表达式在功能上是等价的。如果 Bob 不喜欢牛奶或苹果中的任何一个,第一个表达式就是真的;那么他也不喜欢两者,这意味着第二个表达式也是真的。

反转情况也会得到一致的结果:如果 Bob 不喜欢至少一个选择,他喜欢两者(第一个表达式是假的)。在这种情况下,他也不喜欢两者(第二个表达式也是假的)。

德摩根定律的应用

为了评估超出直觉和一些手势的逻辑语句,构建所谓的真值表是有帮助的,这些真值表展示了从所有可能的真值和假值操作符组合中可以计算出的所有结果。

下面的两个表格表示了上一个例子中的每个表达式。每个表格都包括操作符和所有可能的真值和假值组合的对应结果列。因此,在第一行中,你可以看到前两列——NOT AND(a, b)的两个操作数——都是假的。这导致 AND(a, b)也是假的,因此 NOT AND(a, b)是真的。结果在第三列中给出。

正如你所看到的,两个表达式的行为相同:

NOT AND(a, b): 与结果取反
操作数 1 (a)
---
FALSE
FALSE
TRUE
TRUE
OR(NOT a, NOT b): 操作数取反的 OR
---
操作数 1
---
FALSE
FALSE
TRUE
TRUE

但为什么计算机设计师关心 Bob 的食物偏好呢?因为在布尔操作符的背景下,德摩根定律意味着布尔代数提出的基本操作集实际上是部分冗余的:NOT 和另外两个操作符(OR 和 AND)中的任何一个的组合总是足以合成剩下的一个。例如:

OR (a, b) ⇔ NOT AND (NOT a, NOT b)
AND (a, b) ⇔ NOT OR(NOT a, NOT b)

这种理解将操作符集减少到两个,但布尔系统还可以进一步简化。

便利性是必需的

一些额外的操作符对于实现布尔逻辑不是必需的,但补充了现有的操作集。这些额外的操作符,即 NAND 和 NOR,仅在 AND 和 OR 分别为假时才为真:

NAND(a, b) ⇔ NOT AND(a, b) ⇔ OR(NOT a, NOT b)
NOR(a, b) ⇔ NOT OR(a, b) ⇔ AND(NOT a, NOT b)

这些新函数并不比 AND 和 OR 更复杂。每个都有四个状态(四行)的真值表,因此其值可以用同样的努力确定。

NOR 和 NAND 都不在基本操作数集中,因为它们都不对应于句子之间常用的基本逻辑关系,也没有在通用语言中的原子表示。

我刚刚介绍了一套新运算符,这些运算符是从现有集合中派生出来的,似乎只为那些想要使用形式化符号表达更奇特逻辑依赖或问题的人提供了一种可疑的便利功能。这又是为了什么?

仅引入 NAND 或 NOR 就可以完全消除 AND、OR 和 NOT。这进一步实现了我们的简化目标,并使我们能够用更少的元素和运算符来描述整个布尔代数系统。

那些否定辅助运算符的重要性在于你可以使用它们中的任何一个来构建一个完整的布尔代数系统。实际上,你可以使用 NAND 构造所有基本运算符,如下所示(T 代表一个真命题,F 代表一个假命题^([8]))。怎么做?很明显,以下成对的陈述是等价的:

NOT a ⇔ NAND(T, a)
AND(a, b) ⇔ NOT NAND(a, b) ⇔ NAND(T, NAND(a, b))
OR(a, b) ⇔ NAND(NOT a, NOT b) ⇔ NAND(NAND(T, a), NAND(T, b))

或者,如果我们更愿意只依赖 NOR 而不是 NAND,我们可以说

NOT a ⇔ NOR(F, a)
OR(a, b) ⇔ NOT NOR(a, b) ⇔ NOR(F, NOR(a, b))
AND(a, b) ⇔ NOR(NOT a, NOT b) ⇔ NOR(NOR(F, a), NOR(F, b))

接受复杂性

很难相信所有计算的精髓都可以被包含在某个通用逻辑运算符中。你可以使用包含以下真值表的简单电路来实现大多数复杂算法、高级计算、尖端游戏和互联网浏览,这些真值表将输入信号转换为输出信号:

NAND 状态表
操作数 1
---
FALSE
FALSE
TRUE
TRUE
NOR 状态表
---
操作数 1
---
FALSE
FALSE
TRUE
TRUE

看起来我们似乎没有进展。然而……为什么这个看似微不足道的依赖集使得构建一个能够解决复杂问题的设备成为可能,比如以得体的方式拒绝你的信用申请?基于“真”和“假”状态的这块理论又与数字电路有什么共同之处?


^([8]) 纯粹主义者可能想要假设 T 等同于 AND(a, a),例如,这始终为真,而 F 等同于 NOT AND (a, a),这始终为假。换句话说,我们并没有引入新的概念或方程元素——我们只是在这一点上稍微简化了符号。

走向物质世界

布尔设计的机制并不复杂:它需要两种相反的逻辑状态,“真”和“假”,0 和 1,“青色”和“紫色”,999 和 999 ½。实际意义、物理表示和介质都是无关紧要的;重要的是任意选择的约定,将介质中的一些状态分配给特定的逻辑值集合。

我们所知道的计算机在电子电路中使用两种不同的电压水平,并将它们解释为设计师称为 0 和 1 的值。这些值通过电路传输,代表二进制系统中的两个数字——但没有任何阻止一个人使用几乎任何方法来传递数据的方法,从水流到化学反应,到烟雾信号,到由一组精心制作的木齿轮传递的扭矩。信息保持不变,无论其载体如何。

在物理世界中实现布尔逻辑的关键很简单,一旦我们同意逻辑值的物理表示。接下来,我们只需要找到一种方法来安排一组组件,以操纵这些值以适应我们想要计算机执行的任务(但关于这一点稍后还会讨论)。首先,让我们尝试找出如何操纵信号和实现现实世界的逻辑设备,通常称为门。即木门。

无电计算机

从纯数学世界产生的一组理论操作转移到可以调节水流、扭矩或电信号的设备,以模仿逻辑运算器之一的方式,这似乎是一项艰巨的任务——但实际上并非如此。

图 2-1展示了使用扭矩逻辑实现 NOR 功能的简单齿轮组机制。在闲置状态下,“输出”齿轮代表状态 0;当对齿轮施加扭矩时,其状态为 1。该设备仅在外部源未对两个控制“输入”齿轮施加扭矩时将扭矩从外部源传输到输出。在理论上,不需要外部能源,设计可以更简单;然而,实际上,摩擦和其他问题会使构建更复杂的完全自包含门集相当困难。

机械 NOR 门设计

图 2-1. 机械 NOR 门设计

对输入之一或两个施加扭矩将拉出微小的连接齿轮,使“输出”齿轮闲置。当输入闲置时,弹簧将连接齿轮拉回其位置。该设备的真值表正是 NOR 应有的样子。

如你所回忆的那样,NOR 或 NAND 都是我们实现任何布尔逻辑运算器所需的所有。尽管添加实现其他运算的能力而不需要重新组合 NAND 和 NOR 门会使我们的设备更小、更高效,但设备不需要这种能力才能工作。

假设我们跳过所有门一起以我们习惯的方式工作的繁琐细节,我们可以得出结论,计算机可以用几乎任何技术来构建.^([9])


^([9]) 不言而喻,非电子计算机并非空穴来风。此类设备的著名例子包括查尔斯·巴贝奇的解析机,以及如纳米技术等技术也持有一些希望。参见 Ralph C. Merkle,“两种机械可逆逻辑类型”,纳米技术 4 (1993)。

一种稍微更受欢迎的计算机设计

尽管过去几十年计算机的繁荣源于巧妙的晶体管,但我们对其的依赖并非与任何神奇的价值或独特的品质相关联。简单来说,它目前是我们最经济、最易用和最高效的设计。

与可能远优于木制齿轮机的电子计算机不同,我们使用的电子计算机使用晶体管传递电信号,晶体管是微小的设备,当在第三个节点(连接点)施加电压时,它们在两个节点之间允许电流单向流动。晶体管可以非常有效地小型化,功耗低,且可靠且便宜。

逻辑门

晶体管很简单。实际上,它本身是一个非常简单的设备,无法实现任何有意义的布尔逻辑。然而,当在逻辑门中正确排列时,晶体管使得执行所有基本和补充布尔代数运算变得容易。

AND 门可以通过串联两个晶体管来实现,这样电压才能流向输出,前提是两个晶体管都必须具有低电阻(“开启”)。每个晶体管由单独的输入线控制(激活)。输出通过一个电阻“拉低”,因此它具有地电压 0(“假”),但一旦两个晶体管都开启并允许轻微的电流流动,输出就会超过 0。

OR 门通过设置一个并联晶体管来实现,这样只要任何一个晶体管被激活,输出就能被设定为非零电压,表示“真”。

最后一个基本门,NOT 门,使用一个晶体管和一个电阻来实现。“NOT”输出在空闲状态下为 1(通过电阻拉高),当晶体管开启时被拉低到 0。

图 2-2 展示了三种最基本的晶体管门设计:AND、OR 和 NOT。

基于晶体管的逻辑门——构造和符号

图 2-2. 基于晶体管的逻辑门——构造和符号

你可能会注意到,AND 门和 OR 门都可以通过不引入额外组件的方式转换为 NAND 和 NOR。这只需要使用 NOT 门电路图上观察到的设计——即通过将电阻和“输出点”移向电源电压,从而反转输出逻辑。

我们现在已经到了可以组合晶体管以实现一个通用门之一的时候了,但无论我们可以构建多少个门,这仍然离真正的计算相当远。

前面的讨论都很好,但是什么让布尔逻辑不仅仅是一个解决关于鲍勃饮食谜题的强大工具?

从逻辑运算符到计算

组合简单的布尔逻辑运算可以带来许多令人惊讶的能力,例如在数字的二进制表示上执行算术运算。这就是事情变得有趣的地方。

一组 XOR 和 AND 门,例如,可以用来增加输入数字 1,这是我们走向加法的第一步。图 2-3 展示了基于这个概念的设计。

啊,一个新术语!XOR 是另一个“方便”的布尔逻辑运算符,只有当其操作数之一为真时才为真。在这方面,它更接近英语中“或”的通常含义。XOR 通常用于简化符号,但通常可以通过重新组合 AND、NOT 和 OR 来实现,其他方法也容易实现。它被定义为以下方式:

XOR(a, b) ⇔ AND(OR(a, b), NOT AND(a, b))

回到我们的电路……它能做什么?图 2-3 中展示的设备用二进制数供电。在这个例子中,这个数字限制在三个位上,尽管这个设计可以很容易地扩展以允许更多的输入。

简单的加一电路

图 2-3. 简单的加一电路

这个简单的计算设备的工作方式与人类在一张纸上加十进制数字的方式相同——从右到左工作,最终将一个值带到下一列。唯一的真正区别是它使用二进制。

让我们看看会发生什么。我们有一行写着的二进制数。我们想要增加它 1;我们从最右边的数字开始,就像我们做十进制加法一样。

我们在那里有一个二进制位;当增加一个二进制位时,只有两种可能的结果:如果输入位是 0,输出是 1(0 + 1 = 1);否则,输出是 0,并且我们需要将 1 带到下一列(1 + 1 = 10)。换句话说,我们做了两件事:我们产生一个输出,它是输入的否定(0 对应 1,1 对应 0),并且,如果输入位是 1,我们必须记住这一点并在稍后包含它。

电路正是这样做的:对于第一个输入,I[0.] 顶部的门通过否定输入并将其提供给输出 O[0],同时也将输入值本身提供给负责处理下一列的门(O[1])。

O[0] = NOT I[0]
C[0] = I[0]

好吧,我们已经将数字增加了一;如果没有来自前一位的进位,我们就没有其他事情可做在剩余的列中。如果没有进位,O[1]应该与 I[1]相同。然而,如果有进位值,我们需要以相同的方式处理这种情况,就像我们处理向上一列加 1 一样:否定输出,并在适用的情况下将进位值传递到下一列。

从现在开始,每个后续的输出(对于 n > 0 的 O[n])将直接从 I[n]复制,如果没有从前一列传递过来的位,或者由于进位位的增加而增加 1(这再次归结为否定)。因此,如果 I[n]是 1,这一列的进位 C[n]也将是 1,O[n]将是 0(因为在二进制中,1 + 1 是 10)。你可能已经注意到,位置n的实际输出仅仅是位置n的输入值和来自列n−1 的进位位的异或的结果。因此,电路通过将 C[n−1]携带的位与 I[n]的值进行异或,然后通过将 O[n−1]的进位与 I[n]进行与操作来确定是否应该向下一列传递进位来生成 O[n]:

O[n] = XOR(I[n], C[n−1])
C[n] = AND (I[n], C[n−1])

考虑以下示例。我们想要将一个输入值,3(二进制中的 011),增加 1。输入如下:

I[0] = 1
I[1] = 1
I[2] = 0

电路通过否定 I[0]来产生 O[0],因此 O[0] = 0。因为 I[0]不为零,所以也有一个进位传递到下一列。在下一列,XOR 门将 O[1]设置为 0,因为尽管 I[1]是 1,但前一个列有一个进位值(1 + 1 = 10)。同样,也有一个进位传递到下一列。

在另一列中,I[2] = 0,但 AND 门指示前一行有一个进位值,因为前两个输入都设置为 1。因此,输出是 1。最后一列不会有进位。输出如下:

O[0] = 0
O[1] = 0
O[2] = 1
O[0] = 0
...或者 0100,这偶然地,当转换为十进制数时是 4。

哇哦——这就是+1,用二进制表示。

注释

我们刚刚用布尔代数表达了这个第一个计算问题。你可能想扩展设计,使其能够对两个任意数进行求和,而不仅仅是求一个数和 1 的和。然而,这个基本的电路是计算开始和结束的地方。

数字算术电路通过将某些输入数据通过一系列巧妙排列的逻辑门来运行,这些逻辑门随后对一系列位进行加、减、乘或其他简单的修改。其中涉及到的魔法很少。

到目前为止,我已经解释了硅芯片或手工制作的木材执行某些固定、基本操作的能力,例如整数算术。然而,这幅画面中缺少了某些东西:计算机并没有在 CPU 内部一个复杂而费力的门阵列中硬编码文本编辑器、游戏和对等软件。软件在哪里?

从电子计时器到计算机

计算机的真正价值在于其能够被编程以特定方式行动——根据某个计划执行一系列软件命令。

图 2-4 展示了我们朝着开发一种能够执行更多任务(而不仅仅是单一、硬连线任务)的灵活机器的下一步:数据存储和内存。在这张图中,我们看到一种称为触发器设计的内存存储单元。这个内存单元有两个控制线,“设置”和“复位”。当两者都为低电平时,门通过其输入和输出到或门的反馈连接保持其当前状态。来自或门的先前输出通过一个与门,因为它的另一条线被设置为 1(否定“复位”),然后再次通过或门,因为它的另一个输入是 0(“设置”)。输出状态保持,直到门有电。

具有实用接口的触发器内存

图 2-4. 具有实用接口的触发器内存

当“设置”信号变为高电平时,或门被迫输出 1,并且当“设置”信号变回低电平时,它将保持这个值。当“复位”线变为高电平时,与门被迫输出 0 并打破反馈回路,从而迫使电路输出 0。一旦“复位”信号变回低电平,输出保持为 0。当两个控制线都为高电平时,电路变得不稳定——这在机械计算机中尤其不美观。

该设计的真值表如下(V表示任意逻辑值):

触发器真值表
设置
---
0
1
0
1

触发器电路的一个更实用的变体,它包含一个“更新接口”(见图 2-4),使用两个与门和一个非门,以便在发生外部“触发”控制信号时捕获输入线的状态(采样并保持)。这种设计消除了不稳定的输入组合,使得这种类型的内存更容易用于存储信息。

改进的触发器真值表
输入
---
-
S

这种简单的门配置表现出一个重要的特性:它可以存储数据。单个单元只能存储一个比特,但通过组合多个触发器可以扩展存储容量。尽管今天的内存设计各不相同,但这一功能的重要性仍然相同:它允许程序执行。但这是如何实现的呢?

在基本设计中,芯片在内部芯片内存锁存器(寄存器)中存储一个特殊值,通常称为指令指针,该锁存器由几个触发器组成。由于流行的计算机是同步工作的,所有进程都由一个高频时钟信号发生器计时,因此指针在每个时钟周期从主存储器中选择一个内存单元格。通过这种方式检索的控制数据然后选择并激活适当的算术电路来处理输入数据。

对于某些控制数据,我们的假设芯片执行加法;对于其他数据,它涉及到输入输出操作。在获取每一块控制数据(每条机器指令)之后,芯片必须前进其内部指令指针,以便在下个周期准备好读取下一条命令。多亏了这种功能,我们可以使用芯片来执行一系列机器指令,或者一个程序。

现在是时候找出芯片必须实现哪些操作才能使其可用的了。

图灵与指令集复杂性

事实上,处理器并不需要复杂。实际上,一个芯片能够执行几乎所有任务的指令集非常小。图灵-丘奇定理指出,每个现实世界的计算都可以通过图灵机来完成,图灵机是计算机的原始模型。图灵机以发明者的名字命名,是一种简单的设备,它在一个可能无限长的由单个单元格组成的磁带上操作,这是一种假设的、纯粹抽象的存储介质。每个单元格可以存储机器“字母表”中的一个字符,这个“字母表”只是对可能的有限有序值集合的命名。(这个字母表与人类字母表毫无关系;这样命名是为了在门外汉中引起一些混乱。)

该设备还配备了一个内部寄存器,可以存储有限数量的内部状态。图灵机从磁带上的某个位置开始,处于给定状态,然后从磁带上的一个单元格中读取一个字符。每个自动机都有一个与之相关的转换模式集合,描述了如何修改其内部状态,根据读取后的情况在磁带上存储什么,以及如何(可选地)将磁带向一个方向移动一个单元格。这样一组转换定义了基于系统当前特性的计算系统下一个状态的规则。这些规则通常使用像这样的状态转换表来记录。

状态转换表
当前状态
---
C[t]
0
1

表格告诉我们,如果机器当前位于其下的单元格的当前值是 0,并且机器在那个时刻的内部状态是 S0,设备将改变 C 的状态为 1,将内部状态改变为 S1,并且不会移动读取头。

图 2-5 展示了图灵机位于单元格 C 并处于内部状态 S 的示例。

样例图灵机执行阶段

图 2-5. 样例图灵机执行阶段

让我们一步步来看。正如你在图 2-5 中可以看到的,机器使用两个字符的字母表,0 和 1,并且有两个内部状态,S0 和 S1。它从 S0 开始。(起始条件可以任意定义;我选择在那里开始没有任何特别的原因。)当位于磁带上存储的二进制数(C[0])的末尾(最低有效位)时,机器遵循以下逻辑:

  • 如果机器头下方的字符是 0,它将变为 1,根据表格中记录的第一个转换规则,机器的状态将变为 S1。因为没有从 S1 到其他状态的转换规则,机器将在下一个周期停止。

  • 如果从头部下方读取的字符是 1,它将变为 0,状态保持不变。根据第二个转换规则,机器还将读取头在磁带上向左移动。整个过程然后从新位置开始重复,因为机器仍然处于当前状态,对于该状态,定义了进一步的转换规则。

功能性,终于实现了

虽然这可能会让人惊讶,但这台特定的机器实际上是有用的,并实现了具有超过理论价值的任务:它执行基本的算术。它与我们本章前面讨论的增加电路做完全相同的事情。事实上,它实现了相同的算法:磁带上从最右边位置开始的位被反转,直到遇到 0(也被反转)。

这只是冰山一角。一个合适的图灵机可以实施任何曾经构思过的算法。唯一的问题是每个算法都需要实施一组单独的转换规则和内部状态;换句话说,我们需要为每个新任务构建一个新的图灵机,这在长期来看并不十分实用。

幸运的是,这种特殊类型的机器,即通用图灵机(UTM),具有足够先进的指令集,可以实施所有特定的图灵机并执行任何算法,而无需更改转换表。

这台超级机器既不特别抽象,也不复杂。它的存在是有保证的,因为可以设计一个特定的图灵机来执行任何有限算法(根据上述丘奇-图灵论题)。因为“运行”图灵机的方法本身就是一个有限算法,所以可以设计一个机器来执行它。

至于这台机器的复杂性,一个单比特、两个元素的字母表机器(设计出的最小通用图灵机)需要 22 个内部状态和描述状态转换的指令,以便在顺序无限内存带上执行算法。^([52)] 这并不是什么大问题。

圣杯:可编程计算机

图灵机也远不止是数学家用来娱乐自己的假设性抽象设备。这是一个需要用专门设计的、基于布尔逻辑的电子(或机械)设备实现的构造,也许可以扩展使其更加有用,这使我们离实用计算更近一步。唯一的问题是,在现实世界中无法满足无限长输入带的前提条件。尽管如此,我们可以提供大量的输入带,使得这样的硬件图灵机对于我们的大多数日常问题来说非常实用。通用计算机的时代到来了。

当然,真正的计算机远远超出了顺序访问单比特内存,从而显著减少了实现图灵完备性所需的指令集。一个具有 18 个字符的字母表通用图灵机只需要两个内部状态才能工作。另一方面,真正的计算机通常在一个至少有 4,294,967,296 个字符(32 位)的“字母表”上运行,通常更多,这允许非顺序内存访问和使用大量具有天文数字般可能内部状态的寄存器。

最后,通用图灵机模型证明了,日常实践也证实了,仅使用少量特性(由两个或三个内部寄存器组成,如指令指针、数据读写指针,可能还有一个累加器)和一小套指令,就可以构建一个灵活、可编程的处理单元。仅用几百个逻辑门就能组装这样的设备,尽管今天的设计可能需要更多。

如您所见,从头开始构建计算机的想法并不那么荒谬——即使是木制的。

简单化进步

当然,提出这样一套不起眼的指令集并不会使设备变得快速或易于编程。通用图灵机几乎可以做任何事情(在很多情况下,得益于它们的简单性),但它们运行缓慢且难以编程,到了甚至实现从更易读的语言到机器码的机器辅助翻译都变得困难,至少在没有让程序员临床性疯狂的情况下。

接近实现裸骨 Turing 完整性的架构或语言通常被称为图灵陷阱。这意味着,虽然理论上使用它们可以完成几乎所有任务,但在实践中,这几乎是不可能的,也太耗时、太费力。即使是像整数乘法或移动内存内容这样简单的任务,设置起来可能需要很长时间,执行起来可能需要两倍的时间。完成简单和重复性任务所需的时间和精力越少,以及软件使用多个指令完成的任务越少,就越好。

提高处理单元的功能性和性能的一种流行方法是在硬件中实现某些常见任务,这些任务在软件中执行可能会相当麻烦。这些任务通过使用一系列专用电路来实现(包括乘法和房屋贷款拒绝处理),从而为架构添加方便的扩展,并使程序的部署更快、更合理,同时仍然允许系统以编程和灵活的顺序执行这些功能。

令人惊讶的是,在设计处理器时,除了最初的几个步骤之外,并不总是希望线性增加电路的复杂性,以使处理器达到更高的速度、更高的能效和更好的功能集。当然,你可以构建大量的电路来处理几乎所有可以想象到的常用复杂操作。然而,除非架构真正成熟,并且你的预算允许你投入额外的努力和资源来制造芯片,否则这并不实用。在这样的平台上运行的程序确实需要更少的时间来执行,并且编写起来更容易,但设备本身构建起来更困难,需要更多的电力,并且可能因为体积过大或价格过高而不适合常规使用。像除法或浮点运算这样的复杂算法需要大量的通常处于闲置状态的门,才能在单步中完成这样的任务。

分解任务

与此相反,在拥有工作设计和大量时间来改进它之前,最好是放弃单周期执行模型。在硬件中实现复杂功能的一个更好的方法是将其分解成微小的部分,并在多个周期中执行高级任务。

在这样的多周期设计中,处理器会经过多个内部阶段,就像加一图灵机示例一样。它按照正确的顺序运行数据通过简单的电路,从而逐步实现更复杂的功能,这依赖于更基本的组件。而不是使用一个复杂的设备一次性完成所有数学运算,它可能使用一个电路来乘以 32 位整数的后续位,跟踪进位值,然后在第 33 个周期产生最终结果。或者,它还可以执行某些独立的、准备性的任务,这些任务在实际操作之前进行。这将使我们免于为每个操作码变体设计数十个电路,取决于它应该从哪里获取操作数或存储结果。

这种方法的额外好处是它使得硬件资源管理更加高效:对于简单的操作数;一个可变复杂性的算法可以更快完成,只需要绝对必要的周期数。例如,除以 1 可能比除以 187,371 所需时间更少。

一个简单、便宜、使用率最高且执行时间可变的电路,可能比一个复杂且耗电的、具有恒定执行时间的电路更经济高效。尽管一些今天的处理器试图使用固定数量的周期来完成越来越多的任务,但几乎所有处理器最初都是多周期架构。即使是这些大块头,模型也很少真正保持单周期,正如你马上就会看到的。

但首先,让我们看看这种通过多周期执行实现的简单优势是如何产生反效果的。

执行阶段

多周期执行的一种变体是将任务分割成若干个不同的、但通用的准备和执行阶段,而不是分割成若干个重复的步骤。这种方法被称为阶段化,在今天的处理器中被用来使它们在没有必要线性增加复杂性的情况下表现更好。执行阶段化已经成为处理器的一个重要特性。

今天的处理器可以将每条指令转换成一组大量独立的小步骤。某些步骤可以使用所有指令共享的通用电路来完成,从而有助于整体的简单性。例如,特定任务(再次想到我们最喜欢的乘法)的电路可以通过将其从任何通用 I/O 处理任务中分离出来,作为各种高级指令的一部分而变得更加通用和可重用。执行阶段和转换的集合取决于架构,但通常类似于图 2-6 中所示的方案。

基线指令执行阶段

图 2-6. 基线指令执行阶段

图 2-6 显示了以下阶段:

指令获取/解码

处理器从内存中检索指令,将其转换为低级序列,并决定如何进行以及将哪些数据传递给所有后续阶段。该电路对所有操作都是共享的。

操作数获取/解码

处理器使用一个通用电路从源中获取操作数以执行特定指令(例如,从指定的内部寄存器中获取),这样主电路就不必支持所有可能的操作数组合和获取策略。

ALU

一个针对执行此特定操作(可能需要多个步骤)而定制的算术逻辑单元(ALU)被调用以执行指定的算术任务。对于非算术(内存传输)指令,有时使用通用或专用 ALU 电路来计算源和目标地址。

内存存储

结果存储在其目标位置。对于非算术操作,内存将在计算的位置之间进行复制。

仅此而已,可能看起来只是常规多周期执行和电路重用措施的一种变化——这在今天的大多数 CPU 设计中都很普遍。但正如你将看到的,这对执行速度也极其重要。

较少的内存

电路的简单性并不是这个故事结束的地方。多周期设计的另一个额外优势是处理器速度不再受限于系统中最慢的组件——内存。消费级外部内存的访问速度远低于今天的处理器,并且具有高访问和写入延迟。即使处理器并非始终在访问内存,单周期处理器的速度也无法超过可靠访问内存所需的时间。它需要慢的原因之一是它可能遇到的单周期指令中可能需要内存访问;因此,必须有足够的时间来完成这项任务。另一方面,多周期设计允许 CPU 根据需要花费时间,甚至在必要时闲置几个周期(例如,在内存 I/O 期间),但在执行内部计算时以全速运行。此外,在使用多周期设计时,更容易加快内存密集型操作的速度,而无需投资于更快的内存。

反转门设计,通常被称为 SRAM(静态 RAM),提供低访问延迟并消耗少量电力。当前设计需要大约 5 纳秒,这相当于一些处理器的周期间隔。不幸的是,该设计还需要每个触发器相当数量的组件,通常每个比特大约需要六个晶体管。

与 SRAM、DRAM(动态 RAM)等其他今天流行的内存设计不同,DRAM 使用电容器阵列来存储信息。然而,电容器往往会放电,需要定期刷新。DRAM 比 SRAM 消耗更多的电力,并且访问和修改的延迟相当高,高达 50 纳秒。从积极的一面来看,DRAM 的制造成本比 SRAM 低得多。

由于成本过高,使用 SRAM 作为主内存几乎是不可能的。此外,我们还会遇到使用所有增加的性能的困难,这将要求我们以几乎与 CPU 相同的速度运行内存。遗憾的是,由于主内存体积较大且设计为可扩展,它必须放置在 CPU 外部。尽管 CPU 核心通常可以以比周围世界更高的速度运行,但当数据必须通过更长的距离传输时,会出现严重的可靠性问题(如主板上的轨道电容器、干扰、高速外围芯片的成本等)。

而不是采用使用更快的外部内存或集成所有内存与 CPU 的成本高昂的方法,制造商通常采用更合理的方法。先进的 CPU 配备了快速但相对较小的内核内存,SRAM 或某些衍生品,它缓存了最常访问的内存区域,有时还存储某些特定的 CPU 数据。因此,每当在缓存中找到内存块(缓存命中)时,它就可以快速访问。只有当必须从主内存中获取内存块(缓存未命中)时,才会出现相当大的延迟,此时处理器必须暂时推迟一些操作。 (单周期处理器不能充分利用内部缓存。)

一次做更多:流水线

正如我之前提到的,流水线化提供了相当大的性能优势,这远远超过了传统的多周期方法。尽管如此,它们之间有一个主要区别:由于许多阶段被各种指令共享,因此没有必要不稍微优化一下执行过程。

图 2-6 显示,当各个阶段分别执行时,每个周期只使用设备的一部分。尽管当前正在执行的指令已经通过了第一阶段,但它会阻塞整个 CPU,直到它完成。对于具有大量执行阶段(在今天芯片上,这个数字通常达到或超过 10,Pentium 4 甚至超过 20)的系统来说,这证明是一种严重的计算能力浪费。

一种解决方案是让下一条指令在上一条指令移动到下一个阶段后立即进入执行流水线,如图 图 2-7 所示。一旦第一条指令的某个阶段完成,并且执行移动到下一个阶段,前一个阶段就会用后续指令的一部分来填充,依此类推。当第一条指令完成时,下一条指令只差一个阶段就能完成,第三条指令则相隔两个阶段。因此,通过这种级联方法,执行时间可以显著减少,芯片使用也变得最优化。

流水线执行模型

图 2-7. 流水线执行模型

只要指令不相互依赖,并且没有操作在其前驱仍在流水线中的输出上操作,流水线就可以正常工作。如果指令确实相互依赖,那么必然会出现严重问题。因此,必须实施一个特殊电路来监督流水线,以防止这种锁定情况。

在流水线方面还有更多的挑战。例如,在某些处理器上,不同的操作可能具有不同的阶段集合。并非所有阶段都始终适用,有时跳过一些阶段可能更优。某些简单操作可能通过流水线运行得更快,因为没有要取或存储的操作数。此外,某些阶段可能需要可变数量的周期,这增加了两条指令在同一执行阶段同时到达的风险。为了防止这种情况,必须设计某些额外的机制,例如流水线“气泡”,以及设计用于在必要时引入短暂延迟的无操作阶段。

流水线的重大问题

传统的流水线是利用简单、多阶段芯片设计实现高性能的伟大工具,通过减少后续指令的延迟并确保电路使用最优化,但它们并非没有问题:如果这些指令可能会改变进一步的程序执行,则无法将指令流水线到条件分支指令之后。

实际上,通常是有可能的,但处理器不知道应该遵循哪个执行路径,如果做出了错误的决定,整个流水线必须在分支指令之后立即被清空。(CPU 还必须延迟提交这些指令所做的任何更改,因为这些指令毕竟是没有被执行的。)清空流水线引入了额外的延迟。

不幸的是,对于这种设计,许多 CPU 密集型任务,包括大量的视频和音频算法,都依赖于执行数百万次的小型条件退出循环,从而对流水线架构造成了巨大的性能影响。

这个问题的答案是分支预测。分支预测器通常是相当简单的计数电路,它跟踪最近的代码执行并维护一个小型历史缓冲区,以便对条件分支操作的最可能结果做出明智的猜测(尽管也经常部署更复杂的设计^([53]))。

所有分支预测器都采用一种策略,旨在为给定的代码提供最佳的流水线性能:如果特定的分支指令执行次数多于跳过次数,则最好取指令并流水线化。当然,预测可能会失败,在这种情况下,整个队列必须被丢弃。然而,今天的预测器在典型代码中达到了高达 90%的成功率。

影响:细微的差异

今天处理器中采用的先进优化集产生了一系列有趣的结果。我们观察到执行时间取决于以下特征,这些特征可以分为三组:

指令类型和操作的复杂性。某些操作比其他操作执行得更快。
操作数值。某些多周期算法对于简单输入证明更快。例如,将一个值乘以 0 通常相当简单,可以快速完成。
指令所需数据的内存位置。缓存内存可以更快地访问。

这些特征的重要性、普遍性和影响取决于所讨论的 CPU 架构的确切性质。第一个特征——可变指令执行时间——是所有多周期架构共有的,但在一些基本芯片上可能不存在。第二个特征——对操作数的依赖性——在顶级处理器中越来越少见。

在高端设备中,算术逻辑单元(ALU)和浮点运算单元(FPU)组件有时的工作速度高于 CPU 本身。因此,即使存在计算速度差异,也无法精确测量,因为大部分算术运算都是在单个 CPU 时钟周期内完成的。

最后一组时序模式——内存位置依赖性——与以往不同,仅限于今天的、高性能的计算机,而在低端控制器和各种嵌入式设计中闻所未闻。

前两个时间模式组——操作复杂性和操作数值依赖——也可以在 CPU 本身略高的层面上表现出来,即软件层面。处理器具有处理相当小的整数(通常从 8 位到 128 位)和一些浮点数的算术单元,但今天的密码学以及许多其他应用需要操作大数(通常是数百或数千位)、高精度浮点数或各种未在硬件中实现的高级数学运算。因此,这种功能通常在软件库中实现。这些库中的算法可能因操作和操作数的具体细节而具有可变的时间。

使用时间模式重构数据

可以认为,攻击者可以通过监控程序处理数据所需的时间来推断操作数或操作的某些属性。这构成了潜在的安全风险,因为在几个场景中,至少有一个操作数可能是一个不应向第三方透露的秘密值。

虽然用手中的秒表观察某人恢复数据的概念可能听起来很超现实,但今天的 CPU 提供了精确的计数器,使得各方能够确定确切的时间间隔。此外,某些操作可能需要更多的时间,例如在英特尔平台上,某些高级操作码可能需要数千个周期才能完成。随着网络吞吐量的不断增长和响应时间的持续改善,从远程系统中推断出这些信息并非完全不可能。

作为计算复杂度测量的信息泄露的性质可能并不立即清楚。如果是这样,密码学研究公司的保罗·科赫在上个世纪(即 20 世纪 90 年代)展示了一个很好的攻击示例(即,在 20 世纪 90 年代),使用我们在第一章中讨论的 RSA 算法。

逐位……

科赫观察到,RSA 算法中解密数据的过程相当简单,它基于解决以下方程:

T = c^(k) mod M

其中 T 是解密的消息,c 是加密的消息,k 是密钥,M 是模数,它们是密钥对的一部分。

在典型实现中使用的简单整数模幂指数算法有一个重要的特性:如果指数的某个位是 1,则结果的一部分是通过在基数的一部分(c的一些位)上执行模乘法来计算的。如果位是 0,则跳过该步骤。即使实际上没有跳过该步骤,软件执行乘法所需的时间也会变化,如前所述。大多数简单情况——例如乘以 2 的幂——比其他情况解决得更快。

因此,在这样的系统中,我们可以通过反复检查解密信息所需的时间来确定关于密钥(k)的大量信息。即使在硬件乘法需要固定时间的平台上,软件乘法算法(如 Karatsuba 乘法算法)的使用也会产生时间模式,这些算法是处理大数(如公钥密码学中使用的数)所必需的。指数的后续位构成私钥,而基数是提供给好奇旁观者或可见的消息的表示。

攻击相当简单。反派发送攻击者两个相似但略有不同的加密数据部分。它们在部分 X 上有所不同,因此解密该部分可能需要不同的时间。就反派对受害者模乘实现的想法而言,X 的一个变体是一个简单的情况,因此解密 X 的任务会很快完成。另一个变体预计需要更多时间。

如果攻击者解码和响应两个序列所需的时间相同,攻击者可以安全地假设用于解码部分 X 的密钥部分由零组成。他们还可以假设乘法算法采用了早期优化路径,即根本不执行任何乘法。

相反,如果其中一个场景花费更多时间,那么很明显,在两种情况下都进行了乘法运算,其中一种情况更容易解决。相应的密钥位必须设置为非零值。

通过遵循此程序,将加密消息的后续位视为我们的“部分 X”,生成或(如果时间允许)简单地等待与该场景兼容的加密消息,可以重建密钥的每一位。

注意

研究表明,这种方法可以成功扩展到几乎所有在可变时间内执行的计算算法,并讨论了一些攻击的实用优化,例如部署有限的错误检测和纠正功能。

在实践中

仅根据时间信息推断算术指令操作数的可感知属性,是执行计算复杂度攻击最明显、最有效和最有意思的向量。其他技术,如缓存命中和未命中时间,通常需要更详细的分析,并且在每个周期中揭示的信息较少。

很明显,这个问题在一定程度上会影响许多软件算法,例如在加密应用中常用的大数算术库。但抛开软件算法和理论,还有一些重要的问题仍然存在:执行时间对硬件级别的依赖程度有多真实,以及如何进行测量?

一个例子很容易找到。至少英特尔 IA32 架构的一部分表现出这种行为。《80386 程序员参考手册》^([55]) 描述了一个整数有符号乘法操作码,用助记符 IMUL 表示。该操作码在其基本形式下,将存储在累加器(一个多功能工作寄存器,在此平台上称为 [E]AX)中的值与另一个寄存器中存储的值相乘。然后,结果被存储回累加器。

文档进一步解释道:

80386 使用早期退出乘法算法。实际时钟数取决于优化乘数中最显著位的位位置 [...]。优化适用于正数和负数。由于早期退出算法,给出的时钟数是最大值到最小值。为了计算实际时钟数,请使用以下公式:

实际时钟数 = 如果 m <> 0 则 max(ceiling(log2(m)), 3) + 6 个时钟

实际时钟数 = 如果 m = 0 则 9 个时钟

虽然这看起来可能有些晦涩,但其含义很简单:处理器根据乘数的值优化乘法。它不是将乘数乘到乘数的所有位都耗尽,而是跳过操作数开头的零。

早期退出优化

要理解这种策略对整数乘法的相关性,可以想象一下学校里教授的传统迭代乘法方法,只不过这次是在二进制下。一个假设的“愚蠢”实现算法执行以下一系列操作。

00000000 00000000 11001010 11111110     Multiplicand (P)
* 00000000 00000000 00000000 00000110     Multiplier (R)
 -------------------------------------
  00000000 00000000 00000000 00000000     P * R[0] = P * 0
  00000000 00000001 10010101 1111110      P * R[1] = P * 1
  00000000 00000011 00101011 111110       P * R[2] = P * 1
  00000000 00000000 00000000 00000        P * R[3] = P * 0
  00000000 00000000 00000000 0000         P * R[4] = P * 0
  00000000 00000000 00000000 000          P * R[5] = P * 0
  ...
+ 0                                       P * R[31] = P * 0
 -------------------------------------
  00000000 00000100 11000001 11110100

显然,这些操作中的许多是完全不必要的和不合理的,而且一旦乘数的后续位只剩下零,继续操作就毫无意义。一个更合理的方法是跳过它们:

00000000 00000000 11001010 11111110      Multiplicand (P)
* 00000000 00000000 00000000 00000110      Multiplier (R) - optimizing
 -------------------------------------
  00000000 00000000 00000000 00000000      P * R[0] = P * 0
  00000000 00000001 10010101 1111110       P * R[1] = P * 1
+ 00000000 00000011 00101011 111110        P * R[2] = P * 1
  ...Bail out − ignore leading zeros of R!
 -------------------------------------
  00000000 00000100 11000001 11110100

这本质上是英特尔部署的早期退出优化的本质。

注意

这种优化使得乘法在时间上不对称。2100 的计算速度会比 1002 慢 (!),尽管结果显然是相同的。

通过早期退出优化,英特尔处理器执行乘法需要可变数量的周期,其长度与第二个操作数中最老(最显著)位被设置的位的位置成正比。通过应用文档中提供的时钟计数算法,可以确定乘数和 IMUL 时间的相关性,如下所示:

乘数值范围 完成周期数
0 – 7 9
8 – 15 10
16 – 31 11
32 – 63 12
64 – 127 13
128 – 255 14
256 – 1,023 15
1,024 – 2,047 16
2,048 – 4,095 17
4,096 – 8,191 18
8,192 – 16,383 19
16,384 – 32,767 20
32,768 – 65,535 21
65,536 – 131,071 22
131,072 – 262,143 23
262,144 – 524,287 24
524,288 – 1,048,575 25
1,048,576 – 2,097,151 26
2,097,152 – 4,194,303 27
4,194,304 – 8,388,607 28
8,388,608 – 16,777,215 29
16,777,216 – 33,554,431 30
33,554,432 – 67,108,863 31
67,108,864 – 134,217,727 32
134,217,728 – 268,435,455 33
268,435,456 – 536,870,911 34
536,870,912 – 1,073,741,823 35
1,073,741,824 – 2,147,483,647 36

对于负乘数值也存在类似的依赖关系。

工作代码—自己动手做

以下代码列表展示了适用于 Unix 类系统的 C 语言的实际实现,可用于确认和测量计时模式的不同。程序使用两个参数调用:乘数(不应以任何方式影响性能)和乘数(可能用于早期退出优化,因此影响整个操作的速度)。程序使用所选参数执行 256 次测试,每次测试 500 次后续乘法,并返回最短测量时间。

我们运行了 256 次测试,并选择最佳结果以补偿系统在一段时间内中断执行的情况,这在多任务环境中相当常见。尽管单个测试可能会受到此类事件的影响,但至少在一系列快速进行的短测试中,可以预期至少有一些测试可以无中断地完成。

代码使用系统时钟来测量执行时间(微秒)。

注意

今天的一些英特尔芯片具有通过 RDTSC 指令码提供的精确计时机制。这种方法访问内部时钟周期计数器在旧平台上不可用,因此我们不会依赖它。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <limits.h>

int main(int argc,char** argv) {

  int shortest = INT_MAX;
  int i,p,r;

  if (argc != 3) {
    printf("Usage: %s multiplicand multiplier\n",argv[0]);
    exit(1);
  }

  p=atoi(argv[1]);
  r=atoi(argv[2]);

  for (i=0;i<256;i++) {
    int ct;
    struct timeval s;
    struct timeval e;

    gettimeofday(&s,NULL);

    asm(

      "  movl $500,%%ecx    \n"  /* Loop repetition counter (R) */
      "imul_loop:           \n"
      "  movl %%esi,%%eax   \n"
      "  movl %%edi,%%edx   \n"
      "  imul %%edx,%%eax   \n"        /* Comment out for first run */
      "  loop imul_loop     \n"
        :
        : "S" (p), "D" (r)
        : "ax", "cx", "dx", "cc");

    gettimeofday(&e,NULL);

    ct = ( e.tv_usec - s.tv_usec ) +
         ( e.tv_sec - s.tv_sec ) * 1000000;

    if (ct < shortest) shortest = ct;

  }

  printf("T[%d,%d] = %d usec\n",p,r,shortest);
  return 0;
}

通过将 IMUL 指令最初注释掉并使用任意参数调用程序,我们可以估计计时代码的开销(T[idle])。如果该值超出 10 到 100 微秒的范围——这足以提供精细的读数,但足够低以最大化操作系统中断的机会——则调整循环重复计数器 R,默认设置为 500。

在恢复 IMUL 指令并使用选定的乘数 D 和重复计数器 R 重新编译和运行程序后,可以使用返回的时间近似值 T[D,R] 来估计 IMUL 操作(C[D,R])所花费的 CPU 周期数,只要知道处理器的运行频率(F[MHz]):

C[D, R] = (T[D, R] − T[idle]) · F[MHz]/R

如预期的那样,在更新和更先进的芯片上的流水线和分支预测器将启动并略微扭曲结果,但可以做出良好的估计。

注意

在较新的英特尔处理器上,完成乘法所需的时间已经是恒定的。

预防

你可以采取多种方法来防止计算工作量分析。最明显的方法是使所有操作执行所需的时间相同。然而,这很困难,并且通常会导致严重的性能惩罚,因为所有计算所需的时间都必须延长以匹配最慢的那个。

如果延迟不是关键因素,引入随机延迟有时似乎是应用程序的可接受防御策略,特别是对于许多非交互式网络服务,并且对处理器的压力较小。然而,如果攻击可以重复进行,这种随机噪声可以被有效地过滤掉。

另一种方法,称为“遮蔽”,通过运行随机或其他虚假且不可预测的数据,结合算法的实际输入,在系统中引入一定量的噪声,以使攻击者无法从输入中推断出有意义的属性,即使加密算法容易受到时间攻击——然后丢弃我们无意发送的额外信息。尽管在这种情况下性能惩罚相对较低,但很难很好地执行遮蔽。

思考之食

我带你经历了一段漫长的旅程,但我希望它是值得的。像往常一样,我将给你留下几个可能相当有趣的问题供你思考:

  • 首先,尽管我专注于计算复杂度攻击对密码学相关应用的影响,但这个问题并不严格局限于这个领域,并且通常在处理私人或机密信息时表现出来。当然,通过仔细观察系统上的适当服务,可以推断出有关 HTTP 请求或 SMTP 流的各种基本信息;你能想到更多实际的应用场景吗?

  • 其次,即使服务没有处理任何秘密数据,计算复杂度信息也可能有些用处。考虑一下像网络守护进程这样的应用,它们通过提供可能过于通用的错误或成功消息来防止秘密泄露,目的是例如使攻击者难以确定他是否因为密码输入错误或用户不存在而收到“登录失败”的消息。然而,根据接收此消息所需的时间,一个细心的观察者可能能够确定代码中确实执行了哪条路径,以及错误是在早期(仅检查有效用户名时)还是稍后(在验证密码时)发生的。我鼓励你通过实验常见的网络服务,如 SSH、POP3 和 Telnet,看看是否存在可测量的和一致的区别。

  • 正如往常一样,即使是对信息泄露的最佳防御也往往会意外失败。此外,计算复杂性并不是确定硅芯片内部发生什么情况的唯一方法。考虑以下例子:Biham 和 Shamir^([56])设计了一种巧妙的方法来破解用于智能卡的“安全”芯片设计。智能卡被设计成安全存储个人信息或加密密钥,并且只向特定的认证服务和受信任的客户披露。结果证明,通过滥用设备并诱导由于机械应力、高能辐射、过热或类似的外部因素导致的故障,可以推断出受保护数据或保护机制的性质。

只是想分享一下。

第三章. 水蛇的十个头

我们在探索过程中非常早期出现的其他诱人场景

在第一章和第二章中,我讨论了由于设计上的巧妙但最终考虑不周,导致计算机要么功能更强大,要么更容易维护而出现的两种不同的信息披露场景。这些设计决策所开启的被动监听向量隐藏在实际实现的深处,并为处理信息最早期的威胁提供了迷人的洞察。

另一方面,暴露自然限于被监控环境的物理或逻辑邻近性。尽管在信息泄露的可能性的早期阶段就出现了几乎无穷无尽的可能性,但我选择单独提出这两个案例,因为它们具有独特性、美丽性,以及一个坚定的攻击者相对容易实施潜在攻击的相对容易性。尽管其他场景也值得提及,但在本章中,我简要提到了一些可能不需要详细讨论但你可以自己更深入探索的更有趣的可能性。

揭示的辐射:电视中的 TEMPEST

在 20 世纪 50 年代,研究人员得出结论,电磁辐射(EMR)通常可以实际且容易地用来恢复或重建发射该辐射的设备的行为信息。EMR 是由几乎所有电子、机电和电气设备产生的不可取的噪声,无论其设计和预期用途如何,并且通常通过电力线或空气传播到相当远的距离。

在他们的发现之前,电磁辐射问题被认为与工程相关,因为存在不同设备或电路之间意外干扰的风险,但并未证实对监测设备污染的射频频率的人有任何价值。然而,随着世界即将进入信息战时代,以及电子数据处理和电信设备(一些用于传输或存储机密或敏感信息)的发展和不断增加的部署,一个远程观察者仅通过监听特定频率就能重建系统处理的一些信息的结论,对自由(或不太自由的)世界的政府来说变得相当令人担忧。

术语 TEMPEST(瞬态电磁脉冲辐射标准)起源于 20 世纪 60 年代为美国军方委托进行的一项机密电磁辐射发射研究,最初用于表示一套防止在处理敏感数据的电子电路中泄露辐射的实践。后来,它仅仅成为描述与截获和重建射频(RF)辐射相关的一般问题和技术的流行语。

尽管这种风险最初在怀疑者的耳朵里听起来更像是一部糟糕的科幻小说,而不是一个实际的威胁,但 1985 年 Wim van Eck 发表的一篇重要研究论文^([57])证明了通过截获这种设备内部高压电路产生的射频信号,可以非常容易地重建 CRT 显示器上显示的图像。

一个典型的 CRT 显示器(见图 3-1Figure 3-1))通过按顺序照亮图像的每个像素,逐行逐行,然后逐行逐行,以非常高的速度构建其显示,并根据任何时刻被照亮的屏幕位置调节信号的强度。为此,从设备后部的阴极枪发射出一束窄电子束。这个电子束击中阳极(显示器上的导电材料层),然后阳极反过来发出我们看到的可见光光子。电子束由一个特殊电路调节,但也由一组电磁铁定位,使它从左到右和从上到下扫过整个显示区域,以产生和更新屏幕上的图像。Wim 指出,控制电磁铁和电子枪电子学的振荡器发出几种类型的特征信号,这些信号在标准频率下。在无线电频谱中捕捉这些信号相当简单,^([10])并且每个信号通常都足够清晰和强烈,足以构建一个相当便宜的设备,可以窃听 CRT 显示器,甚至从相当远的距离。

CRT 显示器图像扫描和建立过程

图 3-1. CRT 显示器图像扫描和建立过程

注意

电磁辐射当然不仅限于 CRT 屏幕,在 LCD(TFT,或薄膜晶体管)显示器和任何计算机电路中也很常见。它们在数据总线上的出现同样普遍,信息通过主板上通常相当长且尖锐拐角的大量导电轨道传输,这些轨道除了其他用途外,还充当了一个大天线(尽管提取和解释特定信号以及辐射范围的变化可能相当显著)。

虽然没有关于野外进行电磁辐射攻击的可验证报道,除了军事和情报应用(尤其是在冷战期间[58]))之外,但在文献中可以找到一些关于工业间谍活动的轶事报道[59])。

显然,这种攻击有其局限性:攻击者必须靠近目标。此外,除了监听模拟 CRT 显示器外,攻击者还必须配备昂贵且复杂的设备,尤其是在监听今天低干扰显示器和更高 CPU 和总线速度的情况下。尽管如此,任何此类攻击都难以防范且成本高昂。


^([10]) 由于这个原因,以及电源线干扰,想要收听地球超低频信号的“自然无线电”爱好者必须经常携带他们的录音设备前往偏远、隐蔽的地区。

隐私,有限

到目前为止讨论的暴露场景可以归类为特定技术设计和部署方式的不希望或意外结果,尽管开发者和最终用户的目标或期望是相同的。然而,在某些情况下,暴露结果导致两组的目标和期望存在微小差异。尽管由于程序员的疏忽或恶意导致的软件级隐私问题臭名昭著且通常具有普遍性,但一些不那么明显的设计问题,本身并非缺陷,也开始出现。在这个领域,一些更有趣的问题组属于电子文档数据披露的范畴。

我们在编写文档时自然地假设,与文档内容无关的所有信息(尤其是任何唯一标识原作者的信息)都会被其他能够访问文档的各方隐藏,除非作者明确披露。但纯文本编辑器的时代已经一去不复返了。今天的文档格式支持广泛的元信息存储功能,旨在使文档的独特标记、后续索引、搜索和跟踪变得更加容易。然而,令人担忧的是,编写工具的设计者经常决定自动填写某些信息,通常给予作者很少或没有控制权,并且没有让他们立即意识到这种做法。尽管这种做法可以被视为使环境更加用户友好和透明的另一种练习,但只有少数人对此过程缺乏广泛的认知表示赞赏。

跟踪来源:“他做了这件事!”

编写软件的一个常见问题是,某些应用程序存储了唯一标识标签,这使得将文档与其来源相关联成为可能。特别是,Microsoft Word 长期以来一直使用计算机网络卡的硬件地址(如果有的话)来在文档中构建一个全局唯一标识符(GUID)字段——无论是饼干食谱还是恐怖分子的手册。尽管这个问题已经在微软 Office 应用套件的最新版本中得到了解决,但这种做法产生了一些有趣的后果:

  • 每个设备都有一个唯一的硬件卡地址。由于硬件地址用于在本地网络上定位特定设备,这种唯一性是必要的,以防止两个具有相同硬件地址的计算机连接到同一网络时出现的问题。因此,记录在 Microsoft Word 文档 GUID 字段中的数字可以用来唯一地识别文档的作者,无论这个人是否匿名编写了文档或签了名。这既可以作为一项有价值的法医调查工具,也可以作为在某些情况下抑制言论自由的有效方式(例如,雇主追查告密者)。

  • 硬件地址是分批分配给特定制造商的。此外,在许多情况下,网卡是按顺序制造的,然后批量出售给计算机制造商。因此,一个有知识的人不仅可以确定谁制造了特定的卡,还可以确定谁出售了它以及出售给了谁。在许多情况下,实际上可以将特定的硬件地址追踪到一台特定的机器,并有效地追踪到一个私人实体或特定的公司。这可能会使一个坚定的调查员能够确定特定文档的来源。

  • 由于硬件地址是批量分配的,也可能有可能从创建文档的系统硬件配置中得出有限的结论。尽管这构成了轻微的威胁,但它可能是容易开心或特别好奇的人有趣的信息来源。

一些功能虽然对用户可用,但被深深埋藏在界面中,以至于典型用户不知道正在保存什么以及如何更改这些默认设置。例如,Microsoft Word 和OpenOffice.org这样的生产力软件因插入“默认作者”信息而臭名昭著。这些信息通常来自软件许可提供的数据或首次运行后自动存储在文档的元数据深处,而大多数用户不会费心去查看。尽管这是一个在共享文档时有用的功能,但其隐私影响通常远超过对最终用户可能带来的任何潜在好处。

另一个例子是“用户友好”的做法,即根据文档中的第一句话自动填写元头部的“标题”字段。这是一个很好的细节,但选择通常是永久的,这意味着即使后来第一段被更改(例如,新的商业报价现在面向竞争对手),细心观察者也能推断出原始内容。这个“功能”再次暴露了作者预期之外的信息给文档的接收者。

旧版本的 Microsoft Word 也保存了未正确清除所有已编辑数据的文档,实际上提供了撤销信息,并记录了文本的所有先前修订。这些信息可以很容易地被任何足够熟练的攻击者通过解析对象链接和嵌入(OLE)容器(编辑器存储所有数据的格式)的软件恢复。当将文档的先前版本用作模板并发送给另一方,例如竞争对手时,问题尤其严重。恢复报价的先前版本、动机信或对客户的官方回应的能力无疑是令人娱乐和启发的,但并不总是发送者所希望的。

当然,随着最近对可信计算和为减少盗版而增加“问责制”的推动,预期所有文档都将被标记以便追踪其来源,这是合理的。

“哎呀”曝光:_*~1q'@@ . . . 以及密码是 . . .

各种文本编辑器共同面临的最后一个问题是随机内存泄露。这种披露纯粹是由于无能或测试不足造成的,但它与其他编码缺陷不同,它更多地是向细心观察者透露一些有用的提示,而不是使代码容易受到攻击。无论这个问题是否仅限于程序本身,还是由系统范围内的泄露(在内存保护较差的系统上,如 Windows 3.x或 9x)引起,泄露的数据可以包括其他文档、浏览历史、电子邮件内容,甚至密码等敏感信息。

当一个应用程序分配一块内存(例如用于编辑缓冲区),可能之前用于其他任务,并且在重新用于完全不同的目的之前忘记清除它时,就会发生这个问题。出于性能原因,在分配给应用程序之前,内存并不总是被清零。应用程序可以操作并覆盖内存块的一小部分,但在保存文件时,会写入整个分配的数据块,包括它想要的数据和一些来自不知多久以前的遗留内容。而且,不出所料,较老的微软 Word 版本曾经因在几乎每个文档中丢弃大量随机内存而臭名昭著。

这个问题在微软 Windows 操作系统中多次出现,最早在 1998 年出现在所有系统上,然后在 2001 年只出现在 Mac OS 上。一些轶事证据表明还有其他发现,但这些记录相当不完整。

第四章. 为公共利益工作

当提出并留下关于计算机如何确定用户意图的问题时

任何足够广泛和多样化的计算机网络的美妙之处,但也是最大的问题之一,是你不能盲目地相信任何连接的方就是他们所声称的那样,而且无法确定他们的意图或他们行动背后的真实驱动力。

我将在本书的第三部分讨论确认来源身份的问题,届时我将剖析网络架构并探讨由此产生的风险。然而,发起者意图的问题是计算机安全的一个独立且迷人的方面,它通常具有严重且牵涉广泛的社交和法律影响,这些影响超出了计算领域。随着我们使计算机在预测用户想要做什么(这是一种实现直观性和易用性的手段)方面越来越好,并赋予它们更多的自主权,欺骗机器成为他人使用的工具变得越来越容易,而不是帮助用户。

关于这个主题已经写下了长篇大论,随后是一系列关于当事情出错时应该把责任归咎于谁以及应该起诉谁的激烈争论。我相信解决这个问题是重要的,但并不适合强加任何特定的观点给你。因此,我将用一篇简短且主要技术性的论文结束这本书的这一部分,这篇论文我最初于 2001 年在Phrack杂志第 57 期上发表。我对它做了一些小的编辑,并将避免进一步的评论。

让我来找找……/我在找纸张……啊,找到了:

==Phrack Inc.==
                  Volume 0x0b, Issue 0x39, Phile #0x0a of 0x12
|=---------------=[ Against the System: Rise of the Robots ]=----------------=|
|=---------------------------------------------------------------------------=|
|=---=[ (C)Copyright 2001 by Michal Zalewski <lcamtuf@bos.bindview.com> ]=---=|

-- [1] Introduction -----------------------------------------------------------

" . . . [the] big difference between the Web and traditional well-controlled
 collections is that there is virtually no control over what people can put on
 the Web. Couple this flexibility to publish anything with the enormous
 influence of search engines to route traffic, and companies that deliberately
 manipulate [sic] search engines for profit become a serious problem."

                                    -- Sergey Brin, Lawrence Page [A]

Consider a remote attacker who can compromise a remote system without sending
 any traffic to his victim. Consider an attack that relies on simply creating a
 file to compromise thousands of computers and that does not require any local
 resources to carry it out. Welcome to the world of zero-effort exploit
 techniques, automation, and anonymous as well as virtually unstoppable attacks
 that result from the ever-increasing complexity of the Internet.

Zero-effort exploits create their wish list and leave it somewhere in
 cyberspace where others can find it. The utility workers of the Internet [B] --
 hundreds of tireless, never-sleeping robots, information browsers, search
 engines, intelligent agents -- come to pick up the information and,
 unknowingly, become a tool in the hands of the attacker. You can stop one of
 them, but you cannot stop them all. You can find out what their orders are, but
 you cannot guess what these orders will be tomorrow, lurking somewhere in the
 abyss of not-yet-indexed cyberspace.

Your private army, close at hand, is picking up the orders you left for them on
 their way. You exploit them without having to compromise them. They do what
 they are designed to do the best they can. Welcome to the new reality, in which
 our AI machines can rise against us.

Consider a worm. Consider a worm that does nothing. It is carried and injected by
 others, but does not infect them. This worm creates a list of 10,000 random
 addresses with specific orders. And waits. Intelligent agents pick up this
list, and with their united forces they try to attack the targets. Imagine that
 they are not too lucky and achieve a 0.1% success ratio. Ten new hosts are now
 infected. On every single one of them, the worm does exactly the same thing-
prepares a list. Now the agents come back to infect 100 new hosts. And so the
 story goes (or crawls, if you wish).

Agents are virtually unnoticeable, as people are now accustomed to their
 presence and persistence. Agents just slowly move ahead in a never-ending loop.
 They work systematically. They do not choke connections with excessive data,
 and there are no network meltdowns, traffic spikes, or telltale signs of
 disease. Week after week they try new hosts, carefully, and their exploration
 never ends. Is it possible to notice that they carry a worm? Possibly . . .

-- [2] An example -------------------------------------------------------------

When this idea came to mind, I tried to use the simplest test just to see if I
 was right. I targeted, if that is the correct word, several general-purpose
 web-indexing crawlers. I created a very short HTML document and put it
 somewhere on my home page and then waited for a couple of weeks. And they came
 -- AltaVista, Lycos, and dozens of others. They found new links, picked them up
 enthusiastically, and then disappeared for days.

  bigip1-snat.sv.av.com:
    GET /indexme.html HTTP/1.0

  sjc-fe5-1.sjc.lycos.com:
    GET /indexme.html HTTP/1.0

  [...]

They came back later to see what I had given them to parse.

    http://somehost/cgi-bin/script.pl?p1=../../../../attack
    http://somehost/cgi-bin/script.pl?p1=;attack
    http://somehost/cgi-bin/script.pl?p1=|attack
    http://somehost/cgi-bin/script.pl?p1=`attack`
    http://somehost/cgi-bin/script.pl?p1=$(attack)
    http://somehost:54321/attack?`id`
    http://somehost/AAAAAAAAAAAAAAAAAAAAA...

The bots followed the links, each of the links simulating vulnerabilities.
 Although these exploits did not affect my server, they could easily compromise
 specific scripts or the entire web server on a remote system by causing the
 script to execute arbitrary commands, to write to arbitrary files, or, better
 yet, to suffer a buffer overflow problem:

  sjc-fe6-1.sjc.lycos.com:
    GET /cgi-bin/script.pl?p1=;attack HTTP/1.0

  212.135.14.10:
    GET /cgi-bin/script.pl?p1=$(attack) HTTP/1.0
bigip1-snat.sv.av.com:
    GET /cgi-bin/script.pl?p1=../../../../attack HTTP/1.0

  [...]

Bots also happily connected to the non-HTTP ports I prepared for them and
 started a conversation by sending the data I supplied in URLs, thus making it
 possible to attack even services other than just web servers:

  GET /attack?`id` HTTP/1.0
  Host: somehost
  Pragma: no-cache
  Accept: text/*
  User-Agent: Scooter/1.0
  From: scooter@pa.dec.com

  GET /attack?`id` HTTP/1.0
  User-agent: Lycos_Spider_(T-Rex)
  From: spider@lycos.com
  Accept: */*
  Connection: close
  Host: somehost:54321

  GET /attack?`id` HTTP/1.0
  Host: somehost:54321
  From: crawler@fast.no
  Accept: */*
  User-Agent: FAST-WebCrawler/2.2.6 (crawler@fast.no; [...])
  Connection: close

  [...]

Other than the well-known set of web search engines, a bunch of other, private,
 crawl bots and agents run by specific organizations and companies also
 responded. Bots from ecn.purdue.edu, visual.com, poly.edu, inria.fr,
 powerinter.net, xyleme.com, and even more unidentified engines found this page
 and enjoyed it. Although some robots did not pick all addresses (some crawlers
 do not index CGI scripts at all, while others would not use nonstandard ports),
 the majority of the most powerful bots did attack virtually all vectors I
 supplied; and even those that were more careful always got tricked into
 performing at least some.

The experiment could be modified to use a set of real vulnerabilities in the
 form of thousands and thousands of web server overflows, Unicode problems in
 servers such as Microsoft IIS, or script problems. Instead of pointing to my
 own server, the bots could point to a list of randomly generated IP addresses
 or a random selection of .com, .org, or .net servers. Or, you could point the
 bots to a service that could be attacked by supplying a specific input string.
There is an army of robots encompassing a wide range of species, functions, and
 levels of intelligence. And these robots will do whatever you tell them to do.

-- [3] Social considerations --------------------------------------------------

Who is guilty if a "possessed" web crawler compromises your system? The most
 obvious answer is: the author of the original web page the crawler visited. But
 web page authors are hard to trace, and a web crawler indexing cycle takes
 weeks. It is hard to determine when a specific page was put on the Net because
 pages can be delivered in so many ways or even produced by other robots. There
 is no tracking mechanism for the Web that provides functionality similar to
 that implemented in the SMTP protocol. Moreover, many crawlers do not remember
 where they "learned" new URLs. Additional problems are caused by indexing
 flags, such as "noindex" without the "nofollow" option. In many cases, an
 author's identity and attack origin can never be fully determined.

By analogy to other cases, it is reasonable to expect that intelligent bot
 developers would be forced to implement specific filters or to pay enormous
 compensation to victims suffering from bot abuse, should this kind of attack
 become a reality. On the other hand, when you consider the number and wide
 variety of known vulnerabilities, it seems almost impossible to successfully
 filter contents to eliminate malicious code. And so the problem persists. (An
 additional issue is that not all crawler bots are under U.S. jurisdiction,
 which differs significantly from some of their counterparts when it comes to
 computer abuse regulations.)

-- [4] Defense ----------------------------------------------------------------

As mentioned earlier, web crawlers themselves have limited defense and
 avoidance possibilities, due to a wide variety of web-based vulnerabilities. It
 is impossible to simply ban all malicious sequences, and heuristic
 investigation is risky: input that is valid and expected for one script may be
 enough to attack another. One reasonable defense tactic is for all potential
 victims to use secure and up-to-date software, but this concept is extremely
 unpopular for some reason. (A quick and nonscientific test: A search at http://
www.google.com with the unique documents filter enabled returns 62,100 matches
 for "CGI vulnerability" query [C].) Another line of defense against infected
 bots is to use the standard /robots.txt exclusion mechanism [D]. The price you
 pay, though, is the partial or complete exclusion of your site from search
 engines, which in most cases is undesirable and unacceptable. Also, some robots
 are broken or intentionally designed to ignore /robots.txt when following a
 direct link to new websites.

-- [5] References -------------------------------------------------------------

[A] "The Anatomy of a Large-Scale Hypertextual Web Search Engine"
    Googlebot concept, Sergey Brin, Lawrence Page, Stanford University
    URL: http://www7.scu.edu.au/programme/fullpapers/1921/com1921.htm

[B] "The Web Robots Database"
    URL: http://www.robotstxt.org/wc/active.html

[C] "Web Security FAQ", Lincoln D. Stein
    URL: http://www.w3.org/Security/Faq/www-security-faq.html

[D] "A Standard for Robot Exclusion", Martijn Koster
    URL: http://info.webcrawler.com/mak/projects/robots/norobots.html

|=[ EOF ]=-------------------------------------------------------------------=|

在没有能力预测和分类特定用户行为背后的实际意图的情况下,几乎不可能完全防止自动化滥用,而这种情况在短期内不太可能发生。与此同时,每年依赖与其它实体自动化交互的系统数量都在增加,这使得这个问题可能比最初写这篇文章时更有趣,尤其是在过去几年中,越来越多的复杂和数量庞大的蠕虫病毒袭击了互联网。

这个故事有没有道德寓意或者我们应该得出的明确结论?实际上并没有。然而,记住这一点很重要,即机器并不总是代表其操作者行事,即使它们并没有被明显地损害或直接滥用以变得敌对。确定意图和恶意行为欲望起源的地点可能是一个巨大的挑战,正如你将在后面的章节中看到的。

第二部分。安全港

在计算机和互联网之间潜伏的威胁

第五章。闪烁灯

我们得出结论,美丽也可能致命,我们学会了从 LED 中阅读

本书的第一部分关注与数据输入点系统设计相关的各种问题。这些问题仅限于通过观察似乎无关的行为模式来推断输入,这些模式是由对系统具有本地访问权限的用户观察到的。但随着信息沿着其路径进一步移动并离开这个系统,其暴露范围扩大,问题变得更加具体。

本书第二部分关注数据仍在可触及范围内,但刚刚离开原始系统时出现的一些问题——在它进入互联网之前的一瞬间。这里讨论的暴露范围限于大约一个局域网及其直接周边的物理足迹。在这个层面上进行攻击需要观察点位于源头附近,但不需要系统级访问权限。

本章讨论的具体问题与之前讨论的问题略有不同:现在的暴露现在在硬件层面上体现,类似于 TEMPEST,但有所不同。这一现象的美丽之处,以及无需专用设备就能轻松观察到的便利性,完全有理由更深入地研究它。

传输数据的艺术

计算机与其他电子设备进行通信的需求自实用计算开始以来就显而易见,实现这一任务既可靠又经济困难。我们可以通过在所有主要组件之间提供充足且定制化的接口,保持精确的信号特性,并使用所有操作的公共参考时钟来控制机器的内部通信,这样接收者总是知道何时监听,发送者总是知道何时传输数据。但是,在更长的距离或非专业、廉价的接口设备上进行通信则是一个不同的挑战:计算机被迫通过一种通常不允许我们习惯于在单台机器内部进行操作的自由度进行通信。

实际上,情况正好相反。客户期望简单、方便、实用且价格低廉的解决方案,而要求通过一根价值 100 美元、3 英寸长、100 芯的电缆将计算机连接起来,似乎并不是一个获胜的解决方案。简单是一个必需品。任何外部通信通道的核心几乎总是依赖于后续比特的串行传输,只有当这些比特重新组装并分组在一起时,才能产生数值、文本字符串或其他数据,这些数据是发送方或接收方机器环境中的原生数据。在最看似微不足道和明显的情况下,当仅通过一对电线连接的两台机器或设备需要交换信息时,它们通过将其中一根电线相对于另一根(参考)线设置为高电压或低电压(或者使用任何其他不同的信号或状态)来实现。它们这样做是为了以给定的频率发送后续的数据比特——这个频率必须在两个设备上保持合理接近并同步。

即使在这样的简单设计中,也会立即出现许多问题。首先,设备没有共享一个参考时钟。尽管两者都有基于石英的内部时钟,但由于细微的制造缺陷、干扰和其他物理条件,没有任何两个经济实惠的时钟足够精确,能够在较长的时间内保持可靠和快速的通信。串行通信需要精确的同步。简单的位编码方案,通常称为非归零(NRZ),简单地输出一个信号(电压)表示 0,另一个信号表示 1。在这样的系统中,当值定期变化时,很容易保持两端同步——系统只需检测下降或上升沿,将其用作粗略的参考,并相应地调整自己的时钟。但是,给定一个较长的 1s 或 0s 序列,接收方很难准确地确定正在发送多少比特。事实上,即使是微小的时钟漂移也可能导致问题,而且在交换恒定序列的比特时无法补偿这一点。

显然的方法,即简单地通过一个单独的、可区分的时序信号将数据交织在一起,并不总是最方便和高效的方法;增加的复杂性和降低的吞吐量通常被视为一种麻烦。

为了有效地解决这个问题,许多系统使用一种称为曼彻斯特编码的方案,也称为双相码。曼彻斯特编码的算法,如图 5-1")所示,与 NRZ 一起展示,使用信号边缘来编码数据,而不是使用信号电平。原始的、上述 NRZ 编码使用一个内部时钟以恒定的速度测量电压水平,将低电压解释为二进制 0,将高电压解释为 1。另一方面,曼彻斯特编码通过从低电压到高电压或相反的转换来传输数据。在这种设计中,信号切换到高电平表示二进制 1,切换到低电平表示 0。11]

尽管这种编码不需要时钟保持同步,但它本身也并不足够:无法编码两个二进制 0 或 1,因为不可能在不返回到低电平一半路程的情况下两次从低电压到高电压(反之亦然)。为了允许这种类型的信息被编码,忽略在下降或上升信号边缘之后立即发生的转换,从而允许系统通过在半周期中返回到相同的电压来编码 0 和 1 的多个出现。为了管理转换后的“黑屏”期,需要一个简单的单次间隔时钟。

串行线路传输编码——NRZ 和双相(曼彻斯特)

图 5-1. 串行线路传输编码——NRZ 和双相(曼彻斯特)

基于上述讨论的自同步方案的串行线路设计通常被扩展以提供全双工通信,在这种通信中,双方可以同时交谈,要么通过使用两条独立的线路(发送和接收,简称 Tx 和 Rx)或通过使用高级回声检测和消除技巧来区分其自身的信号和从另一侧发送的数据。某些介质需要或允许更复杂的信号方案,例如在每个周期中发送不止一个比特;然而,通信的基本原理仍然是相同的,在整个领域内,使用最低可能数量的线——通常是两条——的曼彻斯特编码非常普遍。

在掌握了“线对”串行通信的基本知识后,让我们来看看网络世界中两个突出的串行通信示例,看看它们如何内部交换数据,以及如何查看这些信息在没有用户注意的情况下泄露给第三方。

从您的电子邮件到大声噪音……来回往返

毫无疑问,最受欢迎的长距离计算机通信设备是调制解调器。最初在 20 世纪 50 年代推出,用于在偏远地点维护和控制某些类型的军事设备,调制解调器将互联网带给了大众。尽管今天通常被认为有些过时,但调制解调器催生了许多先进技术,例如经济实惠的高速数字用户线路(DSL)系统或电缆调制解调器。这些设备都使用相同技术集的巧妙变体,通过电话线或其他非专用模拟媒介使用可听或不可听信号进行通信。对改进调制解调器所投入的研究也促进了我们对电子学中众多大规模设计问题的理解,特别是计算机和网络设计。因此,了解调制解调器的工作原理对于探索其他,可能更现代的长距离数据传输方法至关重要。

电话线的通用性使其成为计算机用于通信的自然媒介。电话线几乎无处不在,电话系统提供了出色的呼叫路由能力,使得几乎可以轻松地到达任何地点。然而,有一个小小的注意事项:电话线原本是为了传输人类声音而设计的,这些声音以波形形式在狭窄的频率响应范围内(通常不超过几 kHz)传输。因为这些频率被记录为两根电线上的电压变化,并通过多个模拟中继器和放大器进行传输,所以传输的标准质量并不特别高。它只需要足够好,以便人们能够听到并理解对方,而且由于人脑是一个出色的信号滤波和处理系统,偶尔的噪音或音量波动并不是什么大问题——直到后来,当客户变得稍微挑剔一些。

与此相反,计算机通常被设计用来交换二进制信息,这些信息通过在精心设计的、较短的线路中使用相当精确的电压水平进行编码,这些线路具有良好的信号特性和低电容——这与长距离、保护不良且信号特性不足的电话线路正好相反。计算机还需要比人类通常的速度快得多,并且需要交流的信息量也大得多。因此,调制解调器设计师们(这里是一个巨大的夸张)面临着一项艰巨的挑战:他们必须确定一种方法来编码数据位,不仅能够通过线路高效地传输到远程系统(曼彻斯特编码使这一点变得稍微容易一些),而且还能够作为可听信号,在线路的另一端准确地区分,无论电压变化和其他传输伪影通常是完全不可预测的。他们必须采用鲁棒的错误纠正算法和可变传输速度来补偿线路质量差、偶尔的串扰、卡车压过地下电话线、鸟儿在电线杆上筑巢等情况。设计师们点头、挠头,也许在 40 年后,他们为我们带来了一个既经济又相当快速的计算机间通信方法。让我们简要地看看这一发展过程以及技术是如何在接下来的几十年中成熟——尽管本质上保持不变。

商业调制解调器开发和标准化的历史始于 20 世纪 60 年代,当时提出了两个标准,即 Bell 103/113 和 V.21。这两个标准都提供了一种在当时令人惊叹的 300 波特(每秒比特数)全双工连接,使用的技术称为频率移位键控(FSK)。FSK 是一个听起来很神秘的术语,实际上代表了一种相当简单的信号编码方案:它使用两种不同的音调来表示不同的值,一个频率表示“低”,另一个频率表示“高”。使用可听频率而不是其他类型信号的优势相当显著:这是唯一一种可以在电话系统中相当好地转发的信号——毕竟,这就是系统的设计目的。所有其他信号在最佳情况下可能被彻底破坏,在最坏的情况下,可能被线路某处的带通滤波器立即过滤掉。

除了 FSK 编码外,上述贝尔 103/113 和 V.21 标准将电话线可以传输的频率范围分成两部分:一端调制解调器,即呼叫者,使用 980 Hz 来编码低频,使用 1,180 Hz 来编码高频。另一端,即应答者,使用频谱的高部分:分别为 1,650 Hz 和 1,850 Hz。为什么以这种方式分割频率?因为电话线本质上只是一对电线,可以被两个设备同时用来传输(全双工),但前提是它们能够处理它们各自的传输会相互叠加的事实。在全双工通信中,每个设备必须能够区分自己的信号和它接收到的数据,并将其过滤掉。如果不能成功做到这一点,每个设备在另一端说话时都必须暂停(单工模式),这会严重损害已经不太令人印象深刻的吞吐量。通过分割频率,电话线本质上被用来传输它视为两个不同的“声音”,从而确保可以无冲突地同时进行通信。

调制解调器在正确的方向上又迈出了 25 年的步伐。下一个主要的标准集,贝尔 212A 和 V.22,迈出了巨大的步伐,并放弃了频率移位键控,转而采用差分相移键控(DPSK)。DPSK 不是改变波的频率,而是通过改变其相位来表示不同的值。

相移技术本质上引入了最小的时间移位或延迟,导致输出音频信号略微与原始参考波不同步,同时保持完全相同的形状(参见图 5-2)。

频率移位与相移对比

图 5-2. 频率移位与相移

相移的值,也称为移位值,用度数表示(这是对其对三角函数影响的一个参考:y = sin(x)通过 90°移位与 y = sin(90° + x)完全相同。360°的移位值表示整个波长的移位,这简单地将波重新同步,对波形没有影响。各种相移的对应关系如图图 5-3 所示,在左侧。

一旦双方同步并且有方法将电缆接收到的信号与预期的波形进行比较,实际编码的数据就可以轻松检索。差分电路可以比较两个信号,将它们相减,并通过将其与参考信号进行比较,轻松确定信号的精确相位偏移,如图图 5-3 所示,在右侧。

新标准还利用了一种更先进的数据编码方法。与之前简单地使用两个交替信号来传输 0 和 1 的情况不同,V.22 编码整个二进制——即比特对的俚语。一次编码两个比特可以通过使用四个相位偏移值来实现,所使用的偏移量用来表示每个可能的值,以便值在整个 360°频谱中均匀分布,并且可能相隔最远——因此可以很容易地区分彼此(参见表 5-1”))。

相位偏移信号(左)和减去参考波形以更容易区分相位的結果(右)

图 5-3. 相位偏移信号(左)和减去参考波形以更容易区分相位的結果(右)

使用二进制允许显著提高传输速度(1,200 波特),而无需增加实际信号调制的物理速率。每个单个蜂鸣声可以携带两倍多的信息——两倍的比特数。

表 5-1. 使用相位偏移来编码两个比特的数据(二进制)

Dibit 相位偏移
00 90°
01
10 180°
11 270°

备注

虽然在理论上使用这种扩展字母表——即类似于二进制(具有超过两种状态,因此可以一次编码多个比特)的复合信号单元——与 FSK 编码也是可能的,但这样做会有些问题。FSK 信号必须避免通过电话系统发送时特别容易失真的次谐波和其他频率,从而严重限制了可能的状态集。DPSK 相对于 FSK 的优势在于它使用一个已知会导致最少传输问题的固定频率,因此可以在更高的传输速率下更可靠地使用。

在接下来的几年里,研究步伐加快了一些,出现了一些新的标准。V.22bis 标准将宽字母表信号的概念进一步发展,将 DPSK 与信号幅度(响度)调制相结合,构建了一个包含 16 个可能值的二维集合。从测量信号到二进制值的转换使用二维表格表示。信号对应的值首先根据测量的相位偏移值查找列,然后根据幅度测量查找行。一个简化的但类似的二乘四示例显示在表 5-2 中。

表 5-2. 使用两个不同的信号参数对三个比特进行二维编码

相位 0° 相位 90° 相位 180° 相位 270°
低幅度 000 (0) 001 (1) 010 (2) 011 (3)
高幅度 100 (4) 101 (5) 110 (6) 111 (7)

为了增加混乱,这种新方法被称为正交幅度调制(QAM)。QAM 再次使得从 1,200 bps 提高到 2,400 bps 成为可能,而实际上并没有提高信号调制速度,而是通过扩展单个信号原子可以具有的意义的数量。

下一个主要的发展步骤是 V.32。V.32 是第一个引入新概念的设计:它不是分割频率,而是使用先进的回波消除电路^([12])来检测和减去设备本身通过线路传输的信号。这项技术使得发送方和接收方都可以使用整个频谱,而不仅仅是其中的一半,同时仍然进行全双工通信。

发展持续进行,V.34 协议很快出现。尽管在多年间,信号在引入过度失真之前可以安全交替的速度没有明显变化,但该标准比其前辈要快得多。V.34 实现了 28,800 波特的吞吐量,有时通过制造商仅发送大约 2,500 到 3,500 个信号样本(字母表符号)每秒,将其推到了一个非官方的速度 33,600 波特(33.6 Kbps);然而,它结合了四种不同的编码方案,构建了一个具有 1,664 个可能状态的四维结构,使得一次可以发送多达 41 比特。实际上,这并不是关于原始速度,而是关于如何使用你所拥有的。

人们普遍认为,V.34 标准及其衍生标准在通过以语音为导向的电话系统传输数据方面接近理论极限。尽管考虑到 56 Kbps 调制解调器的普遍存在,这个说法可能显得有些奇怪,但有一个转折:56 Kbps 设备以与模拟解决方案完全不同的方式实现这种传输速率。鉴于大多数电话系统自从调制解调器最初开发以来已经从模拟迁移到数字,并且大多数拨号服务提供商现在可以直接将他们的系统与数字电信系统接口,服务提供商可以回归最明显但直到最近仍不可能的解决方案:在向用户发送数据时改变线路电压而不是改变频率。因为信号从一开始就是以数字数据的形式传输的——并且只能通过埋设的铜线传输到最近的电信设施——实际上没有信号质量问题,唯一的限制是电话系统硬件中设计的语音承载能力。以每秒 8,000 个符号的速度工作,但使用相当小的字母表(通常约为 128 个符号或电压等级),可以使用 56 Kbps 调制解调器以比通常更高的速度向连接到数字电话系统的用户发送数据。尽管上行传输仍然以传统方式实现,速度相当慢;因此,调制解调器只部分达到 56 Kbps,并且只有在条件允许的情况下。

今天的日子

自从调制解调器技术的构想以来,变化不大。随着传输协议的进步,所需的错误纠正和回退机制也相应发展,以确保当您最喜欢的四足动物决定咬电话线时,仍能保证可靠的传输。产生了许多标准:V.42 提供了一个基本的 CRC(循环冗余检查)实现,MNP-1 到 MNP-4 提供了专有的错误纠正算法,V.42bis 和 MNP-5 提供了完整性检查与压缩相结合,等等。但真正的革命尚未到来。

或者是吗?你可能会争辩说,DSL 和电缆调制解调器是一项革命性的技术,它改变了世界。我愿意争辩:事实上,它们与它们的较老版本——调制解调器——非常相似。这两者之间唯一的显著区别是,另一个端点——处理所有连接的服务器——已经从服务提供商所在的城市移动到了最近的本地电信设施,并且可以通过来自客户住宅或商业建筑的铜线直接连接到它。因为这种直接连接再次没有通过任何其他设备,这些设备可以使用高、不可听见的频率和更微妙的信号,否则这些信号会在电话网络中被扭曲或根本无法中继。相比之下,那个好老的调制解调器严格限于电话系统旨在携带并且能够良好携带的窄带范围内的可听频率和信号。在许多方面,DSL 设备比老式调制解调器更容易。

正如我们所见,设计调制解调器实际上是一项相当复杂和困难的任务;这就是为什么我们花了数十年时间从笨重且昂贵的 300 波特设备发展到现在的水平.^([60]) 令人惊讶的是,所有这些设备都可以相互通信,甚至可以与十年前的设备通信,即使是在我们早已遗忘的最低速度下。同样,它们通常都了解迄今为止的规范,包括每个规范的数十个替代方案和分支。这难道不是使调制解调器成为计算机工程奇迹的另一个原因吗?

但是谁在拉这些线呢?

有时,调制解调器仅仅是一个调制解调器

调制解调器之间的通信当然不是故事开始或结束的地方。调制解调器只是一个小巧而几乎无用的中间件,甚至不是一个好的镇纸。为了使调制解调器有用,它必须能够与计算机通信以接收命令和交换数据,即使它只是被用于像随机网页浏览这样微不足道的事情。内置调制解调器比较容易:ISA(集成系统架构)、PCI(外围组件互连)、PCMCIA(PC 内存卡国际协会)以及一些其他专用总线提供了高速且相当慷慨的并行接口,使得通信过程几乎变得微不足道。

然而,外置调制解调器(无论是模拟的还是 DSL 类型的)必须通过串行链路来处理事情。大多数模拟调制解调器使用众所周知的串行协议 RS-232(在 90 年代更名为更具描述性的 EIA/TIA-232-E^([61]));许多较新的调制解调器使用 USB(通用串行总线)。当我们接近这些设备的信息披露场景时,我们还想一瞥调制解调器和计算机之间数据传输过程中发生的事情,因为这在攻击中起着至关重要的作用。

尽管外部调制解调器必须使用不人道的手段与远程系统以及本地机器本身进行通信,但由于计算机的邻近性和接口如 RS-232 是数字的,并且最初是为计算机设计的,这一阶段仍然比著名的电话线路调制解调器的调制解调和解调要简单得多。

RS-232 使用了一种相当直接的二进制编码实现,用于在两条独立的线上交换数据,并辅以一组 NRZ 控制线。为了让生活更有趣,RS-232 还提供了一系列的链路或协议功能,这使得从头开始实现它变得相当困难:它的异步特性、广泛的可能设置和速度,以及不寻常的电压水平。但即便如此,对于处理过电话线路信号调制的实现者来说,RS-232 仍然算不上真正的挑战。

另一方面,USB 试图标准化和统一串行接口。尽管 USB 需要比 RS-232 更高端的电路来连接计算机和设备(其中之一是因为更高的抽象层次和更高的支持传输速度),但 USB 是通用的(因此得名),并且具有更少的怪异性和遗留功能。

最后但同样重要的是,与本地设备通信的常用方法是使用以太网,这是一种与 USB 类似但更早出现的机制。让我们现在来看看以太网,我相信所有这些通信协议最终都会在同一个地方相遇。

控制碰撞

从本质上讲,以太网网络是一种多党串行链路的高级类型。62 一个以太网网络由多个通过共享介质连接的计算机组成——在最基本的形式中,这只是一对相当规则的电线。当网络上的设备使用介质时,它会将特定的电压应用到电线上,所有其他连接的系统都可以通过测量电压来解释数据。一系列的检查确保设备不会同时尝试使用链路,如果发生意外,恢复过程将很顺利。尽管如此,即使考虑到这种可能性,与调制解调器相比,基本设计仍然令人难以置信地简单。

为了解决双方同时说话的问题,使用了一个名为载波侦听多路访问与碰撞检测(CSMA/CD)的标准作为控制所有通过以太网通信的核心机制。在发送任何数据之前,连接到以太网的每个设备都遵循 CSMA 程序,通过检查调制解调器的电气特性来查看是否有其他设备正在使用电缆。如果没有其他传输发生,设备进入传输阶段,并将数据发送给所有设备。

在这个阶段,数据以比特序列的形式通过双极性编码在电线上发送;流量包含一个带有所有必要发送者和接收者信息以及一个适当的校验和的报头,旨在保护数据在遭受外部或内部干扰时的完整性,无论是四足动物还是其他。一个将自己视为代表接收方行事的网络接口,可能通过比较数据包中提供的观察到的目标地址与其存储在卡上的唯一 MAC(硬件)地址进行比较,应该接受这种流量并验证校验和。同时,所有其他方都应该忽略这个帧;自然地,如果他们不这样做(几乎每张卡都可以被指示不要这样做),用户可以查看或对指向其他人的流量做出反应。(你可以看到以太网是如何在设计精神上体现了远不可及的信任和利他主义——一种高尚但风险较高的方法。)

在以太网网络上,两个设备可能在同一时刻开始发送数据,尽管它们刚刚检查了微秒或纳秒前是否有其他方正在传输。如果它们确实在完全相同的时刻传输,灾难几乎是不可避免的。两个传输被混合并损坏,发送的数据在目的地应该无法通过校验和测试……或者不是吗?

尽管在以太网帧规范中实现的校验和通常足以验证数据传输的准确性,但如果链路饱和且在短时间内发生数百或数千次冲突,它可能并不特别有效;它只是足够小,有时会意外地正确。概率法则告诉我们,一些损坏的包——仅仅通过偶然——会有与原始包相同的校验和。此外,即使我们忽略校验和不足的问题,我们仍然希望尽快停止冲突——如果让冲突肆虐,你可能会发现你不再能够确保网络中损坏和丢失的帧的及时重传。毕竟,发送者发送时没有表明任何问题,而接收者甚至没有收到任何类似有用数据包的东西。

解决方案包含标准的后半部分:碰撞检测(CD)。规范要求发送者在向他人解释他们的业务时监控网络链路。如果检测到另一方试图同时说话,那么应该检测到(再次,通过简单地测量线路的电气特性),并且应该立即中止传输。设备还应发送一个特殊的拥塞代码,以确保两个帧(正在发送的帧和干扰它的帧)将被无条件丢弃,甚至没有到达校验和验证;接收方应该能够识别拥塞代码并停止处理中的数据接收。设备在每次尝试(称为重传退避)之后都会空闲一段逐渐增加且最好是(最初)随机的时期,以最大限度地减少后续碰撞的可能性。

注意

一个有趣的事实:拥塞代码机制对协议提出了一个不寻常的要求。所有帧都必须有最小(!)长度,其值计算得使得在传输完成之前,拥塞代码能够生成并传播到所有机器。对于非常短的帧,可能没有足够的时间实现这一点。因此,发送者需要人工填充所有他们的输出传输。

图 5-4 显示了典型碰撞场景中事件的精确顺序。正如你所见,发送者 A 希望向接收者发送数据,但注意到另一个传输正在进行,此时他们决定等待那个传输停止。发送者 A 随后准备发送数据,但不幸的是,发送者 B 也做了同样的事情,并且双方都得出结论,几乎同时发送数据是安全的。

两次尝试都试图传输,数据被破坏,此时双方都检测到对方的传输,并迅速发送一个拥塞代码来指示接收方忽略这个帧。最后,双方发送者都会退避一段时间,并希望在下一次不会同时开始。

背后故事:布线汤和我们是如何应对的

尽管不是特别可扩展或优雅设计的例子,但以太网协议非常强大且易于部署;它使得使用同轴电缆在几乎任何地方构建廉价的对等结构网络成为可能。因此,它已经成为一个事实上的标准,取代了许多其他(有时甚至更优越,但更昂贵或专有)的网络架构。

典型以太网对话的阶段

图 5-4. 典型以太网对话的阶段

自然,简单的同轴电缆以太网有其局限性和缺点;它基本上是基于一根长长的电线,各种设备连接在它的各个位置,两端都有电阻,这不是你希望在大型办公室中负责维护的东西。一个简单且难以调试的事故,比如短路终端,可能会使整个基础设施崩溃。一个更先进——但仅略贵一点的——替代品受到了热烈的欢迎。

电子多端口中继器(集线器)使得使用双绞线(Cat-3 和 Cat-5 电缆带 RJ-45 连接器)进行布线变得几乎不需要费力。要使用它们,你只需将一根线从你的机器插入一个黑色盒子,然后所有连接到这个黑色盒子的其他设备都可以与之通信,而不必过多考虑电气问题或单根电缆故障导致整个网络崩溃的风险。

集线器本质上是非常简单的中继器,它们将接收到的所有流量广播到所有其他端口。它们使得构建易于重新配置和更可靠的星型网络成为可能,但除此之外它们几乎不做其他事情。随着网络的扩大,将每比特信息广播到所有位置的成本,以及在整个网络中一次只有一个参与者可以发言的事实,都使得这种设计的简单性成为其主要弱点。

交换机最终成为了解决方案。交换机是集线器的下一代产品。配备了一个不错的处理器和一些内存,它们是比集线器更昂贵的替代品,在正常情况下,它们提供了对以太网帧的额外高级分析。这种分析将硬件地址与特定端口关联起来,并通过将某些数据包直接发送到适当的端口(在单播模式下),而不是广播给所有参与者(见图 5-5)来优化帧路由。这在更大型的网络中大大提高了性能。

注意

另一个有趣的事实:现在的真实集线器几乎已经灭绝。几乎所有标榜为集线器的 10/100 Mb 设备实际上都使用基本的交换芯片组;重新包装芯片比开发和维护几个变体要便宜得多。

本地网络中集线器与交换机的对比

图 5-5. 本地网络中集线器与交换机的对比

我猜这时你可能在想,你到底要带我去哪里?调制解调器与信息披露有什么关系?在这个背景下串行链路有什么意义?以太网网络如何适应?闪烁灯光究竟是什么?

很高兴你问了。我正要说到那里——也就是最后一个问题。

通信中的闪烁灯光

从历史上看,几乎所有的冰箱大小的计算机都配备了众多显眼的诊断接口。这些接口包括一系列小灯,它们显示的内容包括机器内部状态的一些神秘属性,例如内部寄存器或核心处理单元的标志,或者显示今天是否给住在下面的猫喂食了。随着计算机变得更加可靠和紧凑,普通用户不再需要理解机器的内部结构才能高效地使用它,这些灯开始从许多设备中消失。不断增长的时钟速度也导致了这种下降——大多数时候,人类已经无法从这种每秒变化数千或数百万次的视觉信号中获得任何有意义的信息。

然而,在某些应用中,这些灯仍然存在;例如,几乎所有的网络设备在其前面或后面面板上都有发光二极管(LED)。这些提供了链路诊断,例如指示特定模块或插槽是否正常工作,是否有连接,数据是否正在传输等等。这些灯不仅仅是诊断工具;它们的催眠模式具有奇特的吸引力,它们的神秘感在进入服务器房间领域的普通人心目中播下了不确定、恐惧和尊重的种子。

术语 blinkenlightsblinkenlichten 自从计算机的黑暗时代以来就被用来描述计算机设备上备受喜爱的诊断 LED 机构,那些在终端孤独长夜中度过,被舒缓的绿色灯光沐浴的计算机极客。它源自一份有趣的恶作剧便条,这是一份模仿德语的便条(本身是对二战时期另一个非计算机笑话的恶搞),大约在 20 世纪 50 年代在 IBM 实验室展出。这份便条后来传播到世界各地的多数服务器房间和计算机科学实验室,内容如下(摘自 Eric S. Raymond 的《黑客字典》):

无标题图片

通信设备是最后一个仍然盛行和繁荣的闪烁灯领域。但不仅如此。几乎所有这些设备都使用串行线路进行通信。为了简单和美观,有时“活动”LED 几乎直接通过一个简单的驱动电路连接到设备的发射或接收线路。帷幕落下。


^([11]) 或者反过来,这取决于发射器的设计。

^([12]) 消音电路试图区分由设备本身发送的信号和来自另一方的信号,并消除或显著减少前者。这类设备的各种类型不仅广泛应用于数字数据传输,而且用于提高电话通话质量,消除公共活动中的麦克风反馈,以及解决许多其他日常问题。

美学的影响

这个问题被发现花费了数十年时间,一旦发生(在 2002 年),它对我们所有人来说都如此明显和简单,以至于我们想要敲打键盘几次。

Joe Lughry 和 David A. Umphress 在一份题为“光辐射信息泄露”的研究论文中^([63])发现了一种新的信号泄露场景,在特定类型的网络设备中,最常见的是调制解调器。他们得出结论,观察这些灯光的人可以超越仅仅用肉眼观看这些神奇灯光。

与白炽灯泡不同,LED 灯通常具有很短的上升和下降时间,这意味着它们几乎可以瞬间开启和关闭。这并不令人惊讶;毕竟,高端 LED 被用于控制光纤链路和一些其他光电通信通道。因此,连接到串行数据传输线的 LED 闪烁实际上可以经常反映传输线上发生的单个比特。如果有一种方法可以以足够快的速度记录这种活动,那么应该可以从至少你用肉眼(或用长焦镜头)可以看到的设备上的微小闪烁灯光中检索到这些信息。

这项研究在业界引起了一些轰动;它最终既被轻描淡写,又被过度炒作,因此随之而来的是大量的混淆,几乎没有发生变化。这篇论文导致了许多相互矛盾的报道,但它的基本前提简单而真正美丽。这种技术的美妙之处在于,设计这样的接收设备非常简单:与 LED 一样便宜且受欢迎的替代品——光电二极管和光电晶体管——很容易获得,并且同样容易与计算机接口。而且,曝光区域,与我们在第三章中讨论的大多数 TEMPEST 活动不同,不仅仅是城市传说和纯实验室结果的主体,而且可以直接观察和测量。

在他们的研究过程中,作者进行了一系列实验来验证信号可以从 20 米(不到 100 英尺)远的地方成功获取,而不需要额外的数字信号处理。常识表明,这实际上可能是一个低估,尤其是在使用良好的光学设备时。(作者在测试中使用了 100 mm 焦距、f/2.0 的镜头,但许多中端单反相机摄影爱好者都可以获得更好的远摄镜头。那些愿意花钱的人可以购买焦距高达 1,200 mm 的高质量镜头。)

在某些情况下,论文采取了防御性的立场,一个仔细的读者可能会倾向于得出结论,一些被分类的设备并不容易受到该问题的影响。特别是,一些以太网设备可能表现出更微妙的漏洞变体,正如你将在本章后面的预防部分中看到的。但首先,让我们用我们自己的(计算机化)眼睛来看看这个问题,好吗?

构建自己的间谍装备……

构建窃听设备的简单性使其颇具吸引力。本节包含了一些关于如何构建和连接此类设备到普通计算机的建议和粗略的电路图。尽管电路并不特别复杂,也不需要焊接和印刷电路板设计软件的硕士学位,但至少需要具备一定的电子学知识和一些常识。尽管今天计算机的外部接口相当坚固且不易出错,但在疯狂的创新方式下连接自制设备时,总是存在损坏设备的风险。这种情况甚至可能发生在我们最好的情况下。

基线设计极其简单。它需要一个单光敏晶体管(由内置光电二极管驱动的晶体管组件),一个普通的低功耗 NPN(负-正-负)晶体管来进一步放大信号(不一定总是必要的),以及一套电位器(可能为 10 kΩ,以便有足够的灵活性)来实验性地降低电压并控制电路的灵敏度和阈值点。对于组件没有特别的要求,尽管你的使用效果会根据你使用的组件而有所不同。不过,务必选择一个在可见光范围内有良好响应的光敏晶体管,尽管所有便宜的光敏晶体管都应该可以工作。(作为参考,绿色 LED 发出的波长约为 520 nm。)

一个示例电路设计如图 图 5-6 所示。

简单接收电路

图 5-6. 简单接收电路

该电路的最佳运行电压约为 5V,最大电流较低:一个能够提供大约 10 到 50 mA 的电源就足够了。警告:如果您使用能够提供更高电压的电源,您将冒着损坏端口或计算机的风险;同样,如果您使用更强大的电源并且没有防止电路中流动更高的电流,也会造成风险。

注意

将 Rvar1 或 Rvar2 设置到非常低的电阻可能会短路电路。如果您想盲目地摆弄旋钮,可能最好添加一个固定电阻以限制电流消耗。

您必须屏蔽光电晶体管免受外部光源的影响——例如,通过将其封装在一个不透明的管子中。因为光电晶体管没有聚焦机制,它不太可能捕捉到更远的信号(除了环境光)。因此,对于初始测试,完全覆盖它以模拟黑暗,然后放在 LED 旁边以激发电路是一个好主意。您还可以在 GND 和输出线之间临时连接另一个 LED 来测试电路。当传感器指向光源时,测试 LED 应该点亮,否则应该相当暗。

...以及使用计算机

如果带有测试 LED 连接的电路到目前为止工作正常,那么做得好;你已经构建了一个花哨的电视遥控器测试器。因为通用的、便宜的光电晶体管渴望捕捉红外光,你的创造应该能够“翻译”红外光(IR)为可见光,但这几乎就是它能做的所有有趣的事情。为了使其更有用,你需要将电路与计算机连接起来。如果您的计算机有一个行打印机接口(LPT),这是一个很好的方法。不幸的是,这个出色的硬件黑客工具正在被一些更紧凑和花哨的设计所摒弃。

虽然最初设计为单向(仅用于输出),但 LPT 接口提供了一些状态反馈线,如“纸尽”、“忙碌”和“确认”,旨在为打印机提供抱怨问题的手段。您可以通过访问 PC 兼容系统上的端口 0x379(LPT1 状态寄存器)轻松读取通过此接口发出的数据。通过将电路连接到并行端口,您可以轻松地将信息传回计算机。虽然您可能希望将电路连接到不同的接口,但 LPT 比 RS-232 等其他接口要快得多,而且您不必处理任何平凡的协议、信号方案或异常的电压水平。此外,与 USB 和一些其他当前解决方案不同,您不需要特殊的控制器来实现一个相当复杂的协议,甚至能够与您的 PC 通信。

注意

虽然 LPT 也提供双向操作模式(ECP 或 EPP),但对于如此简单的任务尝试使用此功能通常是没有意义的。在单向模式下,有四个输入位可用,对于此应用来说已经足够;切换到双向模式如 EPP 或 ESP 可以提供额外的四个位。

选择要使用的状态行由您决定。表 5-3 显示了用于打印机端口的 DB25 连接器的引脚布局。灰色阴影的行可用于输入。

要将电路与该端口接口,您只需将连接器上的地参考点与电路中使用的地连接,然后将输出线连接到任意五个引脚之一。(请记住先断开用于诊断的 LED。)接下来,在首次将传感器暴露于光线下并覆盖传感器时,监控状态端口。在两种情况下,读取的值取决于您如何连接电路;确切的值并不重要,只要两个值不同即可。

表 5-3. LPT 引脚布局

LPT 端口:DB25 引脚布局(标准模式)
引脚
---
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

由于芯片逻辑需要与您的测试 LED 二极管不同的输入电平,您可能需要调整Rvar2,直到在覆盖传感器和将传感器暴露于光线下时从端口获得不同的读取值。为此,最好能够在计算机上实时监控端口。

监控端口状态的方法将取决于您使用的操作系统和编程语言。如果您使用 C 语言,用于从端口读取值的函数是inb(port),因此在这种情况下,您将发出inb(0x379)并检查返回值。在其他语言中,它可能具有类似的名字。(尝试查找ininportreadport等。)此外,Windows 用户可能会发现内置的“调试”实用程序及其“i”(端口读取)功能非常方便。

备注

在某些系统上,例如 Linux,您可能需要请求系统首先给予您访问特定端口的权限。有关iopl(3)或类似调用的更多信息,请参阅文档。

到目前为止,您已经准备好出发了。您可以选择将探头指向设备上的任何 LED,根据其亮度调整传感器,并开始读取光和暗信号的交替模式,同时发现它们如何与交换的信息相对应,如果有的话。

注意

如果您好奇,您可能会尝试检查指示二极管的亮度,而不仅仅是其状态的二进制表示。结果可能表明,即使特定的 LED 不是为了将串行线上的信号直接映射到其闪烁模式,电路之间仍然存在一些模拟串扰,串行线信号将对亮度产生影响。德州仪器的 TLV571 这样的廉价模拟-数字转换器正是用来这样使用的。

您可以使用这种方法来采样每秒低于 100 万比特的频率,这对于捕获许多接口上的传输应该是足够的,但并不一定适用于以太网端口(以太网端口传输的比特率至少为 1000 万比特每秒)。超过这个捕获能力,您的并行端口(LPT)很可能会达到其物理吞吐量限制,但不要绝望:只要传感器(光敏晶体管)能够以足够快的速度切换以捕获相关通信,您仍然有选择。记住,并行端口(LPT)是一个并行端口。为了达到更快的捕获速度,例如以太网所需的,可以将一个简单的时钟、一个计数电路和一组采样保持锁存器(如 74LS377)结合起来,在计算机端口读取尝试之间顺序存储数据。您可以在短时间内积累这些信息,然后,通过使用不止一个状态引脚(或将端口切换到双向模式),在单个读取周期内一次性发送多个比特——样本——到计算机,从而提高读取速率四倍或八倍。

我将省略进一步的、可能是不必要的对电子世界的探索。如果您想尝试高速或模拟采样,或者只是从组装东西并将其连接到计算机中获得乐趣,您可能想看看我在一个计算机控制机器人设计项目薄薄伪装下的相当全面的入门教程。您应该能在lcamtuf.coredump.cx/robot.txt找到它。

现在,对于那些对实用安全更感兴趣的人:简要讨论如何解决这个问题,而不仅仅是用胶带覆盖办公室里的所有 LED。

防止闪烁灯数据泄露——以及为什么它会失败

解决这个问题的最简单方法,也是原始研究中提出的方法,是脉冲扩展——一种通过延长一些闪烁来扭曲指示器闪烁的实践,从而使任何实际的数据恢复看似不可行。脉冲扩展电路是一组相当简单的设备,它们将遇到的“高”输入信号的持续时间额外延长。大多数基本的脉冲扩展器设计依赖于一个在输入信号存在时充电并在之后缓慢放电的电容器。这个电容器连接到一个二进制判别器,它不是一个邪恶摔跤冠军的昵称,而是一种通过应用特定的阈值将模拟数据转换为二进制输出的设备(对于所有高于n的输入电压输出逻辑 1 的电压,对于所有低于n的输入电压输出 0)。在这种情况下,它使用一定的电容器充电水平作为判别点。

更高级和可靠的设计,包括纯数字电路,也很常见,并且所有这些都可以用于集线器和交换机,使 LED 看起来更美观。没有它们,超过每秒 50 次(被认为是我们的闪烁感知极限)的高速闪烁通常会导致我们看到的灯光看起来较暗但似乎恒定。通过延长每个 1 脉冲的持续时间,判别器使得 LED 被 1 驱动比被 0 驱动的频率更高。这使得 LED 灯光更亮,闪烁频率更低。图 5-7 展示了这种脉冲扩展器的行为:单个尖峰(单个 1)被拉伸到原来的三倍长,而所有的 0 都保持不变。

脉冲扩展器行为,3 倍

图 5-7. 脉冲扩展器行为,3 倍

虽然它们的主要目的是美观,正如我提到的,这似乎也是解决光发射信息泄露问题的好方法,通过让攻击者只能推断出交通的某些一般属性。因此,在最理想的情况下,攻击者只能推断出交通的一般属性,例如何时有东西被发送,何时没有发送。^([13])

然而,看似良好的解决方案并不总是如此。考虑以下样本数据和相应的串行线路信号:

无标题图片

假设信号是使用 5 倍脉冲扩展器处理的,使得每个 1 持续五次额外的周期。(原始论文建议一个安全的极限是 2 倍,但我们将夸张一下以说明问题。)

无标题图片

尽管与我们要拦截的输入信号相比,可能看起来几乎所有的信息都已经丢失,但通过做出四个重要的观察,我们仍然可以恢复其中大部分信息:

  • 显然,所有拉伸器输出为零的区域在原始信号中必须为零。

  • 每个扩展的 1 序列必须是由原始流中的起始位置触发的一个 1。

  • 每个 L 1 序列最初必须包含至少一个 1,每N个周期,其中N是此电路的拉伸因子;否则,运行中会有间隙。在单个 1 扩展输出下表示的数据块中的 1 的计数大于或等于 L/N向上取整。

  • 每个运行在原始流中恰好有N−1 个零后结束。我们知道这些零必须由 1 precede;否则,运行将提前结束。

通过将此知识应用于之前的例子,我们可以重建大部分原始数据,如下所示:

无标题图片

在之前的相当现实的例子中,由于脉冲扩展,只有不到 9 个 32 位数据中的数据丢失,无法最终重建(在图形中以问号标记)。因此,我们恢复了 99.999988%的潜在搜索空间。我们必须猜测剩余的数据,而这些数据(尤其是如果被窃听的数据是常规英语文本,如电子邮件)与起点相比,重建起来相当简单。该研究的作者建议,即使 N = 1.5 或 N = 2 的“开启”时间脉冲扩展也足以混淆数据,但这并不一定如此。

之前的重建方案适用于 0s 或 1s 的扩展。一些链路使用归零(RZ)编码(如之前提到的曼彻斯特方案),由于信号不断交替,2 倍扩展确实可能足以混淆所有数据。然而,这只在 LED 由在初始内部解码到 NRZ 之前的信号驱动时才成立——在大多数情况下并非如此。实际上,将脉冲扩展应用于 RZ 编码信号通常是一个愚蠢的想法,因为 LED 将一直处于开启状态;因此,最初做这件事似乎没有意义。

如前所述,另一个问题源于脉冲扩展器的质量和其对其他内部电路干扰的敏感性:在“拉伸”期间导致 LED 亮度略有变化的光电压波动可能会泄露一些信息。特别是基于电容的解决方案可能会落入这一类别。

因此,一些系统,特别是已知部署脉冲扩展的以太网设备,可能部分容易受到攻击,尽管之前讨论的原始论文得出结论,基于使用示波器观察记录的闪烁模式,传输数据与 LED 行为之间没有直接相关性。

最佳解决方案,尤其是在其他类型的编码中,或者当脉冲扩展不是出于某些其他原因(例如,如果设计者想避免在传输期间使 LED 灯持续发光)所希望的情况时,是在相当低的频率(例如,20 Hz)下采样线路,并将其锁定到一个寄存器中,该寄存器在下一个采样之前保持其状态,并控制 LED。

现在,让我们回到普通的英语。


^([13]) 从技术上来说,这仍然是一个攻击场所,根据第一章中的讨论,但它相对不那么有效和实用,因为我们只能得到一个大致的了解,而不是数据的副本。

思考的食物

除了网络设备的 LED 灯之外,还可以找到许多其他同样有趣的光辐射泄露场景,尽管泄露的信息量可能显著较低。例如,考虑磁盘活动 LED 灯。当然,磁盘通信并不是使用串行信号;相反,数据的一部分,从字节到 32 位字,是通过一组信号线同时发送的。而且,尽管 LED 灯通常只连接到指示特定控制线状态,但通过测量寻道时间或存储和读取的数据量,仍然可以推断出许多系统活动的方面。(根据 LED 灯实际连接的位置,可能可以测量其中一个或两个。)尽管这些信息可能不会给攻击者带来任何立即的优势,但某些诱导的 I/O 活动可以与硬盘驱动器 LED 观察相结合,得出有趣的结论,尽管我对这个领域的任何研究都不了解。

其他潜在的攻击场所涉及许多 USB 设备和其他专有接口。如前所述,USB 是一种串行总线,一些 USB 设备确实有活动指示器。

还提出了各种其他不寻常和神秘的信息泄露场所,部分进行了研究或至少进行了尝试。这包括通过分析 CPU 根据执行指令消耗不同级别的功率时充电电容的声学效应([64]),或者通过统计分析帮助分析其功耗来测量黑盒设备([65])。再次强调,除了经典的电磁场(EMF)辐射之外,还没有对泄露通道领域进行真正全面的研究——这似乎是一个很好的研究想法。祝你好运。😃

第六章。过去的回声

在以太网漏洞的例子中,我们了解到精确表达的好处

上一章讨论了以太网通信的基础。这个看似无懈可击且令人惊讶简单的机制似乎无法引起严重的安全问题,除了可能由于向网络上的所有方定期广播数据而导致的信任关系滥用。这是以太网网络众所周知且被充分理解的特性,其中一些有效的补救措施包括交换机、网桥和网络分段,仅举几例。

尽管如此,这个问题以完全无法预见的方式表现出来,这主要归因于以太网驱动程序官方实施要求中不幸的用词选择,或者缺乏用词。结果是普遍的实施问题,并且已经达到了一个规模,足以使它在这一章中占有一席之地。它为这一类无过错问题提供了一个有趣的案例研究。

建造巴别塔

以太网协议提供了在一段线缆上分配字节的基本手段:一种低级的数据编码方案,以及一种数据格式来包含部分信息。以太网帧包含了它所携带的数据的本地处理信息(即,谁在发送它以及谁应该是接收者)以及封装信息的简要描述。还提供了额外的错误检测方法,然后整个帧被推送到可能的接收者以及所有其他系统。从功能上讲,以太网类似于在不同介质或不同应用中使用的数据部分封装方案,例如帧中继、异步传输模式(ATM)、点对点协议(PPP)等等。

问题是,“这样一个以太网帧应该携带什么数据?”计算机使用数百种格式和应用协议,可以运行从科学模拟到网络游戏和聊天客户端的各种应用程序。因此,尽管可以在以太网帧中简单地封装远程接收者的数据,但通常这不是一个好主意,因为接收者不知道如何处理它。是传入的电子邮件?网页图片?还是可能是配置数据?你无法确定。此外,由于典型的计算机几乎同时运行各种程序,这种区别甚至更加模糊。

以太网在更大规模上又提出了另一个问题;具体来说,如何到达另一端。向本地网络上的所有方广播数据很容易;但如果另一个系统,即本地用户希望到达的一方,不是本地的呢?如果它必须通过广域网(WAN)并且使用完全不同的链路层协议来访问呢?即使找到了将流量路由到那个远程目的地的方法,一个更基本的问题仍然存在:如何寻址这个包。

以太网使用其独特的、专门的寻址方案。它通过制造商在每个以太网适配器上嵌入的理论上唯一的硬件卡识别号(媒体访问控制地址,或 MAC 地址)来称呼主机。这些数字对以太网来说是有意义的;对任何其他类型的网络来说都是没有意义的,如果你不在本地设置中,几乎不可能使用这些数字来追踪硬件。这引发了一个信任问题。例如,谁购买了地址为 00:0D:56:E3:FB:E4 的卡,他们现在在哪里?你能信任他们真的是原始购买者而不是冒名顶替者吗?

低级主机寻址方案,如这个方案,通常在将数据转发到目的地时没有帮助,除非具有特定 MAC 地址的硬件直接连接到发送者的物理网络。没有直接将物理设备标识符映射到地球上的特定位置并确定发送信息应使用的路径的方法。

OSI 模型

链路层协议被设计来支持本地节点之间的通信,或者在极端情况下,在共享链路上的两个固定端点之间进行通信。为了使互连网络成为可能,并使网络的某些更实际的应用成为可行,设计了一个称为开放系统互连(OSI)的网络协议分层结构。

OSI 模型(见图 6-1

图 6-1. OSI 模型中的物理数据布局示例

第三层被设计用来提供有关流量的一般分布情况以及使用网络特定寻址对数据源和最终目的地的通用识别,从而使得路由分组变得更容易。与第二层协议不同,第三层在传输过程中不会被丢弃或修改,并且不包含任何链路特定特征,如 MAC 地址、CSMA/CD(载波侦听多路访问与碰撞检测)开销等。

第四层提供了在给定机器上起始和终止的端点之间建立特定通信通道的手段。这为多种类型和通道的同步通信提供了一种方式。第四层的任何协议都不需要中间系统理解,以便正确地将流量传输到目的地。数据包仅由最终接收者解释,以确定哪个应用程序应该接收数据以及这一信息片段如何与相邻的数据包相关。

OSI 模型的后续层可能不太有趣,并且有融合在一起的趋势。第五层本应提供可靠性功能,这些功能通常包含在第四层协议中,如 TCP/IP(传输控制协议/互联网协议),或者在应用层。在某些情况下,如果没有必要实现可靠的通信,它们甚至根本不会实现。第六层提供“库”功能,如数据的解压缩和解码,并且与第五层一样,通常以应用级功能来感知。最后,第七层是应用层,数据以特定格式在此层传输。

注意,OSI 模型中的高层相对于底层是独立的,因为它们应用于承载的数据。当时机成熟时,底层可以逐渐被丢弃,而不会丢失数据或进一步处理数据的能力。第二层在每一个中间系统中都会被丢弃;第三层在数据被传输到其目的地系统后可以丢弃。第四层在将数据传输到客户端应用程序之前被丢弃。

第三层通常通过提供完整的发送者和接收者信息、完整性保护机制(校验和)以及承载有效载荷的大小信息,与底层链路层协议完全独立。这正是 IP 所做的事情。

这种设计的一个重要后果是,在传输过程中附加到第二层数据包上的任何多余信息都不会影响接收者解释 IP 信息的方式。

缺失的句子

在前一章关于以太网设计的讨论中,我提到了一个有趣的需求,即出于提供可靠的中断代码传播以实现冲突通知的目的:以太网帧的最小尺寸限制。

这个要求被带到了官方的 IP-over-Ethernet 封装规范中,例如 RFC 1042,“在 IEEE 802 网络中传输互联网协议数据报的标准”[66],它要求长度短于此最小长度的帧进行填充。填充可以随意进行,并且对 IP 层上携带的数据没有影响,因为 IP 头中指定的数据包长度不会改变。因此,填充不会被接收方解释为 OSI 模型高层流量的一个部分。

然而,存在一个轻微的问题。尽管 RFC 要求填充必须初始化为零,但它没有指定谁应该提供和准备填充,以及填充应该在哪个软件阶段发生。填充需要具有特定值的需求本质上是一种相当随意的需求;因此,对此没有给予关注——以任何其他方式设置都不会影响协议的工作,因为接收到的额外数据会被简单地丢弃。

更令人困惑的是,许多网络接口卡提供自动填充功能,如果操作系统发送给硬件的数据包太短——但自然地,不是为了确保填充的具体内容,如果帧大小已经在软件中得到了处理。这导致一些选择遵守大小要求的开发者中广泛存在混淆,他们简单地通过增加声明的长度来在软件中扩展数据包的大小。他们通常没有意识到,IP 数据包末尾和填充帧末尾之间的数据没有被驱动程序、操作系统或硬件准备(初始化为零)。

这个问题多年来几乎未被注意,尽管它引起了一些尴尬的问题,这些问题经常让一些网络黑客感到疯狂。他们从本地系统接收到的数据包通常在末尾包含一些额外的垃圾数据——例如网站内容的片段或显然无关的聊天对话。他们责怪接收方(设备故障、网络流量分析应用程序、库)但最终放弃寻找原因,因为这个问题的重要性微乎其微。这个问题从未得到应有的关注。

直到 2003 年,Ofir Arkin 和 Josh Anderson 在@Stake 决定更仔细地研究这个问题。他们的论文“EtherLeak——以太网帧填充信息泄露”[67]更详细地研究了这个问题。作者意识到,大量主流系统,如 Linux、NetBSD、Microsoft Windows 和其他平台,在修改长度后未能初始化新准备的以太网帧末尾的内存。一些实现甚至未能正确更改帧的大小或向硬件层发送适当数量的字节。

因此,IP 数据包被填充了系统之前用于其他目的的内存部分中存储的数据。内存可能包含之前发送的数据包的一部分或其他内核内存片段,这取决于驱动程序或操作系统的设计。这当然创造了一个迷人的信息泄露场景:攻击者向受害者发送不明显且合法的流量,并且如果运气好的话,可以获得可能敏感的信息。泄露的信息量通常足以证明担忧。

这种暴露仅限于单个以太网网络,因此在一个典型的局域网环境中,它相对局部化和非关键。尽管如此,这确实是一个具有一定重要性的问题,尽管任何本地网络都部分容易受到窃听,但这个问题提出了一些结论,这些结论超出了最明显的:

  • 在使用动态缓冲区来处理出站以太网帧的系统(例如 Linux)中,填充可能会暴露不仅前一个帧,还包括其他内存内容,例如编辑或查看的文档、URL、密码或其他敏感资源。在这种情况下,一个细心的观察者可能能够获取到他们原本无法在网络中截获的信息。

  • 在仅使用静态缓冲区来准备以太网帧的系统上,可以利用这个问题来击败那些保护防止流量嗅探的系统,如交换机,从而使得攻击者能够截取来自不同连接的数据。

  • 在某些静态缓冲区设计中,来自多端口机器上另一个段的信息,其中一个网络接口连接到通用局域网,另一个接口连接到受限网络,可能会被暴露,从而将可能被认为是秘密数据的部分转发到公共基础设施。

论文作者广泛审查了几个开源实现,并得出结论,各种方法和缓冲区布局被普遍使用,并且没有占主导地位的缓冲区分配和用法方案。他们的结论是?一个典型的多样化网络环境可能会在某个时刻受到所有三种类型问题的影響。

思考之食

这里讨论的问题不仅限于以太网或网络设计。这些问题几乎总是在一个本应详细的实现指南省略或仅模糊讨论单个必要步骤时出现,导致许多开发者在实现标准时简单地忽略了这个问题。如果他们得到了更模糊的整体指示,开发者可能被迫思考这个问题。相反,他们实施逐步指令,并且更容易犯错误。“万无一失”的指令,告诉如何执行某些任务,而不是要达到什么目标,往往适得其反。

我们将在本书的第三部分 Part III 中,以略有不同的背景重新回到协议泄露场景的问题。

第七章。在交换网络中保持安全

或者,为什么无论我们多么努力,以太网局域网都无法完全固定

以太网网络不提供一种通用的、简单的方法来确保它们传输数据的完整性和机密性,它们也没有设计成能够抵御恶意、故意注入的流量。以太网仅仅是一种将多个本地、假设为可信的系统进行接口的手段。

假设在设计阶段这种信任程度是方便的,并且在理论上对于同一网络上的对等系统和通常位于大致相同物理位置的系统来说是足够的。但是,正如俗话所说,只有在理论上,理论与实践之间没有区别。在实践中,是有区别的。

事实上,本地网络很难完全控制,并且必须保护它们免受自身用户以及外部威胁的影响。任何扩展的本地网络都注定会遇到一个恶意用户,无论是来自组织内部还是外部,利用系统中某个漏洞。这种攻击的发生只是时间问题,因为几乎所有的网络管理员在某一点上都会学到这一点。

实际的网络安全是检测事件、最小化暴露、评估和理解所有层面的风险的艺术,而不仅仅是建立外围防御的练习。问题是什么?裸骨的以太网基础设施容易受到各种数据拦截、劫持和仿冒场景的影响;一旦入侵者或恶意但合法的用户控制了网络上的单个系统——突破了一道防线——这个人就可以对基础设施造成破坏,并以最小的努力获取或接管某些资源和服务。

一些理论

以太网交换机,一类智能设备,旨在将第二层 OSI 上的单播流量路由到适当的端口,而不是像集线器或直接连接那样广播到所有节点,似乎可以解决这个问题。它们通常被认为可以解决与一个系统观察或劫持第三方流量相关的安全问题,但这并非如此。解决方案并不简单,这种假设造成的混淆有时比交换机最初能做的危害更大。但首先,为了理解暴露情况,让我们看看以太网交换机实际上是如何工作的。

地址解析和交换

本地网络内的所有通信都是基于在第五章中讨论的寻址方案。硬件制造商为特定端点设备分配的唯一标识符用于寻址系统和传输数据帧。然而,互联网和今天的大多数私有网络都是围绕一个更灵活和通用的协议套件构建的,并使用第三层 OSI 的寻址方案,通常称为互联网协议(IP)地址。IP 地址首先用于通过全球中间系统上的路由表层次结构将流量引导到适当的地方网络;直到数据包到达目标网络的边缘,才必须以传统方式定位最终接收者,即通过硬件地址查找。

当本地网络上的一个系统决定定位具有特定 IP 地址的另一个本地方时,它使用一个特殊的地址解析协议(ARP)来确定物理卡地址(本地网络系统寻址的基础)与 IP 地址(通用互联网系统标识符)之间的关联。68 发送者将 ARP 查询分发到本地网络上的一个特殊广播地址。这个保留地址保证会被网络上的所有系统接收和处理,无论分配给特定节点的实际硬件地址是什么。在这种情况下,认为自己有权使用查询中指定的 IP 地址的系统应向发送者发送响应,从而在回答查询时披露其硬件地址;其他人则应静默忽略广播 ARP 数据包。在此交换之后,双方现在都知道对方的 IP 地址和媒体访问控制(MAC)地址。他们应该在特殊缓冲区中缓存这个发现,以消除每次交换数据部分时进行额外查找的需要,然后继续实际通信——但除此之外,他们准备根据 IP 地址交换一些数据包。这种设计是一个古老而迷人的信任和礼貌的例子。但如何限制同一网络上的恶意旁观者造成的暴露,他假装成另一个人,以及如何防止好奇心强或邪恶的对手走得太远?以太网硬件制造商通过使大多数设备上的 MAC 地址更改变得可能且简单,肯定没有帮助网络管理员——可能允许用户重新编程,以防万一一批卡片出现重复地址时陷入麻烦。

再次,交换机似乎解决了这个问题。智能交换设备背后的基本设计概念依赖于在临时网络设备级别复制 MAC 地址缓存。交换设备配备了多个以太网端口,每个端口连接到单个系统(或较少情况下,是一组系统)。但交换机不是作为哑重复器,将一个端口接收到的所有流量发送到所有其他端口(就像以太网集线器那样),而是尝试记住连接到每个端口的机器的 MAC 地址,有效地创建 MAC 到端口的关联,而不是由端点系统创建的 MAC 到 IP 映射。

存储在内容可寻址内存^([14])(CAM)中的数据决定了如何交付传入的数据包。每当部分流量到达时,交换机会尝试确定接收者所在的端口。如果此信息可用,数据包将直接(并且仅)发送到该特定端口,将信息从其他人那里隔离开来,从而提高网络性能。

虚拟网络和流量管理

一些更高级的交换机解决方案提供了额外的功能,旨在使管理大型网络更加容易,并降低部署时间和成本。这些功能似乎也有助于网络安全,可能包括以下内容:

虚拟局域网 (VLAN)

这是一个通用的名称,用于描述一系列方法,用于将物理设备上的端口池划分为一组独立的逻辑网络,从而将一组端口的流量与其他流量分离,并防止任何类型的流量在交换机级别之间交叉。(此方案通常使用 IEEE 802.1Q 标准实现,将在下一项中详细讨论。)实现 VLAN 就像将单个交换机分割成多个完全独立的设备,只不过 VLAN 解决方案更加灵活且成本效益更高,因为它允许您随意重新设计网络和重新分配物理资源。VLAN 在网络工作人员中受到了热烈的欢迎,因为它们承诺提供一种简单而强大的方式,在单个设备上构建一组独立的网络,例如,将服务器与工作站分离,无需为每个组购买专用交换机。

聚合

基线 VLAN 设计的自然扩展。 trunk 使用 IEEE 802.1Q 帧标记方案在单个链路上隧道多个 VLAN 流量,而不是强迫用户为每个要填充到另一个设备的 VLAN 运行单独的线缆,如图图 7-1 所示。来自源交换机上的所有或某些 VLAN 的数据包被标记上足够的信息,以便在以太网帧头部确定它们的原始 VLAN,通过传统的链路隧道到另一个端点,解码后,然后推送到目标端适当的 VLAN。尽管这种选项通常会导致比为每个子网运行单独的线缆的性能更低,但它要实际得多。trunk 系统通常还具备 DTP(动态 trunking 协议),这是一种 trunk 自动配置协议,允许设备在没有特殊管理操作的情况下自动发现和交换封装帧,其他 trunk 启用设备。

VLAN trunking in action. VLANs propagated across two devices. Devices on all instances of both VLAN #1 and VLAN #2 can talk with each other, but cross talk between VLAN #1 and VLAN #2 is not possible.

图 7-1. VLAN trunking in action. VLANs propagated across two devices. Devices on all instances of both VLAN #1 and VLAN #2 can talk with each other, but cross talk between VLAN #1 and VLAN #2 is not possible.

生成树协议(STP

允许您构建冗余的网络结构,其中交换机在多个位置相互连接,以保持容错性。传统上,这种设计可能会导致广播流量和一些其他数据包永远循环,同时也会显著降低网络性能,因为在一个接口上接收到的数据被转发到另一个接口时实际上会弹回原始发送者(参见图 7-2, 左侧)。

设计网络时,往往难以避免意外广播环的出现。有时,设计具有潜在环路的架构(其中某个交换机连接到两个或更多交换机)也是可取的,因为这种设计具有更高的容错性,单个设备或单个链路被移除时,整个网络不会分裂成两个独立的岛屿。

为了能够在不引起严重性能问题的同时构建环路和其他非平凡架构,STP 实现了一个选举机制来选择一个“根”节点交换机。基于选举结果,从该节点向下构建一个树状流量分布层次结构,并暂时禁用可能导致广播流量反向传播的链路(参见图 7-2, 右)。当其中一个节点掉线时,您可以快速更改这个简单的自组织层次结构,并重新激活之前认为不必要的链路。

数据包风暴问题和 STP 选举方案;左侧显示了一个没有 STP 的容错网络,其中一些数据包注定会在交换机之间(几乎)永远循环;右侧是相同的网络,其中一个设备被自动选为根节点交换机,并使用 STP 调整了逻辑拓扑以消除环路。当其中一个链路失败时,网络会重新配置以确保正常运行。

图 7-2. 数据包风暴问题和 STP 选举方案;左侧显示了一个没有 STP 的容错网络,其中一些数据包注定会在交换机之间(几乎)永远循环;右侧是相同的网络,其中一个设备被自动选为根节点交换机,并使用 STP 调整了逻辑拓扑以消除环路。当其中一个链路失败时,网络会重新配置以确保正常运行。


^([14]) 如其名所示,这种类型的内存可以直接通过您试图确定值的参数进行寻址,这节省了通常需要花费在搜索参数上的时间。图书馆目录是 CAM 的一个简单例子——您不需要翻阅图书馆中的所有书籍,只需找到一本书;您根据您要查找的内容(关于“内容”的信息)来确定查找位置。

攻击架构

到目前为止讨论的机制都是为了提高底线,同时提供高性能,在没有任何安全特性的网络设计之上。尽管某些常见的、易于理解的且易于预防的攻击,如MAC 欺骗(任何人都能够欺骗 ARP 消息并冒充具有特定 IP 的设备)被广泛认为是局域网的一个陷阱,并且可以通过正确配置的交换机轻松预防,但一些其他严重的设计缺陷并不那么简单,实际上也不容易预防。并非总是明显,那些通常被认为是为了提高安全性而设计的解决方案实际上并没有帮助提高安全性。

CAM 和流量拦截

考虑到交换机不是安全特性之一的一个更引人注目的理由是CAM 溢出场景。存储 MAC 到端口关联的 CAM 具有固定和有限的大小,并且通常以非歧视性的方式构建。每当系统无法在 CAM 中定位时,交换机只有一个方法来交付数据包——它必须回退到集线器模式,向所有系统广播数据包,希望接收者能识别出这是针对自己的流量,而其他系统会足够友好地完全忽略它。因此,一个谨慎的攻击者可以采取策略生成大量虚假的 ARP 请求和响应,或者一些其他的数据包,冒充大量不同的网络设备,只是为了填满交换机的 CAM。一旦 CAM 满了,攻击就有效地降低了网络安全性,通过禁用交换机上的智能帧路由并迫使其回退到广播所有数据。这反过来又允许攻击者窃听所有通信,就像网络根本就没有交换一样。攻击者可以不冒充接收者或明显影响网络的运行来做所有这些,因此受害者可能完全不知道这个问题。这是一个设计问题;这并不是这些设备预期用途中的缺陷,而是一个对交换机工作原理的普遍理解的严重误解。而且,请放心,在典型环境中,几乎不可能完全解决这个问题。一些交换机确实实现了端口和时间限制来防止此类攻击,但这些方法永远不会 100%有效。

其他攻击场景:DTP、STP、Trunks

其他问题通常更容易预防,并且更为明显(受害者通常能够检测到),但仍然说明了以太网级别的安全问题。例如,对上述 DTP 机制的攻击是一种有趣的可能性。DTP 自动协商通常在设备的所有端口上启用,以便更容易设置。问题是,一个聪明的攻击者可以假装成启用中继的交换机,而不是一个普通的终端工作站或谦逊的服务器;一旦被连接的交换机识别为友好设备,他就开始接收 802.1Q 标记的帧,包括由连接的交换机提供的其他虚拟局域网的流量,能够拦截或注入恶意流量到他不应该能够通信的网络。在许多网络中,同一个交换机同时处理受保护的“非军事化”网络和常见的公司局域网基础设施,这种攻击可能通过使一个网络的成员能够窃听或与另一个网络交互,从而获得非常有用的数据。

你可以通过更改默认配置并在交换机上明确定义一组专用中继启用端口来解决某些设备上的 DTP 问题。然而,问题并没有结束——我们的另一个朋友 STP 也可以以类似的方式被滥用,允许攻击者选择自己作为“根”交换机并接收一部分网络流量。在典型的企业环境中,禁用 STP 发现可能更加困难。

当任何中继在非专用 VLAN 中起源或终止时,又会出现另一个问题。(也就是说,用于中继的端口放置在也由工作站使用的 VLAN 中。)通过注入已标记的帧,可以将流量注入到中继。这可以说是配置缺陷,这个问题通常被忽视,因为许多工程师认为实现中继的方法比实际情况要高级和神奇得多。

攻击预防

这些问题通常很难解决,尤其是在一个在其开发和扩展的各个阶段都没有得到严格和紧密监督的网络中。尽管某些高端设备提供了扩展的安全功能来对抗潜在的攻击向量并减轻或消除一些风险,但以太网网络并不是为了提供安全而设计的,许多智能设备也不是为了管理这些网络而创建的。攻击者可以轻易地使一些或所有功能失效,并将网络安全模型降级到最不希望的选择。

虽然有方法和严格的做法可以遵循以确保本地以太网网络的安全,但这个过程复杂性、额外的财务成本和性能影响,以及需要解决的问题数量,都清楚地表明这项技术并不是在设计时考虑到任何实际的安全级别。

思考的食物

当以太网开发时,在设计决策中忽视任何安全考虑似乎是合理的,并将保护网络的责任留给高级架构、加密等。然而,随着时间的推移,这个最初的决策已经开始对以太网网络的总体维护成本以及在不牺牲某些功能的情况下保持它们合理难以被黑客攻击的难度做出了贡献。

问题并不仅限于以太网。许多基于物理访问或设备访问标准设计的网络,例如,世界上大多数电话系统,本质上且无法控制地暴露于内部威胁之下,几乎没有或没有有效的方法来限制这种暴露并控制来自单个系统在电网中受到损害的附带损害。随着网络规模的扩大和交换数量的增加,某个系统被恶意用户操作或物理或远程访问保护不足的概率稳步接近 1。尽管传统上,要损害系统,需要访问骨干网络而不是终端用户站,这使得情况与以太网有所不同——但现在,VoIP(语音-over-IP)系统迅速弥补了这一不便,经常通过过度信任用户端点来允许轻松的欺骗和其他诡计。

第八章。我们与他们对立

在我们的“本地”网络范围内还能发生什么其他事情?相当多!

本地网络设计,如令牌环或现在占主导地位的以太网,是在假设没有必要在用于传输数据的技术的(或层)级别确保安全性的情况下设计的。当计算机最初开发时,共享网络的用户被期望表现得很好。

尽管仅基于这一原因,人们可能会认为以太网的开发者没有必要将完整的安全功能纳入其设计,但他们应受到无根据的乐观和未能预见必然性的责备。以太网根本就没有留下空间来轻松实现 OSI 高层、设备和应用程序中的完整性、机密性和发送者验证机制。随后的协议和通信方案试图实现部分隐私和通信不可否认性的一定程度——但仅仅达到我们意识到,如果不回到并重新工作链路层,就不可能在那里实现适当的安全性的地步。我们剩下的唯一可能性是在系统之上构建计算昂贵且复杂的加密破解,其纯粹复杂性导致每年发现许多安全问题。

这种不幸且后来相当故意的趋势有效地创建了一套网络机制,尽管它们性能良好且价格合理,但在存在敌对一方的情况下,它们不适合处理即使是中等敏感的数据(几乎所有与用户相关的本地网络数据流都是敏感的)。试图解决这些问题的解决方案——如虚拟专用网络(VPN)应用程序、加密封装的幸运的少数最流行的网络协议、高级交换机等——通常比在为以太网通信方案构思初始概念时将安全作为关键因素时要昂贵和复杂得多。

在我们到达那里之前,我们相当长时间处于部分否认的状态。当安全成为现实世界的关注点(随着互联网的扩展和系统妥协的突然激增)时,最初出现的防御措施集中在外部世界,而忽视了可能来自“受信任”网络内部的威胁。但不久之后,一些企业和机构实体吸取了一些痛苦的教训。随着时间的推移,很明显,仅靠外部防御措施,如防火墙和入侵检测系统,即使配置得当,也不足以保护企业。网络层仍然容易受到攻击,允许内部人员在不利用公司任何单个系统的安全漏洞的情况下破坏数据交换。

尽管你可以争辩说,通过在所有接口上部署适当的加密和加密身份及完整性验证机制来保护网络,但这通常是不切实际的或不可能的,尤其是在不影响网络性能和可靠性以及产生重大成本的情况下(更不用说与各种操作系统和应用程序的兼容性问题了)。此外,正如我提到的,加密并不总是答案:当数据可以被看到和拦截时(例如重放或定时攻击),攻击成功的可能性要大得多,而且某些类型的信息——例如之前讨论过的以太网帧填充漏洞——可以阻止所有保护用户的努力。

在本书的第二部分中,我们正在解决一些固有于本地网络中的威胁,这些威胁在没有发生传统攻击的情况下暴露信息。只要网络使用的是旧的和经过测试的设计,这种设计对于今天的网络来说相当不合适,所有这些问题就会伴随着我们。

我们现在准备向前迈进,但在我们深入探索本地边界之外的狂野和迷人的世界之前,让我们先看看一些其他有趣的(并且更具体的)暴露场景。

逻辑闪烁灯及其不寻常的应用

这样一个例子与逻辑指示符的滥用有关——也就是说,计数器、标志和其他没有物理表示但由计算机维护并在软件中提供的装置,通常在本地网络中实现。逻辑指示符是一个有用的功能,再次假设本地网络是可以信赖的。

简单网络管理协议^([70))(SNMP)是监控和有时管理网络设备最流行的方法。SNMP 通常在端点系统(服务器和工作站)以及网络设备(如交换机、路由器和打印机)上实现。

SNMP 提供了一种读取(或修改)许多系统和应用程序内部抽象表示的方法,包括操作和配置参数以及统计数据。使用 SNMP,你可以查询网络打印机有多少网络卡或其运行时间,然后使用完全相同的方法查询主机以获取相同的信息,尽管在每个系统中,设备需要以完全不同的方式内部获取这些信息。因此,SNMP 使得在不实现大量本地访问协议和检查程序的情况下,轻松监控和管理异构环境变得容易。

自然地,SNMP 本身有许多实现和部署安全问题,但这里不是我的重点。即使正确实现,此功能也可能导致安全信息泄露,例如提供对网络接口看似无关的统计数据的只读访问。(如果仔细限制协议,这个漏洞就会被消除,但在某些类型的网络设备上这通常是不可能的。)一个细心的攻击者可以观察运行 SNMP 的系统的帧或数据包计数器,并使用这些信息推导出用于时间攻击的配置文件信息,以恢复交互会话信息或其他有趣的特征,其方法类似于在第一章中讨论的方法。

哎呀。但真的,这么多坏事情会因为这个原因发生吗?

展示你的打字,我会告诉你你是谁

虽然我已经多次提到这类问题,它们可能看起来很抽象,但它们的后果是真实的,即使在我关注的第一章中忽略按键重建向量也是如此。例如,在令人着迷的发展中,来自 Institut für Bankinnovation 的一组德国研究人员创建了一个商业产品 PSYLock,它提供基于打字模式的生物识别^([71)):使用 PSYLock,他们能够通过检查用户如何使用键盘来唯一识别(因此可能追踪)用户。

PSYLock 主要依赖于按键间时间间隔的测量,这是我之前讨论过的技巧。鉴于能够观察特定机器的包计数器并计算在交互会话中用户何时按键的能力,你可以识别出无论他们使用哪个终端的人。基于将此概念应用于网络层,可以提出一些有趣的应用,这些应用既可以是恶意的,也可以是监督性的。如果攻击者知道他们可以监控 SNMP 交换机端口统计信息的某个站点的远程访问协议的交互会话,他们可以通过重复轮询计数器来确定何时按键,从而得出正在输入的内容或谁正在输入的结论。

攻击的一个更轻量级的变体,不需要我们之前必须应对的任何高级建模,也是可行的。在他们的 Bugtraq 帖子“SSH(安全壳)流量被动分析”中,Solar Designer 和 Dug Song 等人(以及其他事项)建议了另一种可能的攻击,这次使用的是 SSH 协议,这是一种常见的连接到远程系统的方法。尽管 SSH 是加密的,但在他们研究之前发布的版本中,通过仔细分析登录期间观察到的数据包的大小,可以测量密码的长度(密码在用户输入后以单个数据块发送)。

这种技术可以成功地应用于其他加密协议,这些协议在发送之前不会通过填充来隐藏密码长度。不出所料,攻击可以通过简单地观察 SNMP 字节数计数器来执行,而不是直接监控流量。

意料之外的片段:周围无处不在的个人数据

另一个我们不应该对敌对方窥视我们网络(无论我们是否认为他们能看到的数据是敏感的)的前景感到兴奋的理由是,大量的软件违反了最小惊讶原则。最小惊讶原则是软件设计的基本规则,基本上说,一个程序应该以最不令人惊讶的方式对用户做出反应——以一致、直观、可预测或预期的其他方式。事实证明,许多来自几个软件出版商的程序发送了大量的有价值信息,远远超出了我们的预期,通常使用户陷入他们未曾预料到的境地。正如往常一样,微软 Windows 在令人惊讶的程序中处于领先地位,并且出色地以有意但经常被忽视且不明显的方式发布信息,但友好的软件巨头并不孤单。

尽管很少有用户知道这一点,但当 Windows 在域中工作并配置为使用漫游配置文件以使用户能够从不同的工作站登录并访问其个人数据时,每次用户登录或注销时,用户的大量注册表都会发送到域控制器。尽管配置文件中包含的信息最初可能看起来毫无价值,但它包括各种个人设置和历史信息,这些信息可能非常有意思,包括最后执行的命令、最后访问的网页和最后打开的文档。

同样地,也许甚至更加令人惊讶的是,如果一个用户的家庭目录位于域内的网络驱动器上,Windows 会首先在远程服务器上,然后在本地上查找用户在运行框中输入的所有命令。因此,用户发出的所有命令的信息都通过服务器消息块(SMB)协议泄露给一个细心的观察者。

这些以及其他许多例子痛苦地表明,几乎所有网络数据都应该被认为是敏感的。因此,大型本地网络并不特别适合传输任何常见的数据,除非是特定的、有限的或额外受保护的环境。而且我们没有很好的方法来保护这些信息,除非部署重型武器,如加密 IP 隧道或类似的软件,或者从头开始重新设计网络的所有方面。

Wi-Fi 漏洞

如果在这个章节的结尾忽略无线以太网替代品:Wi-Fi 的问题,那就太不公平了。

基于 IEEE 802.11 协议的无线网络在企业界以及普通家庭用户中正在获得动力。不幸的是,甚至在获得广泛接受之前,尽管它们的设计意图是在有线连接之上保持一定程度的额外安全性,Wi-Fi 证明很难正确部署,可能是因为它试图过于紧密地跟随其老大哥的脚步。

802.11 标准在其操作原理上与以太网并没有太大的区别。它使用传统的“一个可以说话,其他人在听”的介质访问控制方案,唯一的区别在于,信号载体现在只是一段指定的无线电频率。这让我们来到了 802.11 的第一个问题。

在 2004 年 5 月,昆士兰科技大学的信息安全研究中心(ISRC)宣布了其研究结果:任何企业的任何 802.11 网络都可以在几秒钟内被简单地通过发送一个阻止其他方尝试通信的信号而完全瘫痪。当然,对于以太网来说,情况也是如此,但你必须首先能够连接到网络插孔,这无疑使得攻击者更容易追踪,问题也更容易解决。你可以简单地检查交换机,然后追踪电缆。这种攻击并不完全令人惊讶,但也不是企业采用者所预期的。

问题并没有就此结束。802.11 标准试图阻止运营商级别的攻击,但实际上却失败了。有线等效隐私(WEP)机制是为了为 Wi-Fi 网络提供一种保护水平,以防止外部方窃听网络会话,从而提供与传统局域网相当的安全保障。然而,2001 年,加州大学和零知识系统的研究人员发现了 WEP 方案中的多个设计缺陷,这证明了该方案完全不合适。遗憾的是,即使到那时,Wi-Fi 已经广泛部署,以至于进行必要的修改变得难以实施。^([73)]

更糟糕的是,WEP 的使用是可选的,并且大多数无线网络设备都关闭了 WEP;它们准备好接受和转发它们接收到的任何流量。尽管这在有线网络中通常是可接受的,因为物理层面上还提供了一层额外的安全保护,但无线网络对任何在范围内的随机人员都是开放的。

Tracy Reed 的战飞冒险(由 Copilot Consulting 的 Tracy Reed 提供,邮箱:treed@copilotconsulting.com)

图 8-1. Tracy Reed 的战飞冒险(由 Copilot Consulting 的 Tracy Reed 提供,邮箱:treed@copilotconsulting.com)

wardriving 的做法——在汽车上配备一台具有 Wi-Fi 功能的笔记本电脑,进行城市网络寻找探险——一旦发现大多数大型企业——尤其是在每个城市的大型购物中心和商业区——部分或全部开放无线网络,就变得极为流行。滥用行为通常相当微不足道,从免费使用网络到发送垃圾邮件或通过受害者的网络进行远程攻击,但网络被熟练的攻击者从内部渗透的风险是真实的。

这个问题的真正规模有多大? suffice it to say that at some point wardriving became passé with the birth of warflying (wardriving, but with a plane rather than a ground vehicle). In 2002, Tracy Reed of Copilot Consulting decided to fly around and vicinity with a wireless scanner. Cruising at 1,500 feet, he managed to find nearly 400 access points with default configurations and likely free network access to the Internet or internal corporate networks for any person nearby (see 图 8-1 和 图 8-2)). Only 23 percent of the devices scanned were protected by WEP (which is, in general, easy to crack anyway) or better mechanisms.

Go figure.

硅谷空中侦察

图 8-2. 硅谷空中侦察

第三部分. 野外

一旦你上网,就会变得复杂

第九章. 外国口音

被动指纹识别:我们行为中的微妙差异可以帮助他人识别我们

在互联网,这个网络的网络中,发送给远程方的信息超出了发送者的控制和监督。与通常是一个安全港,直到陌生人闯入的本地以太网不同,一旦数据进入野外,就不再可能估计和有效管理它可能面临的威胁,因为没有单一个人可以控制数据的路径或确定所有参与通信方的意图,更不用说确定他们如何处理安全问题。在这样的复杂网络中,中间方变得恶意既不是微不足道的,也不是容易评估的。事实上,甚至与你建立合法通信的人也可能有隐藏的议程,或者只是有点好奇。

未经请求的数据获取尝试,换句话说,在互联网上进行时,由于其他几个原因,也有所不同。最重要的是,它们不需要有针对性,并且不限于特定的物理基础设施部分。由于它们对攻击者来说需要付出如此少的努力,因此它们成为在确定如何从这种知识中获利或以其他方式从中受益的确切方法之前获取可能有趣的数据的可行途径。此外,好与坏之间的界限变得更加模糊:攻击者可以是你最好的朋友。为了市场侦察和配置文件目的的一般间谍活动和监控的盈利性对许多人来说太诱人了,以至于难以抗拒;服务提供的世界不是非黑即白,灵活的道德观对许多人来说只是一个可行的商业模式。

这一部分的书主要关注互联网开放设计固有的威胁,以及他人获取比你预期的更多关于你的信息的能力——而且比他们为了提供像有趣网站或令人愉快的基于网络的电子游戏这样的服务所需的更多。一旦上网,敌人就不再是坐在街对面的孤独的疯子,通过高科技望远镜镜头观察交换机的 LED 灯。这里涵盖的暴露使得进行大规模的配置文件、跟踪、信息收集、工业间谍活动、网络侦察和攻击前分析成为可能——而且比之前描述的场景更加真实。

为了保持对隐私保护的知情水平,或者为了部署有效的监控,无论是针对你的用户还是完全陌生的人,当他们接近你的系统时,你需要了解这些威胁。了解也是在一个隐私关注与临床偏执之间的界限相当微薄的世界中保持理智的关键。

我将从检查一组在互联网上使用的核心网络协议及其隐私影响开始。我们开始吧?

互联网的语言

互联网的官方语言被称为互联网协议,最流行的方言被标记为版本 4。该协议,在 RFC793 中指定,^([74])提供了一种以尽可能少的努力在广阔的距离和各种网络上传输数据的标准方法。IP 数据包构成了之前讨论的 OSI 模型的第三层,包含一个包含将数据的一部分发送到其最终目的地——远程端点——所需信息的报头,以及紧随报头数据之后构建的更高层信息有效载荷。

发送者在发送 IP 数据包之前在数据包内提供的路由信息包括源地址和目标地址以及一组简化数据传输过程或提高其可靠性和性能的参数。当本地网络上的计算机想要与一个通过网络无法直接到达的远程方进行通信——至少根据主机的知识是这样的——它将一个带有最终接收者目标地址的 IP 数据包转发出去,该数据包封装在一个指向本地机器的更低层帧中,该机器被认为是从发送者所在网络到外界的网关。网关机器不过是一个多宿主设备——它在多个网络中都有存在,作为它们之间的连接点。网关预计知道如何将数据包路由到外界,对数据包做什么,以及如果数据在到达接收者之前必须涉及更多方,下一个应该接收数据的人是谁。

涉及路由流量的系统,从本地网关到目标网络,读取 IP 层提供的信息,以决定如何将数据进一步沿其路径中继,基于它们对如何到达某些网络的了解。(在这个上下文中,网络被定义为位于特定位置的地址池。)

天真的路由

在其基本形式中,路由器使用一个固定的路由表,通过它区分一组本地网络(它可以直接向其发送流量)和外界,后者是未知的。因此,所有目的地在本地网络之外的数据流量都必须被转发到一个更高阶的路由器,该路由器可能对数据发送的目的地有更好的了解。

图 9-1 展示了示例路由结构。发送者(如图左侧所示)试图向一个地址属于网络 C 的系统发送数据包,而发送者对此一无所知。为了便于交付,这个人将流量发送到本地网关,希望它知道在哪里寻找接收者。然而,这个系统,即路由器 1,只能到达发送者自己的网络和网络 A,另一个与 C 无关的网络。因为目标不在它们的本地网络中,路由器决定最好是将数据包发送到更高等级的 WAN 路由器(路由器 2),它恰好能够本地到达。

一个简单的广域网路由方案

图 9-1.一个简单的广域网路由方案

此设备与网络 C 没有直接连接;它只能直接到达网络 B 和 D 上的主机。然而,它知道路由器 3 正在服务目标地址,因此肯定知道该怎么做。因此,数据包被转发到那里,路由器 3 现在可以本地交付流量给最终接收者,此时所有人都可以欢欣鼓舞,庆祝另一个成功的实现。

现实世界的路由

实际上,网络通常是高度冗余的,并且没有严格的线性架构。它们具有复杂的树状结构,如果我们使用静态配置,选择最优和最经济的路径将变得困难。(更不用说随着网络的扩展,保持所有基础设施变化同步的挑战了。)

因此,一旦流量到达骨干路由器,就实施了一种更合理的路由策略。由网络运营商运营的骨干路由器是一种专用 WAN 设备,将特定提供商控制的多个网络绑定成一个称为自主系统的复杂实体。骨干路由器通常配备与其他大型路由器的接口,并使用高级路径发现算法和大量的“电话簿”式网络块及其位置信息,这些信息由边界网关协议动态控制,以找到将数据路由到目标系统的最佳路径,而不会盲目地将交付流量的任务交给某个系统,希望它能够正确中继。

地址空间

如果目标网络仅由分配给世界各地设备的任意地址集组成,这个过程当然会非常不切实际。自治系统的定义将不得不列出所有地址,并且可能轻易地增长到巨大的规模。为了解决这个问题,连续的地址空间块被分配给骨干服务提供商;提供商随后将较小的块租给最终用户或较小的服务提供商。向提供商网络的路由基于在分配给该实体的地址范围内查找目标 IP,然后在基于更详细的路由表的额外查找中基于该网络。因此,自治系统可以被定义为 IPv4 地址的范围(或一组这样的范围),使用子网掩码方法。

用于在所有互联网协议通信中唯一标识端点系统的单个 IPv4 地址具有相当简单的结构,由 32 位组成,为了方便起见分为 4 个字节,共有 4,294,967,296 个可能的地址。地址传统上写作四个介于 0 到 255 之间的 8 位值,每个值之间用点分隔。例如,195.117.3.59 对应于 32 位整数值 3241036664。

持续的 IP 地址块是数据包路由的基础。它们是在 IPv4 地址的基础上定义的,通过定义属于自治系统所有系统的 IP 地址中固定且恒定的部分,以及由网络所有者设置为各种值以赋予计算机唯一标识符的地址部分。

在定义网络时,IP 地址的一组更高位——理论上从 1 到 31 位;实际上从 8 到 24 位——被保留作为网络地址。这个地址的固定部分为属于(并且可能路由到)这个特定网络的地址所共享。较低位的剩余位可以随意设置,以分配网络内系统的地址。

历史上(根据 RFC796^([75])),网络的大小或重要锁定位的数量是地址的函数,可以从网络地址本身确定。仅根据每个地址的最重要位,地址被分组构成 A 类网络(其中 8 个最高位是固定的,提供超过 1600 万个可能的用户地址)、B 类网络(其中 16 位是固定的,提供超过 65000 个主机)或 C 类网络(24 位固定,有 256 个可能的主机)。因此,如果你的系统 IP 地址以数字 1 开头,你可以判断你的系统是 A 类网络,并且所有具有此前缀的其他系统都紧挨着你的设备。

尽管当时这似乎很方便,但随着互联网的最初实施者(美国陆军、施乐、IBM 和其他巨头)在互联网早期被分配了一小撮 A 类网络地址,并且似乎并不急于放弃它们,尽管他们甚至没有使用他们得到的公共基础设施空间的一小部分。此外,一旦互联网商业化,IP 地址成为用户必须付费的资源,用户就要求更适合他们需求的地址空间块;有些人只需要四个地址,而其他人则需要连续的 8000 个地址。用户开始转售或以其他方式划分他们的互联网空间。

结果是,当前的地址空间以奇特的方式划分,通常排除了小块地址空间,并将它们从更大的、本应连续的块中重新路由,而对此原始划分方案不予考虑。现在,每个网络地址都伴随着一个子网掩码规范,因为仅凭 IP 本身已无法确定系统所在的网络。子网掩码的位被设置在应该在网络地址中固定的位置,并在可以自由操作于网络内的位置置零。

如图 9-2 所示,通过在 195.117.3.0 网络中固定 24 位,我们最终得到 8 位可变的尾端位。这使我们能够在 195.117.3.0 和 195.117.3.255 之间创建 256 个地址,这些地址属于此网络(尽管某些实现可能会强制保留第一个和最后一个地址用于特殊目的,从而只剩下 254 个可能的宿主)。通过这样相对简单的地址网络规范,很容易确定哪些地址属于此网络,因此哪些应该被发送到作为其网关的系统(以及哪些不应该)。

尽管这种寻址方案可能看起来令人困惑且过于复杂,但它却取得了成功:它使我们能够将地址池与特定的系统关联起来,并以最小的计算努力区分系统。互联网在其所有复杂性中,通常能够在非常短的时间内找到系统,而无需太多维护。

网络寻址规则

图 9-2. 网络寻址规则

信封上的指纹

我们知道数据是如何从 A 点传输到 B 点的——但沿途发生的事情比确定路径本身更有趣。那么,让我们更仔细地看看路由器与我们的端点系统之间交换的内容。尽管你可能认为在互联网上发送的包内的实际数据负载包含最有趣的信息(考虑到每秒钟都在全球范围内交换的私人电子邮件和奇怪的内容),但事实远不止如此。

用于路由数据的 IP 数据包格式和用于封装实际应用层数据的第四层信息,由 RFCs 相当严格且出人意料地没有歧义地定义。然而,即使有合格的 TCP 堆栈实现,底层信息也能为接收者提供相当多且一致的价值,比它实际接收到的有效载荷数据还要多。在这一层面上的披露是不经意的和意外的,但为了了解更多关于它,我们需要更仔细地查看底层协议的设计。

互联网协议

首先,基础。互联网协议在 OSI 模型的第三层提供了一个通用的长途传输机制。它包含一组参数,这些参数旨在被中间系统解释和最终修改。头部在图 9-3 中显示。

IP 头结构

图 9-3. IP 头部结构

协议版本

这是一个固定为 4(0100)的四位值,在所有 IPv4 数据包中都是如此。IPv4 是互联网上的标准(并且在许多情况下,是唯一支持的)三层协议。向更高级的实现,IPv6,迈进的努力到目前为止并不特别成功——作者愿意猜测这可能是因为新的、扩展的 IP 地址格式对典型的系统管理员来说要难以记住。

头部长度字段

这是一个四位的值,用于指定 IP 头本身的长度总和,以 4 字节块的数量表示(这使得可以使用字段中的 16 个值来表示从 0 到 60 字节的长度)。此参数告诉实现程序在哪里停止解析 IP 头(由于可以在头部末尾附加额外的“选项”,并且紧接在更高层内容之前,IP 头的长度可能是可变的)。它还使得在不查看选项或完全理解它们的情况下跳过 IP 头的一部分成为可能,并直接跳到数据部分。

由于 IP 选项通常不用于除诊断之外的其他任何目的(它们可以做到像强制特定的数据包路由这样的功能,但不多),在野外观察到的几乎所有 IP 数据包都是 20 字节长(这意味着此字段设置为 5),这是头部固定部分的长度。小于 20 的值自然是错误的,这样的数据包不会被理智的实现所认可。(然而,理智并不是一个规则。)

服务类型字段(八位)

该领域的意义通常相当边缘化。它提供了一个基于荣誉的路由优先级描述,其中发送者被信任以诚信行事,并允许指定此流量是否特别重要或需要特殊处理。此值有时用于本地安装中,在这些安装中可以行使这种信任水平,但在开放的互联网上通常被忽略。

该字段由三个部分组成:

  • 前三位指定优先级。

  • 接下来的四个表示所需的路由方法(使用诸如“高可靠性”或“低延迟”等抽象概念,并让路由器进行解释)。

  • 最后的部分,一个单独的位,是保留的,应当设置为 0(是的,没错)。

总包长度(16 位)

这个 2 字节字段指定了此 IP 数据包的总长度,包括其有效载荷。尽管可能的最大值是 65,535,但数据包的最大大小通常由底层协议的限制限制在一个更小的值。例如,以太网的最大传输单元(MTU)为 1,500 字节;因此,连接到以太网的系统不会发送超过此限制的数据包。超过大约 16 到 18 千字节 MTU 的情况实际上很少见;576 到 1,500 字节之间的值是最常见的。

注意

有趣的事实:IP 数据包的大小限制,N 字节(由 MTU 参数产生),也强加了任何 IP 流量的最小带宽开销限制:在更高层发送的 N-20 字节数据中,将始终至少添加 20 字节的头部。

源地址

这个 32 位值——一个格式如前所述的 IP 地址——应该代表通信的起始端点。因为 IP 数据包是由发送者准备的,而且几乎没有动力让任何人在起始网络的边缘检查此参数的正确性,因此这个值本身并不能真正被信任。尽管如此,它确实提供了一个很好的线索,告诉我们应该与谁进行通信——如果我们有理由信任这个线索,我们可以用它来与发送者进行通信。故意伪造此值的行为通常被称为IP 欺骗

目的地址

这个 32 位值指定了流量的最终目的地。像所有其他 IP 参数一样,它由发送者自行选择,并由中间系统用来适当地引导数据包。

第四层协议标识符

这是一个 8 位值,指定了 IP 数据包的有效载荷中携带的内容——TCP、UDP、ICMP 或我们将在稍后更详细讨论的更奇特选项。

存活时间(TTL)

TTL 是一个用于 IP 流量的 8 位“终止计数器”。为了避免路由表出现严重问题时陷入无限循环,每当它通过一个中间系统或停留在传输队列中一段时间,计数器就会减一。当计数器达到零时,数据包就会被丢弃,发送者可能会通过一个 ICMP 数据包得到通知。TTL 值,像所有其它值一样,由发送者自行选择,但由于其位宽,不能超过 255。

TTL 计数器的有趣副作用是它可以用来绘制远程系统的路由图:一个 TTL 值为 1 的消息在其前往指定目的地途中遇到的第一台路由器上就会过期(并且发送者会收到来自路由器的 ICMP 消息);一个 TTL 设置为 2 的消息会在下一个跳转点过期,依此类推。通过发送逐渐增加 TTL 值的数据包并监控 ICMP “生存时间超时”响应的来源,可以绘制出前往目的地的路由器和其它 IP 兼容设备的集合。这种技术被称为 traceroute,是诊断路由问题以及进行攻击前分析的一种常用方法。

对攻击者来说,有用之处在于,可以在不实际损害目标受害者的情况下实现某些效果:要损害 www.microsoft.com,你可能会选择攻击托管此服务器的网络的路由器,或者他们的 ISP 的路由器,希望拦截所有流量并返回伪造的响应。这将有效地切断实际服务器,并通过伪装,使外界看起来像 www.microsoft.com 网站已被更改。当然,这只是一个例子。

标志和偏移参数

这些 16 位值控制着 IP 数据包路由的一个有趣——也许是最有缺陷——方面。当一个大数据包必须通过一个中间系统在 MTU 小于数据包大小的链路上转发时,会使用这些参数。在这种情况下,数据包不会以原样“适应”介质。

以一个任意为例,一个连接到以太网的发送者可以发送大小最多为 1,500 字节的数据包,通常也会这样做。然而,如果一个数据包遇到的第一台路由器将本地局域网与 DSL 调制解调器桥接,就会出现问题:DSL 链路的一个常见 MTU(它本身通常是一个在其他协议封装上的奇怪组合)是 1,492。因此,一个 1,500 字节的数据包将无法适应。

考虑到构成互联网的链接种类繁多,这是一个严重的问题。它通过将 IP 包或更确切地说,其有效负载分成几个独立的 IP 包,并添加使接收者能够在将其传递到更高层之前重新组装有效负载的信息来解决。结果是形成了一组适合该特定链路的新包。每个分片上指定的偏移量指示当最终接收者尝试重新组装原始包时,每个有效负载部分应该如何插入。

除了最后一个分片之外,所有分片在其头部都设置了特殊的更多分片(MF)标志。当目标系统接收到一个带有 MF 标志的包,或者一个设置了块偏移但没有 MF 标志的包(这表示分割包的最后一个块)时,目标系统就知道需要分配一个临时内存区域来促进原始包的重新组装,并在处理包之前等待所有其他剩余的块。

图 9-4 展示了分片和重组的过程,其中一个大包首先被分成两个块,然后由接收者完全重新组装,尽管块是按顺序到达的。

尽管这个过程是可行的,但它有些低效。系统需要时间来分片和重组流量,并且尾部块通常携带很少的有效负载——只有那些不适合通过不同类型链路的几个字节。当然,如果发送者能够确定它们的位置和目的地之间的最低 MTU(也称为路径 MTU 或简称 PMTU)并据此构建它们的包,那就更好了。不幸的是,IP 并没有提供一种灵活且干净的方式来实施这一点,但这并没有阻止研究人员想出一种巧妙的解决方案。

根据这个解决方案,实现 PMTU 发现的系统会在所有发出的流量上设置一个特殊的标志,DF(不要分片)。如果一个路由器无法在不分片的情况下转发 DF 包,它应该将其丢弃,并发送一个适当的 ICMP 消息,内容为“需要分片,但设置了 DF”。发送者在收到这样的消息后,可以相应地调整他们的期望,缓存这个发现,并继续使用更合适的包大小。

数据包分片和重组过程

图 9-4. 数据包分片和重组过程

注意

这种做法,在 RFC1191 中指定,^([76])认为重新发送丢失的数据包的单次开销比由于需要分片而造成的持续性能损失更好。然而,这项技术也相当有争议,因为并非所有设备都发送适当的 ICMP 通知,并且从历史上看,没有这样的要求。因此,启用 PMTUD(PMTU 发现)可能导致发送者无法与某些站点通信,或者导致停滞不前的文件传输,这些文件传输非常难以诊断。

识别号

识别号(ID)是一个 16 位值,当发生分片时用于区分 IP 数据包。如果没有 IP IDs,如果两个数据包同时分片,重组将严重扭曲、交换或以其他方式损坏同时分片的两个数据包的片段。

IP IDs 唯一标识了不同数据包的几个重组缓冲区。用于此目的的值通常简单地通过每发送一个数据包就递增一个计数器来选择;系统发送的第一个数据包的 IP ID 为 0,第二个为 ID 1,依此类推。

注意

在启用 PMTUD 的系统上,不需要唯一的 IPIDs,因为在理论上不会发生分片,该值通常设置为 0(尽管,可以说,并不特别明智,因为一些相当流行的设备倾向于忽略 DF 标志)。

校验和

校验和是一个 16 位数字,提供了一种简单的错误检测方法。校验和必须在每个跳数上重新计算(因为如 TTL 之类的参数会发生变化),因此设计为使用快速算法,这并不特别可靠。尽管在当今世界,“校验和”只是名称上的总和(使用如 CRC32 或密码学安全的快捷函数之类的算法),但 IP 校验和实际上是一个总和,或其变体,其中加入了一些位取反操作^([15])以混淆对手(并且,更严重的是,在常见的传输错误发生时,使校验和保持正确性更不可能)。


^([15]) 从技术上来说,尽管它对讨论没有特别的重要性,IP 校验和是基于校验和数据的 1 的补码的总和的 16 位 1 的补码。

互联网协议之外

在设计 IPv4 时做出的许多设计决策的一个后果是,即使网络本身运行可靠,也无法提供合理的可靠性保证。尽管 IP ID 号码旨在最小化重组冲突的风险,但它们相对较小的 16 位大小(允许 65,536 个可能的值)使得当两个具有相同 IP ID 的数据包同时重组时,偶尔会出现问题。此外,IP 头部校验和不足以提供可靠的完整性保护;尽管不太可能,但数据包中随机的变化仍然可能给出相同的校验和。同样,如果网络实际上失败了,也没有办法找出哪些数据丢失了,即使失败的原因是单个网络组件短暂的过载这样简单的原因。

最后,互联网协议不提供任何验证消息发送者的方式,只是简单地相信真正的发送者是 IP 头部中列出的那个人。这留给了更高层的协议在必要时提供一些完整性和可靠性保证功能——而且通常情况下,这是必要的。因此,需要 IP 之上的更高层协议。

TCP,以及在一定程度上 UDP,不仅为流量提供了必要的保护,还使用户能够在比指向某个特定系统更高的层面上指定接收者(或发送者)。

与 IP 头部仅包含足够的信息来在两个系统之间路由流量,但不足以决定信息应该发送到哪个应用程序相比,UDP 和 TCP 更进一步:它们进入了端点系统的领域,告诉接收者应该将传入的数据发送到哪个应用程序。

用户数据报协议

如 RFC768 所定义,^([77]) UDP 提供了 IP 功能的最小超集。UDP 添加了本地交付数据的机制,但仍然接近底层的不可靠性水平(以及其低开销)。使用 UDP 进行通信可以比作一种电话服务,其中有时单词会被交换或从句子中删除,而且没有可靠的来电显示——但通话成本低,而且你的电话会被迅速接听。

UDP 头部(图 9-5)具有最小的一组功能,相对简单。它引入了一组可以由目标系统解释的参数,并可用于将数据包路由到特定的应用程序或验证数据包有效载荷在传输过程中没有被破坏。

UDP 头部结构

图 9-5. UDP 头部结构

UDP 用于单次查询,在其他不需要维护状态信息的情况下,以及当性能和低开销比可靠性更重要时。例如,UDP 通常用于域名系统(DNS)名称解析、简单的网络引导和自动配置协议(BOOTP)、流媒体技术、网络文件系统共享等。

端口寻址简介

UDP 引入了源端口和目的端口的概念,除了源地址和目的地址之外,它与 TCP(一个更高级的四层协议,我将在下一章介绍)共享这一概念。端口是一个特定的 16 位数字,要么由愿意发送或接收数据的端点应用程序选择,要么由操作系统分配给它(称为临时端口)。

端口作为将数据路由到多任务系统上的特定应用程序或服务的一种手段,以便程序之间可以发生同时通信。例如,一个名称服务器进程可以决定在端口 53 上监听传入的查询,而一个系统日志服务可以监听指向端口 514 的流量。端口使得客户端可以同时与这些进程进行通信。此外,当实现支持适当的源端口和目的端口对的分离时,使用不同临时源端口的两个客户端可以同时与同一服务(例如,端口 514)通信,而不会造成关于哪个客户端应用程序应该从远程服务获得哪个响应的混淆。

为了使目的系统能够区分针对特定应用程序的通信并将其按预期交付,发送者必须在所有流量中指定目的端口号。发送者为每个客户端应用程序指定不同的源端口,以便一旦服务器回复,答案就会发送到正确的组件。

在这种端口寻址方案中,使用四个值——源主机、源端口、目的主机和目的端口——以确保在特定系统上同时发起或终止的连接能够正确地进行流量分离和会话管理。这种设计意味着单个 IP 地址最多可以有 65,535 个客户端连接到外部世界,并且任何时刻最多只能有 65,535 个服务监听单个 IP 地址;也就是说,没有一些巧妙的黑客手段的话。(我们不太可能很快就会遭受这种限制带来的严重后果。)

UDP 头部摘要

之前在图 9-5 中显示的 UDP 头部遵循 IP 头部,并位于 UDP 数据包中的实际用户空间数据之前。它包含少量字段:源端口和目的端口(每个 16 位),数据包长度,以及一个 16 位的校验和,用于额外的完整性验证。

现在,让我们来点完全不同的内容,是……


^([16]) 技术上,这是 65,536;然而,端口号 0 不应被使用。操作系统及其应用程序自然可能允许这样做,但这可能违反了标准。

传输控制协议数据包

TCP(RFC793^([78])),其头部结构如图图 9-6 所示,旨在提供一种可靠、基于流的通信方法,以在两个系统之间建立有意义的对话。TCP 比 UDP 更适合用于所有应用程序,除了那些必须交换比简单、短消息和单个呼喊更多的应用程序。

虽然技术上是通过在网络中穿越的单独的 IP 数据报实现的,但已建立的 TCP 连接——从应用程序的角度来看是一个虚拟通道——允许通信模式类似于常规的电话对话。与 UDP 流量不同,使用 TCP 时,你可以确信接收方总是以发送的方式接收数据(或者如果错误恢复不可能,整个对话将被丢弃)。在正常情况下,你也可以确信呼叫者的身份,但这种便利性是以更高的成本和较低的性能为代价的。

TCP 头部结构

图 9-6. TCP 头部结构

在 TCP 协议中,两个端点首先使用所谓的三次握手算法建立一个连接。通常情况下,使用特殊的空包(只有头部没有实际数据负载的包),双方就意图达成一致,确认彼此的身份,并就初始序列号和确认号达成一致。这些数字(一组 32 位值)确保了可靠且无缝的传输,因为随着数据的发送,这些数字会递增。这反过来又允许接收方按正确顺序排队等待接收到的数据包,并确定数据中是否有任何部分缺失。

控制标志:TCP 握手

当一个远程系统表达出连接到目标机器上特定端口的愿望时,TCP 会话就开始了。远程系统向目标发送一个带有 SYN 标志(意味着在头部设置了指定的位)和设置在头部中的初始序列号的空包。在接收到这个包之后,任何对包的响应都必须引用序列号才能得到认可。如果目标机器在合理的时间内没有发送正确的响应,该包将被重新发送,直到交付成功或发送方认为已经过去了足够的时间并断开连接。

序列号确保对数据包的响应来自实际的接收方,而不是一个知道将要发生通信并打算捕获它的外部人员。序列号还确保响应不是一个丢失的、误导的、来自之前会话的数据包,它最终回到了这里,而是对发送方这个特定请求的响应。(使用 32 位数字和 4,294,967,296 个可能的值,碰撞的可能性远小于在 IP ID 中使用的 16 位,使得意外事故和外部人员的成功猜测都极不可能。)

预期接收方会对一个 SYN 请求用一个类似的、针对发送方和所使用的源端口的包进行响应。这个包应该设置 RST 标志(再次,头部中的另一个位)来表示他们不愿意建立会话。(没有程序准备好在这个端点上回答连接。)这个包还必须引用原始序列号以及响应。或者,在不太可能的情况下,如果接收方实际上愿意建立连接并与陌生人聊天,他们应该用一个类似的结构进行响应,但设置 SYN 和 ACK 标志,表示接受请求。他们还应该包括他们期望在所有与这个会话相关的响应中使用的序列号。

作为握手的最后一部分,发送方交换一个单独的 ACK 数据包,只是为了确保双方都知道之前交换的序列号和确认号,并且他们在交易方面处于同一页面上。假设他们的通信已经达到这一点,两个端点可以合理地假设发送方和接收方都是他们所声称的身份。为什么?因为每个都可以观察到发送到其地址的流量。否则,如果有一个端点只是伪造其 IP 地址,以他人的名义建立虚假连接,它将不知道在对其另一方的响应中包含什么数字。(并且另一方会对有人试图发送未经请求的 SYN+ACK 或 ACK 数据包感到非常惊讶。)

这个握手协议消除了外部人员简单地伪造流量的可能性,但并没有消除在系统之间合法路径上的敌对特权方发起攻击的可能性(尽管与盲目的伪造场景相比,这种事件不太可能发生)。

注意

不言而喻,尽管使用难以预测的初始序列号的问题并未被视为问题,许多系统使用了如简单递增生成器等设计,但随着时间的推移,通过欺骗特定源的 TCP 握手盲目建立会话或由外部人员注入已建立连接的数据的可能性已经变得有些问题。17] 现在仔细选择 TCP 初始序列号,以确保旁观者无法预测你的系统将如何对即将到来的数据包做出响应,被认为是必要的,并且已经设计出几种方法来解决这个问题。79]

一旦完成握手,各方可以交换数据,每次互相确认它们的序列号;序列号发生大于允许“窗口”的错配的包将被简单地忽略。从现在起,这些数字也将稳步增加,以反映到该点发送的数据量,这使得即使在它们到达顺序错误的情况下,也能在目的地正确处理数据包。为了确保可靠性,如果在合理的时间内没有确认部分数据,则必须重新传输该数据包(或数据包)。

当任何一方收到带有适当确认号的 FIN 数据包时,会话终止。如果在任何时候,其中一个系统变得非常激动并希望突然终止会话(可能是因为从他们的角度来看,没有什么可说的,会话超时,或者他们的方严重违反了约定),则会发送一个 RST 数据包。

一个成功的合法 TCP 握手过程如图图 9-7(在左侧所示)。右侧展示了针对无辜旁观者(无意与目标交换任何数据)创建会话的典型 IP 欺骗攻击失败情况。攻击者无法看到或预测其试图代表系统发送的响应,因此无法完成握手,更不用说在 TCP 会话中进行任何实际的数据交换了。

正如所建议的,TCP 提供了对网络可靠性问题的合理保护,并且更适合有序的基于会话的通信。但代价是额外的开销,这来自于完成握手的需求,以及两端维护连接控制信息的需求。维护这种状态需要付出沉重的代价,因为每个连接都必须跟踪序列号和流的当前状态(握手阶段、数据交换阶段、关闭阶段),保留所有已发送但尚未确认的数据的副本,以防需要重新发送,等等。

由于它们的内存和性能成本,TCP 堆栈实现是常见的拒绝服务攻击向量。

完整的 TCP 握手和常见的欺骗尝试失败

图 9-7. 完整的 TCP 握手和常见的欺骗尝试失败

其他 TCP 头部参数

其他 TCP 头部参数也控制了数据包解释和交付的重要方面。当我们试图仅通过查看他们提供的数据包数据来获取发送者的信息时,这些信息将非常有用。图 9-6,在本章前面已展示,提供了 TCP 字段的完整列表。

源和目标端口

这些 16 位值标识了源和目标机器上的逻辑起点和终点。它们类似于 UDP 中使用的源和目标端口参数,尽管在系统级别上 UDP 和 TCP 端口空间是分开的——这意味着一个应用程序可以监听 UDP 端口 1234,而另一个应用程序可以在 TCP 空间中监听相同的端口号。流量是根据 IP 头中的协议规范来定向的。

序列号和确认号

这些 32 位值确保会话完整性。序列号是发送者期望回显的值。确认号是回显给发送者的值,只有在 ACK 标志被设置的情况下才有意义。

数据偏移(不要与 IP 分片偏移参数混淆)

此字段中的信息指示了数据包中头部结束和有效负载开始的位置。与 IP 头部一样,如果在其末尾附加了某些可变长度设置,TCP 头部的长度可能会变化。这些信息使得只需跳到实际数据,而无需遍历所有头部信息变得容易。

标志

这些 8 位值定义了数据包的特殊属性。此字段中指定的每个位代表一个独特的标志,可以独立打开或关闭;因此,TCP 标志可以任意重新组合。"主要标志"(SYN、ACK、RST 和 FIN)定义了如何根据前面讨论的方式解释数据包,作为 TCP 会话的一部分;"次要标志"控制有效负载交付的某些方面和其他扩展功能,如拥塞通知,但不用于改变连接本身的状态。

注意

尽管可以随意组合标志,但许多可能的组合都是非法的或无效的。(例如,SYN+RST 没有意义,从正式的角度来说,是不允许的。)只有一些组合对于握手和正常数据处理是有意义的。不同的系统对非法标志组合的反应不同,因此发送带有不寻常标志的无效数据包是一种流行的操作系统检测机制。

窗口大小

这个 16 位值控制了在不等待确认数据包的情况下可以发送的最大数据量。更高的值允许一次性发送更多数据,而无需等待确认接收,但如果数据在传输中丢失或未确认并需要重发,可能会对性能造成惩罚。

校验和

这种简单的 16 位方法保护了第四层数据的完整性,类似于 UDP 和 IP 头部中使用的数据包校验和机制。

紧急指针

当数据包中设置了次要标志之一 URG 时,该字段仅由接收者解释。如果未设置 URG,则在此头部区域中指定的值将被简单地忽略。此标志表示发送者要求接收者将特定消息转发给处理流量的应用程序,这可能是由于“紧急”情况,因此数据包将被插入到逻辑流中比其原本应属于的位置更早的位置;确切的偏移量由紧急指针值控制。此机制在正常通信中很少使用。

TCP 选项

头部末尾的变长选项块可以指定数据包的附加设置或参数。在某些情况下,它将是空的(长度为零),但它更常用于实现后来设计的协议的附加扩展,而不会破坏无法理解它们的旧实现。选项块的设计使得无法识别特定选项的系统可以安全地忽略它。最流行的选项包括以下内容。

最大分段大小 (MSS)

这个 16 位值等于发送者网络上的最大传输单元减去低层头部的尺寸。它表示可以在不引起途中分片的情况下发送给接收者的最大数据包长度。发送者使用 MSS 设置来确保在接收者返回大量数据时始终获得最佳性能,这些数据如果不进行分片,将需要额外的带宽开销。不幸的是,MSS 选项由端点系统根据其对直接网络邻居可以处理的数据包大小的最佳知识来设置;它无法避免在中间系统上发生的常见问题,即中途分片(因此需要在 IP 层上实现 PMTU 发现,如前所述)。

窗口缩放

RFC1232 中描述的此 8 位值扩展了 TCP 头部中最初指定的窗口大小字段的范围。根据经验,我们发现,在传输大量数据(如多媒体文件)时,每 64 千字节的数据(16 位窗口大小参数表示的最大值)可以创建性能瓶颈,尤其是在高带宽但高延迟的链路上。窗口缩放是一种扩展窗口大小的方法,允许发送更多数据而无需等待确认。这加快了数据传输,但当一个数据包丢失时,也可能需要重新传输更多数据。

选择性确认选项(RFC2018

当使用更大的窗口大小时,丢失单个数据包需要重新传输整个尚未确认的数据组,这是一种带宽的巨大浪费。为了防止这种情况,设计了一种机制,用于对数据块进行选择性确认。端点首先通过指定选择性确认允许选项来声明其能力和意愿实现此功能,然后最终使用头部中的实际选择性确认选项来确认非连续的数据块。实施此技术可以显著提高性能,但代价是增加一定的内存和数据处理开销。

时间戳选项(两个 32 位值

这是在 RFC1232 中建议的另一个高性能扩展。这种发送和回显时间戳(通常以某种方式对应于系统时间或运行时间)的机制为每个端点提供了一种估计流量往返时间的方法。此选项的主要优点是发送者可以测量数据包到达目的地所需的时间,并在没有响应的情况下更早地进行 TCP 重传。时间戳选项的另一个应用是防止序列号冲突(PAWS,防止序列号[号码]回绕),例如,当在交换了几吉字节的数据之后,序列号计数器回绕之后,一个早已消失的数据包到达目的地时。

EOL

此选项应解释为选项的结束;它告诉接收者不要将任何尾随数据作为头部的一部分进行处理。因为 TCP 头部大小是以超过单个字节的单位定义的,所以在将所有相关选项放置在数据开始之前,但在有效载荷数据开始之前(这仅在完整的四字节边界上才可能)之后,可能会留下一些未使用的空间。EOL 选项可以用来防止接收者尝试分析这些数据。

NOP 选项

这个选项意味着“什么也不做”,并且简单地被接收方忽略。发送者可以使用 NOPs(无操作)在数据包中填充,以确保某些多字节选项的正确对齐(由于性能和某些处理器的架构限制,这些选项必须对齐^([18]))。

T/TCP (事务性 TCP)

这种神秘的扩展为在已建立的 TCP 会话内提供独立的虚拟会话(事务)支持。这使得在每次想要与一次性服务执行特定操作时,可以避免因完成握手而产生的开销——如果应用程序想要与服务器处理多个单独的事务,这种方法更为常见。这个扩展很少使用,并且对于某些数据库系统非常有用(参见 RFC1644^([82]))。


^([17]) 凯文·米特尼克,最著名且最具争议的黑帽黑客之一,通过使用 TCP 欺骗冒充他们的一个受信任工作站,破坏了 Tsutomu Shimomura 的计算机——这一行为让 Tsutomu 先生非常不安,据大多数说法,这对凯文长期来说并没有真正帮助。

^([18]) “必须”指的是“为了确保适当处理而必须具备的条件。”一些处理器在访问未对齐 32 或 64 位的多字节数据结构时会有显著的性能损失;而另一些处理器则要求它们必须以这种方式对齐,否则会引发致命异常(执行陷阱)并拒绝执行操作。当然,一个淘气的发送者可能会故意在缓冲区中放置未对齐的数据,并希望接收方的系统在接收到这样的数据包时崩溃。当然,一个理智的操作系统会首先检查这一点,或者在处理之前尝试将选项数据复制到一个正确对齐的区域。然而,系统的理智并不应该被理所当然地接受。

互联网控制消息协议数据包

ICMP 数据包(参见 RFC792^([83]))用于发送其他协议类型的诊断信息和通知。从逻辑上讲,ICMP 数据包是第三层的组成部分,作为 IP 数据包的有效负载传输,因此与第四层有效负载没有区别。ICMP 不携带任何新的用户空间数据,而是为 IP 提供了一种简单的信号方法。图 9-8 显示了 ICMP 头部结构。

ICMP 头部结构

图 9-8. ICMP 头部结构

使用 ICMP 发送了各种消息,以响应 TCP 或 UDP 流量,通常表示某个特定的数据包无法投递、在传输中过期或由于某些原因被拒绝。可以自发地发送几种类型的 ICMP,例如路由器广告、回显请求(ping)等。

与 UDP 数据包一样,ICMP 头部很简单。它由以下字段组成。

消息类型

这个八位字段列出了导致发送此数据包的事件的一般类别(例如“目标不可达”)。此字段还可以携带独立的消息,尽管这种用法不常见。

消息代码

如果适用,这个八位值描述了确切的问题。它取决于消息类型,可能更详细地描述条件(“网络不可达”、“主机不可达”、“端口不可达”、“通信被管理禁止”)。在消息类型字段中应包含的详细程度与应留给消息代码的内容之间的区别尚不明确。

数据包校验和

此字段验证数据包未被损坏(类似于 UDP 和 TCP)。

ICMP 数据包的头部相当简单,本身并不提供足够的信息来成功解决它试图报告的问题,或识别生成此消息的流量类型。这些信息在数据包的有效负载中传达,并紧随数据包头部之后。

尽管 ICMP 数据包的有效负载取决于消息类型,但它通常引用触发响应的数据包的开始部分。这使得接收者能够确定消息适用的通信以及应该通知哪个应用程序出现的问题。它还可以用来确保 ICMP 数据包的发送者实际上位于两台机器之间的合法网络路由上,而不是它们的外部。否则,发送者将无法看到实际交换的数据。(特别是,他们无法确定 TCP 数据包的确切序列号。)这阻止了恶意旁观者发送虚假消息宣布连接问题,并迫使端点之一断开连接——至少在理论上是如此。当然,由于一些系统臭名昭著地篡改或误引原始数据,区分好与坏可能相当困难。

进入被动指纹识别

这个协议的设计如何与用户隐私相关?答案是有点奇怪:尽管 IP、TCP、UDP 和 ICMP 数据包的设计通常相当严格,并且这些头部中传输的信息并不特别冗长,但各种操作系统向这些数据包添加信息的方式的不同使得可以判断出正在使用的操作系统类型,甚至可以判断出机器实例的具体版本。当处理在规范中未明确讨论或未在正常质量保证程序中分析(例如,带有非法标志组合的传入数据包,如 SYN+RST)的流量时,这些差异尤其明显。

通过对系统实现进行压力测试的深入研究,已经表明可以安全地得出结论,操作系统中的任何两个 IP 套件实现都是不同的。通常,可以使用复杂分析来区分在略微不同的平台上运行的相同系统,或者区分系统略微不同的版本。像 Fyodor 的 NMAP 这样的活跃分析工具,一个 TCP/UDP 指纹识别器和端口扫描器,以及 Ofir Arkin 的 Xprobe,一个 ICMP 指纹识别器,通过发送各种类型的不规则或异常数据包来利用每个系统的缺陷或异常,并通过测量和分析这些数据包引发的响应来识别操作系统类型和版本。

检查 IP 数据包:早期阶段

但系统指纹识别的技术并没有停止在这里。实际上,通过发送可疑且易于检测的数据来远程系统“戳探”可能是处理这个问题的最不微妙的方法。

在 2000 年初,Subterrain 安全小组的两名成员,分别以昵称bindaempirei著称,证明了在没有与远程方进行任何侵入性通信,甚至没有发起任何通信的情况下,通常可以获取网络上远程实体的信息。(他们的代码和发现最初在 2000 年的 DefCON 8 上展出,这是一个略过誉的黑客交易会。)他们的技术,现在被称为被动指纹识别,涉及被动地(duh)观察来自远程系统的合法流量。尽管这种技术使用的指标比 Fyodor 和他的前辈们使用的指标更为微妙和有限,但大量的研究(我很自豪地贡献了其中的一些)已经提供了足够的观察结果,达到了相当惊人的精确度。

为了更好地理解从网络上接收到的单个数据包可以告诉我们什么,让我们来看看我们可以基于被动指纹识别的指标,并检查它们可以告诉我们关于另一方的什么信息。这次探索基于分析互联网上最受欢迎的流量类型——IP 封装中的合法 TCP 数据包。

初始生存时间(IP 层)

回想一下,TTL 字段控制着数据包在被丢弃为不可投递之前可以经过多少个系统。每当数据包通过一个路由器时,它的 TTL 值就会减少,直到 TTL 达到零,此时数据包就会被丢弃。

因为没有严格的要求说明发送者应该如何设置此字段,许多 IP 堆栈开发者在确定他们宠爱的系统的默认值时只是掷骰子。尽管没有额外的测试,旁观者无法确定包的确切初始值(因为包在观察之前肯定已经穿越了几个路由器),但他们知道其初始值必须高于实际观察到的状态。此外,到互联网上远程计算机的平均距离通常不超过 15 跳,两个系统之间超过 30 跳的情况很少见。因此,你可以安全地假设原始值位于观察到的 TTL 和观察到的 TTL + 30 之间(但当然小于 256)。

因为我们知道流行操作系统的初始值,我们可以专注于发送者可能运行的操作系统类型。(Linux 和 BSD 衍生系统通常坚持使用 64;Windows 开发者使用 128,而一些真正的 Unix 后裔使用 255。)然后,一旦我们确定了发送包的操作系统,基于这一点和其他因素,我们可能还能够通过从已知的初始值中减去观察到的 TTL 来确定发送者距离观察点的距离。通过将此值与之前观察到的或已知的网络距离相关联,我们可能然后能够对发送者内部网络的组织得出一些结论。

不分片标志(IP 层)

DF 标志表示,“如果这个包不适合特定的网络链路,不要分片它;只需丢弃它。”通过观察这个标志是否被设置,我们可以确定系统是否使用之前描述的 PMTUD 机制,这又给我们提供了关于正在使用的操作系统的另一个线索。这也区分了两个庞大的系统群体:只有较新的 IP 实现使用这种技术,而所有其他系统对在它们发送的包中启用此标志都没有兴趣。

IP ID 号(IP 层)

如前所述(在讨论分片不足的讨论中),某些启用 PMTUD 的系统在某些(或所有)出站流量中将 IP ID 号设置为零,因为他们假设流量不会分片,并且出于对显示 IP ID 号的安全担忧(正如你在第十三章中看到的)。因此,我们可以通过检查传入的包是否将 IP ID 号设置为零来识别这些系统。

然而,这里有一个陷阱。尽管一些启用 PMTUD(路径最大传输单元发现)的操作系统总是将 IP ID 设置为零,但一些其他系统在某些时候也可以将 IP ID 设置为零,仅仅是因为可供选择的 IP ID 可能性并不多。换句话说,如果你看到一个 IP ID 非零的数据包,可以安全地假设这不是一个对所有出站通信都使用零值的系统。然而,如果你看到一个数据包中的 IP ID 为零,你可能看到的是一个特定的启用 PMTUD 的系统,但你也可能看到的是一个“普通”系统,它只是偶然选择了零值来标记这个数据包。

虽然这种情况发生的概率很低,但也不是完全可以忽略不计。你可能要么对零 IP ID 的情况持保留态度(并且只使用非零 IP ID 观察结果来缩小可能的操作系统范围),要么对同一源进行多次观察以确认零值总是被使用。

服务类型(IP 层)

按设计,这个字段应该被选择以对应于流量优先级和类型,以便给中间系统提供关于如何处理数据包的提示,但它几乎从未这样做。大多数操作系统将此字段设置为任意固定值,因为开发者可以随意设置值,而实际上不会影响 TCP 网络的操作。根据开发者的自尊心,他们可能只是将此参数默认设置为零,或者认为将来自他们系统的所有通信标记为“低延迟”、“高可靠性”或其他设置(使用此字段中的位组合)是合适的。[1]

这应该给我们带来优势——通过了解特定系统的默认值,我们再次可以缩小发送者可能使用的系统数量。然而,由于某些不守规矩的 DSL 运营商和其他 ISP 可能会更改此字段的值以应用于所有出站流量,这增加了混乱。他们的希望是,地球另一端的某些远程路由器会被这个伎俩欺骗,相信标记为“高优先级”的流量值得优先处理,并将其优先于其他连接,从而为该 ISP 的客户提供更快的浏览速度(这令人怀疑)。

就像操作系统一样,ISP 选择服务类型参数的选择相当随意。(例如,一家瑞典提供商使用了一个相当独特且有趣的优先级位组合,将值设置为 3,并使用服务类型位设置为“高吞吐量”。)这种做法反过来使得通过观察它们独特的服务类型位选择来检测来自特定 ISP 的流量变得相当容易,而无需执行如 WHOIS 注册查询源 IP 的主动分析。

非零未使用和必须为零的字段(IP 和 TCP 层)

IP 和 TCP 规范要求为未来使用保留一定数量的字段。所有当前系统都应该将这些字段设置为 0,以便将来在数据包中的这些位置分配非零值具有特殊含义。

不言而喻,在发送之前,这些字段在某些实现中并未被清零,正如它们应该的那样。这个问题在质量保证阶段可能不会被捕捉到,因为它不会引起任何明显的问题——其他系统认为“安全第一”更好,并且不会仅仅因为这种麻烦而拒绝数据包——因此,这种缺陷可能持续很长时间(也许直到这些位实际上被用作某些 TCP 扩展的一部分,导致在与这些损坏的系统通信时失败得非常明显)。再次强调,检查这些值的能力是宝贵的信息来源,可以引导我们更准确地识别发送者的操作系统。

源端口(TCP 层)

源端口标识发送方连接的参与者。每个系统都有不同的策略来分配所谓的临时(起源)端口用于出站连接,通过检查观察到的端口号,通常可以确定源操作系统。此外,执行伪装的系统通常为此目的使用相当具体的端口范围。(伪装,或多对一的网络地址转换,涉及重写来自私有网络的出站流量,使得所有连接看起来都起源于伪装系统,并且所有响应在系统接收到时都转换回并交付给实际的发送者。)

伪装在企业和家庭网络中都被广泛使用,以保留地址空间。内部网络可以使用大量地址,从技术上讲,这些地址并未分配给他们,并且从互联网(或任何其他地方)也没有路由到那里。然而,使用这些地址的系统仍然可以通过将它们的出站连接转发到使用其自己的、合法的公共地址的代理盒,以发起者的名义到达远程系统,从而访问互联网。这种方法还可以保护内部系统,使得外部人士无法直接发起未经请求的连接到系统,同时只允许内部人士连接到外部。

检查对方选择的源端口范围,可以使我们更好地猜测发送者所使用的操作系统,并且(一旦该范围与其他观察结果相关联)确定发送者是否在使用地址转换的私有网络中(在这种情况下,系统预期的源端口范围和实际观察到的范围很可能不匹配)。如果发送者的网络正在使用地址转换,还可以根据不同产品使用的不同范围,推断出地址转换设备的类型。

窗口大小(TCP 层)

回想一下,窗口大小设置决定了在不确认的情况下可以发送的数据量。具体的设置通常根据开发者的个人神秘规则和其他宗教信仰来选择。两种最流行的方法认为该值应该是 MTU 减去协议头部的倍数(称为最大分段大小,或 MSS),或者简单地足够高且“圆滑”。Linux 的旧版本(2.0)使用 2 的幂次方值(例如,16384)。Linux 2.2 改为使用 MSS 的倍数(11 或 22 倍的 MSS,原因不明),而 Linux 的新版本通常使用 2 到 4 倍的 MSS。网络连接的家用游戏机 Sega Dreamcast 使用 4096 的值,而 Windows 通常使用 64512。

应用程序有时可以更改操作系统设置的窗口大小值以提升性能,但这很少发生。(存在一个与我们预期操作系统的默认值不匹配的值,是检测特定应用程序的好方法;这类应用程序的少数例子之一是 Opera,一个中等受欢迎的网页浏览器。)

紧急指针和确认号值(TCP 层)

在紧急指针(16 位)和确认号(32 位)字段中指定的值仅在数据包中设置了相应的 TCP 标志(URG 或 ACK)时使用。如果没有设置这些标志,则这些值应该为零,但它们通常不是。一些系统只是将它们初始化为非零值,这不会引起真正的问题:因为如果没有设置适当的标志,这些值将不会被解释,它们只是用来识别特定系统。

然而,在某些情况下,这些值根本未初始化,而是简单地从正在用于构建 TCP 数据包的缓冲区中复制。我在进行被动操作系统指纹识别工作时观察到 Windows 2000 和 XP 堆栈实现的行为:每当同时发生两个 TCP 会话时,这些值会将前一个会话的一些信息泄露给当前会话(我们将在第十一章中再次讨论的情况)。这表明该人在后台做其他事情,并泄露了一些传输给另一方的信息。哈利路亚!

选项顺序和设置(TCP 层)

数据包中选项的确切顺序和选择是每个系统的独特之处。因为没有规则规定选项在数据包中的顺序,所以存在某些“签名”组合。例如,Windows 在 SYN 数据包上使用一个特征序列的“MSS, NOP, NOP, Selective ACK Permitted”选项;Linux 通常坚持使用“MSS, Selective ACK Permitted, Timestamp, NOP, Window scale。”自然地,这又是一次区分系统的绝佳方式。

窗口缩放(TCP 层,选项)

窗口大小的缩放因子通常设置为零。然而,一些系统在得出这样做合理的结论时,要么默认为更高的值,要么永久性地增加特定类型流量的参数,例如,如果用户刚刚从 P2P 网络下载了盗版电影或完成了大量不同类型的下载(后者自然不太可能)。

最大分段大小(TCP 层,选项)

在某些系统中,此字段固定为特定值;在其他系统中,它表示设备的直接网络连接类型。不同类型的网络有不同的 MTU,这使得可以判断一个人是否使用高速 DSL 链路或微弱的调制解调器线路。

时间戳数据(TCP 层,选项)

由于此值通常对应于系统运行时间,因此通常可以通过观察时间戳选项来确定它。此外,给定一组操作系统,可以通过检查传入流量中的时间戳变化来区分它们并跟踪每个系统:不同的系统将有不同的运行时间(并且不太可能有相同的启动时间),而同一台计算机将保持持续增加的时间戳参数值。

这在两种情况下非常有用。第一种情况是一组系统在单个 IP 下运行,就像伪装一样。在这种情况下,好奇的网站管理员可以确定来自公司 X 有多少独特用户访问了他们的页面,以及每个访问者访问他们运营的网站的位置,即使所有请求都来自一个地址,并且最初看起来无法区分。

另一个应用是追踪单个用户,无论出于什么原因,他们可能会跳转 IP 地址。为什么要费这个功夫,对方为什么又想确定用户是否在这样做呢?例如,他们可能在动态 IP 地址池(通过断开再连接)之间切换,希望他们的攻击尝试看起来像是一系列无意义、不相关的活动,而不是一个精心策划、广泛的探测。或者他们可能想要绕过在线论坛、在线调查或投票竞赛(使用一些老式的票箱填充)的限制,等等。这些都是新一代的常见娱乐活动。

时间戳选项的时间测量通常非常精确,因为它基于一个最常见的时钟,每秒滴答声为 100 或 1,000 赫兹(尽管一些系统使用 64 或 1,024 赫兹,以及介于两者之间的值)。这种精度足以区分即使在电源故障后几乎同时启动的相似盒子,因此它提供了极高的准确性。

其他被动指纹识别场所

在本章中,我们探讨了用于确定远程主机操作系统(以及跟踪其用户)的最常用指标,而这些指标的使用完全在用户不知情的情况下。但是,许多激动人心且尚未充分探索的通信方面,可以用来达到相同的目的,甚至更多。

例如,指纹识别的一个有趣变体与检查数据包本身无关,而是测量某些 ICMP 消息、TCP 重传和类似特性的时序和响应速率。所有超时和重传计数设置所使用的值提供了一个精确且独特地识别系统的良好方法。一个基于 Franck Veysset、Olivier Courtay 和 Olivier Heen(Intranode 研究团队)的研究的 CRONOS 项目旨在提供一个基于这些指标的主动指纹识别工具,但被动指纹识别应用同样具有吸引力。

另一个有希望的线索是尝试结合和测量许多其他异常或不常见的设置,例如发送者使用特定的时间戳值、序列号与确认号相同,或非同寻常的标志,以及控制包中的数据负载、使用 EOL 选项等。这些特征也可以用来区分操作系统,尽管这些特征通常只针对一小部分实现。(选择初始序列号的算法通常是宝贵的信息来源,您将在下一章中看到。)


^([19]) 一些开发者甚至选择设置此参数的“必须为零”位——这在合法应用程序中永远不会设置——可能只是为了做出一种风格声明。

实践中的被动指纹识别

这些指标使得精确识别操作系统及其配置、网络参数以及高效且无声地跟踪用户成为可能。尽管这可能看起来难以置信,但我开发的一个工具 p0f 实现了大多数基于分析 SYN、SYN+ACK 和 RST 数据包的完全被动方式收集和分析信息的技术,成功率很高。

让我们通过一个示例数据包来看看这种方法的有效性。以下是从实际捕获的 TCP 数据包中提取的一组重要参数。这能告诉我们发送者的操作系统是什么?

互联网协议(版本 4)
源主机
目的主机
标志
生存时间
标识号
无 IP 选项(数据包大小 = 20)
传输控制协议
源端口
目标端口
标志
序列号
确认号
窗口大小
TCP 选项
#1 最大分段大小
#2 允许选择性确认
#3 时间戳
#4 窗口缩放

很多。以下是我们可以从这些观察中推断出的内容:

  • 因为 IP 头中的 DF 标志被设置,系统必须使用路径 MTU 发现。使用路径 MTU 发现的匹配系统是 Linux 的新版本、FreeBSD、OpenBSD、Solaris 和 Windows。我们可以排除 IRIX、AIX、许多商业防火墙^([20])以及其他出于可靠性原因不实现 PMTUD 的系统。

  • 数据包的生存时间(TTL)为 57。我们知道初始 TTL 值不可能更低,因为它在传输过程中可能会降低,而且不太可能超过 87(这将是一个非常远的系统)。我们可以将此与许多 Unix 系统(所有这些系统都使用初始 TTL 为 64)相匹配,但我们排除了 Windows(初始 TTL 为 128)、Solaris 8 之前的版本(255)和几个网络设备(32)。

  • 数据包的标识号不为零。这排除了 Linux 2.4 及更高版本,以及其他一些流行操作系统的最新版本。

  • 源端口位于最常用的范围内(1,024 到 4,095)。虽然这本身并不能帮助我们排除任何系统,但我们可以安全地假设,在这个连接之前,该系统已经建立了超过 2,700 个连接,并且不太可能隐藏在伪装背后。

  • 选项选择和排序(MSS、选择性 ACK、时间戳、窗口缩放)是 Linux 2.2 及更高版本特有的。

  • 窗口大小是 MSS 的倍数,即 MSS*22。唯一符合这一条件的系统是 Linux 2.2。

  • 在数据包中没有观察到异常、RFC 违规或其他怪异现象,这证实了 Linux 是正在运行的系统的假设。

  • 最大分段大小指示以太网或调制解调器 PPP 连接(MTU 为 1,500)。

  • 系统的运行时间大约为 19 天,并且它位于 7 个系统之外。

当然,单个指标可以被应用程序或用户调整所修改。(例如,用户倾向于在阅读网络优化指南或运行“系统医生”应用程序后修改 TTL 或启用或禁用某些设置。)然而,通过基于我们的观察得出一系列结论,我们找到了一种可靠的方法,通过识别在大多数类别中似乎是最匹配的系统来确定机器的操作系统。

在这种情况下,我们有充分的理由相信所讨论的系统是 Linux 2.2,并且发送者通过以太网或拨号调制解调器连接到互联网。基于这个假设,我们还可以得出结论,该系统距离 7 跳(64-57,其中 64 是 Linux 系统的初始 TTL)并且其运行时间接近 20 天。如果更多用户隐藏在这个特定的 IP 后面,我们可以轻松地计数它们,并根据它们的系统特征和时间戳数据(如果有的话)区分它们的会话。


^([20)) 防火墙本质上是一个过滤路由器,通常也具备理解和基于高层流量特征做出决策的能力。

探索被动指纹识别的应用

当被接收者或旁观者(例如发送者和接收者之间的 ISP)观察时,网络流量可以提供超出实际交换数据的信息,包括发送者系统的一些参数。正如之前所建议的,这种暴露很重要,也非常有趣,因为与应用程序传输的数据不同,它并不一定明显,而且披露往往超出了任何用户的控制。尽管用户可以更改浏览器设置和其他应用程序的设置,以防止被监控、识别和追踪,但较低 IP 或 TCP 层发生的披露很容易破坏这种努力,向观察者透露的信息与受害者试图隐藏的信息一样多。它还可以携带对基础设施安全具有更基本意义的数据,包括一些关于受害者网络结构和保护的有用提示。

话虽如此,在没有侵犯隐私的情况下,被动指纹识别也可以用于相当合法的侦察任务。被动指纹识别的实用(和常见部署)应用范围涵盖了整个道德光谱,从恶意到正当防御。

收集统计数据和事件记录

被动指纹识别的一个更合法的用途是监控网络,以执行对平台和网络环境的非侵入性和客观分析,以确保用户获得针对其软件优化的服务,并保证没有大规模的用户群体以某种方式被忽视。此外,通过使用被动指纹识别,收集有关潜在攻击者或其他未授权活动的数据可以大大增强。事实上,被动指纹识别在蜜罐研究领域特别受欢迎。

备注

蜜罐是 Sun Microsystems 的 Lance Spitzner 积极推广和研究的一个概念。^([[84)]] 目标是让所有者了解他们的对手及其目标,使用(蜜罐)这种设备的价值在于其未经授权和非法使用,并且对基础设施没有任何实际意义,尽管它们被设计成看起来好像有。

内容优化

被动指纹识别的一个活跃应用是基于对客户端使用来访问服务器的设置进行即时分析,提供针对特定接收者的优化服务。我认为我有责任在这里无耻地推荐我之前提到的一个工具,p0f。p0f 提供了一种查询其关于来自其他应用程序的最近传入连接参数的方法,这使得内容优化的任务变得容易得多:一个网络脚本不需要了解很多 TCP 和 IP,只需简单地问 p0f,“嘿,我和谁在说话?”然后就会得到一个有用的响应。

策略执行

检测和最终阻止过时或不合规的系统(例如,违反企业政策或构成安全风险的设备)或未经授权的网络连接的侵扰是被动指纹识别的另一个有趣应用。自 3.4 版本以来,OpenBSD 提供了一种基于操作系统检测结果进行路由和重定向流量的方法,因此基于远程操作系统特性的策略执行变得相当可行。现在,同样的功能作为 Linux netfilter patch-o-matic 代码的一部分提供。这两个实现都受到了 p0f 的密切启发或基于 p0f。

贫民窟式安全

被动指纹识别也可以用来最小化某些类型的暴露。尽管经过一些努力可以欺骗指纹识别技术,但指纹识别可能被用来阻止某些类型的客户端(例如 Windows 系统,这是一个最常被间谍软件、后门和蠕虫感染的平台,通常被用于未经请求的大规模电子邮件分发或攻击跳转)使用网络上的某些底层服务,同时允许“不太可疑”的实体访问它们。

安全测试和预先攻击评估

活动指纹识别通常会被防火墙和其他仔细过滤和分析 IP 流量的解决方案阻止。然而,被动指纹识别可以检查甚至受到积极保护的系统,并且可以在不触发任何警报的情况下绘制网络图。

使用被动指纹技术进行安全测试和评估的方法有两个方面。首先,它可以用来分析传入流量。尽管观察者必须等待远程方连接到他们的系统,但这种连接可以非常容易地诱导而不会引起怀疑。事实上,通常只需向受害者发送一封特定的电子邮件或一个指向网站的链接,即使是最复杂的包过滤解决方案也无法检测到。其次,被动指纹技术可以用来分析对可用服务的合法流量的响应,以确定远程方的参数。如果一个黑帽黑客知道如何破坏内部网络,但想了解更多关于其内部的信息,以最大限度地减少被过早检测到的风险,那么被动指纹技术可能会派上用场。同样,这也可以适用于为进行测试的实体付费的合法安全测试。

客户配置文件和隐私侵犯

许多公司不遗余力地收集和出售有关人们习惯、偏好和行为的有价值信息。尽管这些信息通常用于营销目的,但理论上可以用来针对特定个人。通过关联他们在多个地点访问的指纹识别结果来跟踪用户的能力,无论是为了绘制内部网络和使用的软件、追踪个人还是收集其他有价值的数据,这可能是一种信息来源,它本身可能具有相当大的价值,或者可以用来增强其他不太道德的提供的吸引力。

间谍活动和秘密侦察

收集有关竞争对手网络架构和用户行为及偏好的额外信息通常非常有吸引力。尽管这听起来可能像糟糕的科幻小说,但这实际上只是上述讨论的配置文件类型的一种更精确的形式。

防止指纹识别

由于典型 IP 堆栈的复杂性,通常很难防止指纹识别,但可以通过确定它最依赖的参数并对其进行更改,来解决特定问题并禁用已知指纹识别软件的特定类型。例如,某些包过滤解决方案,如 OpenBSD 中的pf,提供了一种包标准化服务,确保所有传出流量“看起来相同”。尽管这可能在一定程度上防止了指纹识别的一些方面,或者可能只是通过使一些流行的程序不够准确而使指纹识别更加困难,但它并没有完全解决问题。

尽管彻底和看似详尽的操作系统设置或 TCP 参数的修改或自动化修改可能会使系统识别变得更加困难,但某些行为深深埋藏在内核中,并且不可定制。例如,改变数据包中的选项顺序相当困难。此外,当用户进行手动修改时,他们可能会将独特的特征引入来自他们系统的数据包中,这只会进一步影响他们的隐私和匿名性。

幸运的是,某些解决方案确实解决了特定类型的测试。例如,Gael Roualland 和 Jean-Marc Saffroy 的 IP Personality 通过修改 TCP 堆栈,使其对特定工具看起来像来自不同的操作系统。如果你喜欢,你可以使用 IP Personality 让 NMAP 认为你的系统是一个惠普激光打印机。然而,也会出现一些问题。首先,试图模仿使用弱堆栈的设备实际上可能会削弱系统的 TCP 堆栈。例如,如果你为了符合打印机的特定特性,在所有连接上使用简单的序列号,那么有人迟早会利用这一点来轻易地干扰或篡改你的流量。此外,像 IP Personality 这样的软件只能针对最流行、最知名、最文档化的工具,但它对其他工具的成功没有保证,因为每个工具检查的特征以及这些特征的解释方式在不同地方是不同的。你只能希望愚弄那些最不坚定、最天真、使用你已知工具的“主流”攻击者。

注意

与伪装代理不同,代理类型防火墙和其他代理设备不会转发数据包,而是拦截连接并使用它们自己的 IP 堆栈启动新的连接。这些是解决第三和第四 OSI 层指纹的唯一完整解决方案,但它们对性能有严重影响,并且由于引入了极大的复杂性,更容易出现问题。此外,对应用程序本身的更高层指纹识别仍然是可能的。

思考:IP 分片致命缺陷

在讨论互联网协议的标志性特征时,我随意地提到,数据包分片和重组的过程存在致命的缺陷。这个观点主要来源于我在撰写本书时一个相当有趣的观察。尽管这个概念与一个活跃且明显的攻击有关,该攻击由一个公开的恶意实体执行(尽管很难追溯到该实体),但它却是互联网协议设计中固有的独特且有趣的缺陷。这并非一个明确定义的错误的结果,而更多是不同设计层上范式冲突的结果,这两者都由 IP 套件的创始人之一乔恩·波斯特尔(Jon Postel)奇特地指定。我决定将其包含在这里,作为对那些对计算机缺陷病理感兴趣的人的思考材料。

首先,让我们看看今天的状况,或者可能是昨天,因为我们正在回顾一个相当古老的攻击技术,在之前的 TCP 讨论中已经提到。这个技术,即盲注攻击,最初由罗伯特·T·莫里斯(Robert T. Morris)在 20 世纪 80 年代中期描述。^([85)] 它在其十年后的黄金时代达到了顶峰,但自那以后其重要性已经降低。我们将关注盲注攻击的一个具体例子,即将某些数据注入现有会话中,以破坏它,让服务器相信其用户已发出特定命令,或者让用户相信他们正在从服务器获得特定响应。这种技术通常被称为连接劫持

在正常情况下,一个恶意旁观者想要将数据注入现有的 TCP 流中,首先需要确定至少一方使用的序列号。尽管这种攻击高度时间敏感,并且必须针对特定的现有连接进行,但当序列号可预测时,它可以(并且已经被多次成功执行)执行。事实上,在 20 世纪 90 年代末,许多工具被用来干扰 Windows TCP 会话到互联网中继聊天(IRC)网络(为了娱乐或其他目的),利用 Windows 脆弱的初始序列号(ISN)选择算法;在这里注入单个 RST 数据包是微不足道的,这会将一个人从聊天服务器上踢出去。这就是我们当时所说的乐趣。

今天,情况略有不同。多亏了许多研究者的努力(包括这些文字最谦卑的作者),开发人员努力使 TCP 连接的初始序列号更难以预测。许多尝试改进流行操作系统中序列号生成器质量和强度的尝试,最终使得 ISN 预测攻击变得更加困难,只有少数相当不引人注目的例外。使用顺序 ISN 号的系统在很大程度上已经灭绝;攻击者无法确定与另一方对话中使用的数字,被迫在整个 32 位可能值空间中搜索,以便执行精确的数据插入攻击(如果他们只想终止或永久损坏会话,则更少)。这大约有 4,294,967,296 种组合,这种攻击要求攻击者平均发送大约 80 GB 的数据才能成功。不用说,这并不被认为特别可行。

然而,关于从成功的数据注入攻击中可以获得的实际好处,几乎没有变化。尽管越来越多的通信是在支持加密的通道中进行的,但此类攻击的相关性并没有显著下降;许多有成果的攻击场景仍然存在。以下是一些例子。

  • 数据可以被插入到未加密的服务器到服务器或路由器到路由器流量中,例如电子邮件交换、DNS 区域传输、BGP 通信等。大量服务器到服务器的流量可以由攻击者生成,同时包含敏感或受信任的信息,这使得有针对性的定时攻击更加可行。

  • 数据可以被插入到未加密的客户端到服务器流量中,例如文件传输协议(FTP)文件下载或超文本传输协议(HTTP)响应。这种攻击可以用来向知名服务器的访问者提供恶意、有罪或贬低的内容,或者使其看起来像妥协尝试来自无辜的访问者。

  • 数据可以被插入到现有的会话中,以利用服务在非认证用户无法访问的阶段存在的漏洞。这既适用于加密流量,也适用于未加密流量。例如,像 POP3(存在点,远程邮箱访问协议)这样的服务只能接受各种命令,前提是用户之前成功登录。在登录之前,可用的唯一命令是直接与认证过程相关的命令(如USERPASS指令)。如果没有有效的密码,攻击者无法利用后来可用的某个命令(如用于从邮箱中检索特定消息的RETR命令)中的漏洞。然而,如果攻击者能够将恶意的 RETR 请求注入到已认证用户的现有会话中,他们就会获胜。

  • 即使是安全且加密的、完整性受保护的数据流,在会话被单个精心制作的包中断或终止时,也容易受到拒绝服务攻击。

因此,能够轻松注入数据,而无需遍历所有可能的序列号范围,这很诱人。而这就是碎片化大有用武之地。

将 TCP 拆分为碎片

当携带 TCP 有效负载的 IP 数据包被拆分(可以说,在文件传输中这是一个常见的情况,而且仅仅通过设置 DF 标志并不能总是防止这种情况,就像一些系统所做的那样),数据就会在网络中以多个块的形式传输,并且只有在到达接收方时才会重新组装。一个聪明的攻击者,在预料到这种拆分的情况下,可以发送一个特别定制的、非法的 IP 碎片,伪装成来自预期发送者的碎片。在接收到这个碎片后,接收方可能会(如果运气好的话,这是一个精确时间的问题),最终使用它而不是真正的碎片来重新组装原始数据包。

在这个攻击场景中,第一个碎片(包含完整的 TCP 头部,包括确切的端口、序列号等)与攻击者伪造的恶意有效负载合并。因此,攻击者不需要知道序列号或其他会话参数,就可以将他们的数据插入帧中,从而有效地破坏整个 ISN 生成过程。一旦攻击完成,接收方最终处理的数据包由从合法碎片复制来的有效头部数据和攻击者注入的恶意有效负载组成。

备注

攻击者可以通过在碎片之间指定轻微的重叠来替换第一个碎片中的任何部分;许多系统都尊重碎片之间的重叠,并用较新的副本覆盖之前接收到的数据。在极端情况下,攻击者可以成功替换 TCP 数据包中的所有数据,除了序列号。

自然地,拼图中的一些部分仍然缺失。但是,除了需要精确的时间和了解传输发生的时间之外^([21)),在这个场景中的攻击者必须克服仅有的两个障碍:

  • 碎片必须有一个正确的 IP ID 号才能被合并。幸运的是,在许多系统中,这并不是一个问题,因为 IP 标识符是按顺序选择的。因此,当前可能使用的数字可以通过尝试测试连接来简单地推断出来。一些系统,特别是 OpenBSD、FreeBSD 和 Solaris,提供随机化的 ID 号,这可能会使攻击更加困难,但仍然不能阻止它。攻击者只需检查数千(而不是数十亿)种组合,因为 IP ID 字段相当小(只有两个字节)。

  • TCP 头部包含一个校验和,在重组后进行验证,攻击者修改的数据的校验和必须与原始有效载荷的校验和相同。然而,由于 TCP 校验和的设计非常简单(只是一个直接的 16 位和的变体),你可以构建一个不改变数据包校验和的有效载荷,只要攻击者知道要替换的原始部分。这种情况最常见,尤其是在文件传输期间,攻击者想在公开可用的数据部分插入恶意代码或内容。

以下是一个由头部单词 H1 和 H2 以及有效载荷单词 P1、P2 和 P3 组成的数据包的简化校验和示例:

C = H1 + H2 ... + P1 + P2 + P3 ...

H1、H2 和 C 对攻击者来说是未知的。(头部包含序列号,校验和受此数据影响。)攻击者实际上无法检查这个数据包,但知道受害者会在应用层执行特定的(可预测的)交易(例如,检查他们的邮件,下载网页,与朋友聊天等)。攻击者可以推断出有效载荷数据 P1、P2 和 P3,并希望用他们自己的恶意单词 N1 和 N2 来替换它,使用第三个单词作为校验和补偿(CC),以便数据包仍然有效。

C = H1 + H2 ... + N1 + N2 + CC ...

解这些方程式以得到 CC,我们得出结论,校验和必须通过 CC = (P1 + P2 + P3 - N1 - N2) 来补偿。攻击者可以修改数据包,使校验和保持不变,而无需知道整个数据包;他们只需要替换的位。这足以正确计算补偿位并保留校验和。


^([21]) 计时本身并不像最初看起来那样成问题。攻击者可以选择提前发送他们的恶意第二个片段;接收者随后创建一个重组缓冲区,并等待剩余部分在一定时间内到达。一旦第一个合法片段到达,缓冲区内容就被认为是完全重建的,无需等待真正的第二个块到达。

第十章。高级羊计数策略

在这里我们剖析确定网络架构和计算机位置的古艺术

网络侦察和映射是利用互联网核心通信协议中固有的信息泄露向量来识别系统和网络或识别和追踪潜在违法者、用户、客户或竞争对手的艺术。这可能是迄今为止最发达、最广泛部署、最具有意义和最直接有用的被动数据分析应用,但它也存在一些问题,这些问题影响了其在某些场景下的准确性和可用性。这对于已知和测试过的 TCP/IP 被动指纹识别技术尤其如此。

传统被动指纹识别的利弊

使用上一章中讨论的被动指纹识别度量标准将使你能够轻松地识别源系统和网络的一些特征。此外,在某些情况下,这些技术将使追踪个人在更改地址或与其他单个网络用户共享地址时成为可能。只要你能够说服被观察的地球人(地球人)与特定网络互动,或者只要他们的网络通信通过一个足够好奇的人控制的特定系统集,你就可以使用这些技术而无需与远程方互动。因此,除了其他用途之外,被动指纹识别使服务器所有者或特定的 ISP 能够轻易地获取大量且完全隐蔽的信息。

被动指纹识别为第三方提供了一把双刃剑。你可以部署它来获取有关网络内部结构的有用数据,以便使攻击更容易或更深入地了解所使用的网络技术(即使是在相当复杂的环境中,如图 10-1),以便使攻击更容易或更深入地了解所使用的网络技术。你也可以(完全有理由)用它来监控自己的网络以发现违规行为(如非法连接或连接内部网络与外部世界的接入点)或追踪攻击者。

对于单个用户而言,由此产生的隐私损失通常是可以忽略不计的,除非将用户偶然进行的活动与通过指纹识别获得的附加数据联系起来,或者追踪单个用户跨域的能力是一个特定问题(这很可能只有在用户的行为一开始就值得怀疑的情况下才是真的)。但是,所有用户隐私的累积损失可能会非常令人担忧,通过指纹识别或辅助跟踪收集的信息可能会具有明显的市场价值。(如果你的个人数据与你的偏好和兴趣信息相结合,它可以卖给广告商的价格会更高。)此外,暴露网络的内部技术运作确实可能对公司和敏感基础设施的其他部分来说是不受欢迎的。

尽管如此,目前还没有完全失去希望。正如之前所指出的,使用被动指纹识别来获得准确结果存在一些问题。传统被动操作系统指纹识别技术的可靠性问题源于通过仔细调整受观察系统使用的某些或所有网络设置来欺骗观察者的容易程度。即使完全更改所有设置并不特别容易,部分修改可能足以阻止某些自动化分析尝试(万岁!)或误导调查恶意事件的研究人员(哎呀)。尽管这不是一个大规模问题,因此不是统计分析的担忧,但可靠性问题可能在个别情况下引起关注。

此外,我们在第九章(第九章. 外国口音

图 10-1. 您可以使用被动指纹识别通过观察某些节点(最重要的是测量操作系统特征、TTL 和 MSS 值)的流量来映射一个复杂甚至无法访问的网络,然后推断其他组件的存在以匹配观察到的特征差异。留给读者去确定如何仅通过观察外部的流量来最终绘制出这个网络。

然而,另一种可能更有趣、有希望且具有挑战性的被动指纹识别方法,却很容易解决被动指纹识别的不足。这种新的方法使得误导远程观察者变得极其困难,并且几乎适用于所有跟踪系统。更有趣的是,这项技术使得区分在完全相同的配置下完全相同的系统实例成为可能,将伪装检测提升到了全新的水平。这项技术利用了 TCP/IP 中序列号生成机制的属性,并且还能生成一些相当漂亮的图片。

序列号简史

回想一下上一章的内容,初始序列号是 TCP 内部使用的一种机制,用于确保会话完整性,并且实际上可以保证其最基本的安全弹性。

要真正保护纯文本 TCP/IP 会话免受完全陌生人的数据注入、劫持或伪造,唯一真正通用的方法就是确保初始序列号的选择方式对攻击者来说是不可预测的。这降低了他们正确猜测(并伪造一个将被接受为他人会话合法部分的包)的机会,以至于在现实世界中这种风险几乎可以忽略不计,即使攻击者控制了系统,发送成千上万的包,希望至少有一个与序列号大致匹配。

在 20 世纪 80 年代初,基于 TCP 的通信的安全性似乎并不是一个值得担忧的问题:互联网是一个相当小、自成一体的,也许有点精英主义的由科学家等使用的环境。因此,TCP 协议的 RFC 规范没有指定初始序列号选择的要求,几乎所有早期的(以及一些不那么早期的)TCP/IP 堆栈实现都使用了简单、基于时间或计数器的算法,这些算法为后续连接返回后续数字。当时,随机化这些数字的想法似乎是一种不必要的浪费宝贵的计算能力。此外,这样做,会增加序列号碰撞的可能性。(碰撞是指为后续连接到主机的两个 ISN 选择得太相似,从而产生旧数据包在不当时间到达时可能被解释为错误连接的上下文的可能性。显然,在短期内随机选择数字比选择顺序递增的数字更有可能产生碰撞。)

自 20 世纪 80 年代以来,互联网已经取得了很大的进步,当然,其可用性增加,用户基础快速变化和增长;随着越来越多的重要数据通过电线发送,安全问题变得更加相关。不幸的是,流行的可靠完整性和隐私保护机制尚未跟上互联网的扩张:并非所有服务都支持加密,并非所有用户都知道何时使用它,更重要的是,大多数用户不知道如何正确验证远程方提供的加密证书。

随着时间的推移,尤其是在 20 世纪 90 年代中期弱 ISN 生成机制的广泛滥用(尽管主要限于在线聊天服务等等),很明显,有必要为 TCP/IP 流提供基本的完整性保护。这对于所有流量中实际加密保护的那一小部分来说也很重要,因为通过注入垃圾数据或 RST 数据包来破坏传输层同样不受欢迎,即使影响仅限于断开连接(拒绝服务),而不是数据注入。

因为修复问题的唯一方法(无需对人类已知的大多数基于 TCP 的通信方案进行重大改造)就是使协议本身难以被攻击,许多开发者开始努力摆脱过时且危险的简单递增 ISN 生成机制。尽管这些努力确实有助于提高连接对盲伪造攻击的弹性,但也开启了几个有趣的信息收集途径,允许对系统和网络进行更高级别的指纹识别,无论是为了安全评估还是计划中的攻击。

从序列号中获得更多

自然,能够区分好的 ISN 生成实现和差的实现,对于质量保证和安全测试都很重要。直到最近,评估生成初始序列号质量的传统方法要么依赖于源代码分析,要么依赖于对后续 ISN 比特流的某些一维测试,以估计输出中每个比特携带的熵。前者通常复杂且成本高昂,容易出错,并且并不总是可行(在没有特定系统公开可用的源代码的情况下)。后者缺乏以可靠和可读的方式捕捉更细微的序列依赖性和生成器的其他特性的能力,而是更多地关注统计上的不完美,而不是后续连接返回值之间的相关性。显然,仅通过观察输出来证明实现的安全性几乎是不可能的,但检查某些常见问题并确保底层算法足够稳健是容易的。然而,即使在这里,我们用来检查这些的方法在最好情况下也是相当薄弱的。

原始的不安全 ISN 生成器设计和一些今天的解决方案都是基于基于加法、迭代的算术系统,它们根据之前的输出计算新值;只有重新计算算法的复杂性和在过程中引入的实际不可预测性的数量似乎有所不同。唯一不基于传统算术的可靠设计是一些使用相对较慢但加密安全的快捷函数来实现迭代系统的较新设计。然而,在所有情况下,寻找生成器为新连接产生的后续结果之间的非平凡相关性,以检测算法设计中的可能缺陷,将是有趣的。

显然,如果观察到 ISN 生成器在时间 t 和时间 t+x 的输出之间存在明显的依赖关系,攻击者可以选择在希望干扰或伪造的连接之前连接,只是为了获得时间 t 的 ISN 输出。基于他们对返回序列号的观察,他们可以确定未来(t+x)另一方将生成的响应。因此,攻击者可以在无法直接观察到所使用的 ISN 的情况下,伪造一个针对新连接的有效数据包。

在这种想法的指导下,2001 年我进行了一些研究,旨在为从远程系统获取的 ISN 序列中不太明显的时间依赖性提供一种统一的方法。我的工作产生了一篇论文,详细研究了 ISN 生成算法的一些方面,提供了一种方法来检测超出我们已知的最明显模式和缺陷的细微之处。这篇题为“奇异吸引子和 TCP/IP 序列号分析”的论文^([86])采用了在应用数学领域广为人知的方法,但在网络领域却相当新颖。

延迟坐标:捕捉时间序列的图像

当处理今天某些封闭源代码系统中的一个黑盒 ISN 生成器时,你只能看到它的输出,即由 TCP/IP 数据包携带的 32 位值序列,而不是底层的算法。对于许多操作系统,代码是专有的并且受到严格保护,普通人是无法触及的。即使在开源系统中,源代码也可能很复杂且具有误导性,你可能会重复原始开发者的错误。

我们需要评估的典型输入可能看起来像这样:

S0 = 244782
S1 = 245581
S2 = 246380
S3 = 247176
S4 = 247975
S5 = 248771
...

这些数字之间的依赖关系是否立即显而易见?如果是的话,是否存在一种相对通用的方法供计算机遵循这一依赖关系以及更复杂的方案?

一个优雅的解决方案似乎遥不可及。我希望开发一种方法,仅基于对输出的观察来识别 ISN 底层算法的一些通用属性。但在做这件事之前,为了使分析更容易,假设许多实现基于重复某些算术操作,观察后续结果之间的变化比观察绝对值更好是可取的,并且相当方便。观察变化对这类算法有利,并且不会对其他可能的生成器造成太大伤害。为了实现这一点,我们必须计算输入序列的离散导数:S中元素之间的增量。结果序列Δ,显然从t = 1开始,由以下方程给出:

D[t] = S[t]S[t − 1]

在这个例子中,结果序列Δ如下:

D1 = 799
D2 = 796
D3 = 799
D4 = 796
D5 = 799
...

通过忽略实际值,只观察 ISN 的动态,潜在的依赖关系变得更加明显,并且对于所有依赖于此类算术的实现通常都会如此。(对于不基于简单迭代算术的系统,这几乎没有任何相关性,并且不会显著影响分析目的的数据质量。)

注意

一个特别挑剔的研究者还会补偿样本采集过程中的时间不规则性;在这里,我们假设在采集之间始终存在固定的时间量,一个基数为 1 的时间单位。然而,在高速采集过程中,网络性能和其他事件可能会显著影响时间。为了确保这些时间差异不会影响使用时钟输入作为 ISN 生成过程一部分的算法,可能更安全地使用以下方程(其中 T[t]表示采集 S[t-1]和 S[t]之间的延迟):D[t] = (S[t] − S[t−1])/T[t]。

这种方法应用于迭代算术系统时的优势相当明显。然而,除了简单情况之外,这种方法本身几乎是不够的:我们只是从一个相当难以分析的数据平面序列移动到另一个。

接下来,我选择将 delta 序列转换为一种可以由人类或机器轻松检查的形式,以检查可能比前一个例子不那么明显的相关类型。对于数据的第一组预期受众,没有比系统动态的三维模型更好的方法了。不幸的是,对于 ISN,我们只有足够的信息在单轴上绘制一维图。那么我们如何将我们的信息转换成整洁的三维形状呢?

解决方案是通过应用一种称为 延迟坐标 的坐标重建策略来扩展数据集。我们使用的方法是通过构建基于先前样本的虚拟坐标来扩展每个样本。如果现有的样本被认为是 x 坐标值,我们可以使用这项技术为每个现有样本分配 yz 值,从而构建一个坐标三元组—xy,和 z—足以将每个样本映射到三维空间中的单个点(在这里,像素)。(这项技术不仅限于三维。然而,出于可视化和数据分析的双重目的,选择更高的维度似乎不切实际。无论如何,大多数人在清醒状态下并不擅长处理更多的维度。)

延迟坐标的计算是为了构建第二个坐标,使用的是在 t−1 时刻采样的值,第三个坐标对应于在 t−2 时刻观察到的值,以此类推。在这个特定的应用中,时间 t 的数据坐标由以下一组方程给出:

x[t] = D[t] = S[t] − S[t − 1]
y[t] = D[t − 1] = S[t − 1] − S[t − 2]
z[t] = D[t − 2] = S[t − 2] − S[t − 3]

对于一个正在测试时间依赖性的系统,给定一系列新构建的 (x,y,z) 三元组,可以在三维空间中绘制 ISN 生成系统的行为。因为表示给定样本的像素位置既取决于“当前”值,也取决于多个先前结果,许多甚至相当复杂的依赖关系在相空间中产生了抽象但可观察的不规则密度模式,从而为底层算法创造了一个独特的轮廓。(当用于这样的轮廓时,术语 吸引子 表示映射系统动态的形状。这个形状(集合,空间)代表系统在独立运行时循环或演变的“轨迹”。)

图 10-2 是一组数据的表示,原始看起来如下:

4293832719
3994503850
4294386178
134819
4294768138
191541
4294445483
4294608504
4288751770
88040492
...

文本中描述的数据集的三维表示

图 10-2. 文本中描述的数据集的三维表示

图 10-3 到 图 10-5 展示了其他一些常见但并不一定明显的依赖模式。

从复杂但不可靠的随机数生成函数获取的数据集的三维呈现

图 10-3. 从复杂但不可靠的随机数生成函数获取的数据集的三维呈现

没有强烈相关性但有可察觉的统计偏差的 PRNG 呈现

图 10-4. 没有强烈相关性但有可察觉的统计偏差的 PRNG 呈现

美丽的图片:TCP/IP 堆栈图库

可视化方法似乎效果神奇,为许多被认为相对安全的实现产生了独特且往往本能上令人担忧的迷人模式;许多这些图片可以在下一页上找到。但这些图片能做的不仅仅是给我们一个难以量化的参数和特性的视觉表示吗?攻击者能否以有意义的方式使用这些神秘的三维形状,或者计算机能否以某种方式检查它们,给我们一个关于什么是对的、什么是错的明确答案?一个向日葵形状的生成器是否比一个砖块形状的生成器更容易破解?

在回答这个问题之前,请允许我打断自己,并展示一些在撰写原始论文过程中获得的一些更有趣的结果的简要图库。这应该有助于展示一些观察到的模式的广泛多样性和美丽,遵循古老的规则,即三维图比千言万语更有价值。图 10-6 通过图 10-14 展示了几个操作系统的 PRNG 轮廓。

并非所有图表都绘制在相同的比例上;一些形状比其他形状小得多。比例和其他参数可以从每个图表的顶部行中读取,如图图 10-6 所示。

一个常见的时间依赖性模式,如在测试条件不完美的情况下观察到的那样

图 10-5. 在测试条件不完美的情况下观察到的常见时间依赖性模式

Windows 98. 这里显示的集合直径约为 128,这表明后续的 ISN 通过一个包含大约 7 位“随机性”的数字增加。在集合内,存在一个与上一节中讨论的示例相似的强烈频率模式,可能表明所有结果中存在微小的时序依赖性。吸引子的大小令人担忧地小。

图 10-6. Windows 98. 这里显示的集合直径约为 128,这表明后续的 ISN 通过一个包含大约 7 位“随机性”的数字增加。在集合内,存在一个与上一节中讨论的示例相似的强烈频率模式,可能表明所有结果中存在微小的时序依赖性。吸引子的大小令人担忧地小。

FreeBSD 4.2. 一个 16 位宽的均匀立方体,可能是每一步真正随机增加的小量迹象

图 10-7. FreeBSD 4.2. 一个 16 位宽的均匀立方体,可能是每一步真正随机增加的小量迹象

HP/UX 11. 一个奇怪的 X 形结构,宽度为 18 位但显然不规则,可能是存在缺陷的伪随机数生成器高相关性水平的迹象

图 10-8. HP/UX 11. 一个奇怪的 X 形结构,宽度为 18 位但显然不规则,可能是存在缺陷的伪随机数生成器高相关性水平的迹象

Mac OS 9. 一个类似但略有不同的 17 位结构

图 10-9. Mac OS 9. 一个类似但略有不同的 17 位结构

Windows NT 4.0 SP3. 再次,一个强烈的吸引模式和一个微小的 8 位宽吸引子

图 10-10. Windows NT 4.0 SP3. 再次,一个强烈的吸引模式和一个微小的 8 位宽吸引子

IRIX 6.5. 一个 16 到 18 位宽的高度不规则随机云;可能是存在缺陷的算法

图 10-11. IRIX 6.5. 一个 16 到 18 位宽的高度不规则随机云;可能是一个有缺陷的算法

NetWare 6. 一个看似随机的系统,具有 32 位宽的吸引子云,但由大量高密度点组成,并不均匀

图 10-12. NetWare 6. 一个看似随机的系统,具有 32 位宽的吸引子云,但由大量高密度点组成,并不均匀

UNICOS 10.0.0.8. 一个 17 位宽的奇怪云,具有不规则的较高命中概率段

图 10-13. UNICOS 10.0.0.8. 一个 17 位宽的奇怪云,具有不规则的较高命中概率段

OpenVMS 7.2(默认 TCP/IP 堆栈)。一个 32 位宽的结构,几乎无随机性,显示出强烈的但相当不寻常的相关模式,表明 PRNG 设计有缺陷

图 10-14. OpenVMS 7.2(默认 TCP/IP 堆栈)。一个 32 位宽的结构,几乎无随机性,显示出强烈的但相当不寻常的相关模式,表明 PRNG 设计有缺陷

利用吸引子进行攻击

现在,回到向日葵与砖块的问题。是的,这些漂亮图片的相关性不仅超越了视觉上的愉悦,对于硬核计算机爱好者来说也是如此。事实证明,为每个系统捕获的吸引子结构创建了一个可能的 ISN 行为模式矩阵,其密度对应于在一段时间内出现特定类型的时间依赖性或统计模式的概率。吸引子中的高密度区域对应于历史相关性,也更有可能在将来发生;人口较少的区域不太可能被访问。因此,一旦绘制出特定系统的近似吸引子,攻击者就可以猜测未来的结果。但是,这些形状究竟如何映射到确切的 ISN 值呢?

成功攻击的关键在于认识到吸引子中每个点的 x 坐标取决于 D[t] 的值——即时间 tt-1 观察到的序列号(因为 D[t] = S[t]- S[t-1])。另一方面,y 坐标取决于 D[t-1]t-1t-2 时的 ISN),而 z 坐标取决于 D[t-2]t-2t-3 时的 ISN)。

假设攻击者向一个远程系统发送了三个探测,该系统的操作系统已经绘制了吸引子结构图。这些探测对应于时间 t−3t−2t−1,并且——自然地——足以重建在吸引子结构上标记系统在此特定时间行为的点的 yz 坐标。

攻击者可以利用这些信息,根据迄今为止观察到的吸引子结构中的不规则性,推断出比其他值更有可能发生的 x 对于已知的 yz 的值。yz 坐标对应于吸引子空间中的一条直线,垂直于 x 平面(如图 图 10-15 值,如下所示:

S[t] = x + S[t − 1]

在采样了三个先前的序列号 S[t-3]S[t-2]S[t-1] 之后,攻击者可以确定一组可能的候选序列号 S[t],这很可能会被攻击系统(攻击者没有发起,但他希望干扰的系统)选择用于下一个连接。攻击者可以通过发送带有候选序列号的 TCP/IP 数据包来执行攻击;他不需要一开始就做对,因为所有错误的猜测都将被远程实现简单地忽略。然而,一旦任何伪造的数据包的值与预期的数字在预期的窗口大小内一致,流量就会被接受,从而破坏了 TCP/IP 提供的会话完整性保护。

一条“攻击线”与吸引子相交

图 10-15. 一条“攻击线”与吸引子相交

当然,攻击也有一些注意事项:

  • 他们观察到的动态可能仅限于观察条件或来源本身——尽管从这种技术针对常见实现取得的成功比率来看,这似乎不太可能。

  • 如果候选集特别大——例如,对于产生没有明显不规则性的均匀吸引子云的算法——这种技术就相当不实用,因为它需要太多的尝试来做出正确的猜测。

  • 由于在系统中采样 ISN 实现生成的整个值序列通常不切实际(一些系统有长周期甚至无限周期),因此无法构建完整的吸引子。为了解决这个问题,必须使用近似方法:如果在一个特定的(y,z)线上的一个特定点周围给定半径内存在一个点,则选择该值作为候选值,从而补偿吸引子中即使相当密集的区域也可能存在空隙的事实。

为了使结果有意义,并建立一个用于比较评估 ISN 生成器质量的方法,我决定通过有限的尝试次数来经验性地估计成功率。具体来说,我想确定在 5,000 次尝试中击中正确数字的可能性,基于假设一个使用低端到中端网络连接的攻击者可能在短时间内最多发送 5,000 个数据包.^([22])

为了测试该方法的有效性,我选择将来自远程系统的输入数据分为两部分:一部分用于构建吸引子,另一部分用于执行实际测试。测试一次读取四个连续的序列号,然后将其中三个输入到一个基于吸引子数据生成最多 5,000 个值的实现中。最后,将输出与从测试数据集中获取的第四个数字进行比较。对于每个吸引子,重复数百次测试以确定 ISN 四元组的近似成功猜测百分比,这在实践中表示攻击者使用这种方法成功的可能性。

以下是吸引子画廊中系统的部分结果:

操作系统 攻击可行性
IRIX 6.5.15 25%(100 次尝试中的 25 次)
OpenVMS 7.2 15.00%
Windows NT 4.0 SP3 97.00%
Windows 98 100.00%
FreeBSD 4.2 1%
HP/UX 11 100.00%
Mac OS 9 89.00%

这种方法显然相当有效^([23]),促使许多供应商重新设计他们的算法或重新审视他们对算法安全性的声明。(我一年后(2002 年)发表的后继研究回顾了这些变化中的一些,其中并非所有都令人满意。)

但真正的问题是,这与被动操作系统指纹识别有什么关系?


^([22]) 最小的 SYN 数据包有 40 字节;因此,发送 5,000 个 SYN 数据包至少需要 200 千字节的带宽。这么多数据可以在 10-20 秒内通过 V42.bis 调制解调器压缩在调制解调器线上成功发送。这个阈值的选取相当随意,但似乎合理。

^([23]) 这些结果适用于需要精确数据注入或欺骗的场景。如果所需的精度较低,或者攻击者的唯一目标是造成干扰,远程方不仅会接受具有确切序列号的包,还会接受那些在 TCP/IP 参数指定的窗口大小范围内的包(参见第九章,并想确定该网站有多少个系统。我们首先将www.example.com识别为系统 Y,建立几个后续连接,然后观察到的 ISN 如下:10,11,534,13,540,19。

显然,较低的数字形成了一个序列,这个序列来自处理较少流量或运行时间较短的计算机(10, 11, 13, 19),而较高的数字则对应于另一个系统。因此,两台计算机可能在同一个公共 IP 地址后“共同服务”,可能位于负载均衡器后面。此外,通过改变采样间隔,我们可以仔细检查负载均衡器的类型、其请求分配策略以及它接收到的流量。

这种方法不仅可以区分隐藏在常用地址背后的系统,还可以跟踪系统 Y 的用户,当他们从一个 IP 跳转到另一个 IP 时,只要他们不重启机器(因此重置 ISN 计数器)。对于提供比我们示例中更复杂 ISN 生成方案的系统,区分可能更困难,但只要 ISN 不是在所有 32 位上都是纯随机的,这肯定是可能的。(如果是,就会引发与碰撞相关的问题。)

这里使用的方法仅仅要求 ISN 生成算法中存在一定的可预测性。因此,TCP/IP 初始序列号分析似乎是一个有希望的替代方案或补充,可以用于传统的被动指纹识别——并且遗憾的是,也可以作为侵犯隐私和用户跟踪的有用工具。

防止被动分析

防御序列号预测相对简单,像 Steven M. Bellovin 的 RFC1948^([87])规范这样的良好解决方案已经存在很长时间了。然而,防止对数字进行被动分析相当困难,因为问题不仅源于算法的弱点,还源于使用的算法多样性,这导致很少有系统共享相同的 ISN 足迹。即使在实现 RFC1948 或使用其他基于密码学安全、外部熵的生成器的系统中,行为模式也可能因算法的微妙差异和实施者对足以阻止攻击的值的假设而显著不同。

通过部署一个重写所有出站数据包序列号的状态包防火墙,可以实现对一定程度的预防[24];这使得受保护网络内的所有系统看起来大致相同。不幸的是,只有一些防火墙提供这种功能,也只有一些可以从这种功能中受益。


^([24]) Solar Designer 指出,从技术上讲,这也可以作为一个巧妙的黑客攻击在无状态防火墙中实现。防火墙可以通过 XOR 等操作将原始序列号与一个秘密密钥的安全哈希值相结合,再加上一个唯一标识连接的地址和端口号的四元组。返回的数据包可以移除哈希值(通过后续的 XOR 操作),使得数据包在交付时与内部主机的连接想法相匹配,但在防火墙外部仅以不可预测的随机 32 位形式存在。这对于除了最不稳定的(频繁重复且容易发生冲突)ISN 实现之外的所有情况都适用。

思考题

相空间分析方法在远超序列号生成领域的领域中很有用。其他一些参数,如伪随机选择或根据某些内部方案选择的参数——例如 IP 数据包 ID 字段、DNS 请求标识符(如图 10-16 所示 Figure 10-16)、由应用程序生成的“秘密”cookie,用于标识用户会话等——可以成功分析,无论是为了发现设计中的缺陷,还是为了识别实现并简化进一步分析或便于攻击。

Linux 名称解析实现的一个有趣的吸引子模式

图 10-16. Linux 名称解析实现的一个有趣的吸引子模式

在这个方向上的一些工作已经完成或正在进行中;在一篇与我原始研究部分相关的论文中,Joe Steward 对 DNS 系统中随着序列号预测机制的发展而出现的一些问题提供了见解^([88])。他指出,不仅基于 UDP 的 DNS 协议提供的请求验证方法不足以抵御甚至“低成本”的欺骗攻击,而且各种实现生成的唯一请求标识符的低质量进一步削弱了该方案,使其轻易地受到恶意数据注入的攻击。鉴于 DNS 是互联网的核心服务之一,以及欺骗一个知名网站的 DNS 响应以将特定网络的所有用户重定向到不同的网页并不是完全没有诱惑力,DNS 污染是我列出的互联网中被低估的威胁之一。

Dan Kaminsky 在 www.doxpara.com/pics/index.php?album=phentropy (Figure 10-17")) 提供了一些有趣的、更高级的随机数据的可视化,绝对值得一读。

Dan 的 BSD 内核随机性再现(由 www.doxpara.com 提供)

图 10-17. Dan 的 BSD 内核随机性再现(由www.doxpara.com提供)

第十一章. 异常识别

或者可以从网络流量的微妙不完美中学习到什么

在前几章中,我剖析并分析了从嫌疑人通过网络传输的每条消息中附带看似无关的、“技术”参数中提取可能和很可能有价值信息的方法。正如我希望你看到的,我们可以获得大量关于发送者的数据,而这些数据发送者肯定不知道自己提供了(或者至少,不太愿意经常无法选择不提供这些数据)。通过使用一系列的包和流分析技巧,在一个完美和快乐的世界里,我们可以测量远程方的许多特征,并将他们的行为映射到特定系统的签名和网络配置。

然而,现实情况略有不同:一些观察到的参数至少在一定程度上偏离了与嫌疑人使用的特定设备或网络配置通常相关的一组预期值。尽管你可能简单地忽略这些看似无意义的偶然差异,并仍然成功地识别出原始系统或追踪其用户,但这样做并不一定明智。我们学会了不对这种看似无意义的烦恼给予关注,但世界上没有一件事情发生是没有好理由的(至少在“好”的定义相当宽松的情况下),探索这些看似随机的异常和少数模式背后的机制,而不是忽略它们,可以提供有关网络配置先前未见具体细节的有价值信息。

在本章中,我更深入地研究了可能影响系统观察特征的一些过程。我解释了这些技术导致这种行为的根本原因、目的(或缺乏目的),以及这些技术的后果。

不言而喻,这里讨论的大多数可复制的 IP 数据包修改都源自更高级的 IP 感知中间系统。因此,我将从两个长期被忽视的主题开始考虑:一般性的防火墙,特别是网络地址转换(NAT)。

防火墙旨在保持隐秘的堡垒,对其他人使用的了解越少,对他来说越好。然而,尽管有严格的防火墙策略和设置,随着这些设备复杂性的增加和它们更好地适应处理今天的网络安全挑战,它们也变得更容易使用间接或被动探测技术进行检查。

数据包防火墙基础

流行防火墙^([89])在本质上是一类中间路由器设备,其设计目的是违反中间路由器设备的基本设计。与真正的路由器不同,那些预期基于第三层 OSI 编码的信息做出非歧视性路由决策的系统,防火墙通常会在更高层(如 TCP 甚至 HTTP)上解释、执行或甚至修改信息。尽管防火墙技术相对较新,但它提供了一套成熟且易于理解的解决方案,可以在家庭网络和大型企业中找到。防火墙被配置为拒绝、允许或重定向针对特定服务的特定类型流量,并且(不出所料)被用来限制所有通过此类设备的流量对某些功能和资源的访问。因此,它们提供了一种强大、尽管有时被过度炒作和过度依赖的安全和网络管理解决方案。

防火墙在所有网络环境中取得成功的关键在于,它们使用单一且相对更健壮的组件来保护一系列复杂的系统,并在配置问题暴露受保护服务器上的易受攻击的服务或功能时提供一种安全措施。在极端情况下,防火墙被用来掩盖受保护系统的配置不当和维护不足,通常会导致灾难性的后果。

无状态过滤和分段

基本防火墙是无状态数据包过滤器。它们只是检查每个数据包的某些特征,例如传输控制协议 SYN 连接尝试的目标端口。然后,它们仅基于这些特征来决定是否允许数据包通过。无状态设计极其简单、可靠,且内存和资源效率高。例如,无状态防火墙可以通过丢弃所有非目标端口 25(SMTP)的 SYN 数据包,仅允许针对该端口的 SYN 数据包,来限制对邮件服务器的入站连接。因为没有任何连接可以在没有这个初始 SYN 数据包的情况下建立,攻击者无法以有意义的方式与其他端口上的应用程序交互。为了实现这一点,防火墙不需要像邮件服务器本身那样既快又复杂,因为它不需要记录当前建立的连接及其确切状态。

这种完全透明的保护类型的问题在于,防火墙和最终接收者可能对某些参数有不同的理解。例如,假设攻击者说服防火墙它正在连接到一个允许的端口,但构建了其流量,使得最终接收者以不同的方式读取它,并建立了与防火墙本应保护端口的连接。因此,攻击者可以访问易受攻击的服务或管理接口,我们就会陷入麻烦。

虽然造成这种误解听起来不太可能,但借助我们老朋友数据包碎片化,使用通常被称为“重叠碎片攻击”的方法(由 RFC1858 在 1995 年描述),实际上实现起来相当容易([2])。在这种情况下,攻击者向一个由受害者防火墙允许的端口(例如上述端口 25)发送一个初始数据包,该数据包包含传输控制协议 SYN 请求的开始部分。数据包在末尾缺少一小部分,并且在其 IP 头部中设置了“更多碎片”标志,但为什么防火墙要关心数据包末尾的数据呢?

防火墙检查数据包,由于这是一个 SYN 数据包,因此它的目标端口也被检查,并发现是可接受的。数据包被通过,但接收者不会立即解释它(还记得在第九章中讨论的重组过程吗?第九章?)。相反,数据包被保留,等待碎片重组成功完成,而这不会发生在最后一个尾部数据块到达之前。

接下来,攻击者发送第二个数据包碎片。这个第二个数据包被创建出来,足以与原始数据包重叠,从而在重组缓冲区中的位置覆盖目标端口(TCP 头部的一个字段)。这个碎片被精心制作,以从非零偏移量开始,并且缺少大部分 TCP 头部,除了被覆盖的位。

由于这个原因(以及因为它缺少检查 TCP 数据包标志或其他防火墙可能用来确定是否允许或阻止此流量的关键参数所需的信息),第二个碎片通常会被无状态防火墙原样转发。当接收者将其与第一个数据包结合时,这个第二个数据包会覆盖原始目标端口,改为攻击者选择的更恶意的值,实际上打开了一个应该由防火墙保护的端口。

哎呀。

注意

为了防止这种攻击,一个设计良好的无状态防火墙在分析数据包之前先进行初始的碎片重组。然而,这使其在一定程度上失去了“无状态”的特性,并且变得不那么透明。

无状态过滤和不同步流量

无状态包过滤器的另一个问题是,它们并不像我们希望的那样严格。过滤只能在单个数据包包含所有必要信息,以便过滤器能够做出明智的决定来处理该数据包时进行。因为,在初始握手之后,TCP 连接在很大程度上是对称的,双方拥有平等的权利,并使用相同类型的流量(ACK 数据包)来交换数据,因此很难对连接的初始阶段以外的任何内容应用有意义的过滤器。没有方法可以确定(如果有的话)谁通过交换 ACK 数据包发起了连接,而不需要实际跟踪和记录连接。因此,很难以有意义的方式定义防火墙应尝试应用于 ACK 和其他中途数据包(如 FIN 或 RST)的过滤策略。

无法过滤 SYN 通常不是问题。毕竟,如果攻击者无法发送初始 SYN 数据包,他们就无法建立连接。但是有一个问题:系统如何处理特定端口上的非 SYN 流量取决于端口是否关闭或系统是否在该端口上监听。例如,一些操作系统会对漂流的 FIN 数据包回复 RST,而在开启(监听)状态的端口上不产生任何回复.^([25])

技术如 FIN 扫描或 ACK 扫描(后者最初由 Uriel Maimon^([91])在Phrack 杂志中描述),以及 NUL 扫描和 Xmas 扫描(分别使用没有设置标志的非法数据包和所有标志都设置的数据包进行扫描)可以用来对抗无状态包过滤器,以收集关于远程系统上哪些端口是开启的攻击前证据,或者绘制防火墙丢弃的流量图。在没有能力建立到特定端口的正确连接的情况下,了解特定端口是开启的本身并不是一个直接的威胁。然而,这种类型的扫描通常会泄露关于网络内部(如正在运行的操作系统和服务)的极其有价值的信息,这些信息可以在第一道防线被攻破或绕过后,帮助实施更好、更高效且更难检测的攻击。因此,这被视为无状态防火墙的潜在弱点。

可能与 SYN cookies 的机制结合使用时,更严重的安全威胁相关。SYN cookies 用于保护操作系统免受资源耗尽攻击,在这种攻击中,攻击者向主机发送大量伪造的连接请求(这本身并不是一个难以执行的操作)。这迫使接收方发送虚假的 SYN+ACK 响应,并在将即将建立的连接添加到其 TCP 状态表时分配内存和消耗其他资源。在这种攻击下,大多数系统最终会消耗过多的资源而变得缓慢,或者在某些时候拒绝为所有客户端提供服务,直到那些虚假的连接超时。

为了处理这个潜在的问题,SYN cookies 在 ISN 字段内的所有 SYN+ACK 响应中使用一个加密签名(实际上是一个快捷方式,用于唯一标识连接),然后完全忘记这个连接。只有当来自主机的 ACK 响应到达,并且只有当确认号通过加密过程验证后,连接才会被添加到状态表。

然而,SYN cookies 的问题在于,在这种设计中,存在一种可能性,即 SYN(以及 SYN+ACK 响应)从未被发送过。如果攻击者可以创建一个能够验证主机 SYN cookie 算法的 ISN cookie(可能是因为攻击者有足够的带宽,或者因为算法较弱),他可以发送一个 ACK 数据包,这将触发远程主机向其状态表添加一个新的连接,尽管如前所述,从未发送过 SYN 和接收过 SYN+ACK。无状态防火墙将无法知道连接刚刚建立,因为它从未收到过打开请求!因为没有初始的 SYN 数据包,目标 IP 和端口无法被防火墙检查并批准或拒绝,然而,连接突然就建立了。

这真的很糟糕。

状态包过滤器

为了解决无状态过滤器的这些问题,我们需要在防火墙上存储一些关于先前流量和已建立流的状态信息。这是唯一一种可以透明地预测解碎片化结果或获取中间连接数据包的上下文,并决定它们是否是不合法的应该被丢弃,还是预期的应该被传递的方法。

随着可负担的高性能计算的增加,设计出比我们想象的更复杂和先进的防火墙系统成为可能。因此,我们已经发展到状态连接跟踪,在这种情况下,防火墙不仅检查单个数据包,还记住连接的上下文,并验证每个数据包是否与这些数据匹配。这使得防火墙能够紧密地封闭网络,并在不依赖接收方始终能够区分好流量和坏流量的能力的情况下,忽略不希望或意外的流量。状态数据包过滤器试图跟踪连接,并只允许属于活动会话之一的流量;因此,它们提供了更好的保护和日志记录功能。

状态过滤的任务当然比无状态过滤更具挑战性,并且消耗的资源要多得多,尤其是在一个大型网络被此类设备保护时。当保护一个大型网络时,防火墙突然需要大量的内存和快速的处理器来存储和查找有关线路上发生的事情的信息。

状态分析也更容易引起问题或混淆。一旦防火墙和端点对给定 TCP/IP 会话当前状态的理解不同,就会产生问题;鉴于规范的不确定性和使用的堆栈的多样性,这种情况并不罕见。例如,当接收到一个不在接收方接受的序列号限制范围内的 RST 数据包时,一个比最终接收方更宽松地应用序列号检查的防火墙可能会得出连接已关闭的结论,而接收方可能会得出会话仍然开放的结论,并愿意接受与此连接相关的进一步通信,反之亦然。最终,状态检查是有代价的。

数据包重写和 NAT

提高数据包解释能力,以及提供更好的保护以抵御如使用数据包分片绕过防火墙规则之类的攻击的解决方案,是赋予防火墙不仅能够转发,还能重写传输流量部分的能力。例如,一种方法试图通过在将数据包与网络管理员配置的任何访问规则进行比较之前执行强制数据包分片(重组)来消除歧义。

随着更复杂解决方案的发展,很明显,数据包重写不仅会惠及网络,而且通过部署诸如 NAT(网络地址转换)等极其有用的技术,还能为网络安全和功能带来质的飞跃。NAT 是在转发之前将某些 IP 地址映射到另一组 IP 地址,并对受保护系统发送回来的响应进行解包的实践。状态 NAT 机制可以用于多种应用,例如实现容错设置,其中单个公开可访问的 IP 地址由多个内部服务器提供服务。或者为了节省地址空间并提高安全性,可以实现 NAT,允许内部网络使用一组私有、非公开可访问的地址,同时使网络上的主机能够通过“伪装”为单个公共 IP 机器来与互联网通信。

在第一种情况下,NAT 将传入数据包的目标地址重写到防火墙后面的多个私有系统中。这提供了一种容错负载均衡设置,其中对热门网站(例如www.microsoft.com,或许)或其他关键服务的后续请求可以在一系列系统中分配,如果任何一个系统失败,其他系统可以接管。这项任务有时是通过专用设备(不出所料地被称为负载均衡器)完成的,但通常也由启用 NAT 的防火墙支持。

后一种情况,通常被称为伪装,依赖于重写传出数据包的源地址,这样多个私有、受保护的系统(可能使用的是从互联网路由到该网络的私有地址,例如 10.0.0.0)可以通过防火墙拦截和重写它们的传出连接来连接到外部世界。这些系统隐藏在防火墙后面,它们的行为在外部 NAT 保护网络的外部接收者看来似乎是从防火墙发起的。连接被映射到特定的公共 IP 地址和特定的端口,然后流量被推出去。所有从目标返回到该 IP 和端口的流量都会被重写,指向发起连接的私有系统,并转发到内部网络。这使得整个不打算向互联网提供任何服务的私有工作站网络不会直接从外部世界访问,从而大大提高了网络的安全性,隐藏了其部分结构,并保留了本应购买以容纳每个系统的昂贵公共 IP 地址空间。使用这个系统,即使只有一个公共 IP 路由到他们,一个实体也可以构建一个由数百或数千台计算机组成的网络,并为它们提供互联网接入。

翻译中的迷失

再次强调,地址转换比听起来更复杂:一些高级协议并不像仅仅是连接到远程系统并发送一系列命令那样直接。例如,古老但广受欢迎的文件传输协议(FTP)在其最基本和最广泛支持的模式中,依赖于从服务器到客户端建立返回(反向方向)连接,用于传输请求的数据;客户端发起的初始连接仅用于发出命令。许多其他协议——最值得注意的是一些聊天协议、对等协作或数据共享工具、媒体广播服务等等——也使用奇怪或非传统的设计,需要反向连接和端口跳跃或允许特定的无会话流量(如用户数据报协议[UDP]数据包)返回工作站。

为了应对这些挑战,任何不旨在使这些协议失效的伪装实现都必须配备一系列协议助手。这些协议检查连接内交换的应用数据,有时甚至重写其中一些数据,并在防火墙中打开临时孔洞以允许返回连接。

正如 Mikael Olsson 多年前在 FTP 助手中发现的问题一样,这是另一个问题,后来由本书作者等人对其他协议助手进行了研究。问题是,这些助手根据工作站通过特定协议发送到远程系统的信息决定在防火墙中打开孔洞。它们假设由系统产生的流量是在用户的代表和用户的知情下传输的。不用说,一些程序,如网络浏览器,可能会被欺骗发送某些类型的网络流量,包括看起来像程序不原生支持的协议的流量,甚至可以通过构建特定的恶意内容并将其发送到应用程序来强制其自动这样做。这种欺骗流量可能会欺骗助手程序在防火墙中打孔。

这种攻击的一个经典例子是滥用通用网络浏览器:通过在非标准 HTTP 端口(然而,对于 FTP 流量来说却相当标准)上添加对攻击者系统上某个网页或网页元素的引用,客户端可以被强制连接到这个资源并尝试发出 HTTP 请求。因为建立的连接端口通常用于 FTP,防火墙的 FTP 助手开始监听对话,希望在必要时提供帮助。

以下示例 URL 会导致 HTTP 客户端连接到 FTP 端口并发出看似 FTP PORT命令,这将由防火墙助手捕获:

HTTP://SERVER:21/FOO<RETURN>PORT MY_IP,122,105<RETURN>

客户端发出的请求对另一端的合法 FTP 服务来说将毫无意义,而该服务的响应对发起此请求的 Web 客户端来说也是无法理解的——但这不是重点。重要的是,攻击者可以控制请求的一部分——客户端将从服务器请求的文件名。这个由恶意者选择的虚构文件名可以包含恶意者希望包含的任何数据。通过使文件名包含通常与 FTP 请求相关联的子串,攻击者可以欺骗一个正在监听此连接以特定文本命令(PORT)的 FTP 协议助手,使其相信用户正在尝试下载特定文件。因此,远程服务器暂时被允许连接到受害者(在这里,到一个听起来很调皮的端口 31337——122*256+105=31337)。因此,我们让攻击者进入,而受害者却不知道。哎呀——又超过了我们的预期。


^([25]) 此行为的一些方面(对关闭端口上的意外和未预期的数据包回复 RST,以及对监听连接端口的相同流量简单忽略)是由 RFC793 规定的,而另一些则是特定实施者群体选择的做法。

伪装的后果

所述的所有场景都与伪装滥用有关,但伪装本身的存在可以为我们提供有关另一方的有趣信息。

如前所述,伪装不是非侵入性的。其基本操作原理是通过重写部分流量来改变出站流量。在这个过程中,它不仅超越了仅仅调整地址,而且不仅使得可以得出伪装正在进行的结论,而且还使仔细的观察者能够识别正在使用的特定防火墙系统。具体来说,当使用伪装时,我们可能会遇到以下一些变化:

  • 到达数据包的 TTL(时间到寿命)与预期或测量的目标网络距离之间将存在观察到的差异。在伪装背后发起的流量至少比直接从受保护网络获取 IP 地址用于出站连接的系统发起的包“老”一个跳数。

  • 在大多数情况下,原始网络中可以找到各种操作系统或略有不同的系统配置(或运行时间)。这些系统具有略微不同的 TCP/IP 特性,如第九章“外国口音”和第十章“高级羊计数策略”中所述。如果我们观察到从同一 IP 地址看似发起的连接中存在各种 TCP/IP 指纹,我们可以得到一个强有力的提示,即在其内部网络背后的特定机器上是否存在 NAT。

  • 最后,远程观察者可能会注意到源端口偏移。这是一种不寻常的异常情况,因为它是由来自网络的连接使用临时源端口引起的,而这些端口不在特定操作系统的正常范围内。

每个操作系统都为所有出站连接保留了一组特定的源端口,以建立本地端点标识符。然而,防火墙通常使用不同的端口范围来映射伪装连接,这取决于 NAT 设备的操作系统。在这种情况下,如果观察到的范围与检测到的操作系统预期的范围不符(例如,如果 Linux 通常在 1024 到 4999 的范围内运行,但看起来正在使用非常高的端口号),则可以推断出存在地址转换,有时甚至可以确定正在使用的防火墙类型。

这些技术通常被使用,并构成了伪装检测和伪装网络侦察的基础。但还有其他几种检测数据包重写的方法。

分段大小轮盘赌

检测数据包重写设备和了解更多关于网络配置的较不明显且因此不太受欢迎的方法之一是分析传入流量中的最大分段大小字段。

由于 IP 数据包分片会给分片流量增加明显的开销,因此它通常被视为性能噩梦,许多实施者试图防止它。另一方面,如前所述,由于似乎几乎不可能在通信之前准确、快速、可靠地确定路径上的最大传输单元(MTU),因此分片难以消除。即使是最好的方法,路径 MTU 发现,也远非完美,并且在触发时仍然会影响性能。为了通过试错来检测正确的 MTU 设置,可能需要丢弃一些不合适的包并重新发送。

为了防止路径 MTU 发现对性能和可靠性的影响,并减少分片的开销,许多重写出站流量某些参数的 NAT 防火墙也会更改从私有网络发起的连接上的 TCP 头部中的声明的最大分段大小(MSS)参数,使其更适合网络的对外链路。这个新设置可能比局域网的设置略窄(MTU 更低)。这种修改确保接收方不会尝试发送无法通过该链路传输的数据,如果该链路跨越具有最低 MTU 的特定基础设施部分,从而降低分片发生的可能性。(这假设任何 MTU 不兼容性最有可能发生在所谓的最后一英里处的发送方或接收方系统上,在那里经常可以发现各种类型的低 MTU 链路,如 DSL 连接或无线线路,数据包可能需要“缩小”以通过这些管道。)

仅 MSS 的这种减少并不容易检测。事实上,无法确定 MSS 是否被发送方设置为特定值,或者在某些地方被修改。也就是说,除了一个微小的细节。回想一下第九章中提到的,许多今天系统的窗口大小选择算法有一些特殊之处:

窗口大小设置决定了可以发送而不需要确认的数据量。具体的设置通常根据开发者的个人神秘规则和其他宗教信仰来选择。两种最流行的方法认为该值应该是 MTU 减去协议头部的倍数(称为最大分段大小,或 MSS)或者简单地足够高且“圆整”。Linux 的旧版本(2.0)使用的是 2 的幂次方(例如,16384)。Linux 2.2 版本改为 MSS 的倍数(11 或 22 倍 MSS,原因不明),而 Linux 的新版本通常使用 2 到 4 倍的 MSS。网络功能齐全的 Sega Dreamcast 使用 4,096 的值,而 Windows 通常使用 6,4512。

今天越来越多的系统(包括 Linux 和 Solaris 的新版本,某些版本的 Windows 和 SCO UnixWare)使用的是 MSS 的倍数作为窗口大小设置。因此,当数据包中的 MSS 设置被篡改时,很容易就能发现,因为结果数据包的窗口大小将不再是 MSS 的特定倍数。实际上,它可能根本不再能被 MSS 整除。

通过比较 MSS 和窗口大小,你可以可靠地检测到支持 MSS clamp(调整以匹配链路)的一组防火墙存在于各种系统中。尽管在 Linux 和 FreeBSD 上 clamp 是可选的,但家庭防火墙、智能 DSL 路由器或其他家庭网络通常会自动执行。因此,异常的 MSS 设置不仅表明存在一个包重写设备,而且还与 NAT 功能相关联,这可以被视为发送者网络连接的指标。

状态跟踪和意外响应

状态连接跟踪和包重写的一个重要后果是,一些由 RFC 规定的响应是由防火墙而不是发送者生成的。这使得攻击者能够相当高效地发现和探测这样的设备。当一个连接从 NAT 状态表中删除(无论是由于超时还是由于一个端点发送了 RST 包,而这个 RST 包没有到达另一端)时,本会话中的进一步流量将不会转发给接收者,就像无状态包过滤器那样。相反,它将由防火墙直接处理。

TCP/IP 规范要求接收者对所有意外的 ACK 包回复 RST,以告知发送者,他们试图继续的会话不再被接收者尊重或从未被尊重。一些防火墙可能会违反 RFC,拒绝回复此类流量,简单地丢弃看起来不属于现有会话的包。(这并不总是明智的,因为它可以在合法连接由于间歇性网络问题而断开时造成不必要的延迟。)

然而,许多设备会回复一个合法且预期的 RST 包。这为检测和仔细指纹识别防火墙设备开辟了另一条途径。因为数据包是由防火墙从头创建的,所以其参数与防火墙相关,而不是与防火墙所保护的内容相关。这使得可以在第九章中讨论的传统指纹识别技术(例如检查 DF 标志、TTL、窗口大小、选项类型、值和顺序等)被用来识别防火墙。

根据 RFC1122,还存在另一种可能性:^([95])

4.2.2.12 RST 段:RFC-793 第 3.4 节

TCP 应该允许接收到的 RST 段包含数据。

讨论内容:有人建议 RST 段可以包含 ASCII 文本,该文本编码并解释了 RST 的原因。但目前还没有为这类数据建立标准。

事实上,尽管没有建立标准,一些系统在遇到意外的 ACK 时选择以冗长的(尽管通常是晦涩的)RST 消息回复,希望对方能够从知道出了什么问题中得到安慰。这些回复通常包括内部关键字,或者看起来像是某种奇怪的极客幽默尝试,可能是特定于操作系统的,例如no tcp, resettcp_close, during connect(Mac OS);tcp_fin_wait_2_timeout; No TCP(HP/UX);new data when detached; tcp_lift_anchor, can't wait(SunOS)。

每当我们看到在网络问题或意外发送到主机的流量中响应的这种冗长的 RST 数据包,并且我们知道它起源于的远程系统不使用这种冗长的消息时,我们就会得到一个提示。我们可以推断出在我们和接收者之间有一个设备,很可能是一个有状态防火墙,我们可以通过将响应与已知消息进行匹配来识别其操作系统,这些消息是由常见的和不太常见的操作系统产生的。

这两种指纹技术被证明在短期网络问题期间可以观察到网络流量时,在检测状态包过滤器的存在方面极为有效。这些技术还可以通过向目标发送意外的 ACK 数据包来用于主动指纹,而不针对防火墙设备本身,以区分无状态和有状态过滤器。根据目标对数据包的响应,攻击者可以制定最佳方法来接近防火墙(或以其他方式使用获得的知识)。

可靠性或性能:DF 位的争议

路径 MTU 发现(PMTUD)是一个与第九章中描述的 IP 分片避免方案密切相关的指纹场所第九章。

Linux 内核的最近版本(2.2、2.4、2.6)和 Windows(2000 和 XP)默认实现并启用 PMTUD。因此,除非更改此设置,否则从它们起源的所有流量都将设置不进行分片(DF)位。再次强调,路径发现算法在罕见但并非完全未听闻的情况中往往会引起问题。

路径 MTU 发现失败场景

PMTUD(路径最大传输单元发现)的问题在于,它依赖于数据包发送者接收 ICMP 错误消息“需要分片但设置了 DF 位”的能力,并确定连接的最佳设置。触发该消息的数据包在到达目的地之前被丢弃,并需要重新调整大小后再次发送。

如果发送者没有收到这条消息,他们就会对自己的数据包没有通过保持无知。这最多会导致延迟,在最坏的情况下会导致连接无限期地锁定,因为重传也不太可能通过一个最大允许数据包大小小于发送者试图推送的数据包大小的链路。

当数据包对于链路来说太大时生成的 ICMP 消息并不保证能到达发送者。在某些网络中,由于一个旨在提高安全性的不切实际的尝试,所有 ICMP 消息都被简单地丢弃。最后,即使设备发送了这样的消息,它也可能无法送达。

为什么会丢弃 ICMP 消息呢?因为历史上,许多此类消息都已知会导致安全问题:某些过大的或分片后的 ICMP 数据包在许多系统中损坏了内核内存(也称为“死亡之 ping”)。发送到广播地址的 ICMP 消息也被用于触发名为“Smurf”的攻击中对伪造源地址的响应风暴,以及执行 DoS 攻击。此外,配置不当的系统通常将特定类型的 ICMP 广播,即路由器通告消息^([26])解释为修改其网络设置的命令。由于它们会接受这些消息,无论这些消息是否可信,这又开辟了另一个有趣的攻击途径。因此,ICMP 被许多人所恐惧并被阻止。

注意

在一些简单的安全指南中,经常会建议拒绝所有 ICMP 流量,并且一些系统管理员会遵循这一建议。我甚至在一个著名审计员的职业渗透测试建议中看到过这一点,遗憾的是,我无法在此透露他的名字。

另一个问题可能会使 PMTUD 变得不可靠,那就是一些接收到的错误消息来自使用私有地址空间的设备。有时,为了保留有限的公共 IP 地址空间(这通常很昂贵),连接远程网络的路由器和防火墙的电缆接口是从为私有、本地使用保留的地址池中选择,而不是从实际上从外部世界路由到特定网络的地址中选择。

不幸的是,使用私有地址空间可能会破坏 PMTUD。为什么?因为如果来自外部世界的数据包太大,无法由接收方的防火墙转发到目的地,防火墙会发送一个带有防火墙自身源地址的 ICMP 错误消息,该地址属于私有地址池。原始数据包发送方的防火墙随后可以拒绝这样的响应数据包,因为它看起来来自外部世界,但带有来自私有地址池的 IP 地址(可能甚至来自与发送方私有 LAN 相同的池)。防火墙拒绝这种流量,因为它通常是一个试图冒充受信任内部主机的欺骗尝试的迹象。然而,在这种情况下,这个决定破坏了一个相对较新的 PMTU 发现机制,并使原始发送者无法意识到他们的数据包没有通过。

更糟糕的是,即使所有条件都恰到好处,并且数据包到达了目的地,许多现代设备也会限制 ICMP 响应速率,并在特定时间段内不会发送超过一定数量的消息。这也被实施作为一种安全措施。由于 ICMP 消息最初是为了信息目的而设计的,并且在 PMTUD 算法引入之前并不对通信至关重要,因此速率限制似乎是一种合理的防御某些类型的 DoS 或带宽饥饿攻击的方法。

与 PMTUD 的斗争及其后果

鉴于上述情况,有些人认为 PMTUD 是一个相当糟糕的设计。它带来了一点点性能提升,但代价是偶尔出现但持续且通常难以诊断的问题,这些问题可能会阻止用户访问特定的服务器或导致他们的连接意外中断。尽管已经设计了多种“黑洞检测”算法来检测应该禁用 PMTUD 的主机或网络(并且这些算法的效果各不相同),但这并没有完全解决问题,并且可能会引入额外的延迟——通常在不太希望的时候。

为了解决这些问题并避免投诉,一些商业防火墙供应商配置他们的解决方案执行一个肮脏的伎俩:他们在所有出站流量上清除 DF 标志。这是一个微妙且通常受到赞赏的修改,但也是一个很好的方法来识别存在数据包过滤和重写设备。如果在某个地址或某个网络观察到启用了 PMTUD 系统的特征,但预期的入站数据包缺少 DF 标志,那么细心的观察者可以推断出防火墙的存在和类型,从而获得另一小块数据,而无需与受害者有任何交互。


^([26]) 路由器广告的目的是允许网络主机自动配置,而无需手动输入任何设置。路由器定期或根据请求广播一条消息,内容是:“我在这里。使用我。”默认情况下,一些系统在几乎没有犹豫的情况下接受未经请求的广告——这是一个糟糕的想法。

思考材料

这就结束了关于如何通过使防火墙更好、更强大以防止渗透和直接侦察,同时也使它们更容易通过间接评估来检查的简短故事。但请允许我简要地偏离一下。

可能最奇特且有趣的发现之一是我 1999 年左右在某个地方遇到的。尽管这与防火墙的设计没有直接关系,但它仍然为对被动指纹识别临时系统问题感兴趣的人提供了有趣的思考材料。

我曾短暂地与 Jacek P. Szymanski 共事,后来我有幸与他讨论了一些不寻常和可疑的网络流量模式^([27]),他注意到端口 21536(以及在一定程度上,如端口 18477 或 19535)接收到的严重损坏的 TCP/IP 数据包突然增加。这些损坏的数据包总是来自端口 18245、21331 或 17736,并且来自由波兰国家电信运营商 Telekomunikacja Polska 运营的大量拨号地址空间中的系统。

一旦捕获了几次这些数据包,流量就变得严重且奇怪地扭曲。数据包到达时 IP 头部完好(协议类型设置为 TCP),但紧接着就是 TCP 有效载荷——TCP 头部消失了。观察到的端口组合是由将有效载荷的前四个字节解释为一对数字(如果那里有 TCP 头部,则对应源端口和目的端口组合)得出的。这对 18245 和 21536 仅是文本字符串“GET ”的表示——这四个字符是大多数通过网络传输的 HTTP 请求的开头。同样,18477 和 21331 代表 SSH-,这是每个 Secure Shell 会话的开头短语。而 19535 和 17736 代表 EHLO,这是一个打开所有 ESMTP(扩展 SMTP)会话的命令。

然而,这种类型的流量突然出现的原因仍然是个谜。同样,为什么它只来自这个特定的网络?而且,如果某些网络设备确实产生了这种类型的包损坏,为什么这种类型的包损坏不会导致用户连接问题或其他不便?

答案很快就来了。结果证明,所有观察到的流量都来自 Nortel CVX 设备,这是一种调制解调器接入系统,该电信运营商已经开始使用。问题仅在负载过重时偶尔发生。因此,只有一小部分不完整的数据包被发送,而且只有这么少的数据包到达了接收者(令他们非常惊讶)。最可能的原因是不正确的队列锁定或缓冲区管理,只有在大量会话几乎同时处理时才能注意到这个问题。在这种情况下,某些数据包似乎发送得太早,还在“建设中”,或者被实现方式扭曲了。

该公司在波兰部署后不久就修复了他们的 TCP/IP 实现,从此之后一切顺利。但正如你可以想象的那样,他们并不是第一个也不是最后一个在传输的数据包中无意中留下系统独特印记的公司。

这个故事的意义在于,忽视我们通常忽略的东西再次显得过于天真。在当今的联网世界中,细微的暗示、不寻常或出乎意料且未解释的观察结果极其宝贵。它们很容易找到,但很难分析。

也许值得思考并进一步探索的领域是各种用来阻止系统指纹识别的方法。各种防火墙供应商都试图通过调整各种 TCP/IP 参数(如互联网协议 ID、TCP 序列号等)来整合抗指纹识别措施,以改变一些数据包的特征。不用说,这种解决方案实际上帮助了攻击者,并产生了与预期完全相反的结果:除非所有易受指纹识别的特征都改变并统一(包括序列号、重传时间、时间戳值等),否则不仅可能检测到底层操作系统,还可以检测到用于保护网络的防火墙。

C’est la vie.


^([27]) 在某个时候,这种合作导致了一个松散的波兰研究小组的创建,该小组在 1999 年和 2000 年期间通过网络关联、跟踪和试图解释许多奇怪的意外流量模式。

第十二章。堆栈数据泄露

又一个关于如何找到我们根本不想发送出去的东西的短篇故事

有时候,找到关于你的网络同胞和他们所在之处的微妙但引人入胜且有用的提示,只需要一点运气。至少,这对我来说是如此,我在 2003 年发现了一个相当有趣且极其难以捉摸的信息披露向量,那是在几周令人沮丧的搜索之后。

克里斯蒂安的服务器

首先。几年前,我请求我的一个朋友,克里斯蒂安,让我在他的机器上使用一些磁盘空间,这样我就可以在一个可靠且快速的系统上托管我的几个项目。他同意了,不久之后,我开始逐渐将我的大部分程序和论文转移到它们的新家。在我转移的项目中,有一个是我被动操作系统指纹识别工具 p0f 的新版本(你可能还记得第九章中的“Chapter 9. 外国口音”)。这个不起眼的小工具实现了一些有趣的被动分析技术,但要真正强大,它需要附带一个强大且最新的操作系统签名数据库。手动维护它很困难,我很快就没有了可以指纹识别并添加到其中的神秘系统。

幸运的是,虽然收集用于主动指纹识别软件的签名通常需要与目标进行令人反感的交互(引起争议、拉扯网络链路,有时还会导致特别糟糕实现的 TCP/IP 堆栈崩溃),而被动指纹识别则无需此类行动,并且可以在连接到 Kristjan 系统以获取我页面的所有系统上轻松执行。为了鼓励提交,我设置了一个子页面,任何用户都可以立即看到他们的指纹,并纠正系统报告的方式或添加新的签名。这个页面证明是收集签名和改进软件的绝佳方式,但这并不是故事的结局。

在一个奇怪的事件转折中,Kristjan 决定在他的系统上托管一个不同的、盈利性的网站,以便他的系统能够支付自己的账单。正如你可能想象的那样,该网站根本不是致力于网络安全、园艺或其他高尚事业。相反,它关注我们生活中一些不那么显赫,但可能更具吸引力的方面:性、裸露和与之相关的一切。我欣喜若狂,就像任何自重的极客一样,不是因为他所提供的内容,而是因为数百万个连接签名在几小时内开始涌入,由我正在开发的软件进行分析。阿门!

惊人的发现

宁可安全也不后悔:在设计 p0f 的新代码时,我决定实施一系列健全性检查,以检测传入流量中甚至最奇特、最不可能或闻所未闻的模式,涵盖所有可能的非法或无意义的 TCP/IP 设置组合。尽管常识建议我永远不应该遇到参数以奇特方式损坏的包(至少在与流行且因此经过良好测试的系统通信时不会),但实施此功能似乎并无害处。此外,如果一个系统确实被发现正在发送表现出特定类型异常的包,检测它的能力将提供一种很好的方法来区分这个特定的操作系统和外观相似但不存在这种缺陷的实现。

在这个神圣的签名风暴的欢乐月份里,我看到了最奇怪的事情。我最终设法解释了一些这些事情,并为 p0f 记录下来,而有些则仍然是个谜。我之前实施的多数异常检查都准确无误,我立即找到了确实共享更多不寻常 TCP/IP 实现怪癖的系统。但有一件事特别令人不安,难以置信,所以我决定更加关注它。

两个测试——一个是在 TCP/IP 头部 ACK 标志未设置时检查 ACK 值(实际上是一个徒劳的行为),另一个是在 URG 标志未设置时检查 URG 值——一开始似乎相对没有意义,从未产生有趣的结果,直到我注意到一些非常不寻常的事情。我发现一些连接到 Kristjan 服务器的 Windows 2000 和 XP 系统,有时在既未设置 ACK 标志也未设置 URG 标志的数据包中,URG 或 ACK 值不为零(最值得注意的是,SYN 数据包用于打开新的连接)。

当未设置相应的标志时设置 URG 或 ACK 值并不严格是一个问题。根据 RFC793,在这种情况下,这些值将完全失去意义;例如:

紧急指针:16 位

此字段表示当前紧急指针的值,作为从该段序列号的正偏移量。紧急指针指向紧急数据后的八位序列号。此字段仅在设置了 URG 控制位的段中解释。

RFC793 以一种非常特殊的方式告诉我们,这种异常不太可能导致任何网络问题,因此它可能永远没有被注意到。但我就注意到了,因为它有点奇怪。

我最初认为某个特定的网络设备是问题的原因,就像在第十一章中描述的大多数问题一样,但事实并非如此。这些击中来自单个系统,而不是整个网络,并且它们不是持续的;它们只是在几个数据包(值保持不变或随机变化)中显示,然后消失,在随后的连接中再也没有出现。此外,这个问题似乎仅限于 Windows;在表现出这种问题的系统组中,根本没有任何少数操作系统。

我发现自己花了一周又一周的时间试图追踪这个问题。作为我的搜索的一部分,我在更受控的环境中部署了一些其他安装;令我惊讶的是,即使在本地网络中,即使在最新的系统中,这个问题也出现了,尽管只是短暂的时间。用户无法回忆起当这种类型的流量从他们的系统中发生时,他们做了任何不寻常的事情,我也无法追踪到任何特定的通信或一系列动作来触发它;似乎没有模式。

令人困惑。

揭示:现象重现

我几乎要放弃了。我将我的观察结果发布到几个公共邮件列表(最著名的是由 Security Focus 主办的流行漏洞讨论列表 VULN-DEV),寻求其他研究人员进一步分析和反馈,但并未得到任何结果。然后,仅凭运气,我在处理一个完全不同的问题时,捕捉到我的一个测试站正在生成这种确切的行为模式。我恰好有一个嗅探器在后台运行(我们难道不是都有吗)。

很快,我就有了诊断结果:当工作站在进行后台文件传输或其他网络密集型操作时尝试建立连接时,问题发生了。在几乎所有的操作系统中,要发送到线上的数据包首先在系统主内存中构建,使用的是静态缓冲区(内存中用于此目的的固定位置)或动态缓冲区(根据需要分配,可能之前用于其他目的的内存)。在这个特定场景中,当两个连接几乎同时发生时,用于在发送到网卡之前构建出站数据包的缓冲区似乎在使用前没有正确初始化;也就是说,它没有被清除任何剩余内容,因为缓冲区最后用于了不同的目的。实现代码假设缓冲区的内容都是零,并且不费心去接触那些它不需要初始化到特定值的(正如在相应的标志未设置时 ACK 和 URG 值的情况一样)。结果,一些剩余内容被发送到线上。

自然地,所有其他 IP 和 TCP 字段都已被正确初始化,正如它们应该的那样;只有 URG 和 ACK 被省略了,因为在这个特定上下文中它们没有相关性。但这个遗漏意味着一小部分属于不同连接(或计算机操作的另一个方面)的数据被发送给了另一方。这个问题仅在多个会话期间(在网页浏览、后台下载和类似场景中很常见)表现出来,而在系统空闲时则不会。

在这种情况下披露的信息的相关性有两方面:

  • 这可以被视为一个传统的信息披露场景。尽管在每包没有正确初始化 URG 和 ACK 值的包中披露的信息量可能相当小,并且不一定具有意义(除非缓冲区一开始就包含了一些有趣的内容),但在某些情况下可能是有价值的,尤其是在一个可以包含敏感信息,甚至可以触发漏洞的外部实体同时进行的会话中。

  • 这个漏洞可以被认为是一个方便的指纹识别指标,它揭示了关于操作系统及其状态的额外信息——一种简单的方法来区分大量使用网络的系统和空闲系统。

就这样。尽管这个发现的重大意义可能容易被高估,但我决定将其包含在这里,以体现其娱乐价值,并说明即使不询问,从远程方获取复杂数据是多么容易。

思考的食物

很容易将责任归咎于开发者。尽管开发者没有正确初始化内存是自然错误的,但将 TCP 中字段的“启用器”分开的概念可能本身就是 TCP 本身的设计缺陷,并可能导致这类问题。类似的微妙之处困扰着协议规范,正如在第七章(第七章。交换网络中的安全)中所示,其中类似类型的漏洞是由于过于紧密地遵循规范而造成的,没有充分考虑其潜在的副作用。

第十三章。烟雾与幻影

或者如何优雅地消失

到目前为止讨论的许多信息泄露场景都需要仔细分析远程系统发送的信息,以便推断发送者的某些事实或拦截他们最初未意识到的额外数据。然而,在几种情况下,只能收集到某些形式活动的环境证据。正如在第一章(第一章。我能听到你在打字)和第二章(第二章。额外的努力永远不会被忽视)中讨论的那样,通过精确解释这些证据,你可以确定用户或处理敏感数据的应用程序的可能位置,从而间接揭露受害者的机器的秘密,而无需访问数据本身。

IP 的一些特性使得许多其实施方案容易受到环境证据信息泄露漏洞的影响,这与我们之前在某种类型的系统伪随机数生成器或可变复杂度数据处理算法中看到的情况非常相似。仔细观察并解读这些信息可能是有益的,至少可以为我们提供关于对手一般习惯或他们参与的特定活动的急需情报。

迄今为止,本书的这一部分主要关注需要直接观察来自发送者的流量,尽管通常不需要与受害者交互的 IP 层攻击。然而,在本章中,我们将窥视一种非常活跃但间接的基于 IP 的攻击,攻击者通过对其无法看到的内容做出明智的猜测来对其受害者进行画像。他们通过与一个无辜的旁观者互动来实现这一点,这个旁观者不是测试的真正对象,并且没有得到这一方的同意或知情,从而了解他们关于实际受害者的信息。

这种方法听起来并不是收集数据的简单方法。所以,本着极客精神,为什么不选择风景优美的路线,尽管可能需要更长的时间,并且更详细地研究它呢?

滥用 IP:高级端口扫描

恶意网络用户经常使用端口扫描进行攻击前的侦察和系统指纹识别。在端口扫描时,潜在的攻击者尝试与系统上的每个端口建立短暂的连接,并绘制出所有监听网络流量的程序。通过这种方式,他们可以确定攻击目标,通过找到系统上任何有漏洞或可能有趣的网络服务。此外,在许多情况下,他们还可以确定受害者正在使用哪种操作系统,因为默认服务通常是针对特定操作系统的。

传统扫描的第一个问题是它相当嘈杂——受害者很可能会注意到对不寻常端口的连接尝试风暴或甚至持续的流动。隐藏也不容易;攻击者必须能够看到他们对 SYN 数据包的响应,以确定端口是打开的还是关闭的。打开的端口会响应 SYN+ACK,关闭的端口会响应 RST,而通过防火墙过滤的端口可能会产生没有响应或互联网控制消息协议(ICMP)消息。因此,攻击者不能简单地伪造所有传出数据包的源地址;他们必须通过提供可以路由回他们正在监听以接收传入流量的网络的源地址来揭示他们的身份。

森林中的树:隐藏自己

不论是出于好奇(例如,查看竞争对手正在运行什么操作系统)还是跟随攻击尝试,他们通常都希望留下尽可能少的痕迹并避免引起受害者的警觉。网络管理员和某些当局通常对主机和网络扫描持相当负面的看法。尽管关于这些扫描是否应被视为恶意行为仍在进行辩论,但在愤怒的系统管理员决定提交滥用报告或如果你的竞争对手确定你的员工试图探测他们的网络时,好奇的测试者几乎总是输掉比赛,无论其真实意图和进一步的计划如何。

混淆端口扫描的一种常见方法是部署“诱饵”扫描,其中攻击者从多个虚假地址以及他们的实际 IP 地址向每个端口发送 SYN 数据包。受害者将这些伪造的数据包处理得就像真实数据包一样,只不过伪造数据包的响应当然会被发送到空旷无人的地方。因此,受害者很难确定真正是谁在背后进行扫描,因为他们必须通过仔细分析或简单的试错来消除所有诱饵系统从数据包源列表中。尽管如此,只要有决心,还是可以在不求助当局的情况下定位发送者,尽管攻击者希望通过使完全解决这样一个小事件变得过于耗时来阻止受害者。

空闲扫描

防止被发现的最终极手段,就像它经常做的那样,来自于一个手头时间太多并且浪费时间去阅读协议规范而不是做些有用事情的人。因此,一种叫做“空闲”扫描的技术应运而生。最初由 Salvatore“antirez”Sanfilippo 在 1998 年设计,它很快就被广泛实施,并在黑客中(无论是好奇的还是恶意的)变得相当流行。([3]

空闲扫描基于一个重要的观察。引用 RFC793:

作为一般规则,当到达一个明显不是当前连接意图的数据段时,必须发送重置(RST)。如果情况不明确,则不应发送重置。

传输控制协议 RST 数据包用于无条件终止连接,并告诉发送方停止任何进一步的通信尝试。系统在几乎没有犹豫的情况下,根据 RFC793 中的规则,在遇到意外流量时发送 RST。(当然,RST 数据包本身,即使是不预期的,也不会被回复;如果回复了,那么在轻微的网络中断时,RST 数据包会来回弹跳。)

空闲扫描利用并巧妙地滥用了一个旁观者,即见证主机,会以这种方式处理所有意外数据包的事实。这种攻击使得恶意网民能够扫描他们不打算直接与之通信的受害者。当进行空闲扫描时,攻击者使用一个不知情且随机选择的互联网系统来扫描第三系统(真正的受害者),而永远不会透露他们的真实身份。

空闲扫描是这样工作的:攻击者伪造一个 SYN 数据包到他们想要在受害系统上检查的特定端口。这个数据包是针对受害主机的,但使用的是见证系统的伪造回传地址而不是攻击者的系统。单凭这一点听起来并不是一个很好的完成任务的方法,但请稍等片刻。

接下来会发生什么取决于端口是否开放:

  • 如果受害系统的被探测端口向见证主机回复了 RST,见证主机会收到它,并且只是默默地思考这个 RST,而不向受害系统发送任何回传流量。

  • 如果被探测的端口是开放的,受害系统会回复 SYN+ACK。见证者,带着极大的怀疑,得出结论,他们一开始根本就没有发送 SYN 数据包,因此发送 RST 来指示受害方他们犯了一个严重的错误,并且最好立即停止。受害者尴尬地接受了这个响应,并丢弃了它希望接受的连接的所有记录。

这种区别的相关性一开始很难理解。但回到第九章,并回忆一下 IP 头中的一个字段的相关信息:

标识号(ID)是一个 16 位值,当发生分片时用于区分 IP 数据包。如果没有 IP ID,如果两个数据包同时分片,重新组装将会严重扭曲、交换或以其他方式损坏同时分片的数据包的片段。IP ID 唯一标识不同数据包的几个重新组装缓冲区。用于此目的的值通常是通过每次发送数据包时递增计数器来选择的;系统发送的第一个数据包的 IP ID 为 0,第二个为 1,依此类推。

因为攻击者选择了一个确实使用这种 IP ID 选择方案的见证主机(可供选择的人选有很多),他们现在可以轻松地确定见证主机是否在给定时间段内发送了 IP 数据包。他们通过在真实探测前后向见证系统发送一些无意义的流量,并比较它发送的响应中的 IP ID 值来实现这一点。如果观察到的两个 IP ID 只相差 1,则见证系统在这之间没有发送任何数据包。然而,如果差异超过 1,确实交换了一些数据包,尽管我们无法确定是与谁交换的。

攻击者也可以在向受害者发送伪造数据包之前和之后立即发起探测。因此,他们可以根据见证主机的回复确定端口是开启还是关闭。如果见证主机的 IP ID 增加了,它很可能向受害者回复了一个 RST,这意味着受害者最初必须向伪造的数据包发送了 SYN+ACK。然后攻击者可以得出结论,该端口是开放的。另一方面,如果见证主机产生下一个预期的 IP ID,则它没有从受害者那里收到任何流量,或者它决定忽略接收到的 RST 数据包。

当然,还有一些实际考虑因素。最重要的是,见证主机在空闲扫描期间应该相对空闲,并且测试应该重复进行几次以消除假阳性;否则,我们可能会错误地将见证方的一些第三方通信解释为告诉我们受害者的某个端口是开放的。

注意

然而,这两个问题都没有证明是多大的问题,许多高级工具(从 1999 年的 idlescan 开始,现在还有巧妙的 NMAP)实现了空闲扫描,并且做得很好。

空闲扫描的重要性在于,它可以通过实际抑制来自攻击者的任何可识别通信,而不是仅仅试图阻止受害者,来模糊扫描的来源。这使得在没有目击主机所有者(它本身可以作为合法流量的一部分,如 HTTP 会话,被攻击者查询 IP ID,因此很难确定它是否被用作攻击工具)或外部实体(执法机构和 ISP)的帮助下追踪攻击者变得更加困难。由于执法响应通常是在系统被入侵后而不是仅仅被探测(好奇的竞争对手可以安心睡觉)时启动的,并且需要受害者承认系统被入侵(这对某些大型公司来说并不总是方便的),因此攻击者感到相当安全。

注意

尽管最初看起来与常规的 SYN 扫描在提供的结果上没有不同,但空闲扫描提供了一个相当独特的扫描视角。使用目击者扫描使得从目击者的视角观察目标系统成为可能。如果目击者对受害者系统有更高的访问权限(例如,它是一个位于防火墙后面的受保护网络内的系统,或者是一个为了便于访问企业网络而设置了某些宽松的 IP 过滤规则的系统等),你可以使用空闲扫描来发现受保护网络的内部运作。

防御空闲扫描

目前还没有针对空闲扫描的直接防御措施,也没有简单的方法来区分它和常规的 SYN 扫描。然而,通过使用随机或固定的 IP ID,可以很容易地防御成为目击主机,正如在第九章中讨论的那样。尽管这样做不会使针对你的攻击——或者一般的攻击——变得更加困难(许多系统总是会使用连续的标识符),但它将防止你的网络被滥用。

为了避免防火墙绕过(“视角”)攻击,在设计外部系统的访问通道时使用常识,并在网关系统上使用适当的入口过滤,丢弃所有来自互联网且源地址似乎属于受保护网络的数据包。尽管,如前所述,这种过滤可能会破坏路径最大传输单元(PMTU)发现机制,但它通常解决的问题比它破坏的问题要多。

思考食物

尽管不太可行,但仍然可以使用 IP ID 来对 IP 活动进行一般性分析。实际上,当受害者与远程系统建立交互会话时,IP ID 甚至可以用来计时按键或类似操作,从而将这种技术转变为之前讨论过的定时攻击场景之一。同样,通过测量特定主机在两次访问受监控网络之间的数据包数量,你可以增强用户跟踪能力。

你也可以在某些系统上使用 TCP 序列号来实现与 IP ID 分析相同的功能,具体取决于 ISN 生成器的设计。我鼓励你更详细地探索这个想法。

至于追踪空闲扫描(或任何其他欺骗性攻击)的源头,请参阅第十七章。

第十四章。客户端识别:请出示您的证件!

看穿薄薄的伪装在许多场合可能很有用

确定软件的真实身份及其合法性的挑战,在运行该软件的计算机上可以相当容易地解决。但在网络上这样做就不那么容易了。

系统管理员和应用程序开发人员经常试图识别网络会话另一端的软件,成功率各不相同。我们试图识别软件有几个原因。对于万维网(World Wide Web),最常见的目标是根据所使用的渲染引擎优化提供给客户端的内容——无论该内容是合法的还是恶意的。在许多其他通信方案(如即时消息、邮件客户端等)中,识别客户端的目标是确保政策合规性,并检测来自可能危险或不可接受的应用程序的通信。最后但同样重要的是,程序员 themselves 尝试识别软件以防止未经批准(或未经许可)的软件使用特定的网络服务(可能剥夺他们的一些收入)或检测此类情况并采取纠正措施。

识别对方的最为简单和常见的方式是检查远程系统自愿提供的信息。这些信息可能包括简单地注意到服务器提供的“欢迎”横幅,查看客户端发送的协议头部(例如电子邮件中的 X-Mailer,WWW 会话中的 User-Agent 等),以及分析服务在响应某些类型流量时使用的文本状态、错误或警告信息。^([[28])]不幸的是,第一种方法极其不可靠,并且很容易被试图隐藏某些信息的使用者破坏;最后一种方法侵入性很强,而且很难在不引起问题的情况下用于客户端。(大多数客户端软件在遇到第一个错误条件时会退出并抱怨;由于尝试识别他们的软件而遇到错误信息且无法合法访问服务的用户,不会留下深刻印象。)

伪装

检查客户端产生的文本公告并不可靠,不仅是因为用户可以通过伪装他们的互联网软件(网络浏览器、邮件客户端等)来模仿最流行客户端的响应,而且因为他们通常也有很好的动机去尝试:要么与大众融为一体,要么简单地欺骗那些通常更清楚访客需要运行哪个程序版本的服务器。这样做很简单,要么使用客户端内置的功能,要么使用众多免费工具之一修改程序源代码或二进制文件。

同样,由于许多企业环境已经开始实施更严格的内容过滤以阻止不受欢迎的流量,一些从事更具争议性应用的程序员为了应对这种情况,开始模仿无害的软件。不久前,对等音乐共享应用、恶意木马和间谍软件开始假装成最普遍使用的网络浏览器,微软互联网浏览器,在其出站通信中。对于全球范围内许多由低劣营销业务使用的地址收集型网络爬虫来说,情况也是如此。

其他协议也受到模仿者的困扰。不出所料,大多数被垃圾邮件发送者和骗子所憎恨的大规模邮件发送软件假装成微软 Outlook、PINE、Mutt、Eudora、The Bat! 或 Netscape Mail 等程序。基本前提是隐藏在伪装之下,以避开网络管理员,如果他们意识到软件的存在,会发现很容易将其阻止。没有理智的垃圾邮件发送者会宣布他们的电子邮件来自“伯尼叔叔臭名昭著的大规模邮件发送者,终极版”,因为这会很容易被用户或垃圾邮件过滤器过滤掉。

接近问题

由于修改程序返回的基本文本响应和横幅非常简单,我们需要找到一种比简单的文本响应匹配更好的方法来检测欺诈行为,以便以合理的准确性识别客户端软件。仅仅检查不那么明显的参数或响应的解决方案注定会在某个时候失败:尽管在几乎所有情况下,都可以设计一个单独的检查来识别特定类型的不可取软件,但三头怪物会取代被砍掉的那一个。

很快,尝试解决恶意软件的每一个版本都变得不切实际。在某些情况下,通过简单地检查明显表明我们希望防止的滥用类型的模式,可以实现一般的恶意客户端检测:合法邮件客户端与垃圾邮件发送软件之间的区别在于,前者不太可能一次性发送 1,0000,000 封邮件。然而,这种方法非常有限:对于某些协议和一些明确定义的攻击,这可能像魔法一样有效;对于 WWW 流量,这又是另一回事,而且很难击中正确的位置,而不会导致过多的误报或漏检程序。

由于它被视为所有面向最终用户的互联网服务的核心,WWW 是少数必须对几乎所有用户开放的协议之一,因此,网页流量通常被不良应用程序用来伪装其在系统中的行为以及它们传输到远程主机的数据。网页浏览器触发到各种网站的连接爆发或每小时执行数千次请求并不罕见。同时,在单个简短的连接中发送敏感信息到远程主机也不是不可能的。在这里,流量分析几乎无法提供答案。

向解决方案迈进

考虑到这一切,区分间谍软件或特洛伊木马与合法应用程序可能极其困难。然而,实际上,有一些很好的工具可以精确地识别这类软件,从而使得感兴趣的相关方能够更准确、更精确地识别客户端应用程序。最有希望且最通用的方法,通常被称为行为分析(一个对旧式“时间模式”的华丽说法),旨在分析后续流量部分之间微妙内部依赖关系,而不是查看单个请求中的实际数据交换或在一段时间内连接的纯粹数量。因为这些依赖关系与内部算法和程序性能紧密相关,所以它们比我们可能检查的大多数其他指标更难以伪造。我将在本章中讨论这种方法,并提出一个基本的分析工具集,以实现这种准确性和详细程度,并以万维网流量作为一个方便的例子。

但在我们深入细节之前,我们需要一点背景知识。让我们快速浏览一下万维网(WWW)的历史、网络客户端的设计以及它们与服务器通信所使用的协议。这一切都要早于你的想象……


^([28]) 使用指纹分析响应的流行工具是 THC 的 AMAP;你可以在www.thc.org/releases.php了解更多信息。Fyodor 的 NMAP 可以通过分析横幅来识别服务。

网络的简要历史

万维网的概念并不特别难以理解:万维网背后的想法是让用户能够即时访问大量交叉引用、相互链接的文档,这些文档结合了不同类型的信息。这很简单。

我们今天所知道的万维网主要由文本和元数据(如对其他文件的引用、格式化元素、注释、动态或交互式元素)组成,通常还增强了各种多媒体(视频、音乐和各种应用程序)。它代表了我们的时代精神,标志着一种全新的沟通和信息查找方法。但万维网的想法并不新。它是在许多年前,在技术使电子文档实现这一系列功能成为可能之前就产生的——也许在电子文档甚至被认为是一个严肃的可能性之前很久。

根据万维网联盟(W3C)发布的时间线^([97)),超链接的概念最早是在 1945 年由范内瓦·布什提出的,当时他是二战期间及战后的科学研究和开发办公室的主任。他在《大西洋月刊》^([98))上讨论了这个概念。

布什提出了一种名为 Memex 的设备,这是一个个人、机电单元,实际上可以看作是今天 PDA 的早期前身。Memex 为用户的文档和个人文件提供存储,旨在提供直观的数据访问机制。Memex 的一个特性是能够创建和跟踪存储在缩微胶片上的文档之间的链接。由于某种原因,一个运行在缩微胶片上的极其复杂的机械设备的想法当时并没有真正流行起来。

超链接的概念在后来的几年里多次出现,导致在 20 世纪 60 年代出现了基于计算机的第一个实现。尽管这些尝试并不特别成功,但主要是因为当时所需的计算能力还需要几年时间才能让这项技术对用户有吸引力。

正确的时间是在 20 世纪 80 年代末。在微型计算机热潮之后,以及个人电脑平台的前夕,在核子研究欧洲委员会(Conseil Européen pour la Recherche Nucléaire^([29)),即 CERN)提出了一系列关于超链接可能性的谦逊建议。蒂姆·伯纳斯-李,CERN 的研究员之一,据说是官方上应该负责孕育超文本标记语言(HTML)的人,这是一种在文本文档中嵌入元数据、链接和媒体资源的一套控件。(说实话,HTML,我们今天所知道的网络的基石,几乎不是一个全新的设计,它借鉴了一些来自 1986 年 ISO 8879 标准的通用标记语言 SGML 的想法。)第一个网络浏览器随后在现在几乎无人知晓,但当时是一个创新且先进的计算机平台 NeXT 上诞生。这个浏览器被赋予了无处不在的名字——万维网。

现在我们想出了一个吸引人的名字,革命就势不可挡了。1992 年,伯纳斯-李提交了超文本传输协议(HTTP)的初始规范草案^([99]),这是一个用于封装 HTML 数据和其他资源在服务器到客户端通信中的工具。1993 年,几个网络浏览器引擎变得可用,一些网络服务器已经向好奇的访客提供内容。当然,HTTP 只占所有骨干网络流量的 0.01%,但它在增长!

第一个流行的网络浏览器 Mosaic 是在伊利诺伊大学的超级计算应用国家中心开发的。它借鉴了伯纳斯-李的代码,但增加了对除文本之外的内容的支持,并引入了可填写的表单和许多我们现在视为理所当然的功能。Mosaic 的代码最终演变成了 Mozilla,反过来,它又成为了 Netscape Navigator 的核心代码(后来分支成为开源项目 Mozilla,其代码库随后被用作后续几代 Netscape Navigator 的基础——简单,不是吗?)。与此同时,为了进一步混淆用户,一家名为 Spyglass 的公司将 Mosaic 转变为将成为 Netscape 主要竞争对手的 Microsoft Internet Explorer 的核心。

1994 年,为了监督 Web 的发展而设立的 W3C 组织成立。伯纳斯-李、Roy T. Fielding 和 Henrik Frystyk 在 1996 年提交了该协议的第一个官方、改进和扩展版本,随后很快是 HTML 3.2 规范。在随后的几年里,我们看到了 HTTP 和 HTML 的新版本和增强版本,现在由 W3C 管理。你们都知道故事的结局;或者这只是个开始?


^([29]) 瑞士日内瓦欧洲粒子物理实验室。

超文本传输协议入门

HTTP^([100]) 是一个令人惊讶的直接、基于文本的协议,建立在 TCP/IP 之上。该协议的客户端连接到远程服务器上的一个支持 HTTP 的服务,并发出请求,请求服务器上的特定资源。HTTP 请求在查询的第一行包含以下参数:

  • 访问资源的方发。通常,客户端只需发出一个 GET 请求来请求检索一个文件(尽管存在其他方法用于提交表单数据、执行诊断、在服务器上存储数据或执行某些扩展)。

  • 通用资源标识符(URI)。这是一个指向静态文件或指向请求主题的动态可执行文件的路径。如果文件是动态可执行文件,也可以将适当编码的额外参数作为 URI 的一部分传递给此程序。

  • 客户端支持的协议版本以及想要使用的版本。如果客户端使用的版本不受支持,服务器可以选择以较低的协议版本回复。(如果缺少此信息,则假定客户端正在使用 HTTP/0.9,这是协议的一个早期和过时的版本,我们在此不予讨论。)

例如,一个 HTTP 请求可能看起来像这样:

GET /show_plush_toys.cgi?param1=value&param2=this+is+a+test HTTP/1.1
Host: www.plush-penguins.com
User-Agent: Joe's Own Web Client (UnixWare)
Accept: text/html, text/plain, audio/wav
Accept-Language: pl, en
Connection: close

这个请求要求获取一个名为 /show_plush_toys.cgi 的资源,位于 www.plush-penguins.com。根据文件的 cgi 扩展名判断,这是一个动态执行的程序,通过两个参数(param1param2)调用,这些参数列在问号之后。

客户端请求可以(在这个例子中确实如此)后面跟着多个文本头部,每个头部一行,指定额外的参数。这些可以是任何东西,从客户端识别(如前面提到的 User-Agent 字段),到内容的首选语言(这里为波兰语和英语),到客户端引用的虚拟服务器的规范。(如果多个域名指向单个 IP 地址,这个规范使得服务器能够确定用户是否正在寻找www.squeaky-ducks.comwww.plush-penguins.com,这两个网站可能托管在同一个系统上。)

协议强制要求一些这些头部。所需头部的集合取决于其版本,但大多数服务器相当宽松,如果省略一些头部也不会大惊小怪。除此之外,一些头部指定了超出协议本身规范的功能。

每个请求都必须以一个空行结束,表示客户端头部的结束。在此之后,对于大多数类型的请求,服务器应处理查询并生成回复。服务器通常以与查询类似的结构返回消息,以 HTTP 返回代码和一些描述性文本开始,如下所示:

HTTP/1.0 404 Not Found
Content-Type: text/plain
Server: Uncle Mary's Cookie Recipe Server (Linux and proud of it!)
Date: Mon, 09 Feb 2004 19:45:56  GMT

The document you are looking for is nowhere to be found.

返回代码或消息可能报告各种条件,例如请求的成功完成、指示浏览器查找其他位置的指令,或者“文件未找到”或“权限拒绝”之类的错误消息。这些信息后面跟着一组与请求接受的格式类似的头部。这些描述了各种参数,例如服务器软件版本、浏览器应继续前往的位置、返回文件的内容类型规范、用于区分图像与纯文本或 HTML 文档与二进制文件的设置等。如果有实际内容,则紧随其后。

如您所见,基本的 HTTP 相当简单。尽管它确实提供了一些高级功能,但大多数要么有些奇怪,要么很少使用。(我猜您不会每天看到“402 支付所需”的错误消息。)然而,认为基本协议足以满足当今用户的需要和期望是过于天真的。

使 HTTP 变得更好

那个典型的网站由几千字节静态文本和一些次要的图形元素组成的时代已经一去不复返了。随着计算机变得越来越强大,而 300 bps 的调制解调器在博物馆里比在每家每户中更容易找到,形式已经开始在互联网上占据实质性的位置。数百千字节的照片、子页面、子框架和客户端脚本通常被用来使网站更具吸引力和专业性,但效果各不相同。对于许多网站来说,多媒体内容实际上已经成为提供的主要信息类型,而 HTML 仅仅为图像、视频、嵌入的 Java 程序或游戏提供了一个占位符。总的来说,互联网不再仅仅是告诉别人你的私人项目或兴趣的一种方式;其背后的驱动力是能够以比以往任何时候都便宜和快速的方式营销和销售产品和服务。而营销需要产品的引人注目的展示。

网络浏览器、网络服务器以及 HTTP 本身都必须适应这种不断变化的事实,以便轻松部署新技术并跟随新趋势。方便的是,在这个过程中引入的许多技术对普通人来说具有有趣的安保影响,也可以帮助我们以透明的方式识别线另一端的客户端。因此,我们必须考虑自互联网诞生以来引入的可选功能和扩展。

延迟减少:一个糟糕的修补

互联网和一些其他当前协议的问题在于,由单一多媒体网站提供给用户的必须从各种来源(包括完全不同的域名)获取,然后组合。网页的文本和格式信息与实际的图像和其他大量好东西(对于那些带宽有限且只想直接到达目的地的人来说,这种做法确实值得赞扬)是分开的。

这种情况使得客户端必须发出多个请求才能渲染一个网页。最天真实现这一点的办法是逐个请求每一部分,按顺序进行,但在现实世界中这并不是最佳实践,因为它会导致瓶颈:为什么因为横幅服务器运行缓慢就要等待页面加载呢?因此,为了提高内容检索速度,浏览器会同时发出多个请求。

而这正是 HTTP 的第一个缺点:它没有提供原生能力来服务并发请求。相反,请求必须按顺序发出。

顺序(也称为串行获取模型如果网页中的一个元素需要从慢速服务器下载,或者通过信号不稳定链接,或者服务器需要一段时间来准备和发送特定元素,那么它会导致相当大的性能损失。如果顺序获取是唯一的选择,那么任何此类慢速请求都会阻止后续请求的发出和提供,直到(慢速请求)被满足。

由于 HTTP 的新版本并没有改善这种情况,大多数客户端软件实现了一个修补程序:网络浏览器简单地打开多个同时的、独立的 TCP/IP 会话到服务器或一组服务器,并尝试一次性发出多个请求。当页面从多个不同的机器请求资源时,这种解决方案实际上是相当合理的。然而,当请求的资源位于单个系统上时,所有请求都可以在一个会话中完成,并由服务器合理管理,这并不是一个好的解决方案。原因如下:

  • 服务器没有机会确定处理请求的最佳顺序。(如果它能,它将最后处理耗时、体积大或最不相关的对象。)它被迫几乎同时处理所有请求,这仍然可能导致最重要的内容因 CPU 负载增加而被无谓地延迟。

  • 如果同时提供几个较大的资源,并且操作系统调度程序在会话之间切换,那么由于需要在两个可能相距较远的文件之间反复快速地寻找,结果可能会因为磁盘驱动器需要频繁地寻找而造成相当大的性能负面影响。

  • 完成新的 TCP/IP 握手通常会有相当大的开销(尽管在 HTTP 的新版本中,通过保持连接的能力有所减轻)。在单个连接中发出所有请求更有效率。

  • 打开一个新的会话并创建一个新的进程来处理请求,这在操作系统级别会产生开销,并给诸如状态防火墙等设备带来压力。尽管现代网络服务器试图通过保留一些备用、持久化的进程来接受到达的请求以最小化这个问题,但问题很少被完全消除。单个会话避免了不必要的开销,并让服务器仅分配绝对必要的资源来异步处理选定的请求。

  • 最后但同样重要的是,如果瓶颈是网络而不是服务器,那么随着来自多个来源的数据同时到达,链路饱和时数据包丢失,性能实际上可能会下降。

无论如何,这种架构目前仍然存在,并且它比串行获取要好。我们应该承认它的存在,并学会利用它。

这种特性如何帮助我们识别客户端使用的软件呢?很简单。并行文件获取对浏览器指纹识别目的的重要性应该是相当明显的:没有两个并发获取算法是完全相同的,而且有很好的方法来衡量这一点。

但在我们转向并行获取之前,我们需要看看 Web 安全和隐私方程中的另外两个重要组成部分:缓存和身份管理。尽管表面上看似无关,但最终它们构成了一个逻辑整体。因此,这里有一个简短的休息。

内容缓存

在过去几年中,随着网络的快速扩张,保持从服务器接收到的文档的本地缓存是网络的一个重要特性.^([30]) 没有它,运营这项业务的成本将会显著更高。

典型网站重量和复杂性的增加问题在于它需要越来越多的带宽(对于企业来说,这通常相当昂贵),以及更好的服务器以合理的速度提供服务。

如果性能不受带宽瓶颈的影响,那么像并发会话(如前所述)这样的解决方案会给服务提供商带来额外的压力。原因可能相当令人惊讶:如果一个人在相当慢的链路上(例如调制解调器)打开四个后续会话来获取一个相当简单的页面,服务器上需要保持四个连接和四个进程或线程的活跃,从而从那些更快连接的用户那里夺取资源。

最后,事情变得更糟,更重、更复杂的网站并不总是与用户的期望相匹配。曾经被认为相当不错的相对较长的网页加载时间现在似乎很烦人,并驱使用户离开。事实上,研究表明,平均的网页用户在页面下载前不会等待超过 10 秒,然后才会继续浏览.^([101]) 结果是,公司和服务提供商需要更多的资源以及更好的链接来处理涌入的流量。实际上,如果事情按照最初的设计进行,对服务器端资源的需求可能早就超过了我们满足需求的能力。

有一些帮助的是,提供给网络浏览者的内容是静态的或者很少变化,至少与用户检索资源的速度相比是这样的。(这对于大型文件尤其如此,例如图形、视频、文档、可执行文件等等。)通过在更靠近最终用户的地方缓存数据——无论是在 ISP 层面还是在终端浏览器本身——我们可以显著减少后续访问共享相同缓存引擎的用户所使用的带宽,并减轻处理流量的服务器的负担。ISP 也因此受益,降低了带宽消耗,能够在不投资新设备和新连接的情况下服务更多的客户。然而,HTTP 需要的是一个机制来保持缓存准确和更新。页面作者(无论是人类还是机器)需要能够告诉缓存引擎何时获取文档的新版本。

为了实现文档缓存,HTTP 提供了两个内置功能:

  • 一种以最小的努力来判断数据的一部分自缓存引擎最近版本(即上次访问时记录的文档)以来是否已被修改的方法。

  • 一种确定哪些数据部分不应缓存的机制,无论是出于安全原因还是因为数据每次请求资源时都是动态生成的。

在实践中,这种功能相当简单实现:服务器通过常规的 HTTP 会话返回所有可缓存的文档,但会额外添加一个协议级别的头部,即 Last-Modified。不出所料,这个头部代表了服务器认为该文档最后修改的时间。另一方面,无法缓存的文档则由服务器通过头部 Pragma: no-cache(在 HTTP/1.1 中为 Cache-Control: no-cache)进行标记。

客户端浏览器(或由 ISP 运行的中间缓存引擎)应基于适当头部的存在以及最后修改信息缓存每个可缓存的页面副本。它应尽可能长时间地保留缓存的页面,除非用户配置的缓存限制被超过或用户手动清除缓存,除非有特定的 Expires 头部指示在特定日期后丢弃它。

之后,当再次访问网站时,客户端会得出结论,他们在磁盘上缓存了该页面的先前实例,并在访问它时遵循略微不同的程序。只要文档存在于缓存中,客户端就会在用户每次重新访问网站时尝试获取文件,但会使用每次请求中 Last-Modified 头部中看到的值作为 If-Modified-Since 头部的值。服务器预计会将其对给定资源的最后修改时间的了解与 Modified-Since 值进行比较。如果自那时起资源没有发生变化,则返回 HTTP 错误消息“304 Not Modified”,而不是请求的数据。因此,实际文件传输被抑制,从而节省带宽(在此通信期间仅交换了数百字节)。客户端(或中间缓存引擎)预计会使用之前缓存的资源副本,而不是再次下载。

注意

一种更现代的方法,即 ETag 和 If-None-Match 头部,是 HTTP/1.1 实体标签功能的一部分,其工作方式类似,但旨在解决围绕文件修改时间解释的歧义:在短时间内(低于用于 Last-Modified 数据的时钟分辨率)多次修改文件的问题,以及从备份中恢复文件(修改时间早于最后缓存的副本)等问题。

会话管理:Cookies

对于 HTTP 来说,另一个重要且看似无关的要求是它能够区分会话并在连接之间跟踪它们,存储会话设置和身份信息。例如,一些网站极大地受益于能够适应个人的偏好,并在用户每次访问网站时恢复用户选择的界面和感觉。自然地,用户的身份可以通过每次查看页面时提示登录名和密码来建立,此时可以加载用户的个人设置,但这种额外的努力大大减少了愿意为此访问页面的人数。

需要一种透明且持久的方式来存储和检索客户端机器上的某些信息,以确保无缝且个性化的访问网络论坛、公告板、聊天以及许多其他定义了许多人浏览体验的功能。另一方面,通过为返回访客分配一个唯一的标签并在之后检索它,网络服务器管理员能够识别和识别访客,这意味在换取一点便利的同时牺牲了匿名性。这样的机制会给那些道德等级较低的公司提供一个强大的工具来追踪和建立用户档案,记录他们的购物和浏览偏好,确定他们的兴趣等等。搜索引擎可以轻易地将同一用户的请求关联起来,而提供如广告横幅等资源的内容提供商可以利用这些信息来追踪人们,即使在没有他们的许可或网站运营商知情的情况下。^([31)] 然而,不管怎样,似乎没有更好的、足够通用的替代机制。因此,网络 cookies 应运而生。

根据 RFC2109 的规定,^([102)] Cookies 是由服务器在客户端连接到它时发出的小段文本。服务器在响应访客时指定一个 Set-Cookie 头。这部分文本通过其附加参数,其范围被限制在特定的域名、服务器或资源,并且有有限的生命周期。Cookies 由启用 cookie 功能的客户端软件存储在特殊的容器文件或文件夹中(通常被称为“cookie jar”),并在再次建立到特定资源的连接时,通过 Cookie 头自动发送回服务器。

服务器可以选择在 Set-Cookie 头中存储(或推送)用户设置,并在后续访问时读取它们;在理想的世界里,这就是 cookie 功能结束的地方。不幸的是,计算机没有方法知道 cookie 中存储了什么。服务器可以选择使用 Set-Cookie 头为客户端分配一个唯一的标识符,然后读取它以将当前用户活动与系统中的先前操作联系起来。

这种机制普遍被认为具有严重的隐私影响。一些活动家直言不讳地讨厌 Cookie,但如今对这种技术的反对声音越来越小。禁用 Cookie 浏览网页变得越来越困难——一些网站甚至拒绝来自未通过 Cookie 检查的客户端的流量。幸运的是,许多浏览器提供了广泛的 Cookie 接受、限制或拒绝设置,甚至可以在接受之前提示每个 Cookie(尽管后者并不特别实用)。这使得您能够合理地保护您的隐私,至少可以通过定义“好人”是谁以及谁值得信赖来实现。

但我们的隐私真的在我们手中吗?

网络浏览的隐私长期以来一直被视为一个热点问题,并非没有理由。许多人不希望别人窥探他们的偏好和兴趣,即使他们的行踪并不特别可疑。为什么?有时,你只是不希望一家糟糕的广告公司知道你在阅读关于特定医疗状况的内容,然后能够将此信息与你在一个专业论坛上的账户联系起来,尤其是因为无法知道这些信息最终会去哪里。

Cookie 控制使我们的浏览体验相对舒适,同时将坏人拒之门外。但即使关闭 Cookie 也无法阻止信息存储在用户的系统中,以便稍后发送回服务器。在受害者机器上存储和检索数据的所需功能早已存在于所有浏览器中,无论 Cookie 策略设置如何。这两种必要的技术以类似的方式工作,只是在预期用途上有所不同:Cookie 和文件缓存。

在 2000 年左右,Martin Pool 在 Bugtraq 邮件列表上发布了一条相当简短但富有洞察力的信息^([103]),分享了一个有趣的观察,并用一些实际代码来支持它。他得出结论,对于不使用集中式代理缓存并且将已获取的文档副本存储在本地磁盘上的系统(对我们大多数人来说都是如此),Set-Cookie 和 Cookie 功能与 Last-Modified 和 If-Modified-Since 之间没有显著差异。恶意网站管理员可以在受害者访问的页面返回的 Last-Modified 头中存储几乎任何信息(或者,如果这个头进行了合理性检查,它可能会简单地使用一个唯一的、任意的日期来唯一标识这个访问者)。当页面被重新访问时,客户端会发送 If-Modified-Since,其中包含由恶意操作员在他们的计算机上存储的唯一标识符的精确副本。“304 未修改”响应确保这个“Cookie”不会被丢弃。

使用浏览器稍微调整 Last-Modified 数据作为响应似乎是一种防止此类暴露(同时引入一些缓存不准确)的整洁方法,但这并非如此。这种攻击的另一种变体是依赖于存储在缓存文档中的数据,而不是直接使用标签:当网站首次访问时,恶意操作者可以为受害者准备一个特殊页面。该页面包含对作为嵌入资源的唯一文件名的引用(例如,一个图像)。当客户端重新访问此页面时,服务器会注意到 If-Modified-Since 头,并回复 304 错误消息,提示使用旧版本的页面。旧页面包含一个唯一的文件引用,然后从服务器请求该文件,使得将客户端的 IP 映射到之前返回该文件名的会话成为可能。哎呀。

自然地,基于缓存的“cookies”的寿命受缓存大小和用户配置的缓存文档过期设置的限制。然而,这些值通常相当慷慨,对于每隔几周访问一次的资源,存储在元数据中的信息可以持续数年,直到手动清除缓存。对于为数百或数千个网站提供通用组件的公司(再次以横幅为例),这并不是一个问题。

与真正的 cookies 相比,这些缓存 cookies 的主要区别不在于它们提供的功能,而在于控制上述暴露的容易程度。(缓存数据还必须服务于其他目的,并且在不影响性能的情况下,不能轻易限制,否则会与禁用部分或全部缓存相关联的性能影响。)

在这个奇怪的转折中,你可以看到网络的两个方面如何碰撞,有效地抵消了围绕其中一个方面构建的安全保障。实践表明,意图并不总是足够的,因为恶棍们并不总是愿意按照规则行事并使用我们希望他们使用的技术。也许关闭 cookies 最终并没有太大的区别?

但这时,我们讨论的主题应该回到正题。


^([30]) 然而,它的重要性正在逐渐降低:随着越来越多的网页是动态生成的,我们的互联网骨干网变得更加成熟和强大,缓存注定会失去其重要性。

^([31]) 如果广告横幅或网站的任何其他元素放置在共享服务器上,例如banners.evilcompany.com,那么evilcompany.com的操作员可以在任何使用他们提供的横幅的合法网站被访问时发布和检索 cookies。不用说,大多数横幅提供商都会发布 cookies 并跟踪用户,尽管主要是为了市场研究目的。

揭露背叛

检测欺诈和准确指纹识别客户端软件的主题。我迄今为止提到,检测欺骗客户端的任务很复杂,但并非不可能,并且行为分析,即对相关浏览器产生的事件序列的仔细监控,是一条值得探索的途径。

HTTP 是一个特别适合研究的主题,因为我们已经看到,大部分活动都是并行或几乎并行的,而确切的排队和数据处理的算法对每个客户端来说都是相当微妙且独特的。通过测量一次性下载的文件数量、请求之间的相对时间延迟、请求的顺序以及其他会话的细微但明显的细节,可以测量系统的独特特征,这在用户难以干预的层面上要困难得多。因此,你可以毫不费力地区分冒名顶替者和守法公民。

为了以尽可能简单的方式提供一个现实世界的例子,并且尽可能接近实际应用,我决定看看从你们中许多人可能拥有的现有、相当有限的数据样本中可以讲述多少内容,因此我拿出了超过一百万次请求的标准日志,这些请求是针对一个相对受欢迎的网站的。用于此分析的数据是典型的 Apache 网络服务器访问日志,包含请求完成时间、请求 URI、来自 User-Agent 头部的广告浏览器数据以及此类基本信息的其他信息。保留日志的页面由一组相对较小的、大小相仿的图片和一个调用所有这些图片的单一 HTML 文档组成。

行为分析的一个简单案例

Apache 在请求完成后而不是在请求发出时记录请求的做法,可能会被视为一个问题,但实际上却非常有帮助,假设请求的文件集相对同质。请求发起顺序通常更多地受主页面中引用资源的顺序的影响,而完成时间则是一个更复杂的生物。

完成顺序的概率取决于请求的数量、请求间的延迟以及其他参数,这些参数微妙但明显地因浏览器而异。特别是,始终只保持一个连接打开的浏览器总是以已知顺序发出请求,A-B-C-D;同时打开三个连接并快速发出请求的浏览器同样可能产生 B-A-C-D、C-B-A-D、C-A-B-D……在这些后一种情况下,请求排队和会话管理最为重要。

自然地,我们不能忘记观察到的顺序也受到网络延迟、可靠性和其他随机问题的影响。尽管如此,对于如此大量的样本,这些非浏览器特定的效应要么会平均化,要么以类似的方式影响所有客户端的数据。当这种情况发生时,我们希望看到浏览器之间在友好用户界面之下存在的细微差异。

图 14-1 显示了之前提到的包含十个元素的网页在数据集中四个最受欢迎的网络客户端中的加载尝试的统计分布。每个图表被分为十个主要部分。第一个对应于主要的 HTML 文件,它被直接请求并自然成为网站的第一个元素。其余的九个主要部分对应于从该 HTML 引用的九个图像,按照它们在 HTML 中调用的顺序。

每个部分进一步在 X 轴上分为十个离散位置(在此处未明确显示,以避免图表杂乱)。给定部分中第 n 个离散位置的图表高度代表该特定文件作为第 n 个顺序项被加载的可能性。

流行网络客户端的行为模式差异

图 14-1. 流行网络客户端的行为模式差异

为了使图表更易于阅读,分布概率以 1 到 100 之间的百分比形式给出(对应于百分比,所有小于 1%的值向上取整),并且离散点用线条连接。然后,图表在以对数尺度(log10,主要指南在 1、10 和 100)上绘制,以便使细微特征更加明显,并更容易进行视觉比较。

在一个完美的世界中,如果浏览器完全按顺序且可预测,第一个部分将只包含第一个(最左侧)离散位置的一个峰值;第二个部分将只在第二个位置有一个峰值,以此类推。然而,在实践中,一些浏览器会同时发出许多请求,因此顺序更容易被打乱:第三个引用的文件可能先于第二个或后于第四个加载。每个部分中单个峰值越不明显,浏览器获取算法看起来就越激进——这意味着这个文件被无序加载的概率越均匀。

这些差异应该清晰可见,即使在历史上基于相同引擎的浏览器之间:Mozilla 和 Internet Explorer。所有客户端似乎都观察到了在主文档中引用文件的顺序,因此后续的峰值会缓慢地从左到右穿过各个段。然而,正如你所看到的,Mozilla 通常比 Internet Explorer 更不急躁,并且更经常按照请求的顺序完成文件的下载。另一方面,被誉为地球上最快的浏览器 Opera,其顺序性要差得多(许多文件有两个或三个几乎同样明显的峰值,这表明一组请求被迅速发出,完成顺序几乎是任意的,并且主要受网络抖动的影响)。Wget,一个流行的开源网络爬虫,用于比较,顺序性完美(这是自动化爬虫的常见模式),使用单个连接,并按相同的顺序加载所有文件。

给出漂亮的图片含义

图片和图表很漂亮,但对于自动化策略执行或滥用检测几乎没有价值。为了以某种方式量化观察到的模式,并使指纹识别更加现实,我决定引入一个简单的指标,当只有一个峰值时,会给段一个更好的分数(在 0 到 10 的范围内),而当分布更加任意时,会给一个较低的分数。这可以允许为特定的软件创建一个简单的十值指纹,然后匹配观察到的活动与一系列签名,以确定最佳匹配。

为了构建一个表示在主要段 s 上观察到的行为相对质量(线性度) Q 的指标,我使用了以下公式(f[n] 表示文件出现在获取序列中位置 n 的概率,为了方便和打扰纯粹主义者,用百分比表示):

无标题的图片

这个方程,虽然乍一看令人畏惧,但实际上非常直接。我希望公式能够优先考虑这种情况:在这个特定的文件在序列中固定位置加载的情况最为常见(也就是说,一个 f 值接近 100%,其余概率接近 0%)而不是所有位置都同样可能发生的情况(所有 f 值均为 10%)。

因为 f 的所有元素之和是固定的(100%),实现这一点最简单的方法是使用平方和:对于任何非零数字序列;这些数字的平方和总是小于总和的平方。最高和最低结果如下:

10² + 10² + 10² + 10² + 10² + 10² + 10² + 10² + 10² + 10² = 1,000
100² + 0² + 0² + 0² + 0² + 0² + 0² + 0² + 0² + 0² = 10,000

除了主要总和之外,剩余的数学计算仅用于将结果映射到合理的 0 到 10 的尺度(当四舍五入时)。

计算每个浏览器每个观察到的流量段的此指标的结果叠加在图 14-1 上,作为一个描述图表每个部分的数值。正如预期的那样,Wget 在每个部分都取得了完美的分数。其他浏览器的分数证实了之前的视觉观察,并使它们更加具体。尽管 Internet Explorer 和 Mozilla/Netscape 引擎的图表看起来大致相似,但在第 4 至 6 项的加载图表周围以及在整个获取序列中可以观察到明显的差异。Opera 明显与其他浏览器拉开距离,每个部分的分数都持续较低。

因此,通过应用一个相当简单的分析工具,我们最终制定了一个实用的方法框架,用于识别浏览器并在用户 HTTP 流量中检测到统计上有意义的欺骗行为。你可以通过分析其他自动加载元素,如脚本、HTML 样式表、图像映射、框架和其他显示更大浏览器间差异的文件来增强模型。今年圣诞老人可能更容易准备出淘气的用户名单。

除此之外……

我仅仅希望展示通过观察其行为,而不做任何具体假设或剖析程序内部结构,如何轻松地检测未知应用程序的隐藏特性。上述确切数字可能并不直接适用于我使用的网站以外的任何网站,因此如果你发现这项技术有潜在用途,请鼓励你自己做些研究。一旦你对某个网站或一组网站进行了分析,你可以使用这些数据来高效地识别基于其随时间活动模式的变化的系统。

不言而喻,我在这里使用的方法是一种(可能过于)简化的行为分析方法,基于所有可能场景中最简单的一种;我提供它作为鼓励,并诱使你寻找更多。在复杂情况下,你可以轻松地使用在框架、表格和其他视觉容器中渲染内容的过程,或者获取和渲染特殊类型的文件,以确定正在使用的浏览器,即使不进行统计分析——在浏览器活动的各种高度具体方面,差异变得更为明显。巧妙地应用差分时间也可能很有前景。

考虑这一点:你可以将更深入的行为分析形式推进一步,并部署它们不是为了区分不同的渲染引擎,而是为了区分机器和人类,甚至识别单个用户。如第八章所述,键盘使用模式对个人来说通常是如此独特,以至于可以使用它们进行生物识别。同样,研究表明,我们可以使用用户点击链接、做出选择、阅读信息等方式来表明一组请求背后是谁或是什么。^([104)) 虽然现在这更接近科学推测而不是事实,但这是一个值得探索和玩耍的奇妙领域。

. . . 以及超越识别

浏览器活动和行为分析应用不仅限于检测浏览器软件——实际上,一些应用已经进入用户隐私和匿名性的领域。

2000 年,Edward Felten 和 Michael Schneider 发表的一项有趣的研究^([105)) 对该技术的潜在应用做出了令人着迷的贡献,这种能力与今天引擎中部署的缓存机制密切相关,将我们带到所有之前讨论的元素最终汇聚在一起的时刻。

他们研究的基本前提是,通过在特定网站上插入对文件的引用,然后测量浏览器下载它时遇到的延迟,可以判断用户是否在最近几天访问过该网站。这很简单。

我将省略对理论、预测和推测的漫长探索(就这一次),而是提出一个接近现实世界的例子。假设我正在运行www.rogue-severs.com。我决定,出于某种原因,我的主页将引用一张图片(例如首页标志),这张图片来自www.kinky-kittens.com;我将这个视觉元素设置得难以找到或缩小到无法看到,但它仍然会被浏览器加载。

一个未察觉的用户访问我的网站。如果他们从未访问过www.kinky-kittens.com,那么他们需要一段时间才能下载我引用的图片。然而,如果他们是常客,那么图片已经存在于他们的缓存中,几乎可以立即获取。

由于对www.kinky-kittens.com资源的引用前后都跟着对我的网站上其他视觉元素的需求,通过部署巧妙的时机启发式算法,可以可靠地测量整个标志是否已被获取,或者它是否已经在缓存中。所有这些足以确定访问我页面的新来者是否确实是某个特定网站(或网站的一个特定部分)的常客,并且有效地粗暴地侵犯了他们的隐私。尽管这种场景不太可能被用于广泛部署的常规间谍活动(主要是因为留下了明显的证据,可能会被我们想要窥探的服务器运营商注意到),但针对性攻击可能非常有效。

最后,所有的拼图碎片都拼在了一起,也许不是很紧密,但仍然拼在了一起。用户、程序和习惯都可以通过仔细滥用流行的互联网协议的现代功能轻易地暴露出来。这对www.kinky-kittens.com的宝贵访客来说,可能并不总是令人安慰的。

预防

完全匿名化一个人的网络浏览体验似乎已经是一场已经输掉的战斗。尽管一些提高在线网络用户隐私和匿名性的做法被普遍接受,但这些功能可以很容易地被恶意网站规避。

很不幸,这个问题太严重了,不能被忽视。当我们决定信任某个实体(比如 ISP)时,它知道我们的活动是另一回事,但当那些我们不愿意经常打交道的一方收集敏感的个人信息,并且可能像他们的商业模式一样,同样频繁地将其转售给他人时,这完全是一个不同的问题。即使对于那些每天不戴锡箔帽和铝制内衣的人来说,这也足以引起他们的关注。

另一方面,在必须允许 HTTP 流量,同时用户应得到保护并受到监督,而不违反他们隐私的基本必要性的环境中,保持完全匿名或看起来完全无害的相对难度很重要。在企业网络中,能够在不手动检查数据的情况下跟踪违规系统,这对用户和系统管理员来说都真正宝贵且受到欢迎。

思考的食物

HTTP 的任何一个组成部分都没有设计不当、损坏或不合理。然而,当我们把它们放在一起时,许多安全和隐私功能似乎都相互抵消了,用户被暴露在肆无忌惮的窃听者面前。遗憾的是,我们无法在不从头开始的情况下做些什么,而且无法保证结果会像 HTTP、HTML 和 WWW 客户端现在提供的那样好,甚至提供更多的隐私。

第十五章. 成为受害者的好处

在我们得出结论,以适当的乐观态度生活可能有助于我们追踪到攻击者

我讨论了各种可能对日常通信产生重大累积影响的问题,这些问题我们并不总是感到舒适。你已经看到其他人如何利用网络窃取信息,或者获取比你预期或允许的更多的信息,以及如何使用这些技术来收集更多关于你的企业或家庭网络的信息,以及针对这些网络进行攻击的攻击者。

我希望我已经提供了关于这类问题产生的原因以及如何尽可能避免它们的见解。我试图表明,安全和隐私的影响仅仅是每个活动的组成部分,而且仅仅通过做出正确的设计决策、安装正确的软件或建立和执行适当的政策是无法完全消除的。信息泄露根本无法完全抑制,我们唯一的希望是拥有足够的信息和知识关于潜在的泄露或攻击场景,以便在特定应用中尽可能减轻最严重的影响。

这本书的第三部分,主要关注广域网及其潜在威胁。尽管这部分内容最长,现在也即将结束,但它离提供一个开放网络中可能出现的所有问题的全面视角还相去甚远。实际上,讨论所有问题的变体将非常困难,而且很大程度上是徒劳的;因此,我选择只涵盖主机之间通信中最复杂、最具挑战性或最迷人的方面。我专注于发现不同协议层和不同抽象级别上的攻击场景,而不是列举那些重复旧想法且对主题没有新贡献的概念和攻击向量。我希望到目前为止提供的信息能帮助你并鼓励你在网络和计算的其他领域找到这些问题的其他表现形式——也许甚至更远。

在下一部分书中,我们将进行一次重大的范式转变,探讨如何通过仔细观察整个网络(而不是单个系统)来保护自己或攻击他人。但在我们这样做之前,让我们看看网络监控中一个较为不寻常领域的其他可能性:被动反情报——即通过分析他们的行为来了解更多关于攻击者或他们的目标。以这种方式收集的数据可以提供一套强大的调查线索,使识别攻击者的意图、工具集甚至攻击者本人变得容易。构建攻击者档案、试图解读他们的想法,甚至与他们玩一场欺骗游戏,通常本身就是一种令人兴奋的经历。

定义攻击者指标

如预期的那样,你只需应用一些之前讨论过的常见 TCP/IP 流量指标——例如被动操作系统指纹识别——到观察到的流量中,就可以从远程恶意实体中获得大量信息。例如,你可以识别用于端口扫描的具体工具。

类似地,我们也可以将行为分析应用于攻击者的行为特征,例如请求间的延迟和请求顺序(例如,端口扫描的顺序和速度)。我们可以使用行为分析在一定程度上追踪程序,或者在手动进行的入侵或未经授权的评估尝试中,甚至可以确定攻击者的个人特征(例如他们的计算机技能)。

我们可以部署的一个特别有趣的方法来识别攻击者用于扫描我们网络的工具,是应用第九章中讨论的方法之一——端口序列指纹识别——到一个全新的任务上;这是基于观察,目前大多数扫描器要么按顺序从最低端口或地址扫描网络和系统,要么随机化访问资源的顺序。后者更常被使用,并且被认为更好,因为它可以平衡负载并使扫描检测稍微困难一些。但出人意料的是,随机性的使用可能会以几种奇怪的方式对攻击者产生反作用。

问题在于,它们的作者没有将网络扫描工具视为具有高安全要求的任务关键型应用。在不要求加密安全输出的程序中实现伪随机数生成器最常见(也是最容易)的方法是调用标准系统或内置语言功能。世界上使用最广泛的编程语言 C 的 ISO 标准^([106]) 建议使用简单的线性同余算法来实现标准 C 库伪随机数生成器(在第一章中讨论)。标准提出的构建和使用生成器的方案如下:

  1. 生成器应该通过调用标准库函数 srand() 使用一个初始的 32 位值(S[0])来初始化。如果生成器没有被初始化,它将使用一个固定的默认种子,并在所有情况下产生相同的输出序列。

  2. 在每次调用 rand() 时,主函数会反复调用以获取用于用户应用程序的后续伪随机数,种子 S 会按照以下方式重新计算:S[t+1] = S[t] * 1103515245 + 12345。结果被截断到 32 位(模 4294967296)。

  3. 每个 rand() 调用的返回值是 S[t+1] 的更重要的字,模 32768。在 32 位变体中,这是今天计算机上更常用的算法之一,这个步骤和前一个步骤会重复多次,以计算结果值的后续位部分。

所有线性同余生成器,包括这里描述的生成器,都容易受到 20 世纪 90 年代 H. Krawczyk 提出的通用密码分析方法的影响,如第一章中提到的。基于对几个后续(或有序)输出的观察,有可能重建生成器的内部状态,从而预测其所有先前和未来的输出。

自然地,这种可能性的直接含义——受害者根据对先前尝试的了解,确定攻击者将按什么顺序尝试攻击机器或网络上的其他资源——本身并不特别令人兴奋或有价值。然而,在网络探测尝试的背景下,这种可能性有两个重要的后果:

  • 我们可能能够确定 S[0]。如果我们知道或可以估计生成器开始工作的时间(或者,作为替代,初始种子应显示的一般属性),就有可能重建用于初始化生成器的值。因为 S[0]是算法的唯一输入,它必须对相同的种子值产生相同的行为——因此,我们可以通过观察 PRNG 输出来追踪种子。

  • 我们可能能够确定 t 增量。一旦我们重建了生成器状态,就有可能通过在扫描器用于获取值(端口号或主机地址)的两个调用之间调用 rand() 来确定扫描器请求了多少随机值。

这个设计的第一种后果的重要性,即我们重建用于初始化生成器的值的能力,可能并不立即明显。但我们还有另一个谜团需要考虑。初始化随机数生成器的一种常见方法是用一个方便的 32 位值,这个值变化足够频繁,不会太频繁地导致相同的 PRNG 行为。系统时间计数器常用于此目的,有时还会与另一个小数字结合使用,例如当前进程 ID(PID),以降低两个在短时间内运行的程序产生相似结果的可能性。

通过将此知识应用于计算出的 S[0],探测受害者可以揭示攻击者的系统时间(GMT 或本地时间,取决于操作系统设置和扫描器类型)。了解系统的本地时间可以以最简单的方式给观察者提供关于攻击者来源和身份的线索。如果他们试图通过伪造来自各种来源的数据包来迷惑我们,我们可以通过排除那些 S[0]显示的时间区域与源地址所属的地理区域不匹配的感知来源来获得幸运。例如,如果我们通过比较攻击者的估计系统时间与 GMT,确定攻击者的时间比格林威治标准时间慢五小时,我们可能会得出结论,他们很可能位于美国东海岸而不是中国。因此,通过将我们对时区的最佳猜测与各种 IP 地址块的记录进行比较,我们可以判断,在所有观察到的“诱饵”扫描源中,攻击者的真实身份更有可能隐藏在来自波士顿 ISP 的数据包后面,而不是来自北京 ISP 的数据包后面。

此外,一旦我们知道攻击者的本地时间,我们可以通过测量他们的系统时钟与真实时间的距离(以及长期来看其漂移速度)来追踪他们。由于计算机时钟通常不太准确,并且在不定期与外部源同步时(在某些情况下,每天可能漂移几分钟),这可能是一种很好的方法来关联同一人进行的攻击。不同的机器可能系统地偏离不同的时间量,这些时间量会以独特的比率变化。

最后,当 PID 作为初始化种子的一部分与系统时间一起使用,并且已知攻击者的系统时间在某个范围内时,PID 可以用来确定大约的系统运行时间或两次扫描之间的任务执行数量。因为机器上的每个新进程都被分配一个更高的 PID 号,这种依赖性相当直接。^([[32)]]

通过重建 PRNG 状态,我们还可以看到在接收方接收到的两个数据包生成之间生成了多少随机数。当只有一个系统正在被扫描时,应该没有任何间隙,或者只有由于网络问题引起的微小差异。然而,当多个系统正在被扫描时,这些间隙(由发送到不同目标的数据包引起)可以很容易地被检测到。通过检测它们,我们可以确定同时被针对的系统数量。

此外,当扫描软件生成看似来自随机主机的虚假诱饵数据包时,可以消除伪造的地址——那些使用伪随机数生成器(PRNG)生成的地址(因此匹配其可能的输出)并确定哪个不匹配,因此必须是真实的——从而明确指向攻击的真正肇事者。例如,如果我们的重建 PRNG 数据显示来自以下地址的流量:

198.187.190.55 (十进制表示:3334192695)
195.117.3.59 (十进制表示:3279225659)
207.46.245.214 (十进制表示:3475961302)

我们可以确定,3334192695 和 3475961302 都是使用 S[0] 作为种子生成的生成器的最初输出之一;而 3279225659 似乎不是任何重建的伪随机数生成器的最初输出,因此很可能是真实的地址。

我们可以利用所有这些信息来确定攻击者的意图和他们使用的软件。我们甚至可以使用它来追踪他们正在工作的系统,将其与其他数据关联起来以确定他们的真实身份和地理位置,有时甚至可以确定他们随着扫描的进行是如何使用他们的计算机的。

注意

NMAP 为了应对上述的运行时间和扫描历史披露问题,试图使用安全的系统随机数生成器设施(如 /dev/random,如第一章所述第一章)来生成随机数,而不是依赖于标准的 C 库工具。然而,这种方法在许多操作系统(如 Windows)上不可用,其他扫描器也没有采取类似的步骤来防御攻击者。


^([32]) 尽管一些系统提供可选的 PID 随机化,目的是使某些无关类型的本地攻击更加困难。

保护自己:观察观察

在过去的十年中,互联网已经成为一个巨大的战场。新连接的机器立即被自动攻击探测、蠕虫和其他类型的信息所淹没,这些信息对它们的网络安全造成压力。传统的、现在相当流行的入侵检测和预防运动旨在通过警告管理员正在使用特别定制的流量分析工具进行预攻击探测来发现和阻止攻击。在异构或简单足够复杂的环境中,这些通常会产生比一个人能够处理的更多噪音和假阳性。

然而,在某些情况下,观察攻击及其引发的响应能力是管理员了解网络问题和攻击发生情况的一种很好的方式(尽管这些事件本身通常并不引人注目)。首先,在某些网络中,由于政策规定、缓慢的周转时间、很少开放的维护窗口等原因,主动发现和资产扫描以确保政策合规和系统配置难以启动或执行起来过于麻烦。在这样的环境中,能够窥视并确定流氓看到的内容可能是一种非常有价值的替代方案,以进行本地启动的主动侦察。

此外,定期的主动发现可能不足以应对某些威胁;因此,仅通过观察他人得到的结果来了解某事突然出错的能力可能非常有价值。当然,这是一把双刃剑——一个已经破坏或计划破坏网络、但希望保持低调并提前规划步骤的黑客,可以观察其他发现尝试产生的流量,以构建他们对特定系统的知识。

理论上,窃取攻击者获得的知识似乎很简单;然而,关联和处理结果,尤其是在分析大型环境或仅基于来自不同地点的单独攻击尝试的零星信息时,并非易事。尽管如此,一些用于通过“被动扫描”促进网络和系统映射的工具正逐渐出现在地平线上——Preston Wood 的 DISCO^([107])就是一个典型的例子。

思考的食物

我发现奇怪的是,本章中描述的技术通常没有得到全面的研究、发表的白色纸张或易于获取的工具的支持。随着 Lance Spitzner 的蜜罐研究引发的攻击跟踪热潮,以及入侵检测系统等产品的推动,人们本应看到更少的努力去识别攻击(这些攻击通常并不特别引人注目,并且通常使用经过良好记录的向量和方法),而更多的努力去确定攻击的意图和来源,以及关联那些单独看来没有意义但结合在一起可以表明问题的活动。

我只能对冰山一角进行一些探讨,但不用说,这可能是研究和贡献的更具吸引力的领域之一。

现在,让我们来点完全不同的内容……

第四部分。大局观

我们的法律部门建议我们在这里不要说“网络即计算机”

第十六章。寄生计算,或者如何积少成多

旧有的真理——拥有一个手下比亲自做工作更好——再次得到了证实

我希望到目前为止你已经享受了这次旅程。我讨论了许多影响信息安全和隐私的复杂问题,从键盘输入到最终目的地数百或数千英里之外。但对我们俩来说,现在还为时尚早;画面中缺少了某些东西——比我们迄今为止讨论的还要大得多——暗物质。

我们故事中的问题很简单:通信不会在真空中发生。尽管数据交换的过程通常限于两个系统和十几个中间系统,但所有事件的宏大背景绝对不能被忽视;周围环境的特性可以以深刻的方式塑造端点之间闲聊的现实。我们不能忽视那些没有直接参与通信的系统的相关性,以及所有看似孤立、各自微不足道的单个事件的重要性,数据在其路径上遇到这些事件。只关注对特定应用或特定案例看似相关的事物可能是致命的,正如我希望这本书到目前为止已经向你展示的那样。

而不是陷入这种短视的陷阱,我选择拥抱事物的宏伟蓝图,尽其所有光辉。因此,本书的第四部分和最后一部分专门关注整个网络的安全,并将互联网视为一个生态系统,而不是完成特定任务的系统集合。我们向将世界联系在一起看似无生命的物质致敬。

本书这一部分从分析一个概念开始,这个概念似乎是最合适的过渡方式。对于许多计算机爱好者来说,这个被称为寄生计算的概念,彻底改变了我们对互联网的看法。

咬噬 CPU

一篇谦逊的研究论文,由 Albert-Laszlo Barabasz、Vincent W. Freeh、Hawoong Jeong 和 Jay B. Brochman 于 2001 年在《自然》杂志的来信栏目上发表,本可能被轻易忽视。乍一看,这封信似乎不值得过多关注;事实上,它提出了一个看似可笑的论点。作者们建议,在 TCP/IP 等成熟的网络协议中可以创建流量,对远程计算机提出一个简单的算术挑战——一个需要解决的问题;远程系统在解析消息和准备响应时会无意中解决这个问题。但为什么有人会浪费时间向无情的机器投掷谜题呢?从这能获得什么?自己解决它们不是更有趣吗?当然,答案非常有趣。

首先,用计算机解决谜题是一项业务:今天的大多数密码学都是基于解决一组所谓的非多项式^([33])(NP)问题的相对难度。NP 完全问题似乎喜欢在最不合适的时候破坏每个密码破译者的派对。高效解决它们的能力——无论是使用巨大的计算能力、巧妙算法,还是两者兼而有之——可能会让一个幸运的发明家离世界霸权更近一步。因此,有这个激励,但一个人该如何去做呢?

研究中提出的方法相当新颖。论文首先指出,数学中的许多 NP 问题可以很容易地用布尔可满足性(SAT)方程来表述。SAT 方程将这些问题表示为布尔逻辑运算,有效地构建了一系列参数和变量(布尔公式)。一个经典的 SAT 公式示例可能是

P = (x[1] XOR x[2]) AND (~x[2] AND x[3])

在这里,P 是公式(问题)本身,而 x[1] 到 x[3] 是二进制输入,或参数。

虽然对于 x[1]、x[2] 和 x[3] 的值有 2³ 种可能的组合,但只有其中一种使得 P 为真:x[1] = 1,x[2] = 0,x[3] = 1。因此,我们说只有这个三元组是 P 的解。解决 SAT 问题的本质在于确定方程中所有变量的值,使得包含这些变量的整个公式具有逻辑真值。尽管像前面展示的简单 SAT 问题很容易解决,即使不调用任何除了试错之外的求解机制,更复杂的多变量情况确实是 NP 完全的,因此,其他 NP 问题可以在多项式(即合理的)时间内被归结为 SAT 问题。

这就是问题所在。我们可以用 SAT 来表述一个困难的 NP 问题,但这并没有给我们带来太多好处。截至本文写作时,即使是针对一个非平凡方程,已知最好的 SAT 求解算法也并不比穷举搜索更有效,穷举搜索尝试所有可能性,并对每个可能性评估公式的值。这意味着如果我们有一个 SAT 问题,并且有足够的计算能力去考虑它,那么使用穷举法尝试解决方案并不是一个疯狂的方法,而且使用更复杂的方法也不会让我们走得更远。无论如何,尝试一下也不会损失太多。

以下是揭示将 SAT 问题与 TCP/IP 网络联系起来的秘密。研究人员的基本观察相当明显(或者应该是这样的,如果你订阅了 Nature):TCP(或 IP)的校验和算法,如 第九章 中所讨论的,虽然在原则上是为一个完全不同的目的而设计的,即解决方程,但实际上不过是一系列对输入消息位进行的布尔操作。毕竟,在低级别上,该算法简化为在传输数据包的单词上执行纯粹的布尔逻辑。他们得出结论,通过提供数据包的特定内容(“输入”),远程系统可以被迫执行一系列算术运算,然后评估其正确性——即与 TCP 或 IP 报头中声明的校验和的一致性。

尽管远程系统在校验和过程中的操作在每次迭代中都是完全相同的,但它具有足够的功能作为通用逻辑门,这是我们记得的 第二章 中的机制。通过将实际测试的输入与精心选择的“控制”单词交织在一起,这些单词反转或以其他方式改变迄今为止计算的局部校验和,可以执行任何布尔运算。

这反过来意味着,一旦数据暴露于校验和算法,就可以使用数据包中的特定序列控制和“输入”位轻松重新创建 SAT 逻辑;方程变量(以这种方式或那种方式选择)与用于转换当前校验和值的固定单词交织在一起,以便下一个操作的输出模仿特定的布尔运算符。最终结果——数据包的总和值——表示最终结果:要评估公式的逻辑值。

因此,可满足性测试是在远程接收者在到达时尝试验证校验和时意外进行的。如果校验和结果为 1(或在我们 SAT 计算系统中对应于评估为真的 SAT 语句的某个其他值),则该特定数据包选择的变量值通过了可满足性测试(并且流量被传递到更高层并执行)。如果校验和失败,则公式没有被满足,数据包将被静默丢弃。换句话说,如果我们的输入位表示一个特定的假设,接收者要么验证它,要么证明它是错误的,根据结果采取不同的行动。

此外,想要快速解决 SAT 问题的任何一方可以准备一组给定公式的所有可能的变量值(输入)组合,将这些值与导致输入以最理想的方式组合的信息交织在一起,将这些信息放入 TCP 数据包中,并将它们发送到全球范围内的众多主机(几乎并行)。数据包的校验和可以手动设置为已知“假设”如果被证明为真将产生的值,而不是实际计算它。只有接收到具有使公式评估为所需值的变量值的数据包的主机才会响应流量;其他系统会简单地忽略这种由于校验和不匹配而损坏的流量。因此,发送者可以在不执行大量计算的情况下确定正确解决方案,只需查找发送给那些响应请求的主机的数据包中使用的值集。

研究进一步深入,并报告了使用全球范围内的实际主机成功解决 NP 问题的尝试,从而不仅提供了理论背景,而且还实际证实了这种方法。

这种技术的影响相当微妙,但也很重要:它证明了可以将计算任务有效地“外包”给网络上的不知情和不情愿的远程方,包括解决现实世界计算问题所需的一系列操作,而无需实际攻击这些系统、接管它们、安装恶意软件或以其他方式干扰合法任务。因此,一个人可以有效地将一个特定的计算任务分配给大量系统。在这个过程中,他们只需消耗系统计算能力的一小部分,这部分计算能力加起来却可能相当于一台相当不错的超级计算机,当数百万个系统共同处理一个问题时。

世界统治即将到来?并非如此迅速。


^([33]) 在复杂性理论中,多项式问题可以通过图灵机在时间上以多项式比例与输入长度(必须找到答案的变量数量或大小)成比例地解决。这意味着解决多项式问题所需的时间直接对应于输入长度乘以一个常数指数,这个指数可以是零(导致时间完全不依赖于输入长度,例如在奇偶性测试中)。非多项式(NP)问题没有已知的此类解决方案,并且随着输入长度的增加,可能需要显著更多的时间来解决,例如表现出指数依赖性。被称为 NP 完全的 NP 问题子集已被证明没有多项式时间解决方案。NP 问题通常被认为对于非平凡输入是“困难”的,而 P 问题则更容易解决。

实际考虑因素

. . . 或者,也许,还不是时候。上述研究中提出的方法是革命性的和有趣的,但并不一定是一个特别实用的方法来通过窃取富人的资源来构建超级计算机。为了维持合理的计算速率所需的带宽量,以及为其他系统解决准备的计算量相当高。因此,这个方案效率不足,无法将复杂数学问题的解决外包给一个全球超级集群的不情愿的受害者。

在前面概述的方案中,指数级计算能力的需要被交换为指数级带宽的需要。这并不一定是一个合理的权衡,尤其是考虑到大多数网络的包大小限制,只有相对简单的测试可以被推出去。(所有这些可能都在通过以太网传输这些数据所需的时间内被解决。)这项技术证明了攻击是可能的,并提供了一个真正通用的场所来促进它,但使用更具体的攻击场景可能会产生更有用的结果。

其他窃取少量个人计算能力的方法可能作为以低成本实现强大计算能力的方式更有趣。例如,某些类型的客户端软件(如网络浏览器)可以很容易地以相对简单的方式执行甚至相当复杂的算法。一个这样的例子,详细说明在 RFC 3607 中的“中国彩票”计算方案,由让-吕克·库克(Jean-Luc Cooke)的md5crk.com网站鼓励网站管理员添加到他们的网页上。一旦这个小程序被添加到网站上,每个访问者都可以在自己的系统上运行该小程序,借用微不足道的 CPU 周期,以贡献给旨在寻找 MD5 简短函数碰撞的项目。(碰撞是产生相同简短函数的两个不同的消息。它们难以捉摸且具有轶事性质,尽管绝对可能,因为它们可以帮助我们更好地理解简短函数的弱点,并且可以经验性地证明和展示 MD5 对于今天的计算机来说太弱了,无法与之匹配。)

Java 小程序是独立于机器的小程序片段,默认情况下由网络浏览器在特殊的、受限的“沙盒”环境中执行。它们无法访问本地磁盘存储,并且(理论上)无法造成任何伤害,尽管它们可以使用有限的网络连接来执行计算并在网页上添加某些视觉元素。它们最常用于增强网站的功能,例如交互式游戏、视觉效果等。但让-吕克使用这些小程序来做其他事情:利用全球数百或数千个系统的联合计算能力,同时寻找碰撞的潜在候选者。

该小程序操作背后的原理很简单:每当访问一个合作的网站时,该小程序就会在全球的客户端系统上执行;然后,一旦启动,小程序就会尝试计算不同随机选择的消息的 MD5 快捷方式。这个过程会一直持续,直到找到一个与某个任意选择并固定的掩码模式相匹配的快捷方式。这种模式可能是“任何最后四个字节为零的快捷方式”或类似的东西。这种模式的选择是为了确保通过试错找到合适的快捷方式不会花费太多时间(这样人们就不必离开网页并在找到之前停止代码),但同时又只有一小部分所有可能的快捷方式会符合规则。

一旦找到合适的信息,程序就会将候选者“打电话回家”。作者可以检查这些提交。小程序已经检查并拒绝了一些冲突候选者,并且只提交了那些符合预定义条件(部分相同的)的候选者。由于通过这种方式收集的数据变化可能性较小,因此在n个条目中的一块数据发生冲突的可能性比纯随机数据要高得多。通过类比,如果我们只订购重量和颜色几乎相同的苹果进行配送,而不是购买一车任意水果,那么在一天内能够处理的水果中遇到两个视觉上无法区分的苹果的可能性就会更高。

尽管在网络安全伦理的灰色地带,这种由md5crk.com首次公开部署的巧妙方法确实有效,并很好地展示了寄生计算如何既有效又隐蔽。看起来,窃取原本打算用于“正当”目的的处理器周期的能力触手可及,而且可能比我们希望的更频繁地被使用。这种可能性将长期存在。

然而,一个挑剔的怀疑者继续问道,寄生计算除了窃取 CPU 的微小部分来帮助破解加密方案之外,还能做些什么?这项任务很少有人真正感兴趣?


^([34]) 当这本书准备印刷时,来自山东大学的一组中国研究人员——王晓云、冯登国、赖雪佳和余红波——提出了一种寻找 MD4、MD5、HAVAL-128 和 RIPEMD-128 冲突的技术,并提供了样本。这是现代密码学中更为重要的新闻之一,并证实了这些函数对于某些安全相关应用是不够的。虽然md5crk.com项目已经关闭,但其对探索寄生计算领域的贡献仍然有效。

寄生存储:早期阶段

当你喊叫时,声波会穿过空气,逐渐失去能量并向四面八方扩散。然而,如果它们在途中遇到固体障碍物,它们很可能会反弹,并且如果角度恰到好处,它们会反弹回你这里。可听到的结果是,在你喊叫后的瞬间,你会听到自己声音的回声。

但是,当一位信息理论爱好者站在山顶上,大声朗读他们的代码,将他们的话语指向一个岩石山谷时,会发生什么呢?我想你肯定想知道。在这种情况下,他们不可避免地会做出一个聪明的观察:如果他们快速阅读然后立即忘记他们刚才背诵的内容(因为他们被其他事情占据了注意力),他们仍然可以在信息从山谷底部反弹回来并回声返回时最终恢复信息。哇——一个方便的数据存储机制。

听起来很荒谬?也许我们只是太年轻了。早期的计算机内存模块使用了一种类似的技术,允许处理器将一些信息“离线”存储,并在稍后恢复。而不是使用空气(通过空气,声波传播得有点太快,无法在不构建极其庞大的内存单元的情况下提供合理的存储容量),使用了一个装满水银的鼓(在这种环境中,声波传播得要慢得多)。然而,原理保持不变,甚至给“内存泄漏”这个术语赋予了有趣的意义。例如,这种设备,即水银延迟线内存,被用于著名的 UNIVAC I.^([35])。

自然地,这种缓慢、笨重、危险且不便的记忆方式,随着技术的成熟,很快就被其他解决方案所取代。然而,这项发明本身具有一定的魅力,并不会那么轻易地消失在人们的记忆中。2002 年,萨基布·A·汗在拉斯维加斯的 DefCON 会议上做了一次简短的演讲,重新唤起了人们对它的兴趣,并为我们提供了关于如何利用大规模网络的特性,通过互联网作为媒介来构建类似类型的瞬时存储的第一手信息。但这一次,关于声学记忆的描述听起来不再那么荒谬原始,而是对观看这个简短幻灯片的黑客和极客们来说,不可思议地酷。声学记忆以一种时尚的方式回归了。

由于数据包的往返时间(即消息到达远程系统所需的时间和响应返回所需的时间)不为零,可以通过重复发送和接收数据包的部分内容并等待其回声来始终在“线”上保持一定量的数据。萨基布使用了 ICMP(互联网控制消息协议)“回声请求”(ping)数据包来实现这一效果;互联网上的大多数系统都会对这些数据包做出“回声响应”,引用它们收到的原始有效载荷。

这看起来像是一个酷炫的技巧。然而,对于任何合理应用来说,它都离实际应用还很远,因为它需要频繁地重新传输数据的一部分。由于 ICMP“回显应答”在接收到“回显请求”后几乎立即发送,因此在数据被发送回并需要从线路上恢复之前,只能推送出很少量的数据。因此,以这种方式可以存储的数据量不会超过用户在最多几秒钟内(通常不到一秒钟)能够推送出的数据量。

啊,但是寄生存储可以改进。


^([35]) 可能值得指出的是,低容量、模拟延迟线存储器也用于 SECAM(Séquentiel Couleur avec Mémoire,或顺序彩色存储)电视接收器的早期实现中。与 NTSC 或 PAL 不同,SECAM 信号使用降低的色彩分辨率;红色和蓝色色度分量交替传输,永远不会同时传输。其他分量必须从前一帧中获取,以确定特定像素的外观。为了实现这一点,需要实现一个存储设备。

使寄生存储可行

在 2003 年,我和 Wojciech Purczynski 共同撰写了一篇名为“用数据包进行杂耍:寄生数据存储”的论文。我们将寄生存储的概念进一步拓展,并考虑了多种可以显著扩展互联网存储容量同时保持维持信息所需带宽的方法。我们的研究集中在几种其他在远程系统上存储数据的方法上,并根据存储介质的特性(其可见性、易失性和可靠性)对它们进行了分类。我们还对每种技术的假设存储容量进行了详细讨论。

这篇论文相当简短,并且——我希望——清新有趣,它被包含在这里。

==============================================
 Juggling with packets: floating data storage
==============================================

  "Your dungeon is built on an incline. Angry monsters can't play marbles!"

  Wojciech Purczynski <cliph@isec.pl>
  Michal Zalewski <lcamtuf@coredump.cx>

1) Juggle with oranges!
------------------------
  Most of us, including the authors of this paper, have attempted to juggle
 with three or more apples, oranges, or other fragile ballistic objects. The
 effect is usually rather pathetic, but most adept juggler padawans sooner or
 later learn to do it without inflicting excessive collateral damage.

  A particularly bright juggler trainee may notice that, as long as he
 continues to follow a simple procedure, at least one of the objects is in the
 air at all times and that he has to hold at most two objects in his hands at
 once. Yet, each and every apple goes through his hands every once in a while,
 and he can recover it at will.

  After some fun with juggling, he may decide that the entire process is
 extremely boring and go back to his computer. While checking his e-mail, an
 educated juggler might notice that a typical network service has but one duty:
 to accept and process data coming from a remote system and take whatever steps
 it deems appropriate based on its interpretation of the data. Many of those
 services do their best to behave robustly, to be fault tolerant, and to supply
 useful feedback about the transaction.

  In some cases, the mere fact that a service is attempting to process the
 data and reply according to protocol can be used in ways that the authors
 never dreamed of. One of the more spectacular examples of this, which our
 fellow juggler might be familiar with, is research done at the University of
 Notre Dame, titled "Parasitic Computing" and published in letters to "Nature."

  Nevertheless, our hero concludes that such attempts are quite impractical in
 the real world. The cost of preparing and delivering trivia to be solved far
 exceeds any eventual gain since the sender has to perform operations of
 comparable computational complexity simply to deliver the request. "The
 computing power of such a device is puny!" he says.

  A real juggler would focus on a different kind of outsourced data
 processing, one that is much closer to his domain of expertise. Why not
 implement a distributed fruit-based data storage? What if I write a single
 letter on every orange and then start juggling? I can then store more orange
 bytes than my physical capacity (the number of oranges I can hold in my
 hands)! How brilliant. . . . But, but, would it work without oranges?

2) The same, without oranges
-----------------------------

  This paper is based on the observation that for all network communications,
 there is a nonzero (and often considerable) delay between sending information
 and receiving a reply--a result of the physical constrains of the medium and
 the time it takes to process data on all computer equipment.

  Like an orange with a message written on it, a packet used to store a piece
 of data travels for a period of time before returning to the source, and for
 this period of time we can safely forget its message without losing data. As
 such, the Internet has a nonzero momentary data storage capacity, and it is
 possible to push out a piece of information and effectively have it stored
 until echoed back. By establishing a mechanism for the cyclic transmission and
 reception of chunks of data to and from a number of remote hosts, it is
 possible to maintain an arbitrary amount of data constantly 'on the wire,'
 thus establishing a high-capacity, volatile medium.

  This medium can be used for memory-expensive operations, either as regular
 storage or for certain types of sensitive data for which one does not want to
 have leave a physical trail on a hard disk or other nonvolatile media.

  Since it is not considered bad programming practice to return as much
 relevant information to the sender as the sender sends to the service, and
 because many services or stacks maintain a high level of verbosity, our
 juggling experience tells us that it is not only possible, but also feasible,
 to establish this kind of storage, even over a low-end network hookup. Unlike
 traditional methods of parasitic data storage (such as P2P abuse, open FTP
 servers, binary Usenet postings, and so on), this particular method may or may
 not leave a trail of data (depending on how we implement it), and it does not
 put any single system under a noticeable load. Therefore, unlike the
 traditional methods, this technique is less likely to be detected and
 considered an abuse. Hence, the possibility of the data being intercepted and
 purposefully discarded is much less a problem.

3) Class A data storage: memory buffers
----------------------------------------

  Class A data storage uses the capacity inherent in communication delays
 during the transmission and processing of live data as it travels across
 networks between two endpoints. The information stored herein remains cached
 in the memory of a remote machine and is not likely to be swapped out to a
 disk device.

  Examples of class A memory are a variety of schemes that rely on sending a
 message that is known to result in partial or full echo of the original
 request, including the following:

    - SYN+ACK, RST+ACK responses to SYN packets, and other bounces

    - ICMP echo replies

    - DNS lookup responses and cache data. It is possible to store some
      information in a lookup request and have it bounce back with an NXDomain
      reply or to store data in an NS cache.

    - Cross-server chat network message relaying. Relaying text messages
      across IRC servers and so on can exhibit considerable latency.

    - HTTP, FTP, web proxy, or SMTP error or status replies.

  The most important properties of class A storage are:

    - Low latency (milliseconds to minutes), which makes it more useful for
       near random access memory applications.

    - Lower per-system capacity (usually kilobytes), which makes it less
       suitable for massive storage.

    - Only one chance to receive or few retransmits which make it less
       reliable in case of a network failure.

    - Lower likelihood of permanent recording. The data is not likely to be
       stored on a nonvolatile medium or swapped out, increasing privacy and
       deniability.

  In particular, when using higher-level protocols, additional features appear
that might solve some of the low-capacity and short- recovery window problems
 shared by various types of class A storage. For example, it is possible to
 establish a connection to a service such as SMTP, FTP, HTTP, or any other
 text-based service and send a command that is known to result in an
 acknowledgment or error message being echoed along with part of the original
 data. We do not, however, send a fully formatted message; we leave some
 necessary characters unsent. In most cases, end-of-line characters are
 required in order to complete the command. In this state, our data is already
 stored on remote service waiting for a complete command or until connection
 time-out occurs. To prevent time-outs, either on TCP or at the application
 level, no-op packets need to be sent periodically. A \0 character interpreted
 as an empty string has no effect on many services but is sufficient to reset
 TCP and service time-out timers. A prominent example of an application
 vulnerable to this attack is Microsoft Exchange.

  The attacker can sustain the connection for an arbitrary amount of time,
 with a piece of data already stored at the other end. To recover the
 information, the command must be completed with the missing \r\n, and then the
 response is sent to the client.

  A good example is the SMTP VRFY command:

  220 inet-imc-01.redmond.corp.microsoft.com Microsoft.com ESMTP Server
  Thu, 2 Oct 2003 15:13:22 −0700
  VRFY AAAA...
  252 2.1.5 Cannot VRFY user, but will take message for
  <AAAA...@microsoft.com>

  It is possible to store just over 300 bytes, including nonprintable
 characters, this way--and have it available almost instantly. More data can be
 stored if the HTTP TRACE method is used with data passed in arbitrary HTTP
 headers, depending on the server software. Sustained connections can give us
 arbitrarily high latency, thus creating large storage capacity.

  This type of storage is naturally more suited for privacy-critical
 applications or low-latency lower to medium capacity storage (immediate RAM-
extending storage for information that should leave no visible traces). The
 storage is not suitable for critical data that should be preserved at all
 costs, due to the risk of data being lost on network failure.

4) Class B data storage: disk queues
-------------------------------------

  Class B data storage uses "idle" data queues that store information for an
 extended period of time (often on the disk). For example, MTA systems can
 queue e-mail messages for as many as 7 days (or more, depending on the
 configuration). This feature can give us a long delay between sending data to
 store on the remote host and receiving it. Because a typical SMTP server
 prevents the relay of e-mail from the client to itself, e-mail bounces can be
 used to have data returned after a long period of time.

  For example, consider this potential attack scenario:

  1\. The user builds a list of SMTP servers (perhaps servers that provide a
      reasonable expectation of being beyond the reach of their foes).

  2\. The user blocks (with block/drop, not reject) all incoming connections to
      their port 25.

  3\. For each server, the attacker has to confirm its delivery time-outs and
      the IP from which the server connects back while trying to return a
      bounce. This is done by sending an appropriate probe to an address local
      to the server (or requesting a DSN notification for a valid address) and
      checking to see how long the server tries to connect back before giving
      up. The server does not have to be an open relay.

  4\. After confirming targets, the attacker starts sending data at a pace
      chosen so that the process is spread evenly over the period of one week.
      The data should be divided so that there is one chunk per each server.
      Every chunk is sent to a separate server to immediately generate a bounce
      back to the sender.

  5\. The process of maintaining the data boils down to accepting an incoming
      connection and receiving the return at most a week from the initial
      submission, just before the entry is about to be removed from the queue.
      This is done by allowing this particular server to go through the
      firewall. Immediately after the chunk is received it is relayed back.

  6\. To access any portion of data, the attacker looks up which MTA is holding
      this specific block and then allows this IP to connect and deliver the
      bounce. Three scenarios are possible:

     - If the remote MTA supports the ETRN command, the delivery can be
        induced immediately.

     - If the remote MTA was in the middle of a three-minute run in an attempt
        to connect to a local system (keeps retrying thanks to the fact its SYN
        packets are dropped, not rejected with RST+ACK), the connection can be
        established in a matter of seconds.

     - Otherwise, it is necessary to wait from five minutes to one hour,
        depending on the queue settings.

  This scheme can be enhanced using DNS names instead of IPs for users on
 dynamic IP or to provide additional protection (or when it is necessary to cut
 the chain immediately).

  The important properties of class B storage are:

    - High per-system capacity (megabytes), making it a perfect solution for
       storing large files and so on

    - Higher access latency (minutes to hours), likening it to a tape device,
       not RAM (with the exception of SMTP hosts that accept the ETRN command
       to immediately reattempt delivery)

    - Very long lifetime, increasing per-user capacity and reliability

    - Plenty of delivery attempts, making it easy to recover the data even
       after temporary network or hardware problems

    - Likely to leave a trace on the storage devices, making it a less-useful
       solution for fully deniable storage (although it would still require
       examining a number of foreign systems, which does not have to be
       feasible)

  Class B storage is suitable for storing regular file archives, large append-
only buffers, encrypted resources (with a proper selection of hosts, it
 remains practically deniable), etc.

5) Discreet class A storage
----------------------------

  In certain situations, it might be necessary to devise a solution for
 discreet data storage that does not reside on the machine itself and that
 makes it possible to deny the presence of this information anywhere.

  The basic requirement is that the data is:

    - Not returned until a special key sequence is sent

    - Permanently discarded without leaving any record on any nonvolatile
       storage media in the absence of keep-alive requests

  It is possible to use class A storage to implement this functionality using
 the sustained command method discussed earlier. The proper TCP sequence number
 is necessary to release the data, and until this sequence is delivered, the
 data is not returned or disclosed to any party. If the client node goes
 offline, the data is discarded and likely overwritten.

  The sequence number is thus the key to the stored information, and, if the
 lifetime of the data is fairly short when keep-alive \0s stop coming, it is
 often adequate protection.

6) User-accessible capacity
----------------------------

  In this section, we attempt to estimate the storage capacity available to a
 single user.

  In order to maintain a constant amount of data "outsourced" to the network,
 we must be able to receive and send it back on a regular basis.

  The amount of time that data can be stored remotely is constrained by the
 maximum lifetime Tmax of a single packet (including packet queuing and
 processing delays). The maximum amount of data that can be sent is limited by
 maximum available network bandwidth (L). Thus, the maximum capacity
 can be defined as:

    Cmax [bytes] = L [bytes/second] * Tmax [seconds] / Psize * Dsize

where:

    Dsize - The size of a packet required to store an initial portion of data
 on a remote host

Psize - The size of a packet required to sustain the information stored on
 a remote host

  Psize and Dsize are equal and thus can be omitted whenever the entire chunk
 of data is bounced back and forth; they differ only for "sustained command"
 scenarios. The smallest TCP/IP packet to accomplish this has 41 bytes. The
 maximum amount of data that can be sustained using HTTP headers is about 4096 bytes.

  That all, in turn, gives us the following chart:

            Bandwidth  | Class A | Class B
           -----------+---------+---------
            28.8 kbps |  105 MB |    2 GB
             256 kbps |  936 MB |   18 GB
               2 Mbps |  7.3 GB |  147 GB
             100 Mbps |  365 GB |    7 TB

7) Internet as a whole
-----------------------

  In this section, we attempt to estimate the theoretical momentary capacity
 of the Internet as a whole.

  Class A

    To estimate the theoretical class A storage capacity of the Internet, we
     assume the following:

      - ICMP messages offer the best balance between storage capacity and
         preserving a remote system's resources.

      - An average operating system has a packet input queue capable of
         holding at least 64 packets.

      - The default PMTU is approximately 1500 (the most common MTU).

    As an estimate of the number of hosts on the Internet we use an ISC survey
     for 2003, which lists 171,638,297 systems with reverse DNS entries
     (although not all IPs with reverse DNS have to be operational). To take
     this into account, we used the ICMP echo response ratio calculated from
     the last survey that performed such a test (in 1999). The data then
     suggested that approximately 20 percent of visible systems were alive,
     which, in turn, sets the number of systems ready to respond to ICMP
     requests at roughly 34,000,000.

    By multiplying the number of systems that reply to ICMP echo requests by
     the average packet cache size and maximum packet size (minus headers), we
     estimate the total theoretical momentary capability for class A ICMP
     storage to be approximately 3 TB.

Class B:

    To estimate theoretical class B storage capacity, we use the example of
     MTA software. There is no upper cap for the amount of data we feed to a
     single host. Although it is safe to assume that only messages under
     approximately 1 MB will not cause noticeable system load and other
     undesirable effects, we assume that the average maximum queue size is
     500 MB.

    Our own research suggests that roughly 15 percent of systems that respond
     to ping requests have port 25 open. We thus estimate the population of
     SMTP servers to be 3 percent (15 percent of 20 percent) of the total host
     count, or just over 5,000,000 hosts.

    This gives a total storage space capacity of 2500 TB.

应用、社会考虑和防御

但是现在呢?如果实际寄生计算和存储方案的好处仍然不足以让它成为一个比增加更多硬件更有吸引力的替代品,那么拥有这些方案有什么好处呢?

尽管在实用化寄生计算方面取得了进展,但当考虑到廉价内存和千兆赫兹处理器的丰富性时,旨在扩展传统系统纯粹的计算能力或存储空间的应用可能看起来毫无意义。

然而,这项技术的潜在价值可能在于一套完全不同的应用领域:易失性计算。构建可用的分布式计算机的能力,这些计算机可以随意分散,不留任何物理痕迹,在任何单一位置也不存储任何有意义的数据,这可能是一种强大的隐私工具,同时也可能对法医和执法部门构成一些挑战。构建易失性存储和保持内存的能力,在单个节点离线后不久就会崩溃,但又不涉及频繁的数据重传,这可能为犯罪分子(或任何受压迫的实体)提供一定程度的否认能力,并且需要许多常见的证据收集程序发生相当大的变化。

此外,想象一下易失性系统,一旦启动和初始化,就可以在互联网上持续很长时间,没有本地化的物理存在。对于易失性分布式计算机系统,有两种可能的设计,而且都不荒谬:

  • 可以设计系统,使它们通过并行找到解决方案来完成复杂任务(这已经在之前讨论的 SAT 计算方案中基本实现)。这类系统的缺点是必须手动检索计算结果,并偶尔从某个位置重新“播种”整个系统以启动下一次处理迭代。依赖于协议的低级属性(如 TCP)的低级解决方案可能会落入这一类别。

  • 可以设计系统,使它们自己执行分布式计算的后续迭代。所有高级功能(如嵌入式文档渲染算法)和某些网络服务的滥用都可能被用来促进这种活动。

在每种情况下,后果都可能非常严重。例如,如何摧毁一个冗余的自修复机器,该机器不使用单一系统,而是从其他人那里借用一小部分内存和处理能力几秒钟——而且不使用任何漏洞或明显可过滤的流量?而且,意识到我们无法立即识别这种分布式计算机的目标,难道不是也有些令人不安吗?向糟糕的科幻小说大师们表示尊敬,我相信计算机的统治即将到来,并欢迎我们新的机器主宰。

思考食物

防御寄生计算通常极其困难。存储数据或导致对方执行某些简单计算的能力通常与网络协议的基本功能绑定。这是我们无法想象在不抹去我们所知的互联网的同时消除这种特性,并引入一系列比解决的问题更严重的新问题的。

保护单个系统免于成为寄生计算节点也是相当困难的,因为从系统中窃取的资源通常只是空闲 CPU 时间和内存的微不足道的一部分,因此可能很容易被忽视。

很可能寄生计算尚未展现出其全部潜力,而这种威胁——对于单个系统来说可能无关紧要或不存在,但对于整个网络来说却具有重要意义——将会持续存在。

第十七章:网络拓扑

关于我们周围世界的知识如何帮助我们追踪朋友和敌人

互联网的形状是什么?没有委员会来监督它或决定它应该在哪里、如何以及为什么应该扩展,或者新的和现有的系统应该如何组织或管理。互联网在所有方向上以同样由需求、经济、政治、技术和盲目运气驱动的方向发展。

然而,互联网并非无形状的团块:存在由自主系统组成的计划内、本地管理的层次结构,核心路由器周围环绕着较小的节点,链接由自动机制配置或由人类精心设计。互联网是一个壮观的网状结构,一个复杂而脆弱的蜘蛛网,覆盖着整个工业化和发展中国家。捕捉这个不断变化的拓扑结构似乎是一项挑战,但同时也很有吸引力,尤其是当我们意识到我们可以如何从收集到的信息中受益。

在本章中,我将首先讨论两个著名的尝试来绘制互联网的拓扑结构,然后我将再次道德化地讨论收集到的信息的潜在用途,这些用途可以让我们的祖先连做梦都不敢想的事情成为可能。

捕捉这一刻

对互联网进行最全面映射的尝试是由互联网数据分析合作协会(CAIDA)进行的,该组织由联邦研究机构(如 NSF、DHS、DARPA)以及行业(如 Cisco、Sun)资助。该组织的成立是为了提出交通和基础设施分析工具,以供互联网社区共同受益,希望使其变得更好、更可靠、更具弹性和更健壮。

自 2000 年以来,CAIDA 的旗舰公共项目之一就是创建和维护自主系统核心网络图(也称为“Skitter”)。截至本出版物,他们最新的捕获代表了 12,517 个主要自主系统的数据,对应于 1,134,634 个 IP 地址和 2,434,073 个链接(逻辑路径)。

尽管听起来非常晦涩难懂,但 CAIDA 互联网地图仅使用公开可访问的路由器 BGP 配置数据、经验网络测试结果(traceroute)和网络块 WHOIS 记录创建。这个地图使用极坐标组织。代表每个系统的点位于一个角度上,对应于网络声明的总部位置的物理位置,以及对应于该特定自治系统的“对等关系相关性”的半径。后者是通过计算观察到的其他自治系统接受来自该特定节点的流量数量来得到的。因此,庞大的核心系统位于地图的中心,而只与几个节点直接接触的系统则位于外围附近。图中的线条简单地对应于路由器之间的对等关系。

注意

非常遗憾,我们无法在书中免费使用 CAIDA Skitter 图形的图表。然而,我鼓励您在线查看这张令人惊叹的图片,网址为www.caida.org/analysis/topology/as_core_network/pics/ascoreApr2003.gif,公众可以免费获取。

另一个值得注意的尝试绘制网络的方法是,通过分析从特定位置(在本例中为贝尔实验室)观察到的到各种网络的距离,构建一个类似于树状结构,而不是 CAIDA 创建的复杂网状结构。这项分析由比尔·切斯威克于 2000 年进行,^([110]) 结果在图 17-1 中展示。这个结构并不根据系统的物理或行政位置来参数化图;从中心到该节点的相对距离对应于该节点与贝尔实验室之间的跳数。

虽然这两个尝试似乎涉及大量数据收集和分析,但对于业余爱好者来说,即使在低端链路上尝试绘制网络也不是特别困难。使用单个数据包探测所有公开路由的子网可能只需要生成几 GB 的流量——相当于典型 DSL 连接上几个小时到一天的时间。唯一的危险是可能会激怒一些系统管理员,但随着计算机蠕虫和自动化攻击的泛滥,很少有系统管理员对这种低敏感度阈值有反应。绘制观察到的互联网结构是可能的,并且可能会很有成效,尤其是因为它可以告诉我们很多关于全球网络是如何组织的信息。

比尔·切斯威克的互联网地图

图 17-1. 比尔·切斯威克的互联网地图

但实际上,数据,例如 CAIDA、比尔·切斯威克或几乎任何熟练的互联网用户获得的信息,也可以成功地用来更好地理解神秘流量的性质,并更好地检查我们可能有一天偶然遇到的流量的来源。

使用拓扑数据识别来源

欺骗流量是互联网的主要问题之一——或者至少是其中较为令人烦恼的问题之一。盲目地使用虚假或特别选择的但具有欺骗性的源地址发送的数据包,可以用来滥用计算机之间的信任关系,注入恶意内容(如未经请求的大规模邮件)而不留下明确的痕迹和合法的原始信息,等等。盲目欺骗还可以用来隐藏进行系统探测的攻击者的身份(“诱饵扫描”在第十三章中已有讨论)。然而,所有问题中最严重的是用于执行拒绝服务(DoS)攻击的欺骗。

在典型的 DoS 攻击中,管理员有机会看到针对他们服务之一(并且可能旨在使其崩溃并给运营商带来不便或损失)的恶意流量的来源。然而,可能随机欺骗违规数据包,在这种情况下,管理员将无能为力,无法过滤掉来自攻击者的流量,而不切断其他用户的连接。他们唯一的希望是与上游提供商合作,调查链路层上流量的实际来源,并将信息传递给违规者的 ISP;然而,这需要时间,而且需要很多时间。这也要求在没有法院命令的情况下,让所有各方都相信这个案例值得调查(以及他们的时间和金钱)。这种情况使得系统管理员配备区分欺骗流量和合法流量的工具和方法变得尤为重要。

当我以前在美国生活和工作时(现在我在波兰),我的同事马克·洛夫莱斯决定实施唐纳德·麦克拉克兰最初提出的一个想法:他会测量他与一个数据包的假定发送者之间的生存时间(TTL),以自动确定传入的数据包是否被欺骗。在一个信息不可信的世界中,确定网络数据包的来源是一个重要挑战,而且即使只是在特定子集的情况下,这种能力也会极大地促进许多分析和管理任务,如前所述。

要理解唐纳德和马克的想法,考虑一下,我们从其中看到流量的远程系统,与我们之间有一个特定的逻辑距离,由一定数量的网络设备隔开。因此,该系统合法发送的所有数据包在到达时都会显示一定的 TTL,对应于该系统上配置的默认初始 TTL 减去数据包经过的中间系统数量(如第九章第九章. 外国口音中所述)。然而,对于可能源自完全不同网络的伪造流量,初始 TTL 和距离很可能与上述观察结果不同。马克的工具 despoof^([111])比较了特别诱导和先前接收到的流量中观察到的 TTL,以区分合法和伪造的流量。

然而,尽管这种方法在针对毫无防备的攻击者时可能效果很好,但至少有两个问题与之相关:

  • 一个偏执的攻击者可以在攻击之前测量距离并选择一个与预期值匹配的 TTL。尽管可能,但这个技巧实施起来有些困难。一方面,攻击者可能无法将 TTL 设置得足够高,以达到与真实数据包到达目的地时预期值相匹配的具体值。如果他被试图冒充的系统使用的默认 TTL 在 255(最大可能值)或附近,并且他离目标比他试图冒充的系统更远(因此他不可能发送一个在到达目的地时会有所需 TTL 的数据包),那么他的计划可能会被挫败。当然,很少有系统使用最高的 TTL,而且攻击者一开始就想冒充特定系统的可能性也很小。

    攻击者的第二个挑战是,如果他离受害者很远,并且不知道这些主机之间的路由细节,他可能无法确定受害者和冒充系统之间的确切距离。但如果受害者使用 despoof 动态实施过滤规则来切断恶意数据包,攻击者可能会尝试从各种来源尝试不同的 TTL,直到他看到受害者不再能够区分为止。(这将是明显的:被攻击的系统将开始表现出成功攻击的效果,例如性能影响。)

  • 每次接收到可疑数据包时,接收者都必须开始调查,然后等待结果到来。这使得将 despoof 作为自动防御的基础变得不切实际,尤其是在应对 DoS 攻击时。然而,这种方法对于确定“诱饵扫描”的实际来源仍然非常有用。

没有特定网络拓扑的知识,很难比使用 despoof 做得更好;这个工具实现的 TTL 分析技术足够好,可以识别并阻止许多常见的探测和个别攻击,但接下来怎么办?

将马克的工具与网络结构的实时数据相结合,并应用被动指纹识别来确定发送特定请求的系统的初始 TTL,这种技术就变得更加强大。这些额外的数据使我们能够通过比较观察到的初始 TTL 与网络图所示预期的距离来进行对传入流量的初步被动评估。^([36]) 由于我们可以不启动任何主动探测网络拓扑数据来确定我们应该看到的距离,因此我们可以几乎不费吹灰之力就区分合法和恶意流量。这反过来又使得我们能够相当可靠地应对大规模事件,并检测到单个低配置探测,而不会让攻击者知道有欺骗检测系统在位。

显然,在考虑对等关系时,考虑到网络的结构可以获得很多好处。但欺骗检测只是开始。


^([36]) 在这种方法中,必须允许一定的误差范围来比较 TTLs,因为内部网络中可能有几个额外的跳数。此外,一些路由是非对称的,它们的长度可能会根据流量交换的方向略有不同。

网络三角测量与网格型拓扑数据

网络三角测量是网络拓扑网格型数据在流量分析目的上的一个相当有趣的应用。一旦攻击者选择同时或连续攻击多个目标,我们可以使用网络三角测量来确定发送欺骗数据包的攻击者的近似位置,而不需要那些操作底层路由骨干的人员的帮助——这真是痛苦中的快乐。

好吧,为了准确起见:虽然当攻击者选择多个目标时,三角测量效果最佳,但在某些场景中,即使他们选择仅攻击一个服务,也可能效果相当好。特别是,当攻击目标具有多个 IP 地址,并且服务由多个物理位置提供,以分散负载并使整个结构容错(如网络服务常见的那样)时,我们可能能够从不同的角度观察到相同的攻击。在所有其他场景中,当系统管理员注意到多个系统被攻击者针对并分享他们关于事件的资料时,我们可以获得关于攻击的一系列数据。

无论情况如何,一旦认为来自单一来源的数据出现在多个目的地,我们就可以进行三角测量。对于每个观察到流量的目的地,只有一组特定的网络距离可以通过观察违规数据包经过的距离来确定(再次,可以通过检查 TTL 来找出)。所有观察点的这些集合的交集将产生一个更小的集合——或者通常情况下,只有一个网络——攻击可能由此发起,如图 图 17-2 所示。

能够自行执行跟踪使我们摆脱了对 ISP 的无条件依赖,并有助于精确确定谁在攻击或探测我们的网络——也许还能找出原因。

尽管这种方法比传统的去欺骗方法更难阻止,但一个狡猾的攻击者仍然可能通过为每个目标随机化不同的 TTL(或 TTL 范围)来欺骗观察者。诚然,我们目前不知道有工具能做这件事,但这可能会改变。

战斗已经失败了吗?不,有一种方法可以防止肇事者用这种方式欺骗我们。


^([37]) 即使工具使用随机的 TTL,如果可以在每个目的地观察到多个数据包,那么通过观察数据包经过的距离(再次,可以通过检查 TTL 来找出)来判断距离是可能的。例如,如果扫描工具在 32 到 255 的范围内随机化初始 TTL,但在目的地接收到的数千个数据包中,没有一个的 TTL 超过 247,那么主机很可能有 255 - 247 = 8 个系统距离。

网络压力分析

解决方案被称为“网络压力分析”,以一篇由 Hal Brunch 和 Bill Cheswick 在 2000 年 LISA 大会上展示的优秀研究成果的形式出现。^([[112)]] Brunch 和 Cheswick 提出了一种对树形网络拓扑数据(类似于前面在 图 17-1) 中获得的数据的有趣用途,这些数据是针对特定位置的。他们想出了一种使用这些数据来检测特定类型欺骗流量(拒绝服务)来源的方法。这种方法本身相当简单,基于这样一个假设:这种攻击不仅会对所针对的系统造成压力,还会对中间路由器造成压力,而这种压力可以通过受害者外部测量并用来——几乎可以说是——通过拉动电线来回溯找到线索。

一个简单的网络三角剖分:只有一个源头与所有观察结果一致。攻击者可能正在伪造源地址,但无法欺骗受害者。

图 17-2. 一个简单的网络三角剖分:只有一个源头与所有观察结果一致。攻击者可能正在伪造源地址,但无法欺骗受害者。

通过首先从您的位置构建或获取到所有互联网网络的链接树,然后在攻击发生时遍历此树结构的后续分支,来完成对网络链接的压力测试工作。对于每个分支(在现实中,这表示与更高阶路由器的连接),我们可以通过向或通过与其关联的路由器发送测试流量来迭代测量此节点的网络负载。(在这篇特定的论文中,使用了 UDP [用户数据报协议] chargen,但也可以使用 ICMP 请求或其他类型的消息。)我们选择一个更负载的节点作为潜在入流候选,然后列出并测试从这个节点产生的所有分支,直到我们追踪到流量源头。

图 17-3 展示了简单的回溯场景。在第一阶段,当攻击发生时,受攻击的系统试图测量三个最近的互联网路由器的性能;它得出结论,第一个(最顶部的)路由器是最饱和的。

基于这些信息,受害者选择只测试与此设备直接连接(对等)的路由器。在这个特定的图中,只有三个设备需要测试(剩余的六个不需要测试,因为它们不与此设备对等),再次,第一个是最负载的。这个过程会持续进行,直到确定一个直接连接到特定网络的路由器,通过公共数据库可以找到其物理位置和所有者信息,作为最终端点。

使用网络拓扑数据和压力测试进行递归攻击回溯

图 17-3. 使用网络拓扑数据和压力测试进行递归攻击回溯

出现了一个潜在问题:一些设备可能因为处理 DoS 流量以外的其他原因而负载过重;其他设备可能有充足的 CPU 周期,不会因为中继恶意流量而受到严重影响。

为了解决这个问题,研究提出了在路由器上施加人工短期负载(通过生成额外流量)并观察这种测试如何影响 DoS 请求的带宽和延迟;如果这个特定设备确实参与了恶意数据包的中继,那么当我们对设备施加负载时(再次,可能通过生成额外的伪造 TCP、UDP 或 ICMP 请求,这些请求更多地是为了消耗设备的 CPU 功率而不是使其接口拥塞),攻击率应该会下降。因此,应该只在涉及服务恶意流量的分支上存在相关性。

这个聪明而简单的方案已经在测试环境中成功使用。然而,因为它涉及到与路由器交互并在其上放置额外的负载,所以在考虑在现实世界中使用它时,某些伦理问题就出现了。

思考题

在本章讨论的技术用于追踪攻击者的主要困难在于,我们需要为每个地点构建和更新网络图。并不立即清楚这些图应该多久刷新一次,以及哪些方法将证明最可靠且最不具侵入性。

另一个可能的问题是,大部分核心互联网基础设施是冗余的。在某些情况下,只有在主路由失败或饱和时,才会选择某些替代路由,尽管在某些情况下,切换可能作为负载平衡的一部分发生。因此,某些经验性地图可能在几分钟或几小时内就过时了——尽管这种情况并不常见。

最后,尽管使用各种去伪战术的私人、个人用途可能非常成功,但在我们可以大规模部署这些技术之前,还有许多需要回答的问题——其中一些问题并不完全是关于技术问题。

第十八章:监视虚空

当我们凝视深渊时,没有杀死我们的东西使我们变得更强大

我们已经探讨了通过观察两个系统之间的通信或观察此类通信的副作用来发现信息和拦截数据的方法。然而,故事还没有结束。有时,通过避开我们希望探测的目标,我们甚至可以看到更多。

一整套通常被称为“黑洞监控”的方法,致力于观察和分析意外、错误或以损坏形式到达特定目的地的未请求或不想要的流量。这些方法通常包括简单地运行数据包转储工具,然后痛苦地分析和理论化每一个观察结果。

尽管在一个完美的世界中,我们不应该在不应找到数据的地方寻找数据,但现实中我们可以使用这些方法收集大量信息,并获得关于整个网络状况的无价线索。尽管信息大多是随机的,我们无法选择我们听谁的话,但我们仍然可以从这些努力中受益。

直接观察策略

黑洞监控的一个应用在于检测和分析全球攻击趋势。许多掌握新攻击技术的黑帽黑客通常会简单地扫描大量网络地址,以寻找可被利用的脆弱目标,最终用于非法活动(可能是为了收集跳转主机^([38]) 或构建用于自动攻击的攻击无人机网络)。我们可以通过观察来自各种来源的标准网络扫描活动增加,来使用黑洞监控提醒我们野外正在被利用的新漏洞。

许多网络管理员部署黑洞监控。他们有时会将其与蜜罐(在网络中放置一个假的“诱饵”系统来捕捉攻击者,拦截他们的工具并识别他们的技术^([113])) 结合使用,以产生一个预警系统,使他们能够首先了解蠕虫和其他恶意软件即将爆发的信息。(您还可以使用黑洞流量来校准“噪声水平”并更有效地检测针对您的服务器的针对性攻击,而不会收集自动的、无差别的恶意活动。)

如杜格·桑格和何塞·纳萨里奥(何塞最近在他的书《针对互联网蠕虫的防御和检测策略》^([114])) 等研究人员,曾尝试在大量网络蠕虫爆发期间分析黑洞活动。他们的目标是更好地理解和模拟网络的分布(初始传播和再感染)动态,并测试蠕虫感染算法的效率和持续性。他们的研究将帮助我们设计针对大规模、分布式威胁的未来防御措施,同时为今天网络的状态提供宝贵的见解。他们的发现示例显示在图 18-1 通过图 18-4 展示了在爆发期间蠕虫的传播方式。数据基于对 TCP 端口 137 的观察到的攻击尝试次数,这是 Windows NetBIOS 实现的一部分,默认安装在所有 Windows 计算机上,并且被许多类型的自传播恶意软件所针对。注意在这个图中,在最初的传播一周后——当受感染的站点(源)和被观察到的黑洞网络上的攻击系统数量稳步且快速增加时——一个稳定期突然持续了一个多月,出现了戏剧性的高峰和低谷。这种传播足迹对于蠕虫及其运行的网络安全条件来说非常独特;它还反映了作者使用的目标选择和感染算法的微妙之处。

图 18-2 展示了蠕虫传播算法的不同方面,并描述了目标选择算法的特性。在这种情况下,一个针对 Microsoft SQL 服务器的流行蠕虫似乎对地址空间有相当连续的覆盖(尽管在约 200 到 225 之间的八位字节被选择得相当频繁,而且蠕虫似乎完全跳过了 225 以上的值)。

Windows 蠕虫传播特性

图 18-1. Windows 蠕虫传播特性

SQLSnake 蠕虫目标选择算法直方图;注意地址空间的非均匀但总体连续覆盖

图 18-2. SQLSnake 蠕虫目标选择算法直方图;注意地址空间的非均匀但总体连续覆盖

图 18-3 展示了不同网络蠕虫 Slapper 的相同图表。这个蠕虫针对 Linux 系统,利用了流行的 OpenSSL 加密库中的一个漏洞。该算法似乎提供了相当均匀的覆盖,但连续性较差,某些值之间存在巨大的空洞。

Slapper 蠕虫目标选择算法直方图。这显示了远更均匀的分布,但非连续覆盖,存在间隙,表明每个“随机”地址的最不重要位是常数——可能是因为编程错误。

图 18-3. Slapper 蠕虫目标选择算法直方图。这显示了更加均匀的分布,但非连续的覆盖,存在间隙,表明每个“随机”地址的最不显著位是恒定的——可能是因为编程错误。

图 18-4 显示了蠕虫随时间的持久性模式。例如,一些蠕虫似乎随着系统的修补和消毒而稳步死亡,而其他蠕虫则使用算法导致突然和反复的上升和下降模式(对任何尝试过基于自然现象的种群或流行病学模型的人来说都很熟悉)。

正如何塞和他的同事们努力证明的那样,黑洞监控可能不仅是一项常规活动,也许完全是多余的,而且也是发现所有恶意事物秘密生活的一种极好方式。然而,故事还没有结束。仅仅观察我们认为针对我们的流量,我们就错过了最有趣的数据。


^([38]) 跳过主机是一种用作中间跳转以执行进一步攻击或其他非法活动(如发送垃圾邮件)的系统。这种技术使得追踪最终罪犯变得更加困难,因为他们的来源不是直接知道的,需要多个管理员或司法管辖区合作才能找到他们。

攻击后果流量分析

黑洞监控的另一种应用是观察从未针对我们,但仅仅是其他活动副作用的流量。

蠕虫随时间持久性。注意 CodeRed 没有简单的峰值衰减模式,并且模型的行为类似于生物种群模型。

图 18-4. 蠕虫随时间持久性。注意 CodeRed 没有简单的峰值衰减模式,并且模型的行为类似于生物种群模型。

在这里,我们可以看到许多常见的侦察和攻击方案如何使用地址欺骗来隐藏攻击者的身份。假设管理员将难以区分诱饵流量和攻击者实际探测的虚假地址。尽管如我在前几章中所示,这种方法并不能保证攻击者完全匿名;为了成功“去欺骗”流量,管理员必须在攻击时实施广泛的日志记录和额外的措施。因为这些程序并不总是实施,攻击者通常可以非常有效地欺骗他们的攻击,并保持不在聚光灯下。

不论数据包是否被伪造,被攻击的系统都将诚实地响应所有请求,包括那些声称来自虚构地址的请求。然而,只有带有正确源地址的数据包的响应会返回给发送者;所有其他探测都会生成散布在互联网各处的响应,我们通常可以捕捉到它们。

虽然我们收到这种误定向的数据包似乎不太可能,但请记住,相当数量的 SYN+ACK、RST+ACK 和 RST 数据包是作为诱饵扫描或 SYN 洪水攻击的响应生成的。互联网地址空间看似庞大,通常涉及数百万个数据包的此类攻击,但相当可能的是,随着时间的推移,其中一些会到达每个网络块。尽管随机生成的伪造数据包弹回到特定地址的可能性仅为 1/4,294,967,296(1 到 2 的 32 次方),假设典型的小型子网分配给小型公司或组织通常由 256 个地址组成(C 类网络或等效),这种概率增加到 1/16,777,216(1 到 2 的 24 次方)。通过排除已知为特殊用途保留的地址范围或因其他原因在特定类型的攻击中被排除的地址范围,这个概率还可以进一步提高。

由于单个 SYN 数据包的大小大约为 40 字节(并且在大批量中压缩良好),而一个普通攻击者可用的典型网络链路的吞吐量大约为每秒 10 到 150 千字节(分别对应低端 DSL 和 T1 线路),他可以在这一时间段内发送 250 到近 3000 个数据包——或者每小时 900,000 到约 10,000,000 个数据包。39

对于典型的拒绝服务攻击(DoS)要产生任何明显的效果并给受害者造成重大不便,通常需要持续数小时或数天。(攻击者希望尽可能长时间地给受害者带来不便。)因此,会发送数十亿到数亿个数据包,产生数量相似的数量级的 SYN+ACK 或 RST+ACK 响应。

由于流量如此巨大,我们完全有理由预期,即使接收主机丢弃了许多攻击数据包,即使是相对较小的实体也能在攻击发生时注意到小 SYN 洪水攻击的影响。此外,能够监控 B 类等效网络(65,356 个地址,通常由大型公司、ISP、研究机构等拥有)的管理员能够快速捕捉到更小的事件。

由于在伪造的 DoS 攻击中,所有响应的回波都包含攻击者为了最初触发这些响应而编造的消息的某些细节(例如端口号和序列号、时间信息等),我们可以利用这些回复来提取有关攻击类型和规模的重要信息。我们可以利用这些回复来确定是否针对了特定的服务,有多少系统受到了攻击,攻击者可用的带宽,以及执行攻击所使用的工具(通过检查源端口选择、选择的序列号和“随机”IP 模式^([40]))。

最后,通过分析这些回波响应的来源,我们可能会注意到某个网络段正在遭受攻击,或者能够识别全球的“敌对趋势”,这可能有助于更好地准备,如果某个特定行业或企业正在遭受攻击。我们还可以利用这些信息来了解受害者掩盖的攻击,或者识别攻击的虚假声明。(有时,声称某些目标正遭受网络恐怖分子攻击的说法,是一种公关手段,用以证明财务损失或推动特定的政治议程。最近,一些专家指责 SCO 关闭了他们的服务器,并假装成为协调 DoS 攻击的受害者,以诋毁 Linux 用户社区。)


^([39]) 注意,那些熟练掌握拒绝服务攻击的坚定、经验丰富的攻击者通常控制着数十个或数百个“僵尸”节点,这大大增加了这个估计。

^([40]) 例如,由于编码错误,一些工具只“伪造”来自偶数或奇数 IP 地址的数据包。与 Jose Nazario 等人进行的类似分析通常证明在确定攻击工具方面与识别蠕虫一样有效。

检测格式错误或误向数据

这种用于监控黑洞的应用依赖于监控看似没有意义,但仍然似乎到达特定目的地的流量。为了更好地说明问题,请允许我进行一些离题的讨论。

在 1999 年,一群朋友、波兰的同事和我开始了一个谦逊的业余项目。我们的目标是追踪我们维护的网络中到达的难以解释的 RST+ACK 数据包集合,并监控一般到达未使用网络段的不寻常和未经请求的流量模式。这很有趣,正如你可能想象的那样,当我们试图合理地解释一些最不寻常的情况时,这导致了大量的猜测。我们的研究还使我们能够更多地了解我们周围的世界,因为我们遇到了一些极其奇特且看似无法解释的流量,一旦经过适当分析,就为我们提供了对我们有线世界广泛阴谋的更多洞察。

虽然形式上已被放弃,但这个项目最终落入了我的私人“破损数据包博物馆”,^([115])这是一个半幽默的网页,致力于追踪、记录和解释那些本不应该到达目的地或本不应该以那种方式出现的数据包。博物馆的声明目的是如下:

这家博物馆的目的是为我们这些凡人,在人生这条曲折的道路上遇到的各种奇怪、不受欢迎、畸形的数据包——被遗弃和注定要灭亡的自然怪胎——提供一个庇护所。我们的展品——或者如果你愿意,居民——通常只是它们在遇到敌对、故障的路由器之前的样子的一丝影子。其中一些在破损的 IP 堆栈实现深度中天生畸形。另一些则是像他们的朋友(你或我)一样的正常数据包,但在寻找它们存在的终极意义时迷路了,到达了我们不应该看到的地方。在每一种情况下,我们都试图发现每个数据包生活的独特历史,并帮助你理解在充满敌意的比特和字节宇宙中做一个孤独的使者是多么困难。

这就是最后一种黑洞监控类型归结为的东西。虽然这项任务一开始可能看起来毫无意义,但认为它是愚蠢的。博物馆使我们可以被动地揭露关于各种专有设备和受保护的网络的黑暗秘密,在其他地方进行这样的实验无疑会取得相同或更大的成就。

我博物馆中的展品包括以下这样的奇迹:

  • 来自具有特定类型网络加速器、路由器或防火墙的网络的数据包;该设备会附加、删除或以其他方式破坏一些数据。一个很好的例子是某些 Nortel CVX 设备中的缺陷,该缺陷偶尔会从数据包中剥离 TCP 头(如第十一章中讨论的第十一章)。这个缺陷的独特性使我们能够在不实际外出探测的情况下,了解许多远程网络的大量信息。

  • 几个行噪声展品,展示了包含要么是垃圾数据要么是肯定不属于特定连接的数据的数据包。最令人惊讶的展品之一是不请自来的流量,其中包含看起来像是.de DNS 区域内容(德国所有域的列表)的转储。这种流量不可能来自任何地方,因为凡人没有权利获得这样的列表。相反,它必须来自一个有权获取和传输这些数据的授权方,并且可能是在发送者或沿途某个设备中被破坏的。尽管所有这些案例都很少能揭示网络事故的本质,但像这样的案例往往会使观察者获得意外——通常是宝贵的——发现。

其他值得注意的展品包括伪装成常规流量的明显间谍活动以及许多其他编码或网络小故障。但别再吹嘘了——如果你觉得有必要了解更多信息,请访问lcamtuf.coredump.cx/mobp/

思考的食物

许多人认为黑洞监控只是检测针对他们系统攻击的另一种方式(考虑到公共 IP 空间资源的稀缺,这可能是一种昂贵的方式)。但这项技术的真正价值在于,它不仅能够识别已知的攻击(这在许多其他地方也可以做到,而不必浪费 IP 空间),还能够检测和分析那些在其他情况下可能被网络“噪声水平”淹没的微妙模式。

当然,执行这种类型的黑洞监控并不容易,而且仍然很昂贵。需要时间来学习如何在通常的蠕虫和黑客活动的大杂烩中找到那根针,在足够广泛的网络中,这些活动通常除了统计报告外没有其他意义。

然而,为了最终找到那根针的喜悦,尝试一下通常是值得的。

附录 A. 结语

随着本书即将结束

这本书就到这里结束,但我希望您的旅程从这里开始。我为能引导您穿越我最喜欢的复杂且不寻常的安全问题世界而感到自豪,并希望您能分享我的热情。无论您是一位经验丰富的安全专业人士——可能比我更有经验和知识——还是一位刚刚发现这个领域的爱好者,我希望我已经为您提供了对安全的新视角,将其视为一种挑战和艺术,而不是必须消除或绕过的障碍。

通过理解看似无关的组件和过程之间的微妙关系,你可以有效地应对最危险和普遍存在的安全问题,并更有效地评估和减轻日常风险。安全问题应被视为解决 IT 世界中几乎所有挑战的函数,无论其多么微不足道或范围有限;而不是作为商业活动的逆境。只有通过看到互补宇宙的神奇魅力以及它们之间微妙互动的方式,我们才能避免常规,真正开始享受我们的工作,或理解我们的爱好。

但那时并不是展示高调的时刻或地点。

感谢您的参与。

附录 B. 参考文献注释

第一章

^([41])

^([42])

^([43])

^([44])

^([45])

^([46])

^([47])

^([48])

^([49])

^([50])

^([51])

第二章

^([52])

^([53])

^([54])

^([55])

^([56])

第三章

^([57])

^([58])

^([59])

第五章

^([60])

^([61])

^([62])

^([63])

^([64])

^([65])

第六章

^([66])

^([67])

第七章

^([68])

^([69])

第八章

^([70])

^([71])

^([72])

^([73])

第九章

^([74])

^([75])

^([76])

^([77])

^([78])

^([79])

^([80])

^([81])

^([82])

^([83])

^([84])

^([85])

第十章

^([86])

^([87])

^([88])

第十一章

^([89])

^([90])

^([91])

^([92])

^([93])

^([94])

^([95])

第十三章

^([96])

第十四章

^([97])

^([98])

^([99])

^([100])

^([101])

^([102])

^([103])

^([104])

^([105])

第十五章

^([106])

^([107])

第十六章

^([108])

^([109])

第十七章

^([110])

^([111])

^([112])

第十八章

^([113])

^([114])

^([115])


^([41]) Alan Turing, “关于可计算数及其在决定问题中的应用,” 伦敦数学学会会议录,第 2 卷,42 (1936).

^([42]) R.L. Rivest, A. Shamir, L. Adleman, “一种获取数字签名和公钥密码系统的方法,” 马萨诸塞理工学院 (1978).

^([43]) Ueli M. Maurer, “快速生成素数和安全的公钥加密参数,” 瑞士苏黎世联邦理工学院理论计算机科学研究所 (1994).

^([44]) Donald E. Knuth, 计算机编程艺术,第 2 卷:数值算法, 第 3 版. Addison-Wesley (1997).

^([45]) H. Krawczyk, “如何预测伪随机数发生器,” 算法杂志 第 13 卷第 4 期 (1992).

^([46]) S. Bakhtiari, R. Safavi-Naini, J. Pieprzyk, “密码散列函数:综述,” 澳大利亚伍伦贡大学计算机科学系计算机安全研究中心 (1995).

^([47]) Dawn Xiaodong Song, David Wagner, Xuqing Tian, “按键时序分析和 SSH 上的时序攻击,” 加州大学伯克利分校 (2001).

^([48]) Claude E. Shannon, “印刷英语的预测和熵,” 贝尔系统技术期刊 3 (1950).

^([49]) Benjamin Jun, Paul Kocher, “英特尔随机数发生器,” 密码学研究公司 (1999).

^([50]) “VIA C3 Nehemiah 随机数发生器评估,” 密码学研究公司 (2003).

^([51]) Michael A. Hogye, Christopher T. Hughes, Joshua M. Sarfaty, Joseph D. Wolf, “SSH 连接上按键时序攻击的可行性分析,” 弗吉尼亚大学工程学院 CS588 研究项目 (2001).

^([52]) Yurii Rogozhin, “具有 22 个状态和 2 个符号的通用图灵机,” 罗马尼亚信息科学与技术杂志 第 1 卷第 3 期 (1998).

^([53]) Milena Milenkovic, Aleksandar Milenkovic, Jeffrey Kulick, “揭秘英特尔分支预测器,” 阿拉巴马大学亨茨维尔分校电子与计算机工程系 (2002).

^([54]) Paul C. Kocher, “对 Diffie-Hellman、RSA、DSS 和其他系统实现的计时攻击,” 密码学研究公司 (1999).

^([55]) Intel 80386 程序员参考手册, 第 7.2 节.IMUL, 英特尔公司 (1986).

^([56]) E. Biham, A. Shamir, “差异故障分析:识别封装在防篡改设备中的未知密码结构” (1996).

^([57]) Wim van Eck, “视频显示单元的电磁辐射:窃听风险?”荷兰 PTT 实验室 (1985).

^([58]) Ian A. Murphy, “谁在监听?” IAM/Secure Data Systems (1988, 1997).

^([59]) Winn Schwartau, 信息战. 第二版. Thunder’s Mouth Press, 纽约 (1996).

^([60]) John A.C. Bingham, 调制解调器设计的理论与实践. Wiley-Inter-science (1988).

^([61]) 电子工业协会,工程部门, “数据终端设备与使用串行二进制数据交换的数据电路终止设备之间的接口” (1991).

^([62]) Charles E. Spurgeon, 以太网: definitive guide. O’Reilly and Associates (2000).

^([63]) 乔·卢格瑞,大卫·A·安普雷斯,“光辐射信息泄露”,ACM 信息系安全交易 5,第 3 期(2002)。

^([64]) 阿迪·沙米尔,埃兰·特罗默,“声学密码分析:关于爱管闲事的人和嘈杂的机器。”初步演示稿可在撰写本文时在www.wisdom.weizmann.ac.il/~tromer/acoustic/找到(2004)。

^([65]) 保尔·科赫,约书亚·贾夫,本杰明·朱恩,“差分功耗分析。”密码学研究公司(2000)。

^([66]) J. Postel,J. Reynolds,“RFC-1042:在 IEEE 802 网络上传输互联网协议数据报的标准”,网络工作组,www.ietf.org/rfc/rfc1042.txt(1988)。

^([67]) Ofir Arkin 和 Josh Anderson,“EtherLeak——以太网帧填充信息泄露”,@Stake,www.atstake.com/research/advisories/2003/atstake_etherleak_report.pdf(2003)。

^([68]) 大卫·C·普卢默,RFC 826,“以太网地址解析协议”,网络工作组(1982)。

^([69]) 路易斯·塞内卡尔,“层 2 攻击及其缓解”,思科(2002)。

^([70]) J. Case,M. Fedor,M. Schoffstall,J. Davin,RFC 1157,“一个简单的网络管理协议”,网络工作组(1990)。

^([71]) 银行创新研究所,“PSYLock:一种基于打字行为的心理测量认证方法”,pc50461.uni-regens-burg.de/ibi/de/leistungen/research/projekte/einzelprojekte/psylock_english.htm(2003)。

^([72]) Solar Designer,Dug Song,“SSH(安全壳)流量被动分析”,Openwall 项目,www.openwall.com/advisories/OW-003-ssh-traffic-analysis(2001)。

^([73]) 尼基塔·博里索夫,伊恩·戈尔伯格,大卫·瓦格纳,“拦截移动通信:802.11 的不安全性”(2001)。

^([74]) J. Postel,南加州大学,“RFC 791:互联网协议”,网络工作组(1981)。

^([75]) J. Postel,南加州大学,“RFC 796:地址映射”,网络工作组(1981)。

^([76]) J. Mogul,S. Dearing,“RFC 1191:路径 MTU 发现”,网络工作组(1990)。

^([77]) J. Postel,南加州大学,“RFC 768:用户数据报协议”,网络工作组(1980)。

^([78]) J. Postel,南加州大学,“RFC 793:传输控制协议”,网络工作组(1981)。

^([79]) S. Bellovin,“RFC1948:防御序列号攻击”,网络工作组(1996)。

^([80]) V. Jacobson, B. Braden, “RFC1232: 高性能 TCP 扩展,” 网络工作组 (1992).

^([81]) M. Mathis, J. Mahdavi, S. Floyd, and A. Romanow, “RFC2018: TCP 选择性确认选项,” 网络工作组 (1996).

^([82]) B. Braden, “RFC1644: T/TCP – 事务 TCP 扩展 – 功能规范,” 网络工作组 (1994).

^([83]) J. Postel, 南加州大学, “RFC 792: 互联网控制消息协议,” 网络工作组 (1981).

^([84]) Lance Spitzner, 蜜罐:追踪黑客. 哈德森出版公司 (2002).

^([85]) R. Morris, “4.2BSD UNIX TCP/IP 软件中的弱点,” AT&T 贝尔实验室 (1985).

^([86]) Michal Zalewski, “奇异吸引子和 TCP/IP 序列号分析,” BindView 公司, www.bindview.com/Support/RAZOR/Papers/2001/(2001).

^([87]) S. Bellovin, "防御序列号攻击," 网络工作组, www.ietf.org/rfc/rfc1948.txt (1996).

^([88]) Joe Steward, “DNS 缓存投毒:下一代,” www.lurhq.com/dnscache.pdf (2002).

^([89]) Elizabeth D. Zwicky, Simon Cooper, D. Brent Chapman, 构建互联网防火墙. O’Reilly & Associates (2000).

^([90]) G. Ziemba, D. Reed, P. Traina, “RFC1858: IP 分片过滤的安全考虑,” 网络工作组 (1995).

^([91]) Uriel Maimon, “TCP 端口隐蔽扫描,” 《Phrack 杂志》 no. 49 (1996).

^([92]) J. Postel, J. Reynolds, “RFC959: 文件传输协议,” 网络工作组 (1985).

^([93]) Mikael Olson, “扩展 FTP ALG 漏洞到任何 FTP 客户端,” VULN-DEV 邮件列表, www.securityfocus.com/archive/82/50226 (2000).

^([94]) Michal Zalewski, “Linux 内核 IP 伪装漏洞,” Bind-view 公司, razor.bindview.com/publish/advisories/adv_LkIPmasq.html (2001).

^([95]) R. Braden (editor), “RFC1122: 互联网主机通信层的要求,” 网络工作组 (1989).

^([96]) Salvatore Sanfilippo, “新的 TCP 扫描方法,” Bugtraq, seclists.org/bugtraq/1998/Dec/0082.html (1998).

^([97]) 万维网联盟, www.w3c.org/History.html.

^([98]) Vannevar Bush, “As We May Think,” 《大西洋月刊》 176, no. 1 (1945): 101-08.

^([99]) Tim Berners-Lee, “基本 HTTP”,www.w3c.org/Protocols/HTTP/HTTP2.html

^([100]) R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, T. Berners-Lee, “RFC2616: 超文本传输协议—HTTP/1.1。”网络工作组(1999 年)。

^([101]) 多种来源,引用自usability.gov/guide-lines/softhard.html:Anna Bouch, Allan Kuchinsky, Nina Bhatti, “质量在于观察者的眼中:满足用户对互联网服务质量的需求”,CHI(2000 年);Martin, Corl, “系统响应时间对用户生产力的影响”,行为与信息技术,第 5 卷,第 1 期,3-13 页(1986 年);Jakob Nielsen,www.useit.com/alertbox/9605.html(1996 年);Nielsen,www.useit.com/alertbox/9703a.html(1997 年);Nielsen,www.useit.com/alertbox/9712a.html(1997 年);Nielsen,www.useit.com/alertbox/990530.html(1999 年)。

^([102]) Kristol, Montulli, “RFC2109: HTTP 状态管理机制”,网络工作组(1997 年)。

^([103]) Martin Pool, “HTTP Cache-Control 的隐私问题”,Bugtraq,cert.uni-stuttgart.de/archive/bugtraq/2000/03/msg00365.html(2000 年)。

^([104]) Bamshad Mobasher, Robert Cooley, Jaideep Srivastava, “基于 Web 使用挖掘的自动个性化”,ACM 通讯第 43 卷第 8 期,142-151 页(1999 年)。

^([105]) Edward Felten, Michael Schneider, “对 Web 隐私的定时攻击”,ACM 计算与通信安全会议(2000 年)。

^([106]) ISO/IEC 标准 9899, “编程语言 – C”,plg.uwa-terloo.ca/~cforall/N843.ps(1999 年)。

^([107]) DISCO,www.altmode.com/disco

^([108]) Albert-Laszlo Barabasz, Vincent W. Freeh, Hawoong Jeong, Jay B. Bro-chman, “寄生计算”,自然杂志第 412 期(2001 年)。

^([109]) M. Leech, “RFC 3607: 重新审视中文彩票密码分析”,网络工作组(2003 年)。

^([110]) Bill Cheswick, Hal Burch, Steve Branigan, “互联网映射与可视化”,www.cheswick.com/ches/papers/mapping.ps.gz(2000 年)。

^([111]) Despoof,razor.bindview.com/tools/desc/despoof_readme.html

^([112]) Hal Brunch, Bill Cheswick, “追踪匿名数据包到其近似来源,” www.usenix.org/publications/library/proceedings/lisa2000/burch/burch_html (2000).

^([113]) Lance Spitzner, 《蜜罐:追踪黑客》: Addison-Wesley (2002).

^([114]) Jose Nazario, 《针对互联网蠕虫的防御和检测策略》: Artech House (2003).

^([115]) Michal Zalewski, “破损数据包博物馆,” lcamtuf.core-dump.cx/mobp (2001).


  1. [19] ↩︎

  2. 90 ↩︎

  3. 96 ↩︎

posted @ 2025-11-28 09:40  绝不原创的飞龙  阅读(11)  评论(0)    收藏  举报