剖析洋葱路由Tor网桥与可插拔传输(下篇)
2019-12-23 11:09:12 Author: www.4hou.com(查看原文) 阅读量:350 收藏

背景

这是系列文章中的下篇,在上篇文章中,我详细介绍了Tor浏览器的内置网桥如何通过三个进程(firefox.exe、tor.exe和obfs4proxy.exe)工作的,以及Tor浏览器如何与Obfs4网桥客户端进行通信的,最后还介绍了这三个进程之间的关系。在本文中,我将继续说明Tor如何利用Obfs4网桥来绕过审查。

使用Tor时的流量:使用/不使用Obfs4网桥

在讨论Obfs4网桥之前,我想首先解释一下启用Obfs4网桥的Tor浏览器与未启用的浏览器之间的区别。为了清楚易懂,我绘制了两个Tor流量图,如下所示。

未使用Obfs4网桥的正常Tor流量:

1.png

这里展示了未使用Obfs4网桥的正常Tor通信流。我们将这一过程简短描述如下:Tor客户端从Tor主目录中获得三个Tor中继,分别是入口节点、中继节点和出口节点。一旦用户访问网站,Tor客户端就会从Tor浏览器(firefox.exe)接收请求数据包。然后,会使用来自出口节点、中继节点和入口节点的会话密钥,对这个数据包进行三次加密。然后,将多重加密后的数据包发送到入口节点、中继节点和出口节点。在经过解密后,出口中继会得到原始数据包,并将其发送到目标服务器,例如www.google.com。这就是Tor将原始数据包从Tor浏览器传输到目标网站的方式。

但是,通过检查,很容易识别并阻断Tor加密的流量(如上图的红色箭头所示)。

使用Obfs4网桥的Tor流量:

2.png

这里展示了使用Obfs4网桥后新的通信路径。我们可以清楚看到,一旦启用了Obfs4网桥,Tor流量就会发送到Obfs4客户端(obfs4proxy.exe)进行转换后再传输到网桥中继。这里的网桥中继是入口节点,而不是以前Tor的入口节点。为了保证良好的性能,任何特定的Tor电路的节点总数始终为3。

Obfs4网桥客户端获取Tor加密的Payload,并使用Obfs4函数对其进行封装。然后将封装的数据包发送到Obfs4网桥中继。在对其解封装之后,网桥中继会得到Tor加密后的数据包,并将其转发到Tor电路的下一个Tor中继,反之同理。

我们简单地讲解了Obfs4网桥的工作过程。接下来,我们将讨论Obfs4网桥使用哪些技术来保证流量难以识别。

SETCONF命令和网桥控制行

Tor浏览器(firefox.exe)通过控制TCP/9151端口,将带有内置Obfs4网桥信息的SETCONF命令发送到tor.exe,该命令类似于如下格式:

SETCONF UseBridges=1 Bridge="obfs4 109.105.109.165:10527
8DFCD8FB3285E855F5A55EDDA35696C743ABFC4E
cert=Bvg/itxeL4TWKLP6N1MaQzSOC6tcRIBv6q57DYAZc3b2AzuM+/TfB7mqTFEf
XILCjEwzVA iat-mode=1" Bridge="obfs4 85.17.30.79:443 ……

最开始的“SETCONF”是命令名称,“UseBridges=1”表示Tor用户已经启用网桥功能。后面的数据包括完整的内置Obfs4网桥块,在Tor网站上被称为“网桥配置行”(Bridge Configuration Lines)。在这里,我们仅以第一个网桥配置行为例。每个网桥配置行都是以“Bridge=”开头,其中包括:

网桥类型:obfs4,表示具体的网桥类型;

网桥服务器IP地址和端口:109.105.109.165:10527;

网桥ID:14H长的十六进制;

网桥证书:Obfs4中继经过Base64编码的nodeID和IDPublicKey;

网桥IAT模式:IAT模式标志可以设置为0、1或2。

Tor将处理这一命令,然后启动obfs4proxy子进程,也就是Obfs4网桥客户端。Obfs4proxy.exe使用相同的过程,通过Obfs4网桥客户端和Tor客户端之间的回环接口上的TCP端口来传输数据包。

Tor客户端与Obfs4客户端进行通信

Tor客户端分别将Obfs4网桥发送到obfs4proxy.exe,并要求其与Obfs4网桥中继建立连接。Tor使用SOCKS5协议通过Obfs4网桥客户端传输数据。

Tor.exe将一个Obfs4网桥信息实例发送到obfs4proxy.exe:

3.png

上图是使用WireShark抓取到的tor.exe与obfs4proxy.exe之间通信数据包的截图。红色的数据包代表从tor.exe到obfs4proxy.exe(通过回环接口上的随机TCP端口)的流量,而响应数据包标记为蓝色。我们可以看到,这里还使用了SOCKS5协议。

