[转帖]密码分析学——深度分析WEP密钥恢复攻击(PTW攻击)

密码分析学——深度分析WEP密钥恢复攻击(PTW攻击)

几年前研究无线安全的时候曾经分析过WEP破解的原理,当时在网上搜索相关的中文资料时只能找到一些教你使用Aircrack-ng的脚本小子文章,要么纯粹就是胡扯一通,完全找不到靠谱的深入分析,最后只能去看当时的论文。

最近是因为要写书的缘故,需要重新总结一下无线安全相关的知识,到网上搜了一下发现结果多年过去了还是没有什么靠谱的中文资料,于是决定写一写总结一下破解的真正原理,来填补这个空白,希望可以帮到以后有兴趣研究WEP破解的人。

注意,WEP已经被淘汰了,现在新的路由器都不会支持WEP的加密方式,只会支持WPA/WPA2,WPA3也都已经出来了,所以花时间深入研究WEP性价比并不高,如果你只是为了破解邻居WIFI密码,或者学用Aircrack-ng的话可以ctrl + w了。

如果你是抱有学术情怀,想刨根问底那这篇文章会很适合你。WEP作为一个近年中被使用密码分析学完全破解掉的加密系统之一,实际上破解WEP的数学思路还是很值得学习的。

本文难度比较高,需要一定的无线协议基础、密码学基础、数学基础。

1 密码分析学

密码分析学与密码编码学共同组成了密码学这一学科,两者从几千年前诞生以来就开始了永无止境的较量,加密与破解是人类智力的精彩对决,对决的胜败甚至可以影响历史走向。

在第二次世界大战中,盟军破解了德国的密码从而得到了决定性的情报,让欧洲的战争提前了两年结束,这里也推荐大家一部纪录片“密码破译者:布莱切利庄园的幕后英雄”。

也许作为一个普通人,这辈子无法在密码学的历史上留下一点痕迹,但是了解密码的设计思路与破解的思路,也是一件极其具有乐趣的事情,正如Linus Torvalds所说的,Just For Fun。

2 WEP简介

由于无线网络的物理的特性,管理员是很难有效控制网络的覆盖范围的,攻击者可以在信号覆盖范围内窃听数据,或者进行未授权的访问,所以在使用无线网络时必须将数据加密和建立起身份认证机制,WEP就是用于保护无线网络的安全协议。

WEP(Wired Equivalent Privacy,有线等效加密)是1997年发布的第一版IEEE 802.11无线协议标准中的安全协议,目的是让无线网络提供像有线网络一样的数据机密性,所以叫做“有线等效”。

WEP中使用RC4流密码对数据加密,使用CRC-32校验数据的完整性,所有需要使用网络的人通过共享密钥来认证访问网络,不过WEP并没有提供前向安全性,一旦密钥泄露攻击者就可以访问网络,并解密所有之前捕获的流量。

WEP的密钥由24位初始化向量(IV)和根密钥(Rk)构成,格式为 [公式] 。IV会在数据帧中明文传送,每个数据帧使用的IV都是不同的,IV可以是随机生成也可以是计数器式的递增,这是为了防止流密码的“重用密钥攻击”。实际上这种IV放在根密钥前面密钥格式,让我们的后续密钥恢复攻击变得更简单了。

WEP的第一版中由于受到美国政府对出口加密技术的限制,只允许使用64位的密钥,除去IV,剩下只有40位的根密钥可以由用户设置,称为(WEP-40),并不是非常安全。直到后来限制解除后,密钥长度增加到128位,可以用104位根密钥,被称为(WEP-104),后来也有供应商实现了256位的密钥,根密钥达到232位,不过最常见的还是128位的实现。理论上来讲更长的密钥可以提供更强的安全性,让攻击者更难穷举密钥,更难破解,但后面我们会发现更长的密钥对WEP安全性提高并没有帮助。

这里插一点题外话,由于密码学在第二次世界大战中巨大影响,二战后许多国家政府都对加密技术出口进行了监管。由于美国加密技术领先,到了1992年加密技术被列入美国军需物品清单,出口受到法律严格限制,强度超过一定程度的加密技术将禁止出口。如果让其他国家掌握了先进的加密技术将会影响美国对其他国家的监控,以及打击犯罪和恐怖主义的能力。例如Netscape推广SSL技术时受到政策影响需要开发两个版本的浏览器,美国版使用1024位的RSA公钥,以及128位的RC4或3DES,而国际版只能使用512位RSA和40位的RC4,但是由于监管两个版本非常麻烦,所有最终大部分美国人使用的也是削弱了的国际版,40位的密钥几天就可以被破解。在90年代末期,由于受到自由主义者和隐私权倡导者的挑战以及互联网发展监管难度提高,电子商务发展对加密技术的需求,美国政府最终对加密技术的出口管制放松,在1998年如果出口商品增加密钥恢复后门就允许出口56位密钥产品,在1999年去除了需要后门的限制,而在2000年将密钥长度限制的规定删除。

3 WEP密码破解历史过程

在WEP发布的4年后的2001年,Scott Fluhrer,Itsik Mantin和Adi Shamir首次提出了对RC4的密钥恢复攻击(FMS攻击),这种攻击利用特定易受攻击的IV(弱IV)来恢复密钥,弱IV会导致高概率泄露密钥信息。不过这种攻击并不高效,如果捕获不到使用弱IV的数据帧就需要等待,实际操作中需要捕获大约400万到600万个不同的帧才能恢复出密钥,破解一般需要几个小时。厂商为了应对这种攻击,修改了固件,过滤了特定的弱IV不使用。实际上这种攻击由于成本太高,并没有对WEP造成致命打击,WEP依旧被大范围使用,但是意识到WEP脆弱后已经有了重新设计安全协议的必要。

在2003年发布了WPA,也被称为IEEE 802.11i标准草案,用于作为淘汰WEP的过渡,WPA使用TKIP(临时密钥完整性协议),但是加密算法同样为RC4,这样兼容性会比较好,现有硬件通常只需要安装驱动程序或者更新固件就可以使用了。在2004年发布了全新的WPA2,这是完整版的IEEE 802.11i的标准,使用CCMP,这是基于AES的加密协议,提供了非常高的安全性。由于新设备的更换需要成本,新技术推广的时间永远比预想中要更长,所以即使WPA/WPA2出现多年之后,WEP依旧被广泛使用着。

在2004年,一个名为KoreK的人在2004年的netstumbler论坛上发布了一个WEP破解的实现,其中包含了对WEP的17种攻击,一部分攻击是已知的,一部分是KoreK自己发明的。使用KoreK攻击恢复WEP密钥所需要的帧数量大大减少,大概只需要50万到100万个数据帧,但是这对于普通的攻击者来说成本依然比较高,人们对于WEP安全性依然抱着一种掉以轻心的态度。

在2006年,Klein提出了两种针对RC4的攻击方法,证明了RC4密钥序列与内部状态的相关性,这种相关性足以恢复密钥,且不需要任何特定的弱IV,可以利用接收到的每一个数据帧,只需要4万到8万个会话就可以恢复出正确的密钥,但是Klein攻击有一个不足的地方就是需要大量的计算资源。

在2007年,Erik Tews,Ralf-Philipp Weinmann和Andrei Pyshkin(PTW攻击)将Klein攻击应用于WEP破解,并改进了Klein攻击需要大量计算资源的问题。如此有效又轻量的PTW攻击直接成了WEP协议生涯的分水岭,如果说以前攻击者破解WEP网络需要付出一定的时间成本,那PTW攻击几乎是没有成本,PTW攻击通常只需要不到60秒的时间就可以恢复出正确的密钥,而且由于不需要多少计算资源,攻击完全可以自动化,甚至可以实现在一些嵌入式设备上,完全可以走到哪破解到哪。值得注意的是,据作者调查,在PTW攻击发布前的2007年3月的德国城市中使用WEP无线网络依然占了46.3%。

PTW攻击是破解WEP密码最流行的攻击,在Aircrack-ng中破解WEP用的就是PTW攻击,Klein攻击和PTW攻击也是本章中我要分析的攻击方法。

你们以为PTW攻击就是现今破解WEP最强的攻击了吗?当然不是了,实际上在2010年Teramura等人提出了TeAM-OK攻击,只需要2万到5万个数据帧就可以恢复密钥,而在2011年Sepehrdad,Vaudenay和Vuagnoux提出了Tornado攻击,只需要4000到9000个数据帧就可以恢复密钥,不过这些攻击我就以后再给大家分析了。

国内有些中文资料说WEP密码破解是因为使用了重复的IV导致的,重复的IV确实可以导致一些攻击,例如前面所说的“重用密钥攻击”,但离恢复密钥还差了十万八千里呢。

4 流密码与伪随机数

流密码也叫序列密码,原理就是密码算法根据密钥产生密钥序列(也叫密钥流),然后以字节流的方式进行加密解密操作。

