Chinaunix首页 | 论坛 | 博客
  • 博客访问: 263676
  • 博文数量: 52
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 277
  • 用 户 组: 普通用户
  • 注册时间: 2016-06-09 10:57
文章分类

全部博文(52)

文章存档

2016年(52)

我的朋友

分类: 网络与安全

2016-12-14 13:12:36


学习一种协议的最好的方式就是研究它的数据包,这样可以加深对协议的理解。对于研究过某种协议数据包的家伙来讲,他一定知道协议头的哪个位置对应哪个字段,虽然这对于理解协议为什么这么设计可能没有太大的帮助,然而对于排查问题和实际实施是很有帮助的。既然很多人都对Richard Stevens的《TCP/IP详解》情有独钟,咱就剽窃他的风格,解析一下OpenVPN的握手是如何完成的。本文分析70余个数据包,当然,最终我会略去重复的内容,来看看OpenVPN的握手协商过程。

1.分析前的解释

1.1.数据包格式

本文以如下格式分析每一个握手数据包,具体OpenVPN数据包的格式,请参阅《OpenVPN协议解析-网络结构之外》

1.1.1.本文为了节省篇幅,省去了UDP以下各层的数据,直接从OpenVPN的封装开始。

1.1.2.以下为数据包格式,xx为十六进制字节:

A->B:方向,分别为Server->Client和Client->Server
|xx|:操作码+key id,1字节,xx右移5位就是操作码
|xx|xx|xx|xx|xx|xx|xx|xx|:session ID,这个ID是单方向的,8字节
|xx|xx|xx|xx|xx|xx|xx|xx|1a|de|38|06|ab|5e|55|0f|8f|ed|ea|ca|:数据包消息MAC,本文启用tls-auth选项,本文MAC长度20字节(使用SHA1)
|xx|xx|xx|xx|xx|xx|xx|xx|: 包id和时间戳,包括ack包的id和时间戳,在该方向每法送一个包,包id都会递增。8字节
|xx|[...|xx|...]:如果ack buffer length长度为0,没有ack信息,如果不为0,则后面包含确认的包序列号,然后追加一个对端的session id,变长
|xx|xx|xx|xx|:包序列号,不包括ack包,也就是说,发送一个非P_ACK包,包序列号才会递增,实际上如果是P_ACK包,根本就没有这一行字段。4字节

1.2.TLS/SSL握手协议

关于TLS/SSL握手协议,最好的资料是RFC,然后或许看OpenSSL的源码上手更快些。这里仅给出几个图示和几点解释,SSL握手协议包格式如下:


不管是记录协议还是握手协议,都会有一个record header,该record格式如下:




其中,如果是SSL握手协议的话,record header的type字段为0x16,我们使用的TLS,因此其major和minor分别为0x03和0x01。接下来,我们看一下握手头:



其中握手类型分别为:

Client hello:0x01
Server hello:0x02
Certificate:0x0b
Server key exchange:0x0c
Certificate request:0x0d
Server hello done:0x0e
Certificate verify:0x0f
Client key exchange:0x10
Finish:0x14

1.3.SSL握手协议和OpenVPN握手的关系

我们知道,OpenVPN的连接建立使用了SSL握手协议,可是却抓不到SSL握手包,实际上SSL握手协议是作为一种载荷封装在了OpenVPN的协议包里面了,SSL握手协议数据实际上是写入了一块内存,然后OpenVPN从该内存中读出这些数据包,然后封装后通过reliable层发出,数据到达对端后,解封装后写入一块内存,然后SSL从内存中读出SSL握手载荷,一切都是通过BIO实现的。(如不甚理解,请参阅《OpenVPN协议解析-网络结构之外》)

     可以看出,SSL和OpenVPN属于不同的层次,SSL握手协议在OpenVPN握手协议之上,然而当SSL握手结束后,OpenVPN的密钥协议又在SSL记录协议协议之上,更清晰的图示如下所示:




因此,OpenVPN握手协议可以将SSL握手载荷随意拆分和合并,它们通过BIO建立的内存区域建立唯一的联系。

2.OpenVPN数据包详解

2.1.第1个数据包