首先,将包含Base64编码的“cert=”和IAT模式的Obfs4网桥信息从tor.exe发送到obfs4proxy.exe。然后,tor.exe继续以SOCKS5“Connect (1)”数据包(以“|05 01 00 01|”开头)发送Obfs4网桥中继的二进制IP地址和端口。然后,使用IP地址和端口将obfs4proxy.exe连接到该网桥中继。成功建立连接后,将“Succeeded (0)”数据包(“|05 00 00 01 00 00 00 00 00 00|”)发送回tor.exe。这意味着,Obfs4握手已经成功进行(我们将在下一章详细讲解握手过程)。之后,tor.exe可以通过这个连接发送和接收需要由Obfs4网桥转换和传输的所有数据包。

Obfs4客户端与Obfs4网桥开始握手

要连接Obfs4网桥,需要一个握手过程,其目的是传输公钥并进行相互验证。

在对握手数据包进行分析之前,我们首先花一些时间来讨论Obfs4使用的加密算法和密钥对(Keypair)的结构。

Obfs4网桥使用ECC(椭圆曲线加密)算法对Tor Payload进行加密,以确保Obfs4客户端和中继之间的安全通信。众所周知,ECC是一个基于椭圆曲线理论的公钥加密技术。Obfs4使用的ECC在名为“curve25519”的Go语言包中实现,该包提供了两个重要的函数,分别是ScalarBaseMult()和ScalarMult()。

下面是密钥对结构的定义,其主要功能是保存公钥和私钥:

// Keypair is a Curve25519 keypair with an optional Elligator representative.
// As only certain Curve25519 keys can be obfuscated with Elligator, the
// representative must be generated along with the keypair.
 
type Keypair struct {
     public     *PublicKey
     private    *PrivateKey
     representative *Representative
}

客户端和服务器都必须具有自己的密钥对实例。我们来分析一下二者之间的关系。根据curve25519软件包中的定义,会在一定范围内随机生成私钥。每个ECC通过调用curve25519.ScalarBaseMult()根据私钥计算出公钥。然后,将其从公钥转换为代表密钥(Representative Key),也可以在需要时通过调用函数extra25519.RepresentativeToPublicKey()来轻松还原成公钥。这一密钥对的定义和初始化是在源文件ntor.go中定义的NewKeypair()函数中实现的。

客户端和服务器各自保存其自己的私钥,并以代表密钥的形式相对方发送公钥。

Obfs4客户端发出握手数据包以启动连接过程,因此我们来分析一下客户端握手数据包。数据包结构如下:

4.png

在客户端的握手数据包中,第一部分是Keypair.representative,其长度为20h字节。在服务器端,它可以作为还原客户端的公钥。

第二部分是使用随机字节的填充数据,其数据大小范围在4Dh至1FC0h之间,该填充数据会混淆握手数据包的大小,从而使其更难识别。

第三部分在Obfs4源代码中被称为“mark”,它是第一部分Keypair.representative的HMAC值。Obfs4使用SHA-256生成HMAC,长度为20h字节。Obfs4仅将前10h字节保留为HMAC,其余10h字节将被丢弃。

Obfs4使用当前系统时间来计算UNIX Epoch时间的小时值(该计时方式以1970年1月1日 星期四 00:00:00作为起始时间)。在一段时间内,客户端和服务器应该在不同的本地时间用完相同的小时值。然后,会计算数据包中三个部分的HMAC值,加上字符串中的小时值。同样,其结果长度为20h字节,前10h字节作为数据包的第四部分。

Obfs4proxy.exe发送客户端的握手数据包:

5.png

上图是OllyDbg的截图,展示了客户端的握手数据包。我将截图中的内存部分分为四块,每个部分标记了不同的颜色。在这种情况下,填充数据的长度为52h字节。第三部分(“mark”)和第四部分(“HMAC of Entire Data”)可用于在服务器端验证客户端的握手数据包。

即使我们正确使用了上面的所有数据,但如果系统当前的时间不正确,Obfs4网桥的握手过程仍然会失败。下面展示了详细的情况,因为我的测试系统时间没有同步,导致服务器在数据包4中收到客户端握手后,TCP会话在数据包6关闭。

由于系统时间不正确,服务器端身份验证失败:

6.png

建立连接请求后,Obfs4中继将启动一个新线程来处理与客户端的通信,客户端也将在该线程中处理握手数据包。服务器端执行与客户端相同的操作,也就是生成与客户端密钥对相对应的服务器密钥对实例。通过调用curve25519.ScalarBaseMult(),从随机选择的私钥中计算出代表密钥和公钥。