如图1,alice加密就是使用密钥序列与明文逐位XOR,得到密文,然后把密文发送到Bob,Bob解密只要使用同样的密钥序列对密文逐位XOR就可以变回明文了,非常简单。

如果信道上有窃听者Eve,由于只能获取到密文,无法获取加密序列,是不能解密出明文的。

图1

那流密码与伪随机数有什么关系呢?如果写过生成伪随机数的程序的人都会知道,生成伪随机数需要以“种子”作为初始条件,然后用算法不停迭代产生随机数。

实际上流密码就类似这个过程,用户使用的密钥就相当于随机数种子,密钥序列生成器就相当于一个伪随机数生成器,而密钥序列就是根据种子产生的源源不断的随机数,如图2。

图2

由此可见,流密码算法的安全性完全就是由伪随机数生成器去决定了,如果生成出来的密钥序列随机性不够,可以被预测,或者伪随机密钥序列与密钥本身的关系能够被统计分析出来,那么这个流密码算法就被破解了。

应用范围最广泛的流密码算法就是我们即将介绍的RC4算法,只可惜RC4算法已经被使用上述方法破解了。

5 RC4算法

RC4是Ron Rivest在1987年设计的一种流密码,最初是商业机密,后来被匿名发布到Cypherounks邮件列表上,由于实现简单和高效,而且在当时被没有发现什么安全缺陷,于是被广泛使用,曾经WEP、SSL、TLS中都使用了RC4加密算法。

介绍具体的RC4算法之前先看算法中的变量的作用

  • S:S-Box,是长度256的字节数组,密钥的作用就是将这个数组搅乱,密钥序列就是根据这个数组生成的。
  • K:保存密钥的字节数组。
  • i与j:用于搅乱S的临时变量。
  • k:用于生成密钥序列的临时变量。
  • n:值为256。
  • l:密钥的长度

RC4算法分为三个阶段:初始化、密钥调度(KSA)、伪随机数生成(PRGA),所有RC4算法的操作都是在 [公式] 的加法群内完成的,但在实际分析中会将其扩展到 [公式] , [公式] 以下为RC4算法的伪代码。

初始化:

1:    for i from 0 to n 1 do 2: S[i] := i 3: end for

初始化就是将S-Box按顺序赋值为1-255。

 

密钥调度(KSA):

1:    for i from 0 to n 1 do 2: j := (j + S[i] + K[i mod l]) mod n 3: Swap S[i] and S[j] 4: end for

密钥调度就是根据密钥将S-Box搅乱到接近随机的状态,Swap就是交换 [公式] 和 [公式] 的值。

 

伪随机数生成(PRGA):

1:    i := 0 2: j := 0 3: loop 4: i := (i + 1) mod n 5: j := (j + S[i]) mod n 6: Swap S[i] and S[j] 7: k := (S[i] + S[j]) mod n 8: print S[k] 9: end loop

此处的伪随机数生成可以是一个无限循环,可以不断输出密钥序列,一次循环返回一个密钥序列字节 [公式] ,在这个过程中S-Box也会继续被搅乱,将 [公式] 与对应明文字节XOR就可以产生密文字节了。

注意,RC4已经被证明不安全了,TLS中已经禁止使用RC4,如果你还在使用RC4,请马上投入AES的怀抱。

6 符号约定

数组用 [公式] 表示,类似C语言,Java语言。

集合用 [公式] 表示。

我们用X表示生成的密钥序列,K用于表示会话密钥,Rk用于表示根密钥,IV用于表示初始化向量。

我们用 [公式] , [公式] 表示在密钥调度(KSA)阶段中第k次循环后的S-Box和 [公式] 。

[公式] 与 [公式] 则是表示在密钥调度(KSA)还没开始之前的S-Box和 [公式] ,此时的S-Box中 [公式] , [公式] 。

7 Klein攻击

我们从PTW攻击的基础Klein攻击开始说起。

Klein将攻击按轮数分类,将伪随机数生成的 [公式] 个字节划分为一轮,例如 [公式] 到 [公式] 个字节称为第一轮, [公式] 到 [公式] 个字节称为第二轮,以此类推,如果攻击利用了第 [公式] 轮的字节,就成为第 [公式] 轮攻击。

Klein发明了两种攻击方式,第一种是第1轮攻击,第二种是第2轮攻击,在Klein之前的攻击都是第1轮攻击,Klein发明了首个第2轮攻击,不过最高效的还是他的发明的第1轮攻击,本文也只介绍他发明的第1轮攻击。

7.1 RC4密钥调度弱点

在理想情况下,密钥由n个独立同分布的 [公式] 元素组成,密钥调度(KSA)阶段可以产生所有相等概率的 [公式] 个结果,但是S-Box只有 [公式] 种可能,因此密钥调度(KSA)后的S-Box分布肯定与均匀分布不同。

7.2 RC4伪随机生成中的相关性

Klein证明了外部可以观察到的变量 [公式] 、 [公式] 与加密算法的内部状态 [公式] 、 [公式] 和 [公式] 有着强相关性,这些相关性将成为攻击的基础。

现在我们来证明这些相关性,接下来我们来分析伪随机数生成(PRGA)代码中的第7行和第8行,就是 [公式] 的计算和 [公式] 的输出。

7.3 相关性定理证明

假设RC4的内部状态是均匀分布的,那么对于给定的变量 [公式] 我们可以证明:

[公式] (1)

[公式] [公式] (2)

如果是另一种情况, [公式] ,那么我们可以证明:

[公式] (3)

[公式] [公式] (4)

这里解释一下,(3)和(4)指的是 [公式] ,但是 [公式] , [公式] 是除 [公式] 以外的另一个单独的值。所以(3)和(4)指的是等于除 [公式] 以外的另一个单独的值的情况的概率,而不是指等于除了 [公式] 以外的所有其他值的其他所有情况的概率,实际上 [公式] 非常接近 [公式] 。

上述公式证明如下,注意(1)源于(2),(3)源于(4)。

证明(2),我们用和 [公式] 和 [公式] 计算所有内部状态,首先根据RC4的伪随机数生成算法的第7行, [公式] ,重写 [公式]为 [公式] 。

具体过程是将 [公式] 两边同时加上 [公式] ,变成 [公式] 而 [公式] ,所以可以写成 [公式]

然后分两种情况, [公式] 和 [公式]

1. [公式]

这种情况下 [公式] ,然后可以得到 [公式] 。

具体过程是基于 [公式] ,由于 [公式] 所以就可以写成 [公式] 那 [公式] 就可以约掉了,变成 [公式] 而 [公式] 所以上述等式可以写成 [公式] 把 [公式] 移到右边就可以得出 [公式]

在这种情况下S-box中剩下的 [公式] 个成员存在 [公式] 种可能性。

2. [公式]

这种情况下我们可以得出 [公式] 和 [公式] 。

具体过程 [公式] 由 [公式] 得出, [公式] 由 [公式] 得出。

这里的 [公式] 存在 [公式] 种可能性( [公式] 占了一种),S-box中剩下的 [公式] 个成员存在 [公式] 种可能性。

根据上述两种情况,总结起来就是当 [公式] 且 [公式] 的情况下存在 [公式] 种可能性,但是 [公式] 的情况下存在 [公式]种可能性,所以 [公式] ,(2) 证明完毕。

证明(4)也是类似的过程,首先要分成三种情况, [公式] 、 [公式] 、 [公式] 且 [公式] 。

1. [公式]

在这种情况下 [公式] ,所以 [公式] 。那 [公式] 约掉后,变成 [公式] 而由于 [公式] ,所以 [公式] 。

但是由于前面设定 [公式] ,所以这种情况不成立。

2. [公式]

在这种情况下由 [公式] 可以得出 [公式] 。

但是由于 [公式] ,所以这种情况也是不成立的。

3. [公式] 且 [公式]

只有这种情况下是成立的,我们可以得出 [公式] 和 [公式] 。这里的 [公式] 存在 [公式] 种可能性( [公式] 、 [公式] 各占了一种),S-box中剩下的 [公式] 个成员的排列存在 [公式] 种可能性。

根据第三种情况,可以得出当 [公式] 且 [公式] 且 [公式] 的情况下存在 [公式] 种可能性,但是 [公式] 的情况下存在 [公式] 种可能性,所以 [公式] ,(4) 证明完毕。

以上是假设RC4内部状态是均匀分布的,但是实际上内部状态离均匀分布会有一定差异,但是不会差异很大,所以上述概率是近似的。

7.4 第一轮攻击

现在我们把刚刚证明的定理应用到接下来的第一轮攻击中,此处的会话密钥格式与WEP的不同,根密钥在初始化向量之前,格式为 [公式] ,接下来我们的攻击目标是恢复出根密钥的前两个字节,之后我们再来探讨恢复后续密钥字节的方法和初始化向量在根密钥之前的情况。