Client->Server
|38|
|5f|f2|07|b2|27|12|14|2d|
|5d|67|45|d5|59|1a|c5|b5|1a|de|38|06|ab|5e|55|0f|8f|ed|ea|ca|
|00|00|00|01|4d|da|14|7c|
|00|
|00|00|00|00|
这是OpenVPN连接的第一个数据包,当然也是握手的第一个数据包,在这里首先要提出的是,通过紧接着后面两个数据包,我们可以看出,虽然OpenVPN首推UDP,且本文解析的数据包也是基于UDP的,然而对于OpenVPN握手,是必须要保持连接和保证传输的,因此实现了reliable层,OpenVPN的握手完全基于reliable层,等完成了握手,协商好了密钥(注意,这个密钥不是SSL握手协商出来的,而是在SSL记录协议上协商出来的),reliable层就不需要了,OpenVPN记录协议并不需要reliable层(它只是一个运输协议,如IP协议)。
     既然reliable层是一个有连接和保证传输的协议层,那么最简便的实现方式就是模仿已有的实现,那就是TCP,虽然reliable层是一个高度简化的保证传输的版本,但是三次握手是少不了的,因此第一个数据包和下面两个数据包就是三次握手包。
     除了和TCP一样,三次握手建立连接之外,OpenVPN的握手协议的三次握手还协商了一个信息,那就是密钥交换算法,这个协商通过其操作码可以看出,第一个数据包的操作码是0x38,右移5位就是7,因此其表示第二类密钥协商算法。
     其中第二行表示一个session,这个session识别了一个连接,它是单方向的,每一个方向一个。然后第三行是一个HMAC值,在本文,它是20个字节(特定于MD5和SHA1),这个HMAC只有在开启tls-auth时才存在,其计算的密钥是静态配置的,两端必须配置一致。tls-auth相当于OpenVPN协议在已经很安全的SSL协议外又加固了一层,者有效防止了拒绝服务攻击,毕竟,SSL连接的安全性在于其建立安全通路的方式和建立后的安全性,其缺点就是消耗大量的资源。
     再往下一行是一个packet id和一个时间戳,这两个字段一共占据8个字节,它有效的防止了重放攻击。再往后一行,为一个单字节0x00,可见第一个数据包并不想确认任何数据,这也是正常的。最后是一个包序列号,占据4个字节,为0,每个方向的非确认包(P_ACK),都拥有一个序列号,这个序列号是用于保证传输的,并不是用于防止重放攻击的,注意,它和packet id的意义是不同的,确认包并不包含序列号。

2.2.第2个数据包

Server->Client
|40| 
|7a|26|7c|45|a6|72|e1|a2|
|b0|72|21|0a|c1|3f|f1|4a|bc|79|46|14|30|51|f3|0c|64|63|54|de| 
|00|00|00|01|4d|da|14|7c| 
|01|00|00|00|00|5f|f2|07|b2|27|12|14|2d|
|00|00|00|00|
第二个数据包是服务器发往客户端的,类似于TCP的SYN/ACK,和第一个数据包一样,它的操作码也标识了密钥交换算法,这次是8,表示服务器使用第二类密钥交换算法,这样就和客户端达成了一致。第二行是一个session id,这是另一方向的session id。后面的数据意义和第一个包是一样的。最后一行,也是一个4字节的0,这表示在从服务器端到客户端这个方向上,这是第一个数据包。
     值得注意的是,我们看第5行,第一个字节为1,说明有一个数据包确认,紧接着4个字节为0,说明它确认的是Client到Server方向的第一个数据包,再往后的8个字节是Client到Server方向的session id。

2.3.第3个数据包

Client->Server
|28|
|5f|f2|07|b2|27|12|14|2d|
|40|20|5a|f4|92|2c|a7|4e|8e|f5|1a|e8|45|06|e7|5a|ae|64|64|85|
|00|00|00|02|4d|da|14|7c|
|01|00|00|00|00|7a|26|7c|45|a6|72|e1|a2|
这是一个确认包,从操作码可以看出来,0x28右移5位就是5,这就是P_ACK_V1。说明这是一个确认。由于它是一个纯确认包,它没有最后的包序列号。
     我们直接看第5行,它的意义和第二个数据包是一样的。到此为止,实际上我们已经没有必要在继续分析下去了,如果你已经理解了这前三个数据包的话,实际上对于OpenVPN的握手协议你基本已经理解了。然后我还要继续下去,因为接下来就要开始SSL握手了。可以通过接下来OpenVPN的握手协议对SSL握手协议的封装来解释为何抓取不到OpenVPN的SSL连接握手包。

