详解Tor Bridge及Pluggable Transport(Part 2)

 

0x00 前言

在上篇文章中(原文译文),我详细介绍了Tor浏览器的内置Bridge(网桥)信息如何通过3个进程进行传递(firefox.exetor.exeobfs4proxy.exe)、Tor浏览器如何与Obfs4 bridge客户端通信以及这3个进程之间的关系。在本文中,我将继续介绍Tor如何使用Obfs4 Bridge绕过网络传感器。

 

0x01 Tor数据流

在分析Obfs4 Bridge之前,我想先介绍下启用和未启用Obfs4 Bridge时Tor浏览器的不同点。为了让大家有更直观的理解,我画了关于Tor数据流的两张图,如图1及图2所示:

图1. 未启用Obfs4 Bridge时的正常Tor数据流

未启用Obfs4 Bridge时的正常Tor数据流如图1所示,主要过程为:Tor客户端从Tor主目录获取3个Tor中继节点,分别为入口中继、中间中继以及出口中继。当用户访问网站时,Tor客户端就会从Tor浏览器(firefox.exe)收到请求报文,然后使用出口中继、中间中继及入口中继的会话密钥对报文进行3次加密。经过多重加密的报文随后会被发送到入口中继、中间中继以及出口中继。经中继节点解密后,出口中继还原出原始报文,将其发送到目的服务器(如www.google.com)。这大致上就是Tor浏览器将原始报文投递至目标网站的方式。

然而,网络传感器可以轻松识别并阻止经过Tor加密的流量(图1中红色箭头)。

图2. 使用Obfs4 Bridge后的Tor数据流

使用Obfs4 Bridge后的数据流如图2所示。启用Obfs4 Bridge后,Tor流量会先经过Obfs4客户端(obfs4proxy.exe),然后发送给bridge中继节点。Bridge中继节点现在会取代原有的Tor入口中继节点,变成新的入口中继节点。因此,现在任何Tor电路(circuit)包含的中继节点数始终为3,以获得较好的通信性能。

Obfs4 Bridge客户端接收经过Tor加密的payload,然后使用Obfs4函数对其再次封装。经过封装后的报文随后会发送到Obfs4 Bridge中继节点,经过解封装后,Bridge中继节点提取Tor加密报文,将其转发至Tor电路中的下一跳Tor中继。数据包的反向路径与此类似。

以上就是Obfs4 Bridge的简要工作流程。下面我们将探讨Obfs4 Bridge具体使用哪些技术来强化流量,使流量更难识别。

 

0x02 SETCONF命令及Bridge配置行

Tor浏览器(firefox.exe)会通过TCP控制端口9151tor.exe发送SETCONF命令及内置的Obfs4 Bridge信息。命令格式如下所示:

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代表用户已启用Bridge功能。后面的数据包括整个内置的Obfs4 Bridge数据块(Tor网站上称之为bridge配置行)。每个bridge节点都必须保存在一条bridge配置行中,这里为演示方便,我只列出其中一行。每个bridge配置行都以Bridge=作为前缀,下面我们来分析一下具体内容。

配置行中包含如下内容:

  • Bridge类型:obfs4
  • Bridge服务端IP及端口:109.105.109.165:10527
  • Bridge ID:14H长的十六进制值。
  • Bridge证书(“cert”):经过base64编码的Obfs4中继的nodeIDIdPublicKey
  • Bridge IAT-mode:IAT模式标志位,可取01或者2

Tor会处理该命令,然后运行obfs4proxy.exe子进程,该进程为Obfs4 Bridge客户端。obfs4proxy.exe采用了相同的过程,通过环回接口上的TCP端口,在Obfs4 Bridge客户端及Tor客户端之间传输报文。

 

0x03 Tor客户端与Obfs4客户端通信

Tor客户端会将Obfs4 Bridge信息分别发送给obfs4proxy.exe,要求对方与Obfs4 Bridge中继节点建连。Tor使用SOCKS5协议与Obfs4 Bridge客户端传输数据。

图3. tor.exe将某个Obfs4 Bridge信息发送给obfs4proxy.exe

WireShark抓取的tor.exeobfs4proxy.exe之间的通信数据如图3所示。红色部分为tor.exe发往obfs4proxy.exe的数据包(通过环回接口上的随机TCP端口),响应报文为蓝色部分。从上图可知,该过程中还用到了SOCKS5协议。

