本篇将介绍如何构造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建立连接过程
构造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字段
至此一个完整的https报文构造完成