2.4.第4个数据包

Client->Server
|20|
|5f|f2|07|b2|27|12|14|2d|
|57|b8|8a|a1|3c|0f|84|2b|91|42|2a|92|c6|f4|55|c9|7c|82|48|65|
|00|00|00|03|4d|da|14|7c|
|00|
|00|00|00|01|
|16|03|01|00|5f|01|00|00|5b|03|01|4d|da|14|7c|77|15|6d|32|69|2a|3d|90|fc|8d|3d|a6|68|83|3f|11|e2|63|b7|1b|e5|d1|46|95|3b|99|a6|e5|00|00|34|00|39|00|38|00|35|00|16|00|13|00|0a|00|33|00|32|00|2f|00|66|00|05|00|04|00|63|00|62|00|61|00|15|00|12|00|09|00|65|00|64|00|60|00|14|00|11|00|08|00|06|00|03|01|00|
通过前三个包的分析,我们看出,它们是没有包含数据的,这很类似于TCP的三次握手消息。这第四个数据包是第一个包含数据的OpenVPN握手包。
     如果我们仔细观察一下OpenVPN的握手协议头,发现它里面实际上是没有长度信息的。这是因为我们讨论的是UDP上的OpenVPN,UDP数据报的长度是收发一致的,如果是TCP的话,它是流式的,收发不一致,当然是需要一个长度追加在操作码前面。另外,在深入数据载荷之前,我们看一下第六行,也就是一个4个字节的包序列号,这时是0x01,说明这是Client到Server方向的第二个数据包,我们数一下,也真的是这样。
     好了,我们看一下其数据载荷的前面几个字节,其前5个字节0x16,0x03,0x01,0x00,0x5f,我们发现它是SSL记录头的格式,0x16是其type,实际上就是握手协议,后面的0x03和0x01是major和minor号,后面的两个字节表示SSL握手消息长度,那么它到底是什么SSL握手消息呢?我们根据SSL握手协议头的格式,其第一个字节是0x01,也就是Client hello,它的长度是后面的3个字节。最终,我们发现,这个OpenVPN协议封装的数据载荷就是SSL握手消息中的Client hello消息。

2.5.第5个数据包

Server->Client
|20|
|7a|26|7c|45|a6|72|e1|a2|
|4c|b7|ae|78|e9|67|ca|bb|2b|71|0d|fc|48|51|08|9a|30|6c|dd|54|
|00|00|00|02|4d|da|14|7c|
|01|00|00|00|01|5f|f2|07|b2|27|12|14|2d|
|00|00|00|01|
|16|03|01|00|2a|02|00|00|26|03|01|4d|da|14|7c|5b|f3|9a|fa|b9|d3|b1|75|b7|ba|4f|9d|33|6e|4b|25|cc|f3|dd|6f|d6|73|b0|6e|f0|ac|aa|f4|00|00|39|00|16|03|01|04|8b|0b|00|04|87|00|04|84|00|02|41|30|82|02|3d|30|82|01|a6|02|09|00|fb|0e|c2|7f|49|2a|44|8b|30|0d|06|09|2a|86|48|86|f7|0d|01|01|05|05|00|30|61|31|0b|
通过第四个数据包的分析,我们很简单的分析出这个OpenVPN协议封装的数据载荷是Server hello消息,因为|16|03|01|00|2a|02|中02表示Server hello。
     如果仔细往下看,我们最终又发现了一个SSL记录头序列:|16|03|01|04|8b|,紧接着的是|0b|00|04|87|,这明显又是一个SSL握手消息,0x0b是其消息类型,我们发现它是Certificate,它的长度是0x0487,这可够长的了...
     SSL握手消息之所有可以在OpenVPN协议封装的任意位置,是因为它只是数据载荷的一部分,再次重申,SSL协议被OpenVPN协议封装了,SSL是载荷,OpenVPN提供封装和运输服务,不管怎样拆分,最终总是能保证数据能可靠到达对端,由对端的SSL协议负责进行组合。