然后,服务器端解析并验证客户端的握手数据包。在确保数据包的所有内容正确无误后,通过调用函数extra25519.RepresentativeToPublicKey()从握手数据包第一部分的代表值中恢复出客户端的公钥。

下一步,对于ECC算法的“标量乘法”(Scalar Multiplication,函数curve25519.ScalarMult())来说至关重要。标量乘法是一种单向函数,不存在对应的标量除法函数,正是因此才使得ECC算法非常强大。

这里使用服务器的私钥和客户端的公钥执行标量乘法。同样,客户端在收到服务器的握手数据包时将执行相同的操作。在该过程结束时,标量乘法的两个结果必须相同,下图展现了该算法的等式。

ECC算法公式:

7.png

上述公式是ECC算法的重要组成部分,可以确保两端值相等。左侧部分是在服务器端进行计算,右侧是在客户端计算的。

这些随机生成的公钥和私钥仅在一个TCP连接会话中有效,因此它们被称为会话公钥/私钥。它们不同于ID公钥/私钥,后者针对一个Obfs4中继来说是唯一的。

在之前的章节中,我们讨论了从网桥配置行中Base64编码的“cert”中提取的IdPublicKey和nodeID。实际上,与之对应的IdPrivateKey始终保存在Obfs4中继中。

安装并启动Obfs4中继后,将会生成一个IdPrivateKey / IdPublicKey。Obfs4将保留IdPrivateKey,然后在Obfs4客户端将要使用的网桥配置行中公开IdPublicKey。这些内容都固定在某个Obfs4中继上。与会话秘钥一样,这两个密钥都是ECC概念的私有/公开密钥。

接下来,客户端和服务器都会调用ScalarMult()两次,以生成两个常见的结果。首先让我们了解一下服务器的工作方式:

服务器使用服务器的会话私钥和客户端的会话公钥调用ScalarMult(),然后再次使用IdPrivateKey和客户端的会话公钥对其进行调用。现在,我们产生了两个函数结果。然后将它们与nodeID和一些常量字符串放在一起,以生成公开的“keySeed”和服务器的“auth”。

接下来,创建服务器的握手数据包。其中包含客户端握手数据包的所有组件,并且在代表数据和一些填充数据之间,以及不同的填充数据大小范围之间包含服务器的auth元素,如下表所示。

服务器的握手数据包结构:8.png

服务器的auth元素用于客户端的身份验证。填充数据的大小范围在0h-1F73h之间,这使得数据包大小在一个较大的范围内呈现随机化。随后,将该数据包发送回客户端(obfs4proxy.exe)进行验证。我们知道,服务器现在具有用于当前TCP连接会话的通用“keySeed”。

当服务器的握手数据包返回到客户端时,将验证数据包的最后两部分,以确保该数据包有效。然后,它从数据包的第一部分(即:server.representative)中提取服务器的会话公钥。

现在,客户端可以两次调用ScalarMult(),这一点与服务器端一致。使用客户端的会话私钥和服务器的会话公钥调用ScalarMult(),然后使用客户端的会话私钥和IdPublicKey再次对其进行调用。

同样,我们会得到两个函数结果。然后,客户端将二者进行组合,并添加与服务器相同的nodeID和一些常量字符串,以生成公共的keySeed和auth数据。然后,客户端将生成的auth数据与服务器握手数据包中的数据进行比较,从而完成验证过程。

这里生成的普通keySeed客户端应该与服务器的客户端完全相同。使用这个通用的keySeed,客户端和服务器都可以继续生成其自身的最终加密/解密密钥,用于Tor-Payload的加密和解密。客户端的加密密钥与服务器的解密密钥相同,客户端的解密密钥与服务器的加密密钥相同。

上述就是Obfs4客户端与网桥之间的完整握手过程。其结果会使数据包的大小随机化,因为具有较大的范围,并且其数据看起来也是随机的,这使得握手数据包变得更加难以识别。

Obfs4封装Tor Payload

客户端和服务器都使用从公开keySeed派生的加密密钥和解密密钥来初始化其编码器、解码器实例。然后,Obfs4使用这两个实例来实现Tor Payload的封装和解封装。编码器实例用于加密,解码器实例用于解密。

目前,obfs4客户端有两个连接,一个用于在Tor(tor.exe)与Obfs4客户端(obfs4proxy.exe)之间交换数据,另一个用于在Obfs4网桥客户端和网桥中继之间进行Tor Payload传输。握手过程完成后,Obfs4客户端将绑定两个连接。下图展现了负责绑定两个连接的函数copyLoop(位于源文件obfs4proxy.go)中的代码,其中参数“a”和“b”是两个连接的实例。

函数copyLoop的源代码:

9.png