首先,tor.exe将包含base64编码的cert=及IAT-mode的 Obfs4 Bridge信息发送给obfs4proxy.exe。随后,tor.exe通过SOCKS5 “Connect (1)”报文(以|05 01 00 01|开头)发送Obfs4 Bridge中继的IP地址及端口二进制数据。obfs4proxy.exe随后通过该IP地址及端口连接到相应的Obfs4 Bridge中继。连接成功建立后,obfs4proxy.exe会向tor.exe发送“Succeeded (0)”报文(|05 00 00 01 00 00 00 00 00 00|)。这意味着Obfs4握手成功完成(下文我会介绍该握手过程)。随后tor.exe可以通过该连接,发送并接收由Obfs4 Bridge传输的所有数据包。

 

0x04 Obfs4客户端与Obfs4 Bridge握手过程

客户端与Obfs4 Bridge连接时需要完成握手,目的是传输公钥,互相验证身份。

在分析握手报文之前,我想先花点时间介绍下Obfs4所使用的加密算法及“密钥对”(Keypair)结构。

Obfs4 Bridge使用了ECC(椭圆加密)算法来加密Tor payload,确保Obfs4客户端与中继节点之间的通信安全。众所周知,ECC是基于椭圆曲线理论的一种公钥加密技术。Obfs4所使用的ECC采用Go语言包curve25519来实现,该程序包提供了两个重要函数:ScalarBaseMult()ScalarMult()

Keypair结构的定义如下所示,其主要功能是保存公私钥对:

// 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 
}

客户端及服务端都必须有自己的Keypair实例。根据curve25519包中的定义,PrivateKey在一定范围内随机生成。根据ECC算法,PublicKey可以通过调用curve25519.ScalarBaseMult()从私钥生成。Representative秘钥根据公钥生成,在需要的时候可以调用extra25519.RepresentativeToPublicKey()函数再次转换为公钥。Keypair的定义及初始化代码位于NewKeypair()函数中,该函数在ntor.go文件中定义。

客户端及服务端都应当保存好私钥,将公钥以Representative秘钥的形式发送给对方。Obfs4客户端的连接过程从握手报文开始,因此我们来看一下客户端的握手报文。该报文的整体结构如下所示:

图4. 客户端的握手报文结构

客户端握手报文的第1部分为Keypair.representative,大小为20h字节。该数据在服务端可以用来还原出客户端的公钥。

第2部分为随机字节填充数据,数据大小范围为4Dh1FC0h。填充数据可以用来混淆握手报文大小,使其更难识别。

第3部分为Obfs4源码中的mark区域,是第1部分(Keypair.representative)的HMAC值。Obfs4使用SHA-256来生成HMAC,大小为20h字节。Obfs4只将前10h字节作为HMAC,丢弃剩余的10h字节。

Obfs4使用当前系统时间来计算UNIX Epoch时间(从UTC时间1970年1月1日星期四00:00:00开始)的小时值。在一定时间内,本地时间不同的客户端及服务端能够计算出相同的小时值。随后,Obfs4计算前3部分的HMAC值,再加上字符串形式的小时值。所得结果大小为20h字节,前10h字节作为报文的第4部分。

图5. obfs4proxy.exe发送客户端握手报文

Ollydbg中可以观察到客户端的握手报文,如上图所示。我将相应内存数据切分为4部分,每部分采用不同颜色。在上图中,填充数据长度为52h字节,服务端可以使用第3部分(mark)及最后一部分(整个数据的HMAC值)来验证客户端的握手报文。

即使我们正确使用了以上所有数据,如果用户系统使用的是不正确的系统时间,Obfs4 Bridge仍然无法完成握手过程。这种情况如图6所示,当时我的测试系统时间并没有更新,导致当服务端收到客户端握手报文后(序号4),会关闭TCP会话(序号6)。

图6. 系统时间错误导致服务端认证失败

一旦连接请求成功建立,Obfs4中继开始启动一个新线程,处理与客户端的通信数据,其中就包括客户端的握手报文。服务端的处理过程与客户端相似,会根据客户端的Keypair生成服务端的Keypair实例,调用curve25519.ScalarBaseMult(),根据随机选择的私钥计算出Representative秘钥及公钥。

服务端随后会解析并验证客户端的握手报文。在确保报文一切正确后,服务端会调用extra25519.RepresentativeToPublicKey()函数,从Representative秘钥还原出客户端的公钥,Representative值位于握手报文的第一部分。