2.6.第6个数据包

Server->Client
|20|
|7a|26|7c|45|a6|72|e1|a2|

|16|28|bc|a4|3a|34|01|45|26|2b|ab|59|ea|ee|d3|88|b4|dd|11|ee|
|00|00|00|03|4d|da|14|7c|
|00|
|00|00|00|02|
|30|09|06|03|55|04|06|13|02|61|61|31|0b|30|09|06|03|55|04|08|13|02|61|61|31|0b|30|09|06|03|55|04|07|13|02|61|61|31|0b|30|09|06|03|55|04|0a|13|02|61|61|31|0b|30|09|06|03|55|04|0b|13|02|61|61|31|0b|30|09|06|03|55|04|03|13|02|63|61|31|11|30|0f|06|09|2a|86|48|86|f7|0d|01|09|01|16|02|61|61|30|1e|17|0d|31|
这个数据包比较奇怪,因为我们没有在数据载荷的开始处发现任何SSL协议的识别标识,另外,如果我们仔细分析上一个数据包,也就是第五个数据包中在中间封装的Certificate消息,它的长度是0x0487,可是无论怎么看,第五个数据包整个也没有那么长,那么这么长的数据到底在哪里呢?答案就是在后续的数据包中,这就类似IP分片,非头IP分片的载荷中,是没有包含传输层协议头的,也可以通过TCP分段来理解...OpenVPN的实现中,在reliable层中实现了根据虚拟网卡MTU或者当前网络状况任意拆分数据的功能,这样可达到高效利用网络的目的。这样,即使一个1000字节的SSL握手消息,reliable层也可以将其拆成10个100多字节的UDP包,数据到达对端后,每一个100多字节的UDP包进入其SSL BIO的内存BIO中,由SSL协议的实现负责重组数据。
     我们可以将reliable层当成一个在UDP上伪装的伪TCP,该伪TCP直接和SSL的内存BIO接口。
     在了解了上述原理之后,我们直接略过Server传输的Certificate消息,当然还是会分析一个Client的确认包,因为reliable层是需要确认的,这就是接下来马上就要分析的第九个数据包。

2.7.第9个数据包

Client->Server
|28|
|5f|f2|07|b2|27|12|14|2d|
|60|e1|38|28|46|ef|63|5e|46|e6|31|9c|fd|7f|40|7d|df|bd|fd|df|
|00|00|00|04|4d|da|14|7c|
|01|00|00|00|01|7a|26|7c|45|a6|72|e1|a2|
这是一个纯确认包,我们看到其操作码是5,它确认的是Server发送的Certificate的OpenVPN协议封装的一部分,当然这样的包有好几个,这里仅仅揪出一个来分析。它确认的是Server发送的第两个数据包,也就是第五个数据包。

2.8.第27个数据包

Server->Client
|20|
|7a|26|7c|45|a6|72|e1|a2|
|0a|1d|15|d6|1a|42|e6|57|29|18|71|b9|db|fd|d1|43|9d|6d|ef|8a|
|00|00|00|0e|4d|da|14|7c|
|00|
|00|00|00|0d|
|69|67|1e|99|7a|a1|8b|9d|b1|e7|9a|bb|8b|ca|49|16|03|01|01|8d|0c|00|01|89|00|80|bf|88|f7|46|21|4c|81|5c|20|77|d5|5a|52|1b|d4|e8|94|90|e4|84|ae|74|1a|82|f9|c1|0d|f2|ac|ea|10|2f|25|12|41|d8|d2|41|64|44|a2|a1|42|3f|73|1a|79|0c|e1|d4|64|8e|35|a1|1b|00|90|9f|29|8b|bd|ae|16|84|45|d9|44|f9|44|00|d1|03|fa|3e|
在这个OpenVPN协议封装的中间,我们发现了Server的Server key exchange消息。这个道理请参照2.6节。

2.9.第34个数据包

