基于python3的tkinter和scapy可视化报文构造工具(六)

本篇将介绍如何构造https的报文。先简单了解一下什么是https。

HTTPS(全称:HyperText Transfer Protocol over Secure Socket Layer),其实 HTTPS 并不是一个新鲜协议,Google 很早就开始启用了,初衷是为了保证数据安全。 目前Google、Baidu、Facebook 等这样的互联网巨头基本都启用了全站 HTTPS,这也是未来互联网发展的趋势。HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):可以理解为HTTP+SSL/TLS, 即 HTTP 下加入 SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL,用于安全的 HTTP 数据传输。

HTTPS既然有加密的过程,那么和HTTP简单的请求响应要复杂的多,中间交互协商的过程也是有必要了解一下。

SSL建立连接过程

基于python3的tkinter和scapy可视化报文构造工具(六)_第1张图片

  1. client向server发送请求https://baidu.com,然后连接到server的443端口,发送的信息主要是随机值1和客户端支持的加密算法。
  2. server接收到信息之后给予client响应握手信息,包括随机值2和匹配好的协商加密算法,这个加密算法一定是client发送给server加密算法的子集。
  3. 随即server给client发送第二个响应报文是数字证书。服务端必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面,这套证书其实就是一对公钥和私钥。传送证书,这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间、服务端的公钥,第三方证书认证机构(CA)的签名,服务端的域名信息等内容。
  4. 客户端解析证书,这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值(预主秘钥)。
  5. 客户端认证证书通过之后,接下来是通过随机值1、随机值2和预主秘钥组装会话秘钥。然后通过证书的公钥加密会话秘钥。
  6. 传送加密信息,这部分传送的是用证书加密后的会话秘钥,目的就是让服务端使用秘钥解密得到随机值1、随机值2和预主秘钥。
  7. 服务端解密得到随机值1、随机值2和预主秘钥,然后组装会话秘钥,跟客户端会话秘钥相同。
  8. 客户端通过会话秘钥加密一条消息发送给服务端,主要验证服务端是否正常接受客户端加密的消息。
  9. 同样服务端也会通过会话秘钥加密一条消息回传给客户端,如果客户端能够正常接受的话表明SSL层连接建立完成了。

构造HTTPS同样包括三个部分,TCP三次握手,ssl的client hello和server hello,TCP四次挥手。由于在上一篇HTTP报文构造中已经详细详解过如何构造TCP和三次握手和四次挥手。故本篇将不再接受该部分,只介绍如何构造ssl的client hello和server hello。由于构造https报文我只关注IP和SNI字段,故报文中的其他默认字段采取写死的方式,有需要的同学也可以根据自己的需求添加接口。

组织client hello报文的ssl层数据

ciphers = [b'\xc0\x2b', b'\xc0\x2f', b'\xc0\x2c', b'\xc0\x30', b'\xc0\x13', b'\xc0\x14', b'\x00\x9c', b'\x00\x9d', b'\x00\x2f', b'\x00\x35', b'\x00\x0a', b'\xc0\x28', b'\xc0\x24', b'\xc0\x0a', b'\x00\xa5', b'\x00\xa3',b'\x00\xa1', b'\x00\x9f', b'\x00\x6b', b'\x00\x6a', b'\x00\x60', b'\x00\x68', b'\x00\x39', b'\x00\x38',b'\x00\x37', b'\x00\x36', b'\xc0\x32', b'\xc0\x2e', b'\xc0\x21', b'\xc0\x26', b'\xc0\x0f', b'\xc0\x05', b'\x00\x3d', b'\xc0\x27', b'\xc0\x23', b'\xc0\x09', b'\x00\xa4', b'\x00\xa2', b'\x00\xa0', b'\x00\x9e',b'\x00\x67', b'\x00\x40', b'\x00\x3f', b'\x00\x3e', b'\x00\x33', b'\x00\x32', b'\x00\x31', b'\x00\x30', b'\xc0\x31', b'\xc0\x2d', b'\xc0\x29', b'\xc0\x25', b'\xc0\x0e', b'\xc0\x04', b'\x00\x3c', b'\x00\x9a', b'\x00\x99', b'\x00\x98', b'\x00\x97', b'\x00\x96', b'\x00\xff']

ciphers_bytes = b''
for value in ciphers:
    ciphers_bytes += value