函数“io.Copy(destination, source)”负责将数据从源复制到目标。具体过程如下:首先调用source.Read()函数,然后从中提取特定数据,然后再调用destination.Write(),并在参数中提取该数据。

Obfs4客户端将重写Write()和Read()。Write()函数加密Tor的Payload数据包(称为“封装”),并将封装的数据发送到Obfs4中继。因此,Read()函数负责从Obfs4中继接收数据包,然后将其解密(称为“解封装”)。随后,io.Copy将解密的Tor数据包传输到Tor。

重写的Write()和Read()函数:

10.png

我们来详细分析一下这两个函数是如何实现的。如上图所示,它在Write()中调用函数makePacket(),然后在其中调用另一个函数Encoder.Encode()来加密Tor Payload。然后,在函数readPackets()中调用Decoder.Decode(),该函数在Read()中调用。

Encoder.Encode()最终调用secretbox.Seal()进行加密,并生成Tor Payload的MAC。相应地,Decoder.Decode()调用secretbox.Open()解密从Obfs4中继接收的Payload。

加密后的数据不仅仅是Tor Payload,该Payload也将被复制到偏移量+3的数据缓冲区中。因为前3个字节是一种标头结构,其中包含刚刚复制的Tor Payload的包类型和大小。这也就是Encoder.Encode()加密的所有数据。

要加密的Tor Payload:

11.png

下图展示了Obfs4客户端即将调用Encoder.Encode()的示例。其包含的参数之一指向内存中的数据缓冲区,其中第一个字节是数据包类型(00表示是Payload数据包),其后的0x00BF是网络字节顺序的Tor Payload大小。从偏移量+3开始的数据是Tor Payload,来自于tor.exe。

Obfs4还通过将随机大小的填充数据附加到加密数据的方式,来混淆数据包的大小,这也是阻止审查的一种方式。

具有IAT模式的Obfs4拆分数据包

除了随机填充数据之外,Obfs4还支持另一种反检测技术,可以对抗审查,即IAT模式。

在我们之前的分析里,在描述Obfs4网桥信息时提到了IAT模式。IAT模式是“Inter-Arrival Timing”(到达间隔时间)的缩写,可以将较大的数据包(大于1448字节大小的数据包)拆分成MTU大小(最大传输单元)或更小的数据包。MTU定义为可用于传输数据的最大数据包或帧大小。网络驱动程序将大数据包拆分成MTU大小的数据包(在TCP握手过程中协商),可以轻松地对其进行重组,并可能会被检测到。因此,Obfs4引入了IAT模式。

在Obfs4中,IAT模式的值可以分为0、1或2。

其中,0表示禁用IAT模式,网络驱动程序将拆分较大的数据包,这样的特征可能会被检测到。

1表示将大数据包拆分成MTU大小的数据包,而不再让网络驱动程序执行此操作。这里,Obfs4网桥的MTU为1448字节。这意味着较小的数据包无法重组以进行分析和检测。图10展示了计算MTU大小的Obfs4代码片段。

2表示将大数据包拆分为大小可变的数据包。

Obfs4的MTU大小:

12.png

较小的拆分数据包会分别发送到Obfs4中继。这样一来,就可以混淆任意网络指纹,使Obfs4流量更加难以被识别。

下图展示了Obfs4通信的示例,其IAT模式设置为1。红色标出的是一个大数据包,按照IAT模式进行拆分。蓝色表示的数据包是拆分后得到的小数据包。对于其中的第一部分数据,原始数据包大小为2719,被拆分为两个Obfs4 MTU大小的数据包(1448)。

启用了IAT模式的Obfs4流量:

13.png

通过其他方式获取Obfs4网桥

Tor用户可以通过三种方式来获取Obfs4网桥:Tor网络设置、Tor网站以及电子邮件。每次用户可以获得3条Obfs4网桥配置行。下图展示了获取Obfs4网桥配置行的Tor网桥网站截图。

Tor网站,用于获取Obfs4网桥:

14.png

获取Obfs4网桥后,接下来需要复制粘贴这些信息以使其正常工作,如下图所示。

复制粘贴三个Obfs4网桥:

15.png

总结

经过我们对Obfs4网桥的分析,我们了解到它可以有效保护Tor流量不会被检测机制发现和阻止,其原因在于:

1、Obfs4通过添加填充数据(包括在握手数据包中添加填充数据)来加密Tor流量并混淆数据包大小特征。

2、可以通过IAT模式拆分较大的Obfs4数据包,以模糊其网络指纹。

3、除了内置的Obfs4网桥之外,Tor还提供了三种其他方式来获取私有的Obfs4网桥。


文章来源: https://www.4hou.com/technology/22065.html
如有侵权请联系:admin#unsafe.sh