4 import unicodecsv as csv
\r
6 from scapy.all import *
\r
9 def find_matches(pcap_file, router_wan_ip, sig_duration):
\r
10 # Read all packets into memory (stored as a list).
\r
11 # This is slow and consumes lots of memory.
\r
12 # There are more efficient ways to read the pcap (which clear each packet from memory after it's been processed).
\r
13 # However, to simplify the detection implementation we stick with the quick-and-dirty approach.
\r
14 pkts = rdpcap(pcap_file)
\r
15 # The potential signature matches, with the request packet as the first item of a tuple, and all possible reply
\r
16 # packets as the second item.
\r
18 for idx, p in enumerate(pkts):
\r
19 # Only consider IP traffic (note: this does not account for IPv6).
\r
22 # Note: IP addresses are apparently stored in string form (odd)
\r
25 if src != router_wan_ip:
\r
26 # We are trying to find matches for a simple [C->S, S->C] signature, so we want to first identify an
\r
27 # outbound (router-to-cloud) packet and then subsequently find all potential reply packets (cloud-to-router)
\r
28 # If this is a cloud-to-router packet, it is of no interest to us at this stage, so move on.
\r
30 # TODO should we exclude all multicasts+broadcasts? They wouldn't occur and/or be tunneled?
\r
31 if ipaddress.ip_address(dst).is_multicast:
\r
32 # Don't include multicast traffic originating from the router in the results.
\r
34 # Find the set of potential reply packets for this request.
\r
35 replies = find_reply_pkts(router_wan_ip, pkts, idx, sig_duration)
\r
36 # Store packet index alongside packet so that we can provide packet numbers for post analysis.
\r
37 matches.append(((p, idx), replies))
\r
41 def find_reply_pkts(router_wan_ip, pkts, request_pkt_idx, sig_duration):
\r
42 request_pkt = pkts[request_pkt_idx]
\r
43 idx = request_pkt_idx + 1
\r
45 while idx < len(pkts) and pkts[idx].time - request_pkt.time <= sig_duration:
\r
47 if is_inbound_ip_pkt(pkt, router_wan_ip):
\r
48 # Only count IP packets with router WAN IP as destination as potential replies to the request.
\r
49 # Store packet index alongside packet so that we can provide packet numbers for post analysis.
\r
50 reply_pkts.append((pkt, idx))
\r
55 def is_inbound_ip_pkt(pkt, router_wan_ip):
\r
56 return IP in pkt and pkt[IP].dst == router_wan_ip
\r
59 def write_matches_to_csv(matches, csv_filename):
\r
60 key_req_pkt = 'request_pkt'
\r
61 key_reply_pkts = 'reply_pkts'
\r
62 key_reply_pkts_count = 'number_of_reply_pkts'
\r
63 columns = [key_req_pkt, key_reply_pkts, key_reply_pkts_count]
\r
64 with open (csv_filename, 'wb') as csv_file:
\r
65 writer = csv.DictWriter(csv_file, fieldnames=columns)
\r
66 writer.writeheader()
\r
68 request_pkt = m[0][0]
\r
69 request_pkt_idx = m[0][1]
\r
70 # Wireshark packet numbers start from 1 (are not 0-based)
\r
71 request_pkt_num = request_pkt_idx + 1
\r
72 reply_pkts_numbers = []
\r
73 for (reply_pkt, reply_pkt_idx) in m[1]:
\r
74 reply_pkt_num = reply_pkt_idx + 1
\r
75 reply_pkts_numbers.append(reply_pkt_num)
\r
76 row = { key_req_pkt: request_pkt_num,
\r
77 key_reply_pkts: '; '.join(str(pkt_num) for pkt_num in reply_pkts_numbers),
\r
78 key_reply_pkts_count: len(reply_pkts_numbers) }
\r
79 writer.writerow(row)
\r
82 if __name__ == '__main__':
\r
83 desc = 'Perform detection on traffic in a VPN tunnel where traffic is padded; ' + \
\r
84 'i.e., the detection is entirely based on timing information and packet directions. ' + \
\r
85 'NOTE: THIS CODE IS SIMPLIFIED AND ONLY WORKS FOR SIMPLE [Client-to-Server, Server-to-Client] TWO ' + \
\r
86 'PACKET SIGNATURES.'
\r
87 parser = argparse.ArgumentParser(description=desc)
\r
88 parser.add_argument('pcap_file', help='Full path to the target pcap file (detection target trace).')
\r
89 parser.add_argument('router_wan_ip', help='IP of WAN interface of the home router (in decimal format).')
\r
90 h = 'Duration of the signature ' + \
\r
91 '(max time between request and reply packet for the two packets to be considered a match). ' + \
\r
92 'Unit: seconds (floating point number expected).'
\r
93 parser.add_argument('signature_duration',
\r
95 parser.add_argument('output_csv', help='Filename of CSV file where results are to be written.')
\r
96 args = parser.parse_args()
\r
98 pcap_file = args.pcap_file
\r
99 router_wan_ip = args.router_wan_ip
\r
100 signature_duration = args.signature_duration
\r
101 output_csv = args.output_csv
\r
102 print('Parsed arguments:')
\r
103 print(f'pcap_file={pcap_file}')
\r
104 print(f'router_wan_ip={router_wan_ip}')
\r
105 print(f'signature_duration={signature_duration}')
\r
106 print(f'output_csv={output_csv}')
\r
108 events = find_matches(pcap_file, router_wan_ip, signature_duration)
\r
110 # request_pkt = e[0][0]
\r
111 # request_pkt_num = e[0][1] + 1
\r
112 # for (reply_pkt, reply_pkt_num) in e[1]:
\r
113 # print(f"MATCH: Packet number {request_pkt_num} (request) with packet number {reply_pkt_num + 1} (reply).")
\r
114 write_matches_to_csv(events, output_csv)
\r