client_hello=collections.OrderedDict()
client_hello['type']=b'\x16'
client_hello['version']=b'\x03\x01'
client_hello['len']=b'\x00\x00' #dai
client_hello['msgtype']=b'\x01'  
client_hello['msglen']=b'\x00\x00\x00'
client_hello['msgversion']=b'\x03\x03'
client_hello['gmt_unix_time']=b'\x20\x17\xf5\x42'
client_hello['random_bytes']=b'\xfc\x89\x92\x42\x50\x2a\xd4\x64\x8f\xe4\x60\xe5\xd5\xc1\x77\x69\xbb\x33\xce\x8b\x29\x53\xc7\x62\x7f\x08\x73\xb5'
client_hello['sidlen']=b'\x00'
client_hello['cipherslen']=struct.pack('>h',len(ciphers_bytes))
client_hello['ciphers']=ciphers_bytes
client_hello['complen']=b'\x01'
client_hello['comp']=b'\x00'
client_hello['ext_len']=None 
client_hello['ext_type_server_name']=b'\x00\x00'
client_hello['ext_server_name_length']=None
client_hello['server_name_list_len']=None
client_hello['server_name_type']=b'\x00'
client_hello['server_name_len']=None
client_hello['server_name']=None
client_hello['heartbeat']=b'\x00\x0f\x00\x01\x01'


def generate_tls_hello(domain_name):

    domain_length = len(domain_name)
    client_hello['server_name']=domain_name.encode(encoding='utf-8', errors = 'strict')
    client_hello['server_name_len']=struct.pack('>h',domain_length)
    client_hello['server_name_list_len']=struct.pack('>h',domain_length+3)
    client_hello['ext_server_name_length']=struct.pack('>h',domain_length+5)
    client_hello['ext_len']=struct.pack('>h',domain_length+9+len(client_hello['heartbeat']))
    length = 0 
    for key in client_hello:
        length += len(client_hello[key])
    client_hello['len'] = struct.pack('>h',length-5)
    client_hello['msglen'] =b'\x00' + struct.pack('>h',length-9)
    # print(client_hello.keys())
    packet = b''
    for key in  client_hello:
        # print(key)
        # print(client_hello[key])
        packet += client_hello[key]
    return(packet)

由于不关心server hello中的字段故server hello的数据直接用十六进制写死

response = b'\x16\x03\x03\x00\x41\x02\x00\x00\x3d\x03\x03\x0c\x12\x37\x9d\x43\xb6\x1f\x17\xbd\x25\x43\x46\x90\x30\xb6\xa2\xdb\x8c\x99\x34\x79\x2a\xa6\xb4\x44\x4f\x57\x4e\x47\x52\x44\x01\x00\xc0\x2f\x00\x00\x15\xff\x01\x00\x01\x00\x00\x00\x00\x00\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x23\x00\x00\x16\x03\x03\x10\x08\x0b\x00\x10\x04\x00\x10\x01\x00\x06\xa3\x30\x82\x06\x9f\x30\x82\x05\x87\xa0\x03\x02\x01\x02\x02\x08\x68\x86\x4a\x83\x77\x1a\xbb\x7d\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x81\xb4\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31\x10\x30\x0e\x06\x03\x55\x04\x08\x13\x07\x41\x72\x69\x7a\x6f\x6e\x61\x31\x13\x30\x11\x06\x03\x55\x04\x07\x13\x0a\x53\x63\x6f\x74\x74\x73\x64\x61\x6c\x65\x31\x1a\x30\x18\x06\x03\x55\x04\x0a\x13\x11\x47\x6f\x44\x64\x64\x64\x64\x64\x64\x64\x64\x2c\x20\x49\x6e\x63\x2e\x31\x2d\x30\x2b\x06\x03\x55\x04\x0b\x13\x24\x68\x74\x74\x70\x3a\x2f\x2f\x63\x65\x72\x74\x73\x2e\x67\x6f\x64\x64\x64\x64\x64\x64\x64\x64\x64\x2f\x72\x65\x70\x6f\x73\x69\x74\x6f\x72\x79\x2f\x31\x33\x30\x31\x06\x03\x55\x04\x03\x13\x2a\x47\x6f\x20\x44\x61\x64\x64\x79\x20\x53\x65\x63\x75\x72\x65\x20\x43\x65\x72\x74\x69\x66\x69\x63\x61\x74\x65\x20\x41\x75\x74\x68\x6f\x72\x69\x74\x79\x20\x2d\x20\x47\x32\x30\x1e\x17\x0d\x31\x38\x30\x34\x31\x30\x30\x34\x34\x32\x30\x30\x5a\x17\x0d\x32\x30\x30\x35\x31\x31\x31\x38\x32\x32\x34\x36\x5a\x30\x36\x31\x21\x30\x1f\x06\x03\x55\x04\x0b\x13\x18\x44\x6f\x6d\x61\x69\x6e\x20\x43\x6f\x6e\x74\x72\x6f\x6c\x20\x56\x61\x6c\x69\x64\x61\x74\x65\x64\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x0c\x08\x2a\x2e\x20\x20\x20\x20\x6f\x6d\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xd9\xbd\x63\x5a\x05\x92\x69\xc6\x0e\xd7\x86\x02\x43\xd9\x53\xb8\xbb\x0c\x00\x7e\x95\x28\x9c\x8d\x13\x40\xa9\x77\xb6\xcd\xac\x48\xae\x34\x6e\x16\x9b\x99\xf3\x4b\x1d\x56\x52\x69\x14\x77\x48\x35\x6d\x77\x5c\x61\x1b\x12\x4e\x23\x45\x00\x13\xdf\x36\x39\xd5\x27\x46\x77\x5e\x0c\x0b\x53\x79\x16\x9b\x45\xbc\xe5\x43\x0c\xc8\xc6\x77\xa2\x31\x8b\xaa\x74\xba\xa8\x6b\x4c\xf5\x14\x3e\xeb\x00\x3e\x1c\x3b\x4d\x50\x5e\xcb\x3c\x92\xfd\xb2\xab\xcb\x20'