7.5 攻击的基础版本

现在我们来研究密钥调度(KSA)阶段的前两步,就是 [公式] 与 [公式] 的情况。

在第一步中( [公式] ),此时的S-Box刚刚初始化, [公式] ,由于 [公式] ,此时的 [公式] ,所以 [公式] ,然后在下一步 [公式] 中与 [公式] 交换。

在第二步中( [公式] ), [公式] 增加 [公式] ,然后 [公式] 与 [公式] 交换,由于在开始状态下 [公式] ,所以此时的 [公式] 。

除了以下的情况:

1. [公式] , [公式] 。这种情况下 [公式] 。

第一次循环中 [公式] , [公式] , [公式] 和 [公式] 互换,最终 [公式] 。然后第二次循环, [公式] , [公式] ,结果是 [公式]和 [公式] 互换,最终还是 [公式] 。

2. [公式] , [公式] 。这种情况下 [公式] 。

第一次循环中 [公式] , [公式] , [公式] 和 [公式] 互换,最终 [公式] 。第二次循环 [公式] , [公式] ,结果就是 [公式] ,最终 [公式] 与 [公式] 互换, [公式] 。

3. [公式] , [公式] ,这种情况下 [公式] 。

第一次循环中 [公式] , [公式] ,然后 [公式] 和 [公式] 互换。第二次循环 [公式] , [公式] , [公式] , [公式] ,求余 [公式] 后 [公式] ,也就说与上一次循环不变, [公式] 和 [公式] 互换,结果 [公式] 为 0,因为第一次循环已经把0换到 [公式] 的位置上了。

4. [公式] 并且 [公式] 。这种情况下第二次循环后 [公式] 因此 [公式]得到值 [公式] ,这是第一步之后 [公式] 的值。(论文中是 [公式] 但我觉得是 [公式] ,因为第一次循环后 [公式] )

第一次循环中 [公式] , [公式] , [公式] 和 [公式] 互换。第二次循环 [公式] , [公式] , [公式] , [公式] , [公式] 求余后 [公式] ,最终 [公式] 和 [公式] 互换。

 

以上的许多种特殊情况可能看起来让人困惑,但是我们唯一需要知道的是:对于固定的 [公式] ,第二次循环之后 [公式] 的值 [公式] 是很容易就可以通过计算得到 [公式] 的。

在密钥调度(KSA)阶段的其余循环中,除非 [公式] 取值1之外,否则永远不会改变 [公式] 。

这种情况在一个步骤中发生的概率是 [公式] ,(只有取值1这一种情况会改变 [公式] ,剩下 [公式] 种情况都不会改变 [公式] )。

如果会话密钥的长度为n,并且所有密钥字节都是独立同分布的,我们可以得出结论,在第二次循环之后 [公式] 不会被改变的概率为 [公式] 。

当然,对于较短的密钥,独立同分布的假设是错误的,这会导致一些问题,我们在后面再讨论这些问题。 不过,我们可以将 [公式] 作为在密钥调度阶段的第二次循环之后 [公式] 未被改变的概率的良好近似值。

这个时候我们已经实现了以下目标:我们知道RC4伪随机数生成(PRGA)刚开始时有高概率( [公式] ) [公式] 的值将是 [公式] ,而 [公式] 的值只取决于 [公式] 和 [公式] 。

现在我们要使用之前证明的相关性定理,根据RC4生成伪随机密钥序列来获得 [公式] 。为了实现这一目标,我们将研究第一个伪随机字节的生成。

首先 [公式] 设置为1,然后 [公式] 和 [公式] ( [公式] )交换。现在 [公式] 包含我们想要的值 [公式] 。此外,我们是可以观察到RC4伪随机生成(PRGA)的输出 [公式] 的。但是根据之前证明的定理(1),我们知道 [公式] 的概率为 [公式] 。(再次注意,在真正的RC4实例中,S-Box和 [公式] 都不是真正随机的。因此,定理(1)仅给出近似值,但小误差对攻击没有影响。)

总结起来就是:

[公式] (5)

[公式] 很好理解,就是 [公式] 和 [公式] 同时发生的概率。

这里详细解释一下 [公式] 。

[公式] 表示在密钥调度阶段结束时 [公式] 的概率,也就说原来的 [公式] 被交换了,那么 [公式]肯定就在S-Box剩余的其他位置 [公式] ,我们可以假设 [公式] ( [公式] )。

[公式] 表示伪随机生成时 [公式] 而是 [公式] ( [公式] )的概率,由于 [公式] 非常接近 [公式] ,所以我们可以把y当成是一个均匀分布的随机值,有一定概率 [公式] 。

所以 [公式] 表示 [公式] ( [公式] )但是恰好 [公式] 的概率。

我们对RC4可以采取以下的攻击形式:

对于许多不同的初始化向量,我们观察RC4伪随机生成(PRGA)的第一个字节 [公式] 并计算 [公式] 。得到 [公式] 正确值t的值的概率约为 [公式] 。所有其他值的相对概率小于 [公式] 。如果会话数量足够多,我们就可以确定正确的值是出现次数最多的值。

7.6 攻击其他密钥字节

到目前为止,我们只能恢复前两个密钥字节,而且得到正确的t后,我们需要暴力猜测 [公式] 的 [公式] 种可能性才能找到正确的 [公式] 。

现在我们要恢复 [公式] 。我们可以发现,在密钥调度(KSA)阶段的第三次循环中, [公式] 被设置为值 [公式] 。 函数 [公式] 是可以很容易计算的,对于固定的 [公式] , [公式]和 [公式] 存在唯一的 [公式] ,其中 [公式] 。

不考虑特殊情况的话,具体过程如下:第一次循环 [公式] , [公式] , [公式] 和 [公式] 互换。第二次循环, [公式] , [公式] , [公式] 和 [公式] 互换,结果 [公式] 。第三次循环, [公式] , [公式] , [公式] , [公式] , [公式] 和 [公式] 互换,结果 [公式] 。

在概率 [公式] 的情况下, [公式] 的值在密钥调度(KSA)阶段的剩余 [公式] 循环和伪随机生成(PRGA)的第一循环中不改变(实际上应该比 [公式] 不改变的概率更高,因为只有 [公式] 了)。在伪随机生成器的第二次循环中, [公式] 与 [公式] 交换,即 [公式] 设置为 [公式] 。 现在我们应用之前证明的定理来根据RC4伪随机生成(PRGA)的第二个输出字节计算 [公式] ,而从 [公式] 我们可以计算 [公式] 。

密钥字节 [公式] ,[公式] 等可以通过观察RC4伪随机生成(PRGA)输出的第三个,第四个字节用相同方式计算得到。

最终我们可以得出:

[公式] (6)

注意,此处的 [公式] 是在伪随机生成(PRGA)阶段中的,而 [公式] 是在密钥调度(KSA)阶段中的。

举个例子,现在我要恢复 [公式] :

套入等式(6)得到 [公式] ,而从前面的分析中我们知道 [公式] ,我们现在就假设运气很好,碰到了概率 [公式] 的情况,最终可以得到 [公式] 。

[公式]

[公式]

[公式]

很明显,计算 [公式] 就可以恢复出 [公式] 。

7.7 导致攻击失败的特殊密钥字节

在前面我们估计 [公式] 在密钥调度(KSA)的第一次循环之后不改变的概率为 [公式] ,如果会话密钥由 [公式] 个独立字节组成,那么这是正确的,但是在真实的攻击中,会话密钥通常短于 [公式] 个字节,那概率 [公式] 就不太正确了,我们现在来讨论这一事实引起的问题。

其实问题不在于会话密钥短于 [公式] ,而是这样会导致密钥调度期间不同的 [公式] 的值不是独立的。只要会话密钥的长度对于 [公式] 来说不是太小的(长度为8的会话密钥对于 [公式] 就足够了),则不同的 [公式] 的值之间的相关性就能足够小,那么 [公式] 仍然是 [公式] 不改变( [公式] 不取值1)的概率的良好近似值。

现在问题来了,由于会话密钥的格式为 [公式] ,密钥调度阶段的前 [公式] 个步骤在所有会话中都是相同的( [公式] 是根密钥的长度),如果我们运气不好,在前 [公式] 个步骤中 [公式] 取值为1,那么我们假设的 [公式] 就会失效,导致我们的攻击的基本版本将无法恢复密钥。

这里举个例子, [公式] 时, [公式] ,所以 [公式] 会与 [公式] 交换,然后 [公式],循环继续进行,在 [公式] 时, [公式] ,那么会导致 [公式] 与 [公式] 交换,结果 [公式], [公式] 就被改变了。由于 [公式] ,密钥调度阶段的前 [公式] 个步骤在所有会话中都是相同的,每个会话的 [公式] 都会改变,现在 [公式] 不变的概率不是 [公式] 了,而是0了,导致后面进行的密钥恢复攻击必定会失败。

