找回密码
 立即注册
搜索

剖析tor.exe洋葱路由Tor网桥与可插拔传输(下篇)

admin 2026-4-17 04:34 5人围观 资讯

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

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

使用Obfs4网桥的Tor流量:

剖析洋葱路由Tor网桥与可插拔传输(下篇)

这里展示了使用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:

剖析洋葱路由Tor网桥与可插拔传输(下篇)

上图是使用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客户端发出握手数据包以启动连接过程,因此我们来分析一下客户端握手数据包。数据包结构如下:

剖析洋葱路由Tor网桥与可插拔传输(下篇)

在客户端的握手数据包中,第一部分是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发送客户端的握手数据包:

剖析洋葱路由Tor网桥与可插拔传输(下篇)

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

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

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

剖析洋葱路由Tor网桥与可插拔传输(下篇)

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

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

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

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

ECC算法公式:

剖析洋葱路由Tor网桥与可插拔传输(下篇)

上述公式是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元素,如下表所示。

服务器的握手数据包结构:剖析洋葱路由Tor网桥与可插拔传输(下篇)

服务器的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的源代码:

剖析洋葱路由Tor网桥与可插拔传输(下篇)

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

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

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

剖析洋葱路由Tor网桥与可插拔传输(下篇)

我们来详细分析一下这两个函数是如何实现的。如上图所示,它在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:

剖析洋葱路由Tor网桥与可插拔传输(下篇)

下图展示了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大小:

剖析洋葱路由Tor网桥与可插拔传输(下篇)

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

下图展示了Obfs4通信的示例,其IAT模式设置为1。红色标出的是一个大数据包,按照IAT模式进行拆分。蓝色表示的数据包是拆分后得到的小数据包。对于其中的第一部分数据,原始数据包大小为2719,被拆分为两个Obfs4 MTU大小的数据包(1448)。
原作者: admin
精彩评论0
我有话说......
相关推荐