# coding: utf-8
#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import print_function
import traceback
import argparse
import ipaddress
from binascii import hexlify
import socket
import struct
import sys
import dpkt
import textwrap
import os
from asn1crypto import x509
from dpkt import ssl, Packet
import pickle
from constants import PRETTY_NAMES
from cert_filter import CertFilter
global encrypted_streams
encrypted_streams = [] # change_cipher
global ssl_servers_certs
ssl_servers_certs = {}
global ssl_servers_with_client_hello
ssl_servers_with_client_hello = set()
global client_hello_set
client_hello_set = set()
global server_ip_set
server_ip_set = set()
global buffer
buffer = {}
need_more_parse = False
def tls_multi_factory_new(buf):
"""
Attempt to parse one or more TLSRecord's out of buf
:param buf: string containing SSL/TLS messages. May have an incomplete record on the end
:return: [TLSRecord] int, total bytes consumed, != len(buf) if an incomplete record was left at the end.
Raises SSL3Exception.
"""
i, n = 0, len(buf)
msgs = []
while i + 5 <= n:
v = buf[i + 1:i + 3]
if v in ssl.SSL3_VERSION_BYTES:
try:
msg = ssl.TLSRecord(buf[i:])
msgs.append(msg)
except dpkt.NeedData:
break
else:
if i == 0: ############################################ added
raise ssl.SSL3Exception('Bad TLS version in buf: %r' % buf[i:i + 5])
else:
break
i += len(msg)
return msgs, i
class FlowDirection(object):
OUT = 1
IN = 2
UNKNOWN = 3
class Extension(object):
"""
Encapsulates TLS extensions.
"""
def __init__(self, payload):
self._type_id, payload = unpacker('H', payload)
self._type_name = pretty_name('extension_type', self._type_id)
self._length, payload = unpacker('H', payload)
# Data contains an array with the 'raw' contents
self._data = None
# pretty_data contains an array with the 'beautified' contents
self._pretty_data = None
if self._length > 0:
self._data, self._pretty_data = parse_extension(payload[:self._length],
self._type_name)
def __str__(self):
# Prints out data array in textual format
return '{0}: {1}'.format(self._type_name, self._pretty_data)
class OP:
CHECK_TLS_PACKET = 1
MERGE_TLS_PACKET = 2
def analyze_packet(_timestamp, packet, nth, op):
"""
Main analysis loop for pcap.
"""
eth = dpkt.ethernet.Ethernet(packet)
if isinstance(eth.data, dpkt.ip.IP):
# print("timestamp:", _timestamp, "debug")
parse_ip_packet(eth.data, nth, _timestamp, op)
def parse_arguments():
"""
Parses command line arguments.
"""
global filename
global verboseprint
global output_file
global is_white_sample
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=textwrap.dedent('''\
Captures, parses and shows TLS Handshake packets
Copyright (C) 2015 Peter Mosmans [Go Forward]
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.'''))
parser.add_argument('-r', '--read', metavar='FILE', action='store',
help='read from file (don\'t capture live packets)')
parser.add_argument('-v', '--verbose', action='store_true',
help='increase output verbosity')
parser.add_argument('-t', '--is_white_sample', action='store_true',
help='is white sample?')
parser.add_argument('-o', '--output', action='store',
help='output file')
args = parser.parse_args()
if args.verbose:
def verboseprint(*args):
print('# ', end="")
for arg in args:
print(arg, end="")
print()
else:
verboseprint = lambda *a: None
if args.is_white_sample:
print("OK, process white sample data.")
is_white_sample = True
else:
print("OK, process black sample data.")
is_white_sample = False
filename = None
if args.read:
filename = args.read
output_file = "demo_output.pickle"
if args.output:
output_file = args.output
def parse_ip_packet(ip, nth, timestamp, op):
"""
Parses IP packet.
"""
sys.stdout.flush()
if isinstance(ip.data, dpkt.tcp.TCP):
# print("****TCP packet found****", "tcp payload:", list(ip.data.data))
"""
try:
tls = dpkt.ssl.TLS(ip.data.data)
if len(tls.records) < 1:
return
except Exception as e:
print(e)
return
"""
parse_tcp_packet(ip, nth, timestamp, op)
# TLS version
def check_tls_version(data):
version2 = False
version3 = False
if len(data) > 2:
# ssl
tmp = struct.unpack("bbb", data[0:3])
else:
return version2, version3
# SSL v2. OR Message body too short.
if (tmp[0] & 0x80 == 0x80) and (((tmp[0] & 0x7f) << 8 | tmp[1]) > 9):
version2 = True
elif (tmp[1] != 3) or (tmp[2] > 3): # 版本,SSL 3.0 or TLS 1.0, 1.1 and 1.2
version3 = False
elif (tmp[0] < 20) or (tmp[0] > 23): # 类型错误
pass
else:
version3 = True
return version2, version3
def parse_tcp_packet(ip, nth, timestamp, op):
"""
Parses TCP packet.
"""
record_server_ip(ip)
stream = ip.data.data
if len(stream):
record_data_flow(ip, stream, nth, timestamp)
# def record_server_ip(ip):
# """
# >>> int(ipaddress.IPv4Address('192.168.0.1'))
# 3232235521
# >>> int(ipaddress.IPv4Address('0.0.0.1'))
# 1
# >>> int(ipaddress.IPv4Address('0.0.1.1'))
# 257
# """
# src_ip = socket.inet_ntoa(ip.src)
# dst_ip = socket.inet_ntoa(ip.dst)
# # 这里使用一个确定规则(小ip作为源),把相同方向的数据汇聚到一起
# if int(ipaddress.IPv4Address(src_ip)) < int(ipaddress.IPv4Address(dst_ip)):
# connection_key = "{}-{}".format(dst_ip, src_ip)
# global server_ip_set
# if connection_key not in server_ip_set:
# server_ip_set.add(connection_key)
# buffer[connection_key] = [{"out":[], "in":[]}]
def record_server_ip(ip):
src_ip = '{0}:{1}'.format(socket.inet_ntoa(ip.src), ip.data.sport)
dst_ip = '{0}:{1}'.format(socket.inet_ntoa(ip.dst), ip.data.dport)
tcp = ip.data
fin_flag = ( tcp.flags & dpkt.tcp.TH_FIN ) != 0
syn_flag = ( tcp.flags & dpkt.tcp.TH_SYN ) != 0
rst_flag = ( tcp.flags & dpkt.tcp.TH_RST ) != 0
psh_flag = ( tcp.flags & dpkt.tcp.TH_PUSH) != 0
ack_flag = ( tcp.flags & dpkt.tcp.TH_ACK ) != 0
urg_flag = ( tcp.flags & dpkt.tcp.TH_URG ) != 0
ece_flag = ( tcp.flags & dpkt.tcp.TH_ECE ) != 0
cwr_flag = ( tcp.flags & dpkt.tcp.TH_CWR ) != 0