[2022] 人肉GRPC协议实现(一) tls协议实现 - client hello huoji TLS,http2 2022-11-25 617 次浏览 0 次点赞 ### 0x0 简介 年尾了,发点存货吧.人肉实现GRPC.主要需求是windows内核实现GRPC的交互 GRPC是由三个部分组成的,继承了谷歌丧心病狂的设计.要实现GRPC,就要依次实现 > TLS 1.2 HTTP2 Protobuf payload (内核WSK我就不说了,烂大街了) 今天来介绍第一部分,TLS1.2握手 https://www.ietf.org/rfc/rfc5246.txt ![](https://key08.com/usr/uploads/2022/11/708767689.png) !!注意!!此图不完整,虽然在网上疯传,但是真的不完整.后面会说 这部分感谢@heromantf 的聪明的大脑帮助,没有它圆锥曲线计算部分就很痛苦. ### 0x1 Client Hello 这个跟客户端说 hello 一样.数据结构如下,依次告诉客户端如下信息: ```cpp 协议版本 客户端随机数据(稍后在握手中使用) 要恢复的可选会话 ID 密码套件列表 压缩方法列表 扩展列表 ``` buff如下: ```cpp 16 03 01 00 a5 01 00 00 a1 03 03 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 00 00 20 cc a8 cc a9 c0 2f c0 30 c0 2b c0 2c c0 13 c0 09 c0 14 c0 0a 00 9c 00 9d 00 2f 00 35 c0 12 00 0a 01 00 00 58 00 00 00 18 00 16 00 00 13 65 78 61 6d 70 6c 65 2e 75 6c 66 68 65 69 6d 2e 6e 65 74 00 05 00 05 01 00 00 00 00 00 0a 00 0a 00 08 00 1d 00 17 00 18 00 19 00 0b 00 02 01 00 00 0d 00 12 00 10 04 01 04 03 05 01 05 03 06 01 06 03 02 01 02 03 ff 01 00 01 00 00 12 00 00 ``` 让我们来拆分他: ##### 16 03 01 00 a5 TLS 会话分为"记录"的发送和接收,"记录"是具有类型、协议版本和长度的数据块 ```cpp 16 - 类型是 0x16 (handshake record) 03 01 协议版本为 3.1(也称为 TLS 1.0) 00 a5 - 0xA5 (165) 握手消息的字节长度 ``` 注意协议版本为什么是3.1 在golang的crypto库中的原因如下: ```cpp if vers == 0 { // Some TLS servers fail if the record version is // greater than TLS 1.0 for the initial ClientHello. vers = VersionTLS10 } ``` ##### 01 00 00 a1 这个是握手头,每个头都有 每个握手消息都以类型和长度开头 ```cpp 01 本消息类型是 0x01 (client hello) 00 00 a1 - 0xA1 (161) 长度 ``` ##### 03 03 客户端版本 这里给出了"3,3"(意思是 TLS 1.2)的协议版本。 不寻常的版本号("3,3"代表 TLS 1.2)是由于 TLS 1.0 是 SSL 3.0 协议的次要修订版。因此 TLS 1.0 表示为"3,1",TLS 1.1 表示为"3,2",依此类推 ##### 00 01 02 ... 1e 1f 客户端提供 32 个字节的随机数据 TLS 1.2 规范说前 4 个字节应该是当前时间(以秒为单位,自 1970 年以来),**但现在建议不要这样做,因为它可以对主机和服务器进行指纹识别** https://datatracker.ietf.org/doc/html/draft-mathewson-no-gmtunixtime-00 ##### 00 会话ID 客户端可以提供它能够恢复的针对该服务器的先前 TLS 会话的 ID。为此,服务器和客户端都将记住内存中先前连接的关键信息。恢复连接可以节省大量计算和网络往返时间,因此只要有可能就会执行 ##### 00 20 cc ... 12 00 0a 加密套件(重要) 客户端提供了一个有序列表,其中列出了它将支持哪些加密方法来进行密钥交换、使用该交换密钥进行加密以及消息身份验证。该列表按照客户偏好的顺序排列,优先级最高的优先 ```cpp 00 20 - 0x20 (32) bytes of cipher suite data cc a8 - assigned value for TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 cc a9 - assigned value for TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 c0 2f - assigned value for TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 c0 30 - assigned value for TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 c0 2b - assigned value for TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 c0 2c - assigned value for TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 c0 13 - assigned value for TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA c0 09 - assigned value for TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA c0 14 - assigned value for TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA c0 0a - assigned value for TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 00 9c - assigned value for TLS_RSA_WITH_AES_128_GCM_SHA256 00 9d - assigned value for TLS_RSA_WITH_AES_256_GCM_SHA384 00 2f - assigned value for TLS_RSA_WITH_AES_128_CBC_SHA 00 35 - assigned value for TLS_RSA_WITH_AES_256_CBC_SHA c0 12 - assigned value for TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA 00 0a - assigned value for TLS_RSA_WITH_3DES_EDE_CBC_SHA ``` ##### 01 00 压缩方法 客户端提供它将支持的压缩方法的有序列表。这种压缩将在加密之前应用(因为加密数据通常是不可压缩的)。 **01 00代表不压缩.因为最新标准确认启用压缩会让加密变得不那么可靠** https://en.wikipedia.org/wiki/CRIME ##### 00 58 扩展长度 客户端提供了一个可选扩展列表,服务器可以使用这些扩展来采取行动或启用新功能。 00 58代表这个区域最长0x58字节 这个区域也是放DNS信息的地方,所谓通过TLS知道DOMAIN也是从这个地方解码的 **当然这个地方信息对于中间人来说不可靠 因为可以给任意的域名!** ##### 00 00 00 .... 6e 65 74 扩展信息 - 服务端名字 客户端提供了它正在联系的服务器的名称,也称为 SNI -服务器名称指示 如果没有此扩展,HTTPS 服务器将无法为单个 IP 地址(虚拟主机)上的多个主机名提供服务,因为在协商 TLS 会话并发出 HTTP 请求之前,它无法知道要发送哪个主机名的证书 ```cpp 00 00 - 扩展“服务器名称”的值 00 18 - 底下有24字节长度 00 16 - 底下有22字节长度 00 - type 0x0代表是 "DNS主机名" 00 13 - 0x13个字节的长度 65 78 61 ... 6e 65 74 - "example.ulfheim.net" ``` ##### 00 05 ... 00 **客户端允许服务器在其响应中提供 OCSP 信息。 OCSP 可用于检查证书是否已被吊销。 客户端必须发送空扩展名,因为服务器使用客户端未先提供的扩展名进行回复会导致致命错误.因此,客户端发送一个空形式的扩展,服务器用填充了数据的扩展进行回复** ##### 00 0a 00 ... 18 00 19 客户表示它支持 4 条曲线的椭圆曲线 (EC) 加密。此扩展最初名为“椭圆曲线”,但已重命名为“受支持的组”以通用于其他密码类型。 ```cpp 00 0a - 扩展类型: 支持的组 00 0a - 10字节使用 00 08 - 8字节使用 00 1d - "x25519" 00 17 - "secp256r1" 00 18 - "secp384r1" 00 19 - "secp521r1" ``` ##### 00 0b 00 02 01 00 EC Point Formats 在椭圆曲线 (EC) 加密期间,客户端和服务器将以压缩或未压缩的形式交换有关所选点的信息。该扩展表明客户端只能解析来自服务器的未压缩信息 ##### 00 .... 01 02 03 Signature Algorithms 此扩展指示客户端能够理解哪些签名算法,并可能影响服务器发送给客户端的证书选择 ```cpp 00 0d - 扩展类型: Signature Algorithms 00 12 - 18字节以下的内容 00 10 - 16字节以下的内容 04 01 - RSA/PKCS1/SHA256 04 03 - ECDSA/SECP256r1/SHA256 05 01 - RSA/PKCS1/SHA384 05 03 - ECDSA/SECP384r1/SHA384 06 01 - RSA/PKCS1/SHA512 06 03 - ECDSA/SECP521r1/SHA512 02 01 - RSA/PKCS1/SHA1 02 03 - ECDSA/SHA1 ``` ##### ff 01 00 01 00 重新协商信息,这个扩展存在漏洞,已经不用 https://kryptera.se/Renegotiating%20TLS.pdf ##### 00 12 00 00 SCT 用于时间戳校验.保持不变即可 ### 0x2 包构造 这边为了演示临时写的python脚本,用的是AES加密方法(因为python不想搞太复杂了),但是GRPC是要求最低加密方法为圆锥曲线的,ECDH,所以你自己想办法,**代码是不可能给你抄的.** 由于只支持AES,所以必须让服务端认为我们只有TLS_RSA_WITH_AES_128_CBC_SHA1这个协议.所有包结构的长度都要变 ```cpp client_hello_data_start = [0x16, 0x03, 0x01, 0x00, 0xCC, 0x01, 0x00, 0x00, 0xCC, 0x03, 0x03] client_hello_data_rand_num = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f] client_hello_data_session_id = [0x00] # 默认全部支持 #client_hello_data_cipher_length = [0x00, 0x20] # client_hello_data_cipher_support_list = [0xcc, 0xa8, 0xcc, 0xa9, 0xc0, 0x2f, 0xc0, 0x30, 0xc0, 0x2b, 0xc0, 0x2c, 0xc0, # 0x13, 0xc0, 0x09, 0xc0, 0x14, 0xc0, 0x0a, 0x00, 0x9c, 0x00, 0x9d, 0x00, 0x2f, 0x00, 0x35, 0xc0, 0x12, 0x00, 0x0a] # 只支持TLS_RSA_WITH_AES_128_CBC_SHA1 client_hello_data_cipher_length = [0x00, 0x02] client_hello_data_cipher_support_list = [0x00, 0x2f] client_hello_data_start_1 = [0x01, 0x00, 0x00, 0xCC, 0x00, 0x00, 0x00, 0xCC, 0x00, 0xCC, 0x00, 0x00, 0xCC] client_hello_data_end = [0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19, 0x00, 0x0b, 0x00, 0x02, 0x01, 0x00, 0x00, 0x0d, 0x00, 0x12, 0x00, 0x10, 0x04, 0x01, 0x04, 0x03, 0x05, 0x01, 0x05, 0x03, 0x06, 0x01, 0x06, 0x03, 0x02, 0x01, 0x02, 0x03, 0xff, 0x01, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00] ``` 注意: 所有包结构都要动态变化,因为支持的加密套件变了. **此外根据协议可以知道,扩展中的domain name最长是size(short).我这边由于是百度的www.baidu.com所以只占了一个字节** ```cpp head_length_pos = 4 body_length_pos = 8 # 全密码长度是81 但是我们要自定义密码.所有要减掉一部分长度,基础长度是47 extern_length_pos = 47 + len(client_hello_data_cipher_length) + \ len(client_hello_data_cipher_support_list) # 全密码85 extern_1_length_pos = extern_length_pos + 4 # 全密码87 extern_2_length_pos = extern_1_length_pos + 2 # 全密码90 extern_3_length_pos = extern_2_length_pos + 3 domain_host_name = "www.baidu.com" domain_host_name_binary = [] for iter in range(len(domain_host_name)): domain_host_name_binary.append(ord(domain_host_name[iter])) domain_host_name_length = len(domain_host_name_binary) full_client_hello = [] full_client_hello.extend(client_hello_data_start) # client_hello_data_rand_num要自己生成 full_client_hello.extend(client_hello_data_rand_num) full_client_hello.extend(client_hello_data_session_id) full_client_hello.extend(client_hello_data_cipher_length) full_client_hello.extend(client_hello_data_cipher_support_list) full_client_hello.extend(client_hello_data_start_1) full_client_hello.extend(domain_host_name_binary) full_client_hello.extend(client_hello_data_end) full_client_hello[extern_3_length_pos] = domain_host_name_length full_client_hello[extern_2_length_pos] = domain_host_name_length + 3 full_client_hello[extern_1_length_pos] = domain_host_name_length + 5 full_client_hello[extern_length_pos] = len( client_hello_data_end) + domain_host_name_length + 9 # 8是头大小,如果太大则需要占两位.记得做长度校验! full_client_hello[body_length_pos] = len(full_client_hello) - 9 # 4是头大小,如果太大则需要占两位.记得做长度校验! full_client_hello[head_length_pos] = len(full_client_hello) - 5 print("client hello:") for iter in range(len(full_client_hello)): print("%02x" % full_client_hello[iter], end=" ") # client hello all_hand_shake_message += bytes(full_client_hello[record_layer_head_size:]) socket_client.send(bytes(full_client_hello)) ``` 让我们看看: ![](https://key08.com/usr/uploads/2022/11/3296187550.png) 这样我们第一个client hello world就完成了 本文由 huoji 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。 点赞 0
还不快抢沙发