接下来就是用scapy模块结合准备好的数据来构造报文

def ceate_https_packet(path,domain,srcip,dstip):
    
    pktdump = PcapWriter(path, append=True, sync=True)
    def get_random_port():
        return random.randint(10000,65535)

    try:
        dport = 443
        userport = get_random_port()
        #三次握手
        ethersrc = "e4:11:5b:2c:52:a3"
        etherdst = "00:00:5c:53:a8"
        srcSyn = Ether(src=ethersrc, dst=etherdst) / IP(src=srcIP, dst=desIP) / TCP(sport=userport, dport=dport, seq=1000, ack=0, flags='S')
    
        ansSyn = Ether(src=etherdst, dst=ethersrc) / IP(src=desIP, dst=srcIP) / TCP(sport=dport, dport=userport, \
                                                                                    seq=2000, ack=1001, flags='SA')
        # ansseq = ansSyn['TCP'].seq + 1
        sack = Ether(src=ethersrc, dst=etherdst) / IP(src=srcIP, dst=desIP) / TCP(sport=userport, dport=dport, seq=1001,
                                                                                  ack=2001, flags='A')
        list = [srcSyn, ansSyn, sack]
        for pcap in list:
            pktdump.write(pcap)
            time.sleep(0.1)
        
        raw_layer = generate_tls_hello(domain)
        length = len(raw_layer)
        
        ether_up = Ether(src=ethersrc, dst=etherdst)
        ether_down = Ether(dst=ethersrc, src=etherdst)

        tcp_up = TCP(sport=userport,dport=dport,seq=1001,ack=2001,flags=24)
        
        ip_up = IP(dst=dstip, src=srcip) 
        ip_down = IP(src=dstip, dst=srcip)
        
        packetrequest=ether_up/ip_up/tcp_up/raw_layer

        tcp_down = TCP(dport=userport,sport=dport,seq=2001,ack=1001+len(packetrequest)-54,flags=24)
        packetreponse=ether_down/ip_down/tcp_down/response
        
        list2 = [packetrequest, packetreponse]
        for pcap in list2:
            pktdump.write(pcap)
            time.sleep(0.1)

        # 四次挥手
    
        srcfin = Ether(src=etherdst, dst=ethersrc) / IP(src=srcIP, dst=desIP) / TCP(sport=userport, dport=dport,
                                                                                    ack=2001+len(packetreponse)-54,
                                                                                    seq=1001+len(packetrequest)-54, flags=17)
        srcfinack = Ether(src=ethersrc, dst=etherdst) / IP(src=desIP, dst=srcIP) / TCP(sport=dport, dport=userport, seq=2001+len(packetreponse)-54,
                                                                                     ack=1001+len(packetrequest)-53,
                                                                                     flags='A')
        desfinack = Ether(src=ethersrc, dst=etherdst) / IP(src=desIP, dst=srcIP) / TCP(sport=dport, dport=userport,
                                                                                       flags=17, ack=1001+len(packetrequest)-53,
                                                                                       seq=2001+len(packetreponse)-54)
        srcack = Ether(src=etherdst, dst=ethersrc) / IP(src=srcIP, dst=desIP) / TCP(sport=userport, dport=dport,
                                                                                    ack=2001+len(packetreponse)-53,
                                                                                    seq=1001+len(packetrequest)-53, flags=16)
        list3 = [srcfin, srcfinack,desfinack, srcack]
        for pcap in list3:
            pktdump.write(pcap)
            time.sleep(0.1)
    except Exception as identifier:
        print(identifier)
        traceback.print_exc()
    finally:
        pktdump.close()

来看看构造出来client hello中我们关注的SNI字段

基于python3的tkinter和scapy可视化报文构造工具(六)_第2张图片

至此一个完整的https报文构造完成
 

 

你可能感兴趣的:(Python,ssl,tcpip,client,server)