Server->Client
|20|
|7a|26|7c|45|a6|72|e1|a2|
|8c|6e|d5|13|e0|f8|14|13|c3|c6|d7|e3|b0|7f|7b|10|92|0c|85|b5|
|00|00|00|12|4d|da|14|7c|
|00|
|00|00|00|11|
|b5|e4|de|cb|85|b9|10|0c|1f|d6|7a|21|56|2c|7e|ca|15|16|03|01|00|74|0d|00|00|6c|04|03|04|01|02|00|65|00|63|30|61|31|0b|30|09|06|03|55|04|06|13|02|61|61|31|0b|30|09|06|03|55|04|08|13|02|61|61|31|0b|30|09|06|03|55|04|07|13|02|61|61|31|0b|30|09|06|03|55|04|0a|13|02|61|61|31|0b|30|09|06|03|55|04|0b|13|02|
在这个OpenVPN协议封装的中间,我们发现了Server的Certificate request消息。这个道理请参照2.6节。

2.10.第36个数据包

Server->Client
|20|
|7a|26|7c|45|a6|72|e1|a2|
|a4|af|b5|cd|45|2e|33|ea|ff|6b|0b|58|b2|fc|1b|41|bd|e4|ef|99|
|00|00|00|13|4d|da|14|7c|
|00|
|00|00|00|12|
|61|61|31|0b|30|09|06|03|55|04|03|13|02|63|61|31|11|30|0f|06|09|2a|86|48|86|f7|0d|01|09|01|16|02|61|61|0e|00|00|00|
最终,在数据载荷的最后,我们发现了|0e|00|00|00|序列,这是什么呢?我们知道,Server hello done的握手type是0x0e,其后三个字节为消息长度,为全0,这个消息只是想告诉Client,Server发送的消息结束了,Client可以发送数据了。注意,和往常不一样,Server hello down消息并没有SSL记录头序列,而是直接贴在消息后面的。
     我们只是想分析OpenVPN协议,而不是SSL协议,因此不会过分叙述SSL握手的细节。接下来的OpenVPN协议封装的载荷包含了Client发送给Server的Certificate,Verify,
Client key exchange消息等,我们略过这部分重复且枯燥的叙述。

2.11.第65个数据包

Client->Server
|20|
|5f|f2|07|b2|27|12|14|2d|
|56|ae|02|5f|0c|22|95|78|b7|7e|fb|24|8e|f9|34|56|69|09|87|e7|
|00|00|00|23|4d|da|14|7c|
|00|
|00|00|00|10|
|0f|17|d5|75|85|7b|ba|84|e9|b2|a0|9b|22|cc|ad|cc|cf|aa|38|6d|1c|ef|2b|23|69|8f|06|ac|81|79|50|7c|00|cb|21|0d|26|7d|26|81|14|28|bd|45|36|02|14|03|01|00|01|01|16|03|01|00|30|fc|bf|67|e0|a3|95|5d|ee|e1|59|fb|16|2d|c0|7a|55|82|de|73|34|02|1c|a2|88|15|4a|92|c6|6e|41|87|e4|a1|94|e8|f8|e5|31|e1|e0|ae|2f|7d|
注意,我们发现了|14|03|01|00|01|01|序列,这个序列特殊吗?是的,很特殊,因为这并不属于SSL握手协议,而是一个单独的SSL协议,那就是CHANGE_CIPHER_SPEC协议,这个协议说明,SSL通道上的安全密钥(加密对称密钥,摘要密钥等)已经建立,接下来的消息将会被这些密钥作用,因此接下来的消息我们将无法再看到了,它们是加密的。CHANGE_CIPHER_SPEC协议规定,其数据载荷就是一个0x01,因此其长度也就是1,故而出现了|14|03|01|00|01|01|序列。
     我本来想把这个数据包作为一个终结,结束讨论,因为以后的数据包都是通过SSL握手协商出来的安全通道加密的,也就无法理解其中的内容了,这样分析还有什么意义呢?我们知道,SSL握手的最后一步是Finish消息,这个消息是协议规定的,可是我们再也看不到了。

2.12.第71个数据包