这种情况在其他 [公式] 中也会出现,例如 [公式] 前 [公式] 步骤中取值2就会改变 [公式] ,取值3就会改变 [公式] ,导致对应密钥字节无法正确恢复,现在我们必须找到一种方法来恢复上述情况的密钥字节,让我们来仔细看看这次攻击。

在前面我们知道可以恢复S-Box在密钥调度阶段的前 [公式] 个步骤之后的 [公式] , [公式] 到 [公式] (在密钥调度阶段的 [公式] 步骤中,已经开始处理IV了)。这是可行的,因为虽然在前 [公式] 步骤中 [公式] ,导致 [公式] 被改变,但是在后面的密钥调度中我们依然可以假设 [公式] 不再改变的概率是 [公式] 。我们现在的任务是从这些信息中恢复根密钥。

因为 [公式] 只有 [公式] 个可能值( [公式]),但是只有 [公式] 个可能的密钥,我们可以预计 [公式] 个密钥产生相同的 [公式] 。这意味着对于 [公式] 和 [公式] ,我们只需要搜索 [公式] 个密钥,这种工作量完全可以忽略不计,恢复这种特殊密钥是完全可行的。

我们现在分析怎么找到这些密钥的集合。

我们首先猜测 [公式] 。 现在我们可以模拟密钥调度阶段的第一次循环。 我们称在第一次循环之后, [公式] 的值,为旧值,每一个其他的值我们称之为新值。

我们称在 [公式] , [公式] 的 [公式] 和 [公式] 之间的交换为有问题的交换,如果 [公式],那么这个有问题的交换后果会很严重。

如果我们能确切的知道哪些有问题的交换发生,我们可以调整我们的恢复密钥的策略,现在我们就来想办法做到这一点。

由于有问题的交换不会改变集合 [公式] ,不会用新值替换旧值。我们根据恢复出来的密钥调度阶段的第 [公式] 步之后的 [公式] 中搜索旧值。预期的旧值数量是 [公式] (对于 [公式] , [公式] ,这是3.75),每个存在的旧值表示可能存在有问题的交换。

举个例子,假如 [公式] ,我们猜测 [公式] ,旧值为1 ,2, 3, 4。在密钥调度阶段的前5个步骤之后,我们有 [公式] , [公式] , [公式] 且 [公式] ,只有一个旧值,因此最多只有一个可能有问题的交换。 由于刚开始 [公式] ,有问题的交换必须是交换 [公式] 和 [公式] ,必然在步骤 [公式] 或 [公式] 时发生。

我们针对有问题的交换的每个组合计算相应的密钥并测试该密钥是否正确这样计算工作量肯定大于普通密钥的情况,但是依然是可以做到的。

还是刚刚那个例子,如果是 [公式] 时交换 [公式] 和 [公式] ,那是没有问题的,不会影响攻击,但是 [公式] 时交换 [公式] 和 [公式] 问题就大了。

此时我们应该假设原来的 [公式] 在 [公式] 中,而 [公式] 应该等于2,然后再计算密钥值测试是否正确。

7.8 初始化向量在根密钥之前(WEP的情况)

现在假设初始化向量在主密钥之前,格式为 [公式] 。如果 [公式] 是初始化向量的长度,我们已经知道会话密钥中的字节 [公式] ,所以攻击者能够计算密钥调度(KSA)阶段的前 [公式] 次循环。因此他可以将第 [公式] 步之后的[公式] 的值 [公式] 用于计算第一个未知密钥字节 [公式](这个取决于初始化向量,每个会话 [公式] 都是不一样的)。在概率为 [公式] 的情况下,该值在密钥调度(KSA)的剩余步骤,和伪随机生成(PRGA)的前 [公式] 步骤中不会改变。

我们可以使用前面证明的定理猜测 [公式] ,进而计算 [公式] 。这种情况比根密钥在初始化向量之前的情况更简单,因为在这里我们不需要暴力猜测第一个密钥字节。

在这里,初始化向量是随机数或者是计数器,都是没有区别的,对于攻击来说,计数器是也是良好近似的随机数。

另外,由于前面的IV增加了足够的随机性,避免了前面讨论的“7.7 导致攻击失败的特殊密钥字节”的问题,由于 [公式] 也由IV决定,那么前 [公式] 步骤中就不可能每个会话都出现 [公式] 了。

实际上Klein攻击在其他情况下也有可能无法恢复密钥,这种情况我们在介绍完PTW攻击后一起讨论。

7.9 具体应用Klein攻击恢复RC4密钥

Klein成功证明了密钥序列与密钥本身的关系,通过前面所证明的定理(6)计算,得到正确密钥字节的值概率约为 [公式] ,得到其他值的相对概率小于 [公式] ,那为了让正确值能够与其他值区分开来,我们必须收集大量使用了不同IV的密钥序列,对每个密钥序列用定理(6)计算所有 [公式] (如果是 [公式] 需要暴力猜解第一个密钥字节),并建立一个表格,统计每个密钥字节计算结果的值出现的次数,这个过程通常被称为“密钥投票”。

当计算了数量足够多的密钥序列之后,正确密钥值出现的次数会明显比其他值多,我们就可以得知正确的密钥值了。

注意,在计算一个密钥序列的 [公式] ( [公式] ) 时,我们不能根据同一个密钥序列计算得出的 [公式] 去计算 [公式] ,因为同一个密钥序列计算得出 [公式] 很有可能是错的,需要使用当前统计结果中出现次数最高的 [公式] 的值去计算,这就导致了一个很严重的问题,而PTW攻击的出现就是为了解决这个问题。

8 PTW攻击

相信大家看到这里已经明白了Klein攻击的原理,Klein攻击中只要收集足够多的会话,就可以迭代计算所有密钥字节。现在我们来思考一下Klein攻击的缺陷,Klein攻击最大的问题在于恢复 [公式] ( [公式] )之前我们必须先把 [公式] 到 [公式] 全部都恢复了。

实际上这是非常困难的,我们必须要先收集到足够多使用不同IV的密钥序列才能确定 [公式] 到 [公式] 的正确值,这就导致了一旦前面的密钥推测错误,后面的就全错了,一旦前面的密钥有一个正确率更高的选择,后面的密钥就全部需要重新计算重新投票,而且为了实现这一点我们还需要把前面捕获的会话的相关数据全部保存起来。这就是为什么我在前面说Klein攻击需要大量计算资源。

PTW攻击扩展了Klein攻击,使得可以独立地计算每个密钥字节。在PTW攻击中我们计算的不是密钥字节本身,而是密钥字节的“和”。与Klein攻击不同的是,PTW攻击完全为WEP设计。

8.1 将Klein攻击扩展到多个密钥字节

我们回顾一下Klein攻击的定理(6):

[公式]

从这条等式我们可以推导出 [公式] 的等式:

[公式] (7)

实际上根据密钥调度(KSA)的原理 [公式] 可以写成 [公式] ,然后替换到上面等式中,可以得到

[公式]

左右交换一下就可以得到

[公式] (8)

现在我们可以通过计算得到 [公式] 的和。

通过不断的替换 [公式] ,我们就可以得到 [公式] 的值,而在WEP中我们想知道的是 [公式] ,我们使用符号 [公式] 作为密钥字节的总和,我们可以得出:

[公式] (9)

注意,等式右边的 [公式] 是需要根据 [公式] 到 [公式] 才能计算得到,但是这样就跟Klein攻击没有区别了,所以我们要用 [公式] 替换 [公式] , [公式] 只取决于 [公式] 到 [公式] ,最终得到 [公式] 的近似值 [公式] ,如下:

[公式] (10)

在理想条件下 [公式] 的概率为:

[公式] (11)

证明与前面的Klein攻击相同。

不过由于用 [公式] 替换 [公式] ,所以 [公式] 是正确值的概率会稍微下降。

现在我们来计算用 [公式] 替换 [公式] 中出现的所有 [公式] 的情况下,完全不影响接下来对密钥的推算的概率。

只有 [公式] 到 [公式] 中的其中一个值为 [公式] 时, [公式] 才会与 [公式] 不同。例如 [公式] ,那么 [公式] 就会与 [公式] 交换,那么 [公式] ,这样 [公式] ,到时候计算出来的密钥肯定是错误的。如果所有 [公式] [公式] 具有 [公式] ,那么 [公式] 所有值与 [公式] 相同,假设 [公式] 是随机的,那么这会发生的概率为 [公式] 。

另外,如果j在前面循环中没有取值 [公式] 而且 [公式] 也没有取值 [公式] ,那么在循环 [公式] 到 [公式]中 [公式] 就不会改变。 [公式] 在前面循环中没有取值 [公式] 发生的概率为 [公式] , [公式] 没有取值 [公式] 发生的概率为 [公式] 。