下一步对ECC算法的“标量乘法”(即curve25519.ScalarMult()函数)而言非常关键。标量乘法是一种单向函数,没有对应的标量除法,可以确保ECC算法的安全性。

服务端使用私钥以及客户端的公钥执行标量乘法。与此类似,客户端收到服务端的握手报文后也会执行相同操作。计算结束后,两次标量乘法的结果应保持一致,等式如图7所示。

图7. ECC算法等式

该等式是ECC算法的关键点,能确保两端相等。等式左侧计算在服务端完成,右侧计算在客户端完成。

此外,随机生成的公私钥只对一个TCP连接会话有效,因此被称为会话公钥/私钥。这与ID公钥/私钥要区分开来,对某个Obfs4中继而言这两个秘钥保持不变。

前面我们提到过,bridge配置行中经过base64编码的cert中可以提取出IdPublicKeynodeID。这里的IdPublicKey实际上始终保存在Obfs4中继中。当Obfs4中继安装并启动后,会生成IdPrivateKey/IdPublicKey对,然后保存好IdPrivateKey,通过bridge配置行公布IdPublicKey,以供Obfs4客户端使用。这些秘钥对在Obfs4中继上保持不变,也是ECC概念上的秘钥对,与会话秘钥类似。

接下来,客户端及客户端会两次调用ScalarMult()来生成2个值。服务端会使用自己的会话私钥以及客户端的会话公钥来调用ScalarMult(),然后再使用IdPrivateKey及客户端的会话公钥再次调用该函数。现在我们得到了两个函数结果,这两个结果将与nodeID及某些字符串常量拼接在一起,以生成keySeed,该值作为服务端的auth(认证数据)使用。

现在可以开始构造服务端的握手报文,报文中包含客户端握手报文的所有组件,并且服务端的auth元素位于服务端Keypair.representative及填充数据(数据大小可变)之间,如下图所示:

图8. 服务端握手报文结构

服务端的auth元素用于客户端身份认证,填充数据大小范围为0h~1F73h,确保数据包大小有较大随机性。该报文随后会发送回客户端(obsf4proxy.exe)进行验证。需要注意的是,服务端现在已经拥有了当前TCP连接会话的通用keySeed

当服务端的握手报文发送回客户端时,客户端会验证报文最后两部数据,确保报文数据有效。随后客户端从报文第一部分提取出服务端的会话公钥(也就是server.representative)。

现在客户端会两次调用ScalarMult(),过程与服务端类似。首先客户端使用自己的会话私钥及服务端的公钥调用ScalarMult(),然后使用客户端的会话私钥及IdPublicKey再次调用该函数。

得到两次函数结果后,客户端将两个结果拼在一起,加上nodID及其他字符串常量,生成通用的keySeedauth数据。客户端随后将生成的auth数据与服务端握手报文中的数据对比,完成验证过程。

客户端生成的keySeed应该与服务端生成的值相同。通过这个通用的keySeed,客户端及服务端可以生成自己的加密解密秘钥,用来加密及解密Tor payload。客户端的加密秘钥与服务端的解密秘钥一样,而客户端的解密秘钥与服务端的加密秘钥一样。

以上就是Obfs4客户端与bridge的握手过程,数据包大小在一个大的范围内随机,并且数据看上去也呈现随机化,因此网络传感器很难识别这种握手报文。

 

0x05 Obfs4封装Tor Payload

客户端及服务端会使用前面生成的公用keySeed来初始化自己的EncoderDecoderObfs4随后使用这两个实例来封装(“Sealing”)和解封装(“Unsealing”)Tor payload。Encoder实例用于加密,而Decoder实例用于解密。

现在obfs4客户端拥有2个连接:一个用来在Tor(tor.exe)及Obfs4客户端(obfs4proxy.exe)之间交换数据,另一个用来在Obfs4 Bridge客户端及Bridge中继之间传输Tor payload。当完成握手过程后,Obfs4客户端会将两个连接绑定在一起。绑定两个连接的copyLoop函数(位于obfs4proxy.go源文件中)代码如下图所示,其中ab参数为两个连接对应的实例。

图9. copyLoop函数源代码

io.Copy(destination, source)函数用来将数据从源拷贝到目的地,具体操作过程为:函数首先调用source.Read(),提取特定数据,然后调用destination.Write(),将数据以参数形式传入。

