学习一种协议的最好的方式就是研究它的数据包,这样可以加深对协议的理解。对于研究过某种协议数据包的家伙来讲,他一定知道协议头的哪个位置对应哪个字段,虽然这对于理解协议为什么这么设计可能没有太大的帮助,然而对于排查问题和实际实施是很有帮助的。既然很多人都对RichardStevens的《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|:操作码+keyid,1字节,xx右移5位就是操作码|xx|xx|xx|xx|xx|xx|xx|xx|:sessionID,这个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|...]:如果ackbufferlength长度为0,没有ack信息,如果不为0,则后面包含确认的包序列号,然后追加一个对端的sessionid,变长|xx|xx|xx|xx|:包序列号,不包括ack包,也就是说,发送一个非P_ACK包,包序列号才会递增,实际上如果是P_ACK包,根本就没有这一行字段。4字节1.2.TLS/SSL握手协议关于TLS/SSL握手协议,最好的资料是RFC,然后或许看OpenSSL的源码上手更快些。这里仅给出几个图示和几点解释,SSL握手协议包格式如下:不管是记录协议还是握手协议,都会有一个recordheader,该record格式如下:其中,如果是SSL握手协议的话,recordheader的type字段为0x16,我们使用的TLS,因此其major和minor分别为0x03和0x01。接下来,我们看一下握手头:其中握手类型分别为:Clienthello:0x01Serverhello:0x02Certificate:0x0bServerkeyexchange:0x0cCertificaterequest:0x0dServerhellodone:0x0eCertificateverify:0x0fClientkeyexchange:0x10Finish:0x141.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连接的安全性在于其建立安全通路的方式和建立后的安全性,其缺点就是消耗大量的资源。再往下一行是一个packetid和一个时间戳,这两个字段一共占据8个字节,它有效的防止了重放攻击。再往后一行,为一个单字节0x00,可见第一个数据包并不想确认任何数据,这也是正常的。最后是一个包序列号,占据4个字节,为0,每个方向的非确认包(P_ACK),都拥有一个序列号,这个序列号是用于保证传输的,并不是用于防止重放攻击的,注意,它和packetid的意义是不同的,确认包并不包含序列号。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,表示服务器使用第二类密钥交换算法,这样就和客户端达成了一致。第二行是一个sessionid,这是另一方向的sessionid。后面的数据意义和第一个包是一样的。最后一行,也是一个4字节的0,这表示在从服务器端到客户端这个方向上,这是第一个数据包。值得注意的是,我们看第5行,第一个字节为1,说明有一个数据包确认,紧接着4个字节为0,说明它确认的是Client到Server方向的第一个数据包,再往后的8个字节是Client到Server方向的sessionid。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,也就是Clienthello,它的长度是后面的3个字节。最终,我们发现,这个OpenVPN协议封装的数据载荷就是SSL握手消息中的Clienthello消息。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|