[公式] 这里举个反例, [公式] 在前面循环中取值 [公式] ,例如 [公式] ,那么 [公式] 就会与 [公式]交换,在 [公式] 时, [公式] 已经不是原来的值 [公式] 了,而是 [公式] 了,然后再交换到 [公式] ,那我们在计算 [公式] 时肯定是错误的。

[公式] 这里也举个反例, [公式] 取值 [公式] ,例如 [公式] ,那么 [公式] 就会与 [公式] 交换,那么之前的 [公式] 就被改变了,导致后面进行的恢复[公式] 的攻击必定会失败,其实这个类似之前分析Klein攻击时的“7.7 导致攻击失败的特殊密钥字节”。

总结起来,用 [公式] 替换 [公式] 中出现的所有 [公式] 的情况下,完全不影响接下来对密钥的推算的概率为:

[公式] (12)

最终我们可以计算 [公式] 取正确的 [公式] 值的概率下限为 [公式] :

[公式] (13)

PTW攻击的作者使用104位WEP密钥进行超过50,000,000,000次模拟的实验结果表明, [公式] 这个近似值与正确值的相差不到0.2%。

8.2 应用PTW攻击恢复WEP密钥

假设使用了104位的WEP根密钥,对于从 [公式] 到 [公式] 的每个 [公式] ,以及每个会话的密钥序列,我们用公式计算 [公式] ,并对每个 [公式] 建立一个表,进行密钥投票,统计各个 [公式] 出现的次数,当处理完足够多的会话的密钥序列后,我们就可以假设每个 [公式] 的正确值,是出现次数最多的 [公式]

第一个根密钥字节 [公式] ,而其他的根密钥字节可以通过 [公式] 计算得到。

如果是40位根密钥的情况,只需要通过计算 [公式] 到 [公式] 就可以得出。

9 强密钥字节

在前面的等式中我们假设 [公式] 代替 [公式] 不影响密钥计算,但是对于部分情况来说,这是错误的,在前面我们列举了三种情况,但不是每一种我们都需要担心。

[公式] 到 [公式] 中的其中一个值为 [公式] 时, [公式] 会与 [公式] 不同。这种情况不需要担心,因为 [公式] 到 [公式] 是取决于IV的,而IV每一个会话都会变化,也就是说不可能每一次都那么凑巧是 [公式] 。

[公式] 取值 [公式] 也是同理, [公式] 由IV决定,不可能每次都凑巧是 [公式] 。

以上两种情况只要出现概率不高就不会对攻击产生影响。

真正需要担心的是 [公式] 的值在 [公式] 之后到 [公式] 之前的循环中被 [公式] 取值,一旦出现这种情况就会导致 [公式] 提前与一个未知的值交换(取决于IV和前面的密钥字节),而到了 [公式] 的循环中这个未知的值代替了 [公式] 与 [公式] 交换,显然在计算密钥时根据这个未知的值是无法得出正确的密钥字节的值的。

如果密钥中有一部分特殊的密钥字节,那么上述情况出现的概率是非常高的,这种特殊的密钥字节我们称为“强密钥字节”,其他密钥字节称为“普通密钥字节”。只要密钥中至少有一个强密钥字节我们就称这个密钥为“强密钥”,如果密钥没有任何强密钥字节则称为“普通密钥”。

更正式的来说,Rk是强密钥, [公式] 是Rk的其中一个字节,且 [公式] 是强密钥字节,那么 [公式] 满足以下情况:

[公式] [公式] (14)

如果上面这条等式看不懂,我给大家详细举个例子

由于 [公式] ,而由于是刚开始搅乱大部分情况下 [公式] ,所以 [公式] 可以写成 [公式] 可以写成 [公式] ,然后为了方便举例我们把 [公式] , [公式] ,以此类推,一直到[公式] (40位根密钥),然后我们假设 [公式] 是强密钥字节。

那么 [公式]

如果 [公式] ,那么 [公式]

如果 [公式] ,那么 [公式]

如果 [公式] ,那么 [公式]

如果 [公式] ,那么 [公式]

显然符合等式的话 [公式] 会被之前循环中的 [公式] 取值,导致 [公式] 提前被交换。

除非有IV让 [公式] 到 [公式] 取值为 [公式] 到 [公式] ,让 [公式] 提前改变,破坏掉 [公式] ,让强密钥字节存在的情况公式也不成立,但是这种情况概率很低, [公式] ,所以大部分情况下只要有强密钥字节,等式就成立。

这最终导致的结果就是 [公式] 在前面循环中没有取值 [公式] 发生的概率接近于0,进而导致 [公式] 接近于0,最终导致 [公式] ,这非常接近 [公式] 。

[公式] ,这是一个多小的数字呢?我们代入 [公式] 的话

[公式]

在 [公式] 的情况下 [公式] 与 [公式] 具体的数值为

[公式]

[公式]

[公式] 的情况下 [公式] 甚至比 [公式] 还高那么一点,虽然差别非常非常小,几乎可以忽略。

以至于PTW攻击作者测试了在35000到300000个会话的强密钥字节的投票分布,强密钥字节的正确值的投票数依然不能和其他值区分开来,其他值的票数很可能比正确值更高。

在论文中PTW攻击的作者一直表示Klein攻击不受强密钥的影响,但是我个人觉得Klein攻击在强密钥下一样会失效。

Klein攻击下虽然没有了 [公式] 与 [公式] 不同的问题,但是 [公式] 的值在 [公式] 到 [公式] 之间的循环中被 [公式] 取值后,同样会导致 [公式] 提前与一个未知的值交换,然后到了 [公式] 的循环中这个未知的值代替了 [公式] 与 [公式] 交换,导致 [公式] 并不是我们想要的 [公式] 正确值,Klein攻击就无法进行下去。

注意,这里与前面的“7.7 导致攻击失败的特殊密钥字节”是有区别的,前面那个是导致 [公式] 在交换后的剩余 [公式] 次的循环中不变的概率为 [公式] 不成立,而强密钥是 [公式] 成立,但是 [公式] 在交换后就是已经是一个错误的值。

9.1 找出哪些字节是强密钥字节

如果[公式] 是普通密钥字节,那么 [公式] 的正确值应该以概率 [公式] 出现,我们可以假设除了正确值以外的所有其他值出现的概率为 [公式] 。

如果 [公式] 是强密钥字节,我们可以假设所有值出现概率都是 [公式] 。

现在让 [公式] 得到 [公式] 的投票比例,我们计算一个密钥字节不是强密钥的概率和不是普通密钥的概率。

[公式] (15)

如果是强密钥字节,由于各个值出现概率接近 [公式] ,所以 [公式] 接近为0。如果是普通密钥字节,正确值会特别突出,其他值出现概率比 [公式] 小,全部平方后相加 [公式] 会明显增大。

[公式] (16)

如果是强密钥字节,即使是投票数最多的值依旧接近 [公式] ,与 [公式] 差别比较大,而 [公式] 与 [公式] 也有微小的差距,全部平方后相加, [公式] 会增大。如果是普通密钥字节,投票数最多值基本符合 [公式] ,而其他值基本符合 [公式] ,所以 [公式] 接近为0。

总结起来就是

强密钥字节的情况 [公式] 接近为0,小于 [公式] 。

普通密钥字节情况, [公式] 接近为0,小于 [公式] 。

 

如果能收集足够的会话样本,如果 [公式] 小于 [公式] , [公式] 很可能是强密钥字节,就可以对其进行强密钥字节推测。如果只有少量会话样本可用,则可以使用 [公式] 计算密钥字节 [公式] 是强密钥字节的可能性。

9.2 找到强密钥字节的正确值

假设 [公式] 是强密钥字节,并且密钥 [公式] 到 [公式] 的正确值都已经得出,那我们可以使用定理(14)推导得到计算强密钥字节 [公式] 的等式。

[公式] (17)

因为 [公式] 最多可能有[公式] 种值,我们可以尝试 [公式] 的每种可能值,也就是 [公式] 最多可能有 [公式] 个可能的值,例如 [公式] 是104位的根密钥的最后一个密钥字节,则有12种可能值。

还是前面那个 [公式] 是强密钥字节的例子, [公式] ,现在我们要恢复 [公式],由于 [公式] 到 [公式] 的正确值都是已知,所以A、B、C都是可以计算得到的。

如果 [公式] ,那么 [公式]

如果 [公式] ,那么 [公式]

如果 [公式] ,那么 [公式]

如果 [公式] ,那么 [公式]

我们只需要通过计算得到这4种不同的 [公式] 的值,然后逐个代入已知的其他密钥字节中进行密钥测试,就可以得出正确的密钥值了。

如果是强密钥字节,我们就不能使用密钥投票了,最高票的 [公式] 不再是正确值,需要假设用公式得出的 [公式] 计算 [公式] 是 [公式] 的正确值。

