Completing files.
authorrtrimana <rtrimana@uci.edu>
Wed, 12 Feb 2020 22:22:38 +0000 (14:22 -0800)
committerrtrimana <rtrimana@uci.edu>
Wed, 12 Feb 2020 22:22:38 +0000 (14:22 -0800)
14 files changed:
Code/Projects/PacketLevelSignatureExtractor/.idea/PacketLevelSignatureExtractor.iml [new file with mode: 0644]
Code/Projects/PacketLevelSignatureExtractor/.idea/modules.xml [new file with mode: 0644]
Code/Projects/PacketLevelSignatureExtractor/.idea/modules/PacketLevelSignatureExtractor_main.iml
Code/Projects/PacketLevelSignatureExtractor/execute_layer2_smarthome_all_detection.sh
Code/Projects/PacketLevelSignatureExtractor/execute_layer2_smarthome_all_detection_results_analysis.sh
Code/Projects/PacketLevelSignatureExtractor/execute_layer3_unb_all_detection.sh
Code/Projects/PacketLevelSignatureExtractor/execute_signature_generation.sh
Code/Projects/PacketLevelSignatureExtractor/execute_signature_validation_results_analysis.sh
Code/Projects/PacketLevelSignatureExtractor/gradle/wrapper/gradle-wrapper.properties
packet-padding/timing_detection_tls_padding.py [new file with mode: 0644]
packet-padding/timing_detection_vpn_padding.py [new file with mode: 0644]
vpn/run-all.sh [new file with mode: 0755]
vpn/run-in-loop.sh [new file with mode: 0755]
vpn/run.sh [new file with mode: 0755]

diff --git a/Code/Projects/PacketLevelSignatureExtractor/.idea/PacketLevelSignatureExtractor.iml b/Code/Projects/PacketLevelSignatureExtractor/.idea/PacketLevelSignatureExtractor.iml
new file mode 100644 (file)
index 0000000..0db0396
--- /dev/null
@@ -0,0 +1,13 @@
+<?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
diff --git a/Code/Projects/PacketLevelSignatureExtractor/.idea/modules.xml b/Code/Projects/PacketLevelSignatureExtractor/.idea/modules.xml
new file mode 100644 (file)
index 0000000..e20fa83
--- /dev/null
@@ -0,0 +1,10 @@
+<?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
index f03acab8eab41dffd04116f1280c0f369d6b2288..e1b3b2d178083b084419bb089526126780bfe0b5 100644 (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
index 3d444f6e0e8966553a0e1db426b7247540347182..44346db155758be09acdd534fea9c7c2f35e46f8 100755 (executable)
@@ -731,8 +731,6 @@ OFF_SIGNATURE="$SIGNATURES_BASE_DIR/wemo-insight-plug/signatures/wemo-insight-pl
 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"
index e5a36d386a4d0ca42873be057dc3f1d8aedd2a5c..1ee4d43ce466b939fac8a0779cad5d12a5754ddb 100755 (executable)
@@ -397,7 +397,7 @@ RESULTS_FILE="$RESULTS_BASE_DIR/tplink-plug/tplink-plug.wlan1.detection.pcap___d
 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"
@@ -406,6 +406,16 @@ EXACT_MATCH="true"
 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
@@ -423,7 +433,7 @@ PROGRAM_ARGS="'$TIMESTAMPS_FILE' '$RESULTS_FILE' '$ANALYSIS_RESULTS_FILE' '$EXAC
 #./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"
index 1aed5deb6b918097ca794e8261eb4556d1a41354..2bde57503044c7e6a652a7a7259d2dce8055a656 100755 (executable)
@@ -396,8 +396,6 @@ DELTA="21"
 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"
 # ======================================================================================================================
 
index 8afb3f1297d75d13103c149a2bc8c73f9c9b8d4f..99d60f98fc8d3d0406b8642dce193fc59a3bc736 100755 (executable)
@@ -1032,24 +1032,6 @@ PROGRAM_ARGS="'$INPUT_PCAP' '$OUTPUT_PCAP' '$TIMESTAMP_FILE' '$DEVICE_IP' '$ON_S
 #./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"
 
index 1a83a7288f42caab123e99e236c0d4fb7ca2f752..1cd383e4514e5174561f0b7e1cd53673789a1df4 100755 (executable)
@@ -444,6 +444,8 @@ PROGRAM_ARGS="'$TIMESTAMPS_FILE' '$RESULTS_FILE' '$ANALYSIS_RESULTS_FILE' '$EXAC
 # ==================================================== 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"
index a17f18410426d2cd9a9455ea21e70a331607bad4..d6ac6d28edcb7df37ed73033ba0cef1d85e56593 100644 (file)
@@ -1,6 +1,6 @@
-#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
diff --git a/packet-padding/timing_detection_tls_padding.py b/packet-padding/timing_detection_tls_padding.py
new file mode 100644 (file)
index 0000000..2f9952e
--- /dev/null
@@ -0,0 +1,226 @@
+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
diff --git a/packet-padding/timing_detection_vpn_padding.py b/packet-padding/timing_detection_vpn_padding.py
new file mode 100644 (file)
index 0000000..dd63c23
--- /dev/null
@@ -0,0 +1,114 @@
+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
diff --git a/vpn/run-all.sh b/vpn/run-all.sh
new file mode 100755 (executable)
index 0000000..18060ef
--- /dev/null
@@ -0,0 +1,112 @@
+#!/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
+
diff --git a/vpn/run-in-loop.sh b/vpn/run-in-loop.sh
new file mode 100755 (executable)
index 0000000..e1fe6dd
--- /dev/null
@@ -0,0 +1,20 @@
+#!/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
diff --git a/vpn/run.sh b/vpn/run.sh
new file mode 100755 (executable)
index 0000000..096a715
--- /dev/null
@@ -0,0 +1,16 @@
+#!/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"