Server->Client
|20|
|7a|26|7c|45|a6|72|e1|a2|
|c8|eb|b1|85|e7|b2|bb|34|f1|96|0e|00|1f|56|df|21|30|34|a3|0e|
|00|00|00|23|4d|da|14|7c|
|01|00|00|00|11|5f|f2|07|b2|27|12|14|2d|
|00|00|00|13|
|14|03|01|00|01|01|16|03|01|00|30|8f|57|7d|42|ae|a3|89|2d|34|5a|a7|d1|f7|08|ef|a1|bd|8b|52|1c|32|56|da|5d|33|0b|f2|95|82|d2|f3|c8|60|69|e6|0b|c2|0d|88|65|cf|fc|af|b4|93|6e|0c|31|
无疑,这个数据包的载荷中也包含了|14|03|01|00|01|01|序列,这是Server发送的CHANGE_CIPHER_SPEC消息,表示接下来就是加密数据了。接着上节的话题,本来该结束了,但是为何还没有结束呢?因为OpenVPN的握手协议之后,紧跟着的就是OpenVPN的密钥协议,这两个协议同在OpenVPN的控制通道中进行,也就是说都用到了reliability层,然而OpenVPN密钥协议却不是用于封装SSL载荷的,而是在SSL记录协议之下,作为SSL记录协议的载荷存在的。

2.13.第72个数据包

Client->Server
|20|
|5f|f2|07|b2|27|12|14|2d|
|95|6f|3c|de|d3|4c|45|71|89|7f|86|5c|c0|58|01|47|7d|72|1a|61|
|00|00|00|25|4d|da|14|7c|
|01|00|00|00|13|7a|26|7c|45|a6|72|e1|a2|
|00|00|00|12|
|17|03|01|00|20|42|32|5b|54|35|72|45|07|f7|41|dd|5d|d8|ba|42|46|d9|bf|a7|44|2d|38|38|f3|11|cf|bc|81|df|e4|33|37|17|03|01|01|20|a6|81|5e|5a|cf|5a|dd|9a|4d|b0|1d|8a|87|91|0c|e6|3f|ad|94|16|cf|f9|6c|9f|fb|08|5f|d1|52|35|32|47|3e|93|76|31|27|db|0b|7d|77|cb|4c|c6|b5|6d|da|e1|3d|67|a1|23|24|c4|4f|81|a2|54|
在这个OpenVPN数据包中,包含了|17|03|01|00|20|序列,这无疑也是SSL记录头,其中0x17是type,这是什么呢?是SSL记录协议。也就是说,从这里开始,OpenVPN使用了SSL记录协议。但是前面不是说OpenVPN使用自己的“尽力而为”协议传输数据的吗?而SSL是基于可靠连接的啊!事实上,这里使用的SSL记录协议并不是实际的数据传输,而是OpenVPN使用的真正的密钥的协商,我们前面提到过,这个协商是作为SSL记录协议的载荷存在的。
     再仔细观察这个数据包,我们发现其中有两个|17|03|01|00|20|序列,在相反的方向上又有两个这样的序列,当然后两个字节是不同的,这实际就是OpenVPN密钥协议的载荷,这里的握手明显使用的是第二类方式(因为最开始的三次握手中的操作码已经决定了)。由于这里的密钥协议数据已经使用SSL记录协议在SSL加密通道中了,因此其协议包我们不得而知(这个实验中,我使用的cipher是none,然而digest却不是none)!

3.总结

通过OpenVPN握手数据包的抓取和分析,我们了解到了OpenVPN协议的更多的细节,它分为两个部分,控制协议和记录协议,两个协议通过OpenVPN协议头复用到了UDP,其中控制协议又分为了握手协议和密钥协议,它们都跑在一个可靠的伪TCP虚拟连接上,它实际上很复杂同时又很简单。
     通过分析数据包的方式学习网络协议是一个不错的方式却不是一个不错的开始,对于OpenVPN,首先我们必须首先明白它的设计,它的框架,它的用途以及必须可以熟练使用它。其中的前提是使用。如果只是认识到OpenVPN是虚拟网卡加SSL协议的结合,那未免太肤浅了,一个革命性的开源VPN肯定不可能如此肤浅的,它拥有自己的协议。
阅读(5422) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~