--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<module external.linked.project.id="PacketLevelSignatureExtractor" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="edu.uci.iotproject" external.system.module.version="1.0-SNAPSHOT" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <excludeFolder url="file://$MODULE_DIR$/.gradle" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ <excludeFolder url="file://$MODULE_DIR$/out" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/PacketLevelSignatureExtractor.iml" filepath="$PROJECT_DIR$/.idea/PacketLevelSignatureExtractor.iml" />
+ <module fileurl="file://$PROJECT_DIR$/.idea/modules/PacketLevelSignatureExtractor_main.iml" filepath="$PROJECT_DIR$/.idea/modules/PacketLevelSignatureExtractor_main.iml" group="PacketLevelSignatureExtractor" />
+ <module fileurl="file://$PROJECT_DIR$/.idea/modules/PacketLevelSignatureExtractor_test.iml" filepath="$PROJECT_DIR$/.idea/modules/PacketLevelSignatureExtractor_test.iml" group="PacketLevelSignatureExtractor" />
+ </modules>
+ </component>
+</project>
\ No newline at end of file
<orderEntry type="library" name="Gradle: org.jgrapht:jgrapht-core:1.2.0" level="project" />
<orderEntry type="library" name="Gradle: org.slf4j:slf4j-api:1.8.0-beta2" level="project" />
<orderEntry type="library" name="Gradle: net.java.dev.jna:jna:4.2.1" level="project" />
- <orderEntry type="module-library">
- <library>
- <CLASSES>
- <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/16.0.2/annotations-16.0.2.jar!/" />
- </CLASSES>
- <JAVADOC />
- <SOURCES />
- </library>
- </orderEntry>
</component>
</module>
\ No newline at end of file
RESULTS_FILE="$OUTPUT_DIR/wemo-insight-plug/wemo-insight-plug.wlan1.detection.pcap___device-side.detectionresults"
SIGNATURE_DURATION="521"
EPSILON="10.0"
-#ON_SKIPPED_PACKETS="5"
-#OFF_SKIPPED_PACKETS="5"
PROGRAM_ARGS="'$PCAP_FILE' '$ON_ANALYSIS' '$OFF_ANALYSIS' '$ON_SIGNATURE' '$OFF_SIGNATURE' '$RESULTS_FILE' '$SIGNATURE_DURATION' '$EPSILON'"
#./gradlew run -DmainClass=edu.uci.iotproject.detection.layer2.Layer2SignatureDetector --args="$PROGRAM_ARGS"
ANALYSIS_RESULTS_FILE="$RESULTS_FILE.analysis"
EXACT_MATCH="false"
PROGRAM_ARGS="'$TIMESTAMPS_FILE' '$RESULTS_FILE' '$ANALYSIS_RESULTS_FILE' '$EXACT_MATCH'"
-./gradlew run -DmainClass=edu.uci.iotproject.evaluation.DetectionResultsAnalyzer --args="$PROGRAM_ARGS"
+#./gradlew run -DmainClass=edu.uci.iotproject.evaluation.DetectionResultsAnalyzer --args="$PROGRAM_ARGS"
# DEVICE SIDE OUTBOUND
RESULTS_FILE="$RESULTS_BASE_DIR/tplink-plug/tplink-plug.wlan1.detection.pcap___device-side-outbound.detectionresults"
PROGRAM_ARGS="'$TIMESTAMPS_FILE' '$RESULTS_FILE' '$ANALYSIS_RESULTS_FILE' '$EXACT_MATCH'"
#./gradlew run -DmainClass=edu.uci.iotproject.evaluation.DetectionResultsAnalyzer --args="$PROGRAM_ARGS"
# ======================================================================================================================
+# IFTTT
+TIMESTAMPS_FILE="$TIMESTAMPS_BASE_DIR/tplink-plug/timestamps/tplink-plug-ifttt-smarthome-dec-11-2019.timestamps"
+
+# DEVICE SIDE
+RESULTS_FILE="$RESULTS_BASE_DIR/tplink-plug/tplink-plug.wlan1.detection.pcap___device-side.detectionresults"
+ANALYSIS_RESULTS_FILE="$RESULTS_FILE.analysis"
+EXACT_MATCH="false"
+PROGRAM_ARGS="'$TIMESTAMPS_FILE' '$RESULTS_FILE' '$ANALYSIS_RESULTS_FILE' '$EXACT_MATCH'"
+#./gradlew run -DmainClass=edu.uci.iotproject.evaluation.DetectionResultsAnalyzer --args="$PROGRAM_ARGS"
+# ======================================================================================================================
# ================================================== WEMO INSIGHT PLUG =================================================
# LOCAL
#./gradlew run -DmainClass=edu.uci.iotproject.evaluation.DetectionResultsAnalyzer --args="$PROGRAM_ARGS"
# ======================================================================================================================
# IFTTT
-TIMESTAMPS_FILE="$TIMESTAMPS_BASE_DIR/wemo-insight-plug/timestamps/wemo-insight-plug-ifttt-smarthome-dec-17-2019.timestamps"
+TIMESTAMPS_FILE="$TIMESTAMPS_BASE_DIR/wemo-insight-plug/timestamps/wemo-insight-plug-ifttt-smarthome-dec-19-2019.timestamps"
# DEVICE SIDE
RESULTS_FILE="$RESULTS_BASE_DIR/wemo-insight-plug/wemo-insight-plug.wlan1.detection.pcap___device-side.detectionresults"
PACKETLIST="592,1234,593,1235"
PROGRAM_ARGS="'$PCAP_FILE' '$ON_ANALYSIS' '$OFF_ANALYSIS' '$ON_SIGNATURE' '$OFF_SIGNATURE' '$RESULTS_FILE' '$SIGNATURE_DURATION' '$EPSILON' '$MINUS_R' '$DELTA' '$PACKETLIST'"
-
-PROGRAM_ARGS="'$PCAP_FILE' '$ON_ANALYSIS' '$OFF_ANALYSIS' '$ON_SIGNATURE' '$OFF_SIGNATURE' '$RESULTS_FILE' '$SIGNATURE_DURATION' '$EPSILON'"
#./gradlew run -DmainClass=edu.uci.iotproject.detection.layer3.Layer3SignatureDetector --args="$PROGRAM_ARGS"
# ======================================================================================================================
#./gradlew run -DmainClass=edu.uci.iotproject.SignatureGenerator --args="$PROGRAM_ARGS"
# ======================================================================================================================
-# ==================================================== TP-LINK BULB ====================================================
-INPUT_PCAP="$SIGNATURES_BASE_DIR/tplink-bulb/tplink-bulb-onoff/wlan1/tplink-bulb-onoff.wlan1.local.pcap"
-
-OUTPUT_PCAP="$OUTPUT_DIR/tplink-bulb/tplink-bulb-onoff/wlan1/tplink-bulb-onoff-processed.pcap"
-TIMESTAMP_FILE="$SIGNATURES_BASE_DIR/tplink-bulb/tplink-bulb-onoff/timestamps/tplink-bulb-onoff-retraining-dec-23-2019.timestamps"
-DEVICE_IP="192.168.1.140"
-ON_SIGNATURE="$OUTPUT_DIR/tplink-bulb/tplink-bulb-onoff/signatures/tplink-bulb-onoff-onSignature-device-side.sig"
-OFF_SIGNATURE="$OUTPUT_DIR/tplink-bulb/tplink-bulb-onoff/signatures/tplink-bulb-onoff-offSignature-device-side.sig"
-ON_ANALYSIS="$OUTPUT_DIR/tplink-bulb/tplink-bulb-onoff/analyses/tplink-bulb-onoff-onClusters-device-side.cls"
-OFF_ANALYSIS="$OUTPUT_DIR/tplink-bulb/tplink-bulb-onoff/analyses/tplink-bulb-onoff-offClusters-device-side.cls"
-EPSILON="10.0"
-DELETED_SEQUENCES_ON="-1"
-DELETED_SEQUENCES_OFF="-1"
-
-PROGRAM_ARGS="'$INPUT_PCAP' '$OUTPUT_PCAP' '$TIMESTAMP_FILE' '$DEVICE_IP' '$ON_SIGNATURE' '$OFF_SIGNATURE' '$ON_ANALYSIS' '$OFF_ANALYSIS' '$EPSILON' '$DELETED_SEQUENCES_ON' '$DELETED_SEQUENCES_OFF'"
-#./gradlew run -DmainClass=edu.uci.iotproject.SignatureGenerator --args="$PROGRAM_ARGS"
-# ======================================================================================================================
-
# ================================================== WEMO INSIGHT PLUG =================================================
INPUT_PCAP="$SIGNATURES_BASE_DIR/wemo-insight-plug/wlan1/wemo-insight-plug.wlan1.local.pcap"
# ==================================================== TP-LINK PLUG ====================================================
# LOCAL
TIMESTAMPS_FILE="$TIMESTAMPS_BASE_DIR/tplink-plug/timestamps/tplink-plug-nov-8-2018.timestamps"
+# TODO: Timestamp file for retraining PCAP file
+#TIMESTAMPS_FILE="$TIMESTAMPS_BASE_DIR/tplink-plug/timestamps/tplink-plug-retraining-dec-25-2019.timestamps"
# DEVICE SIDE
RESULTS_FILE="$RESULTS_BASE_DIR/tplink-plug/tplink-plug.wlan1.validation.pcap___device-side.detectionresults"
-#Tue Aug 21 11:14:11 PDT 2018
+#Wed Aug 21 12:49:44 PDT 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
--- /dev/null
+import argparse\r
+import ipaddress\r
+import socket\r
+import unicodecsv as csv\r
+\r
+from scapy.all import *\r
+\r
+\r
+def full_duplex(p):\r
+ """\r
+ For reassembling bidirectional sessions (streams). By default, Scapy only groups packets in one direction. That is,\r
+ bidirectional sessions are split into two sessions, one with client-to-server packets, and one with server-to-client\r
+ packets.\r
+\r
+ Note that this is simplified session reassembly as it does not consider TCP FIN/RST packets --- packets are mapped\r
+ to their respective session based solely on the (src_ip, src_port, dst_ip, dst_port) four-tuple. If the client (or\r
+ server) closes a TCP stream and the client by chance selects the same ephemeral port number when contacting the same\r
+ server again, the two DIFFERENT TCP streams will be identified as a single stream.\r
+\r
+ Code courtesy of: https://pen-testing.sans.org/blog/2017/10/13/scapy-full-duplex-stream-reassembly\r
+\r
+ Also note that this assumes Ethernet as layer-2 wrapper for everything. This assumption holds for our TP-Link trace,\r
+ but will not hold in general. See discussion at:\r
+ https://gist.github.com/MarkBaggett/d8933453f431c111169158ce7f4e2222#file-scapy_helper-py\r
+\r
+ :param p: A Scapy packet object.\r
+ :return: Session identifier for the packet.\r
+ """\r
+ sess = "Other"\r
+ if 'Ether' in p:\r
+ if 'IP' in p:\r
+ if 'TCP' in p:\r
+ sess = str(sorted(["TCP", p[IP].src, p[TCP].sport, p[IP].dst, p[TCP].dport],key=str))\r
+ elif 'UDP' in p:\r
+ sess = str(sorted(["UDP", p[IP].src, p[UDP].sport, p[IP].dst, p[UDP].dport] ,key=str))\r
+ elif 'ICMP' in p:\r
+ sess = str(sorted(["ICMP", p[IP].src, p[IP].dst, p[ICMP].code, p[ICMP].type, p[ICMP].id] ,key=str))\r
+ else:\r
+ sess = str(sorted(["IP", p[IP].src, p[IP].dst, p[IP].proto] ,key=str))\r
+ elif 'ARP' in p:\r
+ sess = str(sorted(["ARP", p[ARP].psrc, p[ARP].pdst],key=str))\r
+ else:\r
+ sess = p.sprintf("Ethernet type=%04xr,Ether.type%")\r
+ return sess\r
+\r
+\r
+def get_tls_app_data_pkts(session):\r
+ """\r
+ Extract the TLS Application Data packets from a (TCP) stream.\r
+ :param tcp_session: The (TCP) stream.\r
+ :return: The (ordered) list of TLS application data packets in session.\r
+ """\r
+ return session.filter(lambda pkt: TLS in pkt and pkt[TLS].type == 23)\r
+\r
+\r
+def find_matches(pcap_file, device_ip, sig_duration):\r
+ """\r
+ Find all matches of [C-->S, S-->C] signatures in TLS conversations involving the device with IP=device_ip. Packet\r
+ lengths are not considered, only directions and timing (packet lengths are assumed unavaiable due to TLS padding).\r
+ :param pcap_file: The pcap file that is the target of the signature matching.\r
+ :param device_ip: IP of the device whose TLS sessions are to be examined for matches.\r
+ :param sig_duration: Maximum duration between request and response packets.\r
+ :return: A list of (request_packet, reply_packets) tuples, where reply_packets is a list of reply packets that\r
+ satisfy the signature match conditions (i.e., that they are within sig_duration after the request packet\r
+ and that no other request packet interleaves the request_packet and the reply packet).\r
+ """\r
+ # Read all packets into memory (stored as a list).\r
+ # This is slow and consumes lots of memory.\r
+ # There are more efficient ways to read the pcap (which clear each packet from memory after it's been processed).\r
+ # However, to simplify the detection implementation we stick with the quick-and-dirty approach.\r
+ pkts = rdpcap(pcap_file)\r
+ matches = []\r
+ # Group packets into sessions (streams)\r
+ sessions_dict = pkts.sessions(full_duplex)\r
+ for sess_key in sessions_dict:\r
+ session = sessions_dict[sess_key]\r
+ tls_app_data_pkts = get_tls_app_data_pkts(session)\r
+ if len(tls_app_data_pkts) == 0:\r
+ # Session w/o any TLS traffic, not relevant.\r
+ continue\r
+ first_pkt = tls_app_data_pkts[0]\r
+ if IP not in first_pkt:\r
+ # Only consider IPv4 traffic.\r
+ continue\r
+ if first_pkt[IP].src != device_ip and first_pkt[IP].dst != device_ip:\r
+ # Traffic from some other device; ignore -- not relevant to us.\r
+ continue\r
+ if ipaddress.ip_address(first_pkt[IP].src).is_multicast or ipaddress.ip_address(first_pkt[IP].dst).is_multicast:\r
+ # Don't include multicast traffic in the results.\r
+ # (Should never occur as TLS is not used for multicast?)\r
+ continue\r
+ # Now let's find all the potential matches for the current TLS session.\r
+ for i, request_pkt in enumerate(tls_app_data_pkts):\r
+ if request_pkt[IP].src != device_ip:\r
+ # We are trying to find matches for a simple [C->S, S->C] signature, so we want to first identify an\r
+ # outbound (device-to-cloud) packet and then subsequently find all potential reply packets\r
+ # (cloud-to-device). If this is a cloud-to-device packet, it is of no interest to us at this stage, so\r
+ # move on.\r
+ continue\r
+ # All subsequent cloud-to-device packets (replies) in this TLS session that lie within the signature\r
+ # duration after this packet AND that are not preceded by a device-to-cloud packet that is later than the\r
+ # current packet can be paired with the current packet to constitute a potential signature match.\r
+ idx = i+1\r
+ replies = []\r
+ while idx < len(tls_app_data_pkts) and tls_app_data_pkts[idx][IP].dst == device_ip:\r
+ reply_pkt = tls_app_data_pkts[idx]\r
+ if reply_pkt.time - request_pkt.time <= sig_duration:\r
+ # Could have this check in the loop condition as well. But some times packet order != timestamp\r
+ # order.\r
+ replies.append(reply_pkt)\r
+ idx += 1\r
+ matches.append((request_pkt, replies))\r
+ return matches\r
+\r
+\r
+def get_pkt_key(pkt):\r
+ """\r
+ Get a string representation of a packet that can be used as a key in a dictionary.\r
+ :param pkt: A Scapy packet.\r
+ :return: A string representation of a packet that can be used as a key in a dictionary.\r
+ """\r
+ return f'src={pkt.src} dst={pkt.dst} timestamp={pkt.time}'\r
+\r
+\r
+def build_pkt_number_dict(pcap_file):\r
+ """\r
+ Create a dictionary mapping packets to their packet number in pcap_file.\r
+ The keys are generated by passing each packet to get_pkt_key(pkt).\r
+ :param pcap_file: The pcap file for which a packet number dictionary is desired.\r
+ :return: A dictionary mapping packet keys (obtainable from get_pkt_key(pkt)) to the packets packet number.\r
+ """\r
+ pkts = rdpcap(pcap_file)\r
+ map = {}\r
+ for i, pkt in enumerate(pkts):\r
+ pkt_num = i + 1\r
+ key = get_pkt_key(pkt)\r
+ assert(key not in map)\r
+ map[key] = pkt_num\r
+ assert(len(map) == len(pkts))\r
+ # Double check that numbers come out right. Can be removed in final version.\r
+ pkts = rdpcap(pcap_file)\r
+ for i, pkt in enumerate(pkts):\r
+ pkt_key = get_pkt_key(pkt)\r
+ assert(pkt_key in map and map[pkt_key] == i+1)\r
+ return map\r
+\r
+\r
+def add_pkt_numbers_to_matches(pcap_file, matches):\r
+ """\r
+ Hacky way to augment the matches with packet numbers. Assumes the same device does not send or receive more than\r
+ one packet at a given timestamp.\r
+ :param pcap_file: The pcap file where the matches were found in.\r
+ :param matches: The matches.\r
+ :return: matches augmented with packet numbers; each packet is converted to a (pkt, pkt_number) tuple.\r
+ """\r
+ pkt_nums_dict = build_pkt_number_dict(pcap_file)\r
+ result = []\r
+ for req_pkt, replies in matches:\r
+ req_pkt_num = pkt_nums_dict[get_pkt_key(req_pkt)] #find_pkt_number(req_pkt, pcap_file)\r
+ numbered_req_pkt = (req_pkt, req_pkt_num)\r
+ numbered_reply_pkts = []\r
+ for reply_pkt in replies:\r
+ reply_pkt_num = pkt_nums_dict[get_pkt_key(reply_pkt)] #find_pkt_number(reply_pkt, pcap_file)\r
+ numbered_reply_pkts.append((reply_pkt, reply_pkt_num))\r
+ result.append((numbered_req_pkt, numbered_reply_pkts))\r
+ return result\r
+\r
+\r
+def write_matches_to_csv(matches, csv_filename):\r
+ """\r
+ Output matches to a .csv file.\r
+ matches argument is expected to be in the format returned by add_pkt_numbers_to_matches(pcap_file, matches).\r
+ :param matches: A list of matches w/ packet numbers, as returned by add_pkt_numbers_to_matches(pcap_file, matches).\r
+ :param csv_filename: Path to the .csv file where the output is to be written.\r
+ :return: None.\r
+ """\r
+ key_req_pkt = 'request_pkt'\r
+ key_reply_pkts = 'reply_pkts'\r
+ key_reply_pkts_count = 'number_of_reply_pkts'\r
+ key_conversation_info = 'tls_conversation_between'\r
+ columns = [key_req_pkt, key_reply_pkts, key_reply_pkts_count, key_conversation_info]\r
+ with open (csv_filename, 'wb') as csv_file:\r
+ writer = csv.DictWriter(csv_file, fieldnames=columns)\r
+ writer.writeheader()\r
+ for m in matches:\r
+ request_pkt = m[0][0]\r
+ request_pkt_num = m[0][1]\r
+ reply_pkts_numbers = []\r
+ for (reply_pkt, reply_pkt_num) in m[1]:\r
+ reply_pkts_numbers.append(reply_pkt_num)\r
+ info = f'{request_pkt[IP].src+":"+str(request_pkt[TCP].sport)} and ' + \\r
+ f'{request_pkt[IP].dst+":"+str(request_pkt[TCP].dport)}'\r
+ row = { key_req_pkt: request_pkt_num,\r
+ key_reply_pkts: '; '.join(str(pkt_num) for pkt_num in reply_pkts_numbers),\r
+ key_reply_pkts_count: len(reply_pkts_numbers),\r
+ key_conversation_info: info}\r
+ writer.writerow(row)\r
+\r
+\r
+if __name__ == '__main__':\r
+ desc = 'Perform detection on padded TLS traffic; ' + \\r
+ 'i.e., the detection is entirely based on timing information and packet directions. ' + \\r
+ 'NOTE: THIS CODE IS SIMPLIFIED AND ONLY WORKS FOR SIMPLE [Client-to-Server, Server-to-Client] TWO ' + \\r
+ 'PACKET SIGNATURES.'\r
+ parser = argparse.ArgumentParser(description=desc)\r
+ parser.add_argument('pcap_file', help='Full path to the target pcap file (detection target trace).')\r
+ parser.add_argument('device_ip', help='Perform detection on TLS flows from this device (identified by IP) only.')\r
+ h = 'Duration of the signature ' + \\r
+ '(max time between request and reply packet for the two packets to be considered a match). ' + \\r
+ 'Unit: seconds (floating point number expected).'\r
+ parser.add_argument('signature_duration',\r
+ help=h, type=float)\r
+ parser.add_argument('output_csv', help='Filename of CSV file where results are to be written.')\r
+ args = parser.parse_args()\r
+\r
+ pcap_file = args.pcap_file\r
+ device_ip = args.device_ip\r
+ signature_duration = args.signature_duration\r
+ output_csv = args.output_csv\r
+\r
+ load_layer('tls')\r
+\r
+ matches = find_matches(pcap_file, device_ip, signature_duration)\r
+ matches = add_pkt_numbers_to_matches(pcap_file, matches)\r
+ write_matches_to_csv(matches, output_csv)\r
+\r
--- /dev/null
+import argparse\r
+import ipaddress\r
+import socket\r
+import unicodecsv as csv\r
+\r
+from scapy.all import *\r
+\r
+\r
+def find_matches(pcap_file, router_wan_ip, sig_duration):\r
+ # Read all packets into memory (stored as a list).\r
+ # This is slow and consumes lots of memory.\r
+ # There are more efficient ways to read the pcap (which clear each packet from memory after it's been processed).\r
+ # However, to simplify the detection implementation we stick with the quick-and-dirty approach.\r
+ pkts = rdpcap(pcap_file)\r
+ # The potential signature matches, with the request packet as the first item of a tuple, and all possible reply\r
+ # packets as the second item.\r
+ matches = []\r
+ for idx, p in enumerate(pkts):\r
+ # Only consider IP traffic (note: this does not account for IPv6).\r
+ if not IP in p:\r
+ continue\r
+ # Note: IP addresses are apparently stored in string form (odd)\r
+ src = p[IP].src\r
+ dst = p[IP].dst\r
+ if src != router_wan_ip:\r
+ # We are trying to find matches for a simple [C->S, S->C] signature, so we want to first identify an\r
+ # outbound (router-to-cloud) packet and then subsequently find all potential reply packets (cloud-to-router)\r
+ # If this is a cloud-to-router packet, it is of no interest to us at this stage, so move on.\r
+ continue\r
+ # TODO should we exclude all multicasts+broadcasts? They wouldn't occur and/or be tunneled?\r
+ if ipaddress.ip_address(dst).is_multicast:\r
+ # Don't include multicast traffic originating from the router in the results.\r
+ continue\r
+ # Find the set of potential reply packets for this request.\r
+ replies = find_reply_pkts(router_wan_ip, pkts, idx, sig_duration)\r
+ # Store packet index alongside packet so that we can provide packet numbers for post analysis.\r
+ matches.append(((p, idx), replies))\r
+ return matches\r
+\r
+\r
+def find_reply_pkts(router_wan_ip, pkts, request_pkt_idx, sig_duration):\r
+ request_pkt = pkts[request_pkt_idx]\r
+ idx = request_pkt_idx + 1\r
+ reply_pkts = []\r
+ while idx < len(pkts) and pkts[idx].time - request_pkt.time <= sig_duration:\r
+ pkt = pkts[idx]\r
+ if is_inbound_ip_pkt(pkt, router_wan_ip):\r
+ # Only count IP packets with router WAN IP as destination as potential replies to the request.\r
+ # Store packet index alongside packet so that we can provide packet numbers for post analysis.\r
+ reply_pkts.append((pkt, idx))\r
+ idx += 1\r
+ return reply_pkts\r
+\r
+\r
+def is_inbound_ip_pkt(pkt, router_wan_ip):\r
+ return IP in pkt and pkt[IP].dst == router_wan_ip\r
+\r
+\r
+def write_matches_to_csv(matches, csv_filename):\r
+ key_req_pkt = 'request_pkt'\r
+ key_reply_pkts = 'reply_pkts'\r
+ key_reply_pkts_count = 'number_of_reply_pkts'\r
+ columns = [key_req_pkt, key_reply_pkts, key_reply_pkts_count]\r
+ with open (csv_filename, 'wb') as csv_file:\r
+ writer = csv.DictWriter(csv_file, fieldnames=columns)\r
+ writer.writeheader()\r
+ for m in matches:\r
+ request_pkt = m[0][0]\r
+ request_pkt_idx = m[0][1]\r
+ # Wireshark packet numbers start from 1 (are not 0-based)\r
+ request_pkt_num = request_pkt_idx + 1\r
+ reply_pkts_numbers = []\r
+ for (reply_pkt, reply_pkt_idx) in m[1]:\r
+ reply_pkt_num = reply_pkt_idx + 1\r
+ reply_pkts_numbers.append(reply_pkt_num)\r
+ row = { key_req_pkt: request_pkt_num,\r
+ key_reply_pkts: '; '.join(str(pkt_num) for pkt_num in reply_pkts_numbers),\r
+ key_reply_pkts_count: len(reply_pkts_numbers) }\r
+ writer.writerow(row)\r
+\r
+\r
+if __name__ == '__main__':\r
+ desc = 'Perform detection on traffic in a VPN tunnel where traffic is padded; ' + \\r
+ 'i.e., the detection is entirely based on timing information and packet directions. ' + \\r
+ 'NOTE: THIS CODE IS SIMPLIFIED AND ONLY WORKS FOR SIMPLE [Client-to-Server, Server-to-Client] TWO ' + \\r
+ 'PACKET SIGNATURES.'\r
+ parser = argparse.ArgumentParser(description=desc)\r
+ parser.add_argument('pcap_file', help='Full path to the target pcap file (detection target trace).')\r
+ parser.add_argument('router_wan_ip', help='IP of WAN interface of the home router (in decimal format).')\r
+ h = 'Duration of the signature ' + \\r
+ '(max time between request and reply packet for the two packets to be considered a match). ' + \\r
+ 'Unit: seconds (floating point number expected).'\r
+ parser.add_argument('signature_duration',\r
+ help=h, type=float)\r
+ parser.add_argument('output_csv', help='Filename of CSV file where results are to be written.')\r
+ args = parser.parse_args()\r
+\r
+ pcap_file = args.pcap_file\r
+ router_wan_ip = args.router_wan_ip\r
+ signature_duration = args.signature_duration\r
+ output_csv = args.output_csv\r
+ print('Parsed arguments:')\r
+ print(f'pcap_file={pcap_file}')\r
+ print(f'router_wan_ip={router_wan_ip}')\r
+ print(f'signature_duration={signature_duration}')\r
+ print(f'output_csv={output_csv}')\r
+\r
+ events = find_matches(pcap_file, router_wan_ip, signature_duration)\r
+ # for e in events:\r
+ # request_pkt = e[0][0]\r
+ # request_pkt_num = e[0][1] + 1\r
+ # for (reply_pkt, reply_pkt_num) in e[1]:\r
+ # print(f"MATCH: Packet number {request_pkt_num} (request) with packet number {reply_pkt_num + 1} (reply).")\r
+ write_matches_to_csv(events, output_csv)\r
--- /dev/null
+#!/bin/sh
+
+TRACES_DIR="../smarthome"
+
+DIR="amazon-plug"
+FEATURE="amazon-plug"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="arlo-camera"
+FEATURE="arlo-camera"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="blossom-sprinkler/blossom-sprinkler-quickrun"
+FEATURE="blossom-sprinkler-quickrun"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="blossom-sprinkler/blossom-sprinkler-mode"
+FEATURE="blossom-sprinkler-mode"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="dlink-plug"
+FEATURE="dlink-plug"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="dlink-siren"
+FEATURE="dlink-siren"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="ecobee-thermostat/ecobee-thermostat-hvac"
+FEATURE="ecobee-thermostat-hvac"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="ecobee-thermostat/ecobee-thermostat-fan"
+FEATURE="ecobee-thermostat-fan"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="kwikset-doorlock"
+FEATURE="kwikset-doorlock"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="nest-thermostat"
+FEATURE="nest-thermostat"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="rachio-sprinkler/rachio-sprinkler-quickrun"
+FEATURE="rachio-sprinkler-quickrun"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="rachio-sprinkler/rachio-sprinkler-mode"
+FEATURE="rachio-sprinkler-mode"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="ring-alarm"
+FEATURE="ring-alarm"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="roomba-vacuum-robot"
+FEATURE="roomba-vacuum-robot"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="sengled-bulb/sengled-bulb-onoff"
+FEATURE="sengled-bulb-onoff"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+#./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="sengled-bulb/sengled-bulb-intensity"
+FEATURE="sengled-bulb-intensity"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="st-plug"
+FEATURE="st-plug"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
+DIR="tplink-plug"
+FEATURE="tplink-plug"
+echo "==> START TIME for $DIR: "
+date +"%m/%d/%Y %r"
+./run.sh $TRACES_DIR/$DIR/eth0/$FEATURE.eth0.detection.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.from-router.pcap $TRACES_DIR/$DIR/event/$FEATURE.eth0.event.to-router.pcap
+
--- /dev/null
+#!/bin/sh
+
+FILE_TO_INJECT=$1
+#RAND_START=0
+#RAND_END=60
+TIMES=101
+
+COUNT=1
+while [ $COUNT -lt $TIMES ]
+do
+ #RAND=$[`shuf -i $RAND_START-$RAND_END -n 1`]
+ #RAND="$(awk 'BEGIN{srand();print int(rand()*60)}')"
+ # This one works well to generate a random number between 0-999 on OpenWrt
+ RAND=$(head -30 /dev/urandom | tr -dc "0123456789" | head -c3)
+ sleep $(($RAND%240))
+ tcpreplay -i eth1 -q "$FILE_TO_INJECT"
+ echo "Delay for $FILE_TO_INJECT: $(($RAND%240)) seconds"
+ date +"%m/%d/%Y %r"
+ COUNT=`expr $COUNT + 1`
+done
--- /dev/null
+#!/bin/sh
+
+# File that is to be injected by traffic
+FILE_MAIN_PCAP=$1
+
+# File that contains event packets that are sent from router
+FILE_PACKETS_FROM_ROUTER=$2
+
+# File that contains event packets that are sent to router
+FILE_PACKETS_TO_ROUTER=$3
+
+./run-in-loop.sh "$FILE_PACKETS_FROM_ROUTER" &
+./run-in-loop.sh "$FILE_PACKETS_TO_ROUTER" &
+tcpreplay -i eth1 -q "$FILE_MAIN_PCAP"
+echo "==> Finished with the file $FILE_MAIN_PCAP: "
+date +"%m/%d/%Y %r"