当然,其他普通字节还是使用密钥投票来确定 [公式] 的正确值。

以上的方法只有在密钥中的强密钥字节数量很少才可行。

实际上一个密钥中所有字节都可以是强密钥字节,在40位根密钥中可以有4个字节都是强密钥字节,这种情况下我们需要测试的密钥数量只有 [公式] ,但是在104位根密钥中12个字节都是强密钥字节的情况下,我们需要测试高达 [公式] 个不同的密钥,这虽然可以做到的,但是会严重拖慢我们的攻击速度。

我现在用的电脑的CPU是i5 2500K(比较旧),一秒钟大概可以测试43万个RC4密钥,测试479001600个密钥大概需要18.5分钟。

当然,所有密钥字节都是强密钥字节的情况非常少见,但是要找出强密钥字节正确的值除了按上述方法穷举以外似乎没有其他更好的办法。

如果你现在有支持开放WEP热点的设备,你把密钥设置为16进制的FDFCFBFAF9F8F7F6F5F4F3F2F1,你会发现Aircrack-ng就无法破解了,因为每个字节都是强密钥字节,计算量太大了,如图3。

0xFD = 253, [公式]

0xFC = 252, [公式]

0xFB = 251, [公式]

0xFA = 250, [公式]

以此类推。

图3

10 PTW攻击的密钥排名

在能获取到大量会话时,投票数最多的就是 [公式] 的正确值,但是在获取到的会话数量不够的情况下, [公式] 未必是投票数最多的那个,但通常是投票数排名(从多到少)靠前的那几个中的一个。

在无法获取到足够的会话的时候就需要根据已知信息想办法了,我们可以通过多条计算路径来恢复密钥,例如假设 [公式] 是在投票值的前三名中,就根据这个三个不同的密钥字节创建三条密钥计算路径,这个过程被称为“密钥排名”,以下是一些密钥排名策略:

静态路径数

对每个密钥字节的排名靠前的前 [公式] 个值创建路径,这是一种最简单的实现,但是缺点就是太死板了,每个密钥字节都搜索排名靠前的 [公式] 个值的情况下,我们需要计算 [公式] 个不同的密钥( [公式] 为密钥长度)。对于104位的密钥有13个密钥字节,那么假如 [公式] ,我们就需要计算 [公式] 个不同的密钥,如果 [公式] ,那么就需要计算 [公式] 个不同的密钥,这计算量就很大了。

智能选择路径

如果我们在测试所有最高票的密钥字节组成的密钥时发现密钥不正确,那么肯定有至少一个密钥字节不是最高票的密钥字节,那么我们查看每个密钥字节的排名列表,寻找第二名与最高票差别最小的密钥字节,这是错误可能性最高的字节,然后用第二名的值取代此密钥字节最高票的值再进行测试,记录此字节的搜索次数加一,如果还是错误则重复这个过程。

设 [公式] 为密钥排名列表中密钥字节 [公式] 的搜索次数,那么我们需要计算的密钥数量为 [公式],这种策略可以让搜索密钥的数量更少。

密钥排名的一般改进

这里有一些策略可以辅助密钥排名工作,或者独立于密钥排名使用,提高攻击的有效性:

密钥空间限制:假如你对密钥内容有一定的了解,例如在饭店的WIFI密码可能是电话号码,这就意味着密钥只包含表示十进制数字的ASCII字符,每个 [公式] 的值将介于48和57之间,那么在密钥排名时就可以筛选掉不符合范围的密钥字节。

暴力猜解密钥字节:如果只有少量密钥字节(例如只有一两个)的投票值还没拉出差距(例如强密钥字节,各个值出现次数非常接近),无法判断哪个才是正确值,那么我们可以暴力猜解这个密钥字节,尝试所有256种可能性,只要计算时间能接受范围内。

11 IEEE 802.11数据帧格式

理论部分现在已经介绍完了,但是如果想要编写一个应用PTW攻击恢复WEP密钥的程序,把理论变成现实,我们还需要先了解一些关于IEEE 802.11协议的相关知识。

实际上IEEE 802.11协议非常复杂,详细的说可以再写一本书,这里我只分析我们要用到的部分,有兴趣详细了解的话推荐去看 “802.11无线网络权威指南”这本书,虽然这本书已经比较旧了,但是非常系统。

IEEE 802.11帧主要有三种类型:管理帧,控制帧,数据帧。

  • 管理帧:主要用于监管网络,身份验证、解除身份验证、关联网络、探测网络、宣告网络的存在等功能。
  • 控制帧:主要用于提高物理层数据传输的可靠性,协助数据帧传输,包括数据确认,取得信道使用权、清除发送、省电模式控制等功能。
  • 数据帧:主要用于传输上层数据,上层数据存放在数据帧的帧主体中,是恢复WEP密钥最需要关注的一种帧类型,图4为数据帧的帧格式。
图4

我们需要关注的字段是:帧控制、地址字段、帧主体、QoS控制。

帧控制

图5

帧控制字段实际上由图5中的四个子字段构成。

版本字段是固定的,值为0,因为暂时来说802.11只有一个版本,未来可以会有新的版本。

类型字段用于区分帧类型,现有的类型为:管理帧(00)、控制帧(01)、数据帧(10),我们只需要关注数据帧(10)。

子类型字段用于进一步区分不同功能的帧,我们只需要关注数据帧类型下的两种子类型:Data(0000)、QoS Data(1000),其他子类型要么是不携带数据的要么是很少出现的不需要关注。

我们可以根据上述的类型和子类型字段筛选捕获到的802.11帧。

图6

图6为帧控制中标志字段的每个位的功能,我们只需要关注To DS位和From DS位。

这两位用于表示来源或目的地是否为分布式系统(distribution system,DS),地址字段也会受到这两个位的影响。

地址字段

一个802.11帧最多可以包含4个地址字段,不同的帧类型地址字段的功能也不同,大部分情况下只会用到前三个地址,第四个地址只有在无线桥接器(WDS)的情况下才会使用,地址字段主要用于以下用途:

  • 目的地址(Destination address,DA):代表最后的接收端,即负责将帧交付上层协议处理的工作站。
  • 源地址(Source address,SA):代表传送的来源,每个帧只能来自单一工作站。
  • 接收端地址(Receiver address,RA):代表负责处理该帧的无线工作站。如果是无线工作站,接收端地址为目的地址。如果帧的目的地址是与接入点相连的以太网结点,接收端地址为接入点地址,而目的地址可能是连接到以太网的一部路由器。
  • 发送端地址(Transmitter address,TA):代表将帧传送至无线媒介的无线接口。
  • 基本服务集标识符(Basic Service Set ID ,BSSID):要在同一个区域划分不同的无线局域网络,可以为工作站指定所要使用的BSS(基本服务集)。在基础结构型网络里,BSSID即是接入点无线接口所使用的MAC地址。而独立型网络则会产生一个随机的BSSID,以防止与其他官方指定的MAC地址产生冲突。

802.11对来源地址(SA)与发送端地址(TA)以及目的地址(DA)与接受端地址(RA)是有明确的区别的,帧的发送端不见得就是帧的来源,接受端也不见得就是帧的最终目的地,可能只是中转,只有帧到达最终目的地才会由上层协议处理。

地址字段与To DS、From DS位的关系。

QoS控制

这个字段只有在QoS数据子类型中才会出现,用于标识数据帧的服务质量(QoS)参数,由于格式与普通的数据子类型不同,处理时记得留意。

帧主体

IEEE 802委员会将OSI七层模型的数据链路层分为两个子层,分别是逻辑链路控制层(Logical Link Control, LLC)和介质访问控制层(Medium Access Control, MAC),如图7。

这样划分的原因是为了将物理部分与逻辑部分完全分开,介质访问控制层主要负责控制物理层的物理介质,而逻辑链路控制层主要负责与上层协议协调。

图7

802.11就使用了这种结构,以802.2的逻辑链路控制层来封装携带上层数据,数据帧中除了帧主体以外的其他字段都是属于介质访问控制层的,帧主体属于逻辑链路控制层,帧主体格式如下:

图8

逻辑链路控制层作为一个单独的层它有着自己的特定协议,在IEEE 802.2中定义了802.2 LLC头部。

图9
  • 8位DSAP:Source SAP,用于表示接收方的上层协议的类型
  • 8位SSAP:Destination SAP,用于表示发送方的上层协议的类型

SAP为Service Access Points的缩写,中文为服务访问点。网络模型中,一个层可以请求另一个层提供服务,而SAP就是用来区分这些服务。在数据链路层中的SAP也被称为LSAP(Link Service Access Point)链路服务接入点。虽然LSAP字段的长度是8位,但是第8位是保留用于特殊用途的,也就是说只有128个值可以用于区分类型,此字段值由IEEE全球统一分配,以下为常见值:

0x06 IPv4协议

0x98 ARP协议

0xAA SNAP协议

  • 8位或16位Control:控制字段,分为三种类型:I帧(用于传输面向连接的数据,16位)、监控帧(监督链路,例如数据确认、请求重传,16位)、未编号帧(设置命令和响应以及传输信息,8位)

当最初的IEEE 802.3/802.2 LLC帧格式推出之后,IEEE很快就意识到在IEEE 802.2标准里的LLC头部中给LSAP字段只分配一个字节是行不通的,一个字节无法满足未来出现更多新协议的需求,于是推出了802.2 SNAP协议(子网访问协议)作为802.2 LLC头部的扩展。802.2 SNAP头部如图10。

图10
  • 24位OUI:Organizationally unique identifier的缩写,组织唯一标识符,允许特定组织给自己定义协议,如果OUI为0,就使用Ethernet II帧格式中的类型,如果OUI为特定组织的OUI则是使用特定组织定义的类型。
  • 16位类型:与Ethernet II帧格式中的类型字段完全相同,这避免分配新的SNAP类型值,同时也解决了原有的LSAP只有一字节不够用的问题。

如果802.2 LLC帧需要使用802.2 SNAP协议,那802.2 LLC头部中的SSAP与DSAP都会设置为0xAA,控制字段设置为0x03,表示未编号信息。

如果使用WEP帧主体需要使用8个字节存储WEP相关的信息,3字节的IV、1字节的密钥编号、4字节的ICV,格式如下:

图11

24位的初始化向量(IV)与8位的密钥编号会以明文形式传输,而原来帧主体与ICV以密文形式传输。WEP只保护帧主体,802.11帧头部(介质访问控制层)以及更底层的协议头部都不会得到保护。

WEP支持同时定义4个密钥,使用的密钥通过密钥编号区分,发送方必须指定使用的密钥编号,但是大多数情况下只会使用其中一个密钥。

ICV(integrity check value)指完整性校验值,用于校验未加密数据的完整性,ICV与帧主体一同被加密,保证不会被攻击者篡改,与802.11帧头部的FCS不同,ICV只校验帧主体,而FCS校验整个帧。

Radiotap

图12

Radiotap 是网卡驱动添加在802.11 MAC头部前的数据,记录了信号强度、噪声强度和传输速率等物理层信息,想Radiotap更多信息可以参考,要注意的是如果抓包的数据带有Radiotap,那我们在发包的时候也需要构造Radiotap。

12 获取密钥序列

上述介绍的WEP密钥恢复攻击实际上都是“已知明文攻击”,我们需要在知道部分明文和部分密文的情况下恢复出密钥序列,然后才可以进行进一步的攻击(仅密文攻击也是可以实现的,只不过难度更大,这里暂不分析)。如果我们要恢复40位的WEP根密钥我需要密钥序列的第3到第7个字节,如果我们要恢复104位的根密钥,则需要密钥序列的第3到第15个字节,如果是一些更长的密钥的特殊实现(如232位根密钥)则需要更多。

根据流密码的特性,我们只需要用密文 xor 明文就可以得到密钥序列了,如图13。

图13

密文非常容易得到,只需要捕获无线协议帧就可以了,明文这里就需要我们来思考一下,数据链路层上面的通常是什么呢?

答案就是IPv4协议,即使现在都9102年了,IPv4协议依然是部署最广泛的协议,根据谷歌的数据IPv6在全球范围内的使用比例也就26.77%,而在WEP流行的年代就更少了,只有0.04%左右,完全是IPv4的天下。

12.1 通过IPv4头部恢复密钥序列

那我们来观察一下,上层为IPv4协议的帧有什么特征,图4中为帧格式,图中固定的或者可以猜测的字节用绿色去表示,很难猜测的字节用橙色去表示,无法猜测的字节用红色去表示。

图14

在前面我们知道802.11是通过802.2 LLC来封装上层的数据的,而由于使用了802.2 SNAP所以802.2 LLC头部中的SSAP与DSAP都是0xAA,控制字段为0x03,而802.2 SNAP头部中的OUI由于是使用Ethernet II帧格式中的类型,所以为0,这上述这部分字节无论上层协议是什么都是固定的,而Ethernet II帧格式中的IPv4类型的值为0x0800。

IPv4版本是固定的,为4,而头部长度绝大部分情况下也是固定的20字节,没有选项字段,DS(DiffServ,区分服务优先级)和ECN(拥塞标识符)在绝大部分情况下都是不会使用的,为0。

数据包总长度虽然不是固定的,但是WEP不会掩盖帧主体的长度,所以我们可以观察帧长度计算出这个值。

标识字段用于跟踪组装分片数据包,无法猜测,只能假设为随机。

标志与片段偏移:据PTW攻击作者分析了来自各种来源的流量后,发现大约85%的数据包是在设置了不分段标志的情况下发送的,大约15%的数据包清除了所有标志,在密钥排名中这两个字节可以加权投票。

往后的TTL、上层协议、校验和、源IPv4地址、目标IPv4地址字段都是非常难以猜测甚至无法猜测的了。

综上所述IPv4报文中能有把握恢复密钥序列也就前12个字节和第15、16字节,这对于恢复40位的密钥来说已经绰绰有余了,但是对于104位的密钥来说显然还是不够的。

那我们再来想想,在使用IPv4协议的情况下还有什么其他的协议可以让我们恢复密钥序列呢?相信大家很快就能想到,那就是ARP协议。

12.2 通过ARP报文恢复密钥序列

ARP协议全称为Address Resolution Protocol,即“地址解析协议”。顾名思义,它的作用就是将网络层地址解析为链路层地址,让数据可以在物理网络中进行传输。在IPv4下,ARP协议将IPv4地址解析为以太网的MAC地址。那我们现在来看看ARP报文可以让我们恢复多少字节的密钥序列。

图15

802.2 LLC与802.2 SNAP头部在刚刚的IPv4中已经说过了,唯一不同的是类型字段,ARP的值为0x0806。

ARP报文的头部中的字段都是固定值的,硬件类型为1表示以太网,协议类型为0x0800表示IPv4协议,硬件大小为6,是MAC地址的长度,协议大小为4,是IPv4地址的长度。

操作类型为1或2,1是请求,2是响应。ARP请求始终发送到广播地址,而ARP响应发送到单播地址,因此很容易区分ARP请求和响应。

在ARP请求中发送方MAC为发送主机的MAC,目标MAC通常为全0,而在ARP响应中发送方MAC为响应主机的MAC,目标MAC为发送请求的主机的MAC,这些都是可以通过802.11帧头部中的未加密的源地址(SA)和目标地址(DA)字段获取。

源IPv4地址和目标IPv4地址字段就非常难猜到了。

综上所述ARP报文中能有把握恢复密钥序列是前22个字节和第27到第32字节,这对于恢复104位的密钥来说也足够了。

由于ARP请求和ARP回复的报文大小都是固定的,我们很容易通过帧主体的长度判断哪个帧是携带了ARP报文。这里有可能会把少量TCP、UDP短的数据包识别为ARP导致失败,不过只要识别正确的情况占大多数就影响不大。

不过由于ARP报文在流量中占比极少,很难收集足够数量的报文恢复密钥,这里需要使用“ARP注入攻击”。

由于WEP并不会阻止重复的IV,也没有任何检测重放攻击的机制,所以我们可以将捕获到的ARP请求重放,重放后我们可以得到三个新的加密分组:

1.请求由接入点中转到广播地址(即使是广播报文也需要接入点中转)。

2.目标主机返回对该请求的ARP响应,并将响应发送到接入点。

3.接入点将该响应转发给原始请求站。

通过重复ARP注入过程就能够快速得到大量用不同IV加密的ARP报文。

如果捕获不到ARP请求,那我们可以使用“强制解除验证攻击”让网络中的客户端掉线,客户端重新连接无线网络时会刷新ARP缓存表,等客户端需要发送IP报文时,自然就会发送ARP请求,这样就可以获取到ARP请求报文了。

13 编写PTW攻击程序

现在相关的知识都了解完了,我们可以动手编写一个PTW攻击程序了,以下程序使用C++编写,以下为main.cpp的源码:

#include <stdio.h>
#include <string.h> #include <array> #include <set> #include <unistd.h> #include <sys/socket.h> #include <netinet/ether.h> #include <arpa/inet.h> #include <net/if.h> #include <netpacket/packet.h>  #define TYPE_DATA 2  #define SUBTYPE_DATA 0 #define SUBTYPE_QOS_DATA 8  #pragma pack(1) struct MACsublayer { //unsigned short FrameControl;  unsigned char Version:2; unsigned char Type:2; unsigned char SubType:4; //unsigned char Flags;  unsigned char ToDS:1; unsigned char FromDS:1; unsigned char Flags:6; unsigned short DurationID; unsigned char Address1[ETH_ALEN]; unsigned char Address2[ETH_ALEN]; unsigned char Address3[ETH_ALEN]; unsigned short Seqctl; }; struct RadioTaphdr { unsigned char Version; unsigned char Pad; unsigned short Length; unsigned int PresentFlages; }; struct WEPInfo { unsigned char IV[3]; unsigned char KeyIndex; }; struct AttackInfo { unsigned char IV[3]; unsigned char X[7]; //密钥序列 }; #pragma pack()  static unsigned int RKeyRanking[5][256]; //密钥排名表,5个密钥字节,每个字节256个值 static unsigned char HighestVote[5] = {0}; //当前最高票的密钥字节总和 static unsigned char RealKey[5] = {0}; //当前最高票的密钥字节  static std::set<std::array<unsigned char,3>> ReceivedIV; //保存接收过的IV,用于避免对相同IV重复投票,增加准确率(这是可选的)  void Swap(unsigned char *a, unsigned char *b) { unsigned char tmp = *a; *a = *b; *b = tmp; } void PTW_UpdateKeySumVote(unsigned char keyindex, unsigned char keysum) { RKeyRanking[keyindex][keysum]++; //如果加一后当前值的投票数大于最高票的值的投票数则替换最高票的值  if(RKeyRanking[keyindex][keysum] > RKeyRanking[keyindex][HighestVote[keyindex]]) { HighestVote[keyindex] = keysum; //替换后重新计算密钥字节  RealKey[0] = HighestVote[0]; for(int i = 1; i < 5; i++) RealKey[i] = HighestVote[i] - HighestVote[i - 1]; } } void PTW(struct AttackInfo info) { unsigned char Key[8] = {info.IV[0], info.IV[1], info.IV[2], 0, 0, 0, 0, 0}; //当前会话密钥,除了IV其他都要计算  unsigned char j, SBox[256], S_Sigma; //对S-Box初始化  for(unsigned short i = 0; i < 256; i++) SBox[i] = i; //根据IV,执行前三次的密钥调度循环,得到j3、S3  for(unsigned char i = 0; i < 3; i++) { j += SBox[i] + Key[i]; Swap(&SBox[i], &SBox[j]); } for(unsigned char i = 3; i < 8; i++) { /*  计算S3[l]的总和,这里是为了接近公式  实际上你可以把S_Sigma = 0;移到循环前,然后把循环去掉,改成S_Sigma += SBox[i];  */ S_Sigma = 0; for(unsigned char l = 3; l <= i; l++) S_Sigma += SBox[l]; Key[i] = i - info.X[i - 1] - (j + S_Sigma); //定理(10) PTW_UpdateKeySumVote(i - 3, Key[i]); //更新此密钥字节的密钥投票 } } int main(int argc, char *argv[]) { char *bssidmac = argv[1]; //指定热点的BSSID,用于过滤数据帧 char *ifname = argv[2]; //指定要抓包的接口名称 unsigned char PlainText[7] = {0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x08}; //用于xor密文还原密钥序列的明文 struct ether_addr bssid; ether_aton_r(bssidmac, &bssid); //将字符串的BSSID转换成二进制格式 int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); //创建数据链路域套接字 struct sockaddr_ll lladdr; memset(&lladdr, 0, sizeof(lladdr)); lladdr.sll_family = AF_PACKET; lladdr.sll_protocol = htons(ETH_P_ALL); lladdr.sll_ifindex = if_nametoindex(ifname); //获取接口索引 bind(sockfd, (struct sockaddr *)&lladdr, sizeof(lladdr)); //绑定抓包接口,否则会收到其他接口的帧 struct RadioTaphdr *radiohdr; struct MACsublayer *mac; struct WEPInfo *wep = NULL; unsigned char *payload = NULL; struct AttackInfo info; char buf[1024]; int len = 0; unsigned long framecount = 0; //统计捕获的会话数量 while((len = read(sockfd, buf, 1024)) > 0) { radiohdr = (struct RadioTaphdr *)buf; mac = (struct MACsublayer *)(buf + radiohdr->Length); if(mac->ToDS == 1 && mac->FromDS == 0) { if(memcmp(mac->Address1, bssid.ether_addr_octet, ETH_ALEN) != 0) continue; } else if(mac->ToDS == 0 && mac->FromDS == 1) { if(memcmp(mac->Address2, bssid.ether_addr_octet, ETH_ALEN) != 0) continue; } if(mac->Type == TYPE_DATA) { if(mac->SubType == SUBTYPE_DATA) wep = (struct WEPInfo *)((char *)mac + sizeof(struct MACsublayer)); else if(mac->SubType == SUBTYPE_QOS_DATA) wep = (struct WEPInfo *)((char *)mac + sizeof(struct MACsublayer) + 2); //QoS子类型需要跳过QoS字段 else continue; //如果已经计算投票过此IV,则跳过 if(ReceivedIV.find({wep->IV[0], wep->IV[1], wep->IV[2]}) == ReceivedIV.end()) { ReceivedIV.insert({wep->IV[0], wep->IV[1], wep->IV[2]}); framecount++; payload = (unsigned char *)wep + sizeof(struct WEPInfo); memcpy(info.IV, wep->IV, 3); for (int i = 0; i < 7; i++) info.X[i] = payload[i] ^ PlainText[i]; //还原密钥序列 printf("IV : %02x %02x %02x X: %02x %02x %02x %02x %02x %02x %02x Frame count : %lu\n", info.IV[0], info.IV[1], info.IV[2], info.X[0], info.X[1], info.X[2], info.X[3], info.X[4], info.X[5], info.X[6], framecount); PTW(info); printf("Highest vote keysum: %02x %02x %02x %02x %02x\n", HighestVote[0], HighestVote[1], HighestVote[2], HighestVote[3], HighestVote[4]); printf("Highest vote real key: %02x %02x %02x %02x %02x\n", RealKey[0], RealKey[1], RealKey[2], RealKey[3], RealKey[4]); } } } } 

由于使用了Linux系统特有的API,所以只可以在Linux系统上编译运行。

g++ main.cpp -o ptw
./ptw c8:3a:35:36:88:a0 wlan0

第一个参数是AP的BSSID,第二个参数是无线接口名称。

注意,在运行程序之前需要先将无线接口设置为“监听模式”才能捕获到原始的802.11帧,否则只能捕获到驱动程序处理过后的以太网帧。

将网卡设置为监听模式可以使用以下命令:

#先停用无线网卡,否则会提示Device or resource busy错误
ip link set wlan0 down  
#设置无线网卡工作模式为监听模式
iw dev wlan0 set type monitor #设置无线网卡工作信道 iw dev wlan0 set channel 8 #启用无线网卡 ip link set wlan0 up 

以上这种方法有一个地方不好,那就是你一旦设置了监听模式你就没办法用无线网卡连接AP了,你就没办法连上互联网了,如果你想一边破解密码一边上网看视频你可以用以下命令在无线网卡上创建一个虚拟监听接口:

#在wlan0上创建一个虚拟监听接口mon0
iw dev wlan0 interface add mon0 type monitor 
#启用虚拟接口
ip link set mon0 up 

运行攻击程序时将接口名称参数改为mon0就可以了

./ptw c8:3a:35:36:88:a0 mon0

如果要删除虚拟接口可以使用命令:

iw dev mon0 del

以下为运行程序的截图,我设置AP的WEP根密钥为ASCII的“ABCDE”,换成十六进制就是0x41 0x42 0x43 0x44 0x45。

图16

如图所示,在捕获了27000个左右的数据帧后,所有密钥字节的正确值已经投票得出,PTW攻击成功。

14 密钥投票过程的演示视频

为了帮助大家更好的理解密钥投票过程,更直观的看到在普通密钥情况下正确值与其他值在投票数(出现次数)上的差异,以及在强密钥情况下各个值投票数的接近,我特地将Klein攻击与PTW攻击的真实投票过程动画化,并上传到了b站,大家可以到以下链接中查看:

WEP密码破解的密钥投票过程​www.bilibili.com

15 唠嗑

写这篇文章花费了不少时间和精力,本来是作为书籍里的内容,现在发到网上是想让知识更好的传播,帮到更多的人,也想激励大家不要只流连于工具的使用,更要深究背后的原理,这些原理并没有你想象中那么难,那些看起来高大上、深不可测的黑客工具,深入研究下去你会发现其实也不过如此,实际上你完全也可以动手写一个!

最后的吐槽,本来这篇文章是想发到安全客、Freebuf这些更专业的安全媒体的,无奈这些网站都不支持编辑数学公式,只好发到知乎来了,但是知乎只支持手动插入公式也是让人痛苦,不支持$$E=mc^2$$这种的MathJax格式,一个一个手动插入真是太累人了。

猜你喜欢

转载自www.cnblogs.com/jinanxiaolaohu/p/12434538.html