Obfs4客户端重写了Write()以及Read()函数。Write()函数用来加密Tor payload报文(即封装),并将封装后的数据发送到Obfs4中继。Read()函数负责从Obfs4中继接收报文,然后解密这些报文(即解封装)。io.Copy随后将解密的Tor报文发送给Tor。

图10. 重写Write()Read()函数

来详细分析这两个函数的内部原理。如上图所示,Write()会调用makePacket(),该函数会调用另一个函数Encoder.Encode()来加密Tor payload。而Read()会调用readPackets()函数,后者会调用Decoder.Decode()

Encoder.Encode()最终会调用secretbox.Seal()来加密Tor payload,生成对应的MAC信息。与之对应的是,Decoder.Decode()会调用secretbox.Open()来解密从Obfs4收到的payload。

经过加密的数据不单单包括Tor payload,payload会被拷贝到某个数据缓冲区的offset + 3地址,而前3个字节为某种头部结构,包含报文类型及Tor payload的大小。这些数据为Encoder.Encode()加密的所有数据。

图11. 待加密的Tor payload

如上图所示,其中Obfs4客户端正准备调用Encoder.Encode(),其中一个参数指向内存中的数据缓冲区,第1个字节为数据包类型(00表示这是一个payload数据包),后面紧跟着的0x00BF为Tor payload的大小(网络字节序)。从offset + 3开始的数据为来自于tor.exe的Tor payload。

此外,Obfs4还会在经过加密的数据后附加一些大小随机的填充数据,进一步混淆报文大小,这也是对抗网络传感器的一种方式。

 

0x06 Obfs4使用IAT-Mode拆分报文

除了随机填充数据之外,Obfs4还支持另一种反检测技术,以对抗网络传感器:IAT-mode。在前文中,我们在描述Obfs4 Bridge信息时提到了“IAT-mode”。IAT-mode的全称为“Inter-Arrival Timing”,可以将较大的报文(大小超过1448字节的报文)按MTU(Maximum Transmission Unit,最大传输单元)大小拆分,或者拆分成更小报文。

MTU的定义为可用于传输数据的最大报文或者帧大小。网络驱动器会将大报文拆分成MTU大小的报文(在TCP握手过程中协商),以便再次重组(当然网络传感器也能轻松识别)。这也是为什么Obfs4会引入IAT-mode的原因所在。

obfs4中,IAT-mode可取01或者2这3个值。

0表示IAT-mode处于禁用状态,大报文由网络驱动器拆分,因此网络传感器能探测到对应的网络指纹。

1表示Tor自己会将大报文拆分成MTU大小的报文,不需要网络驱动器介入。Obfs4 Bridge使用的MTU值为1448字节,这意味着更小的报文无法被网络传感器重组并分析。Obfs4用来计算MTU大小的代码片段如下图所示。

2表示将大报文拆分成大小可变的报文,具体大小在Obfs4中定义。

图12. Obfs4使用的MTU

拆出的小数据包会分别发送给Obfs4中继。通过这种方式,Tor可以混淆网络指纹特征,使Obfs4流量更难以识别。

典型的Obfs4流量如下图所示,其中IAT-mode设置为1。每个红色方框代表使用IAT-mode拆分的大报文,蓝色方框为被拆分出的小报文。第一个原始数据包的大小为2719,根据Obfs4的MTU值(1448),该数据包会被拆分成2个更小的数据包。

图13. 启用IAT-mode的Obfs4流量

 

0x07 通过其他方式获取Obfs4 Bridge信息

Tor用户可以通过3种方式获取Obfs4 Bridge信息:Tor网络设置、Tor网站以及邮件,每种方式都会获取到3个Obfs4 bridge配置行。用来获取Obfs4 bridge配置行的Tor网站截图如下所示:

图14. 用来获取Obfs4 bridge信息的Tor网站

一旦获取到Obfs4 Bridge信息,接下来我们需要拷贝并粘贴这些信息,如下图所示:

图15. 拷贝并粘贴3个Obfs4 bridge信息

 

0x08 总结

Obfs4 Bridge进行分析后,我们发现Obfs4Bridge可以用来保护Tor流量,避免被网络传感器识别和阻拦,主要原因包括如下3点:

1、Obfs4会加密Tor流量,并添加填充数据来混淆数据包大小(握手报文同样采用该操作);

2、IAT-mode会将较大的Obfs4报文拆分,以混淆网络指纹;

3、除了内置的Obfs4 Bridge之外,Tor还提供了3种方式,用来获取其他Obfs4 Bridge。

(完)