Pārlūkot izejas kodu

Merge branch 'master' of https://git.tflucke.name/tflucke/SSH-Master-Thesis

Ethan Goldfarb 6 gadi atpakaļ
vecāks
revīzija
e58a854756

+ 7 - 0
.gitignore

@@ -8,3 +8,10 @@ data/*/
 *.a
 *.so
 *.pdf
+*.plo
+src/flow-seperator/flow-seperator
+src/pcap-matcher/pcap-matcher
+src/packet-matcher/packet-matcher
+src/common/*.a
+data/keylog-matchings.txt
+**/__pycache__/

+ 15 - 8
data/match-flows.sh

@@ -1,13 +1,20 @@
 #!/bin/sh
 
+readonly MATCH_FOUND=1
 readonly BASE_DIR="$(dirname $0)"
-readonly MATCHER="$BASE_DIR/../packet-matcher/packet-matcher"
+readonly MATCHER="$BASE_DIR/../src/pcap-matcher/pcap-matcher"
+readonly OUTFILE="$BASE_DIR/keylog-matchings.txt"
 
-for k in "$BASE_DIR"/keylogs/*/*.log; do
-    outdir="$BASE_DIR/timings/$(basename $(dirname $k))"
-    outfile="$outdir/$(basename $k)"
-    mkdir -p "$outdir" > /dev/null
-    if ! "$MATCHER" "$k" flows/*.pcap > "$outfile"; then
-        rm "$outfile"
+for k in "$BASE_DIR"/keylogs/*/*/*.log; do
+    printf "%s " "$k"
+    #seq 2 15 | while read i; do
+    echo 12 | while read i; do
+        if "$MATCHER" -t $i -l 250 "$k" flows/*.pcap; then
+            return $MATCH_FOUND
+        fi
+    done 2> /dev/null
+    if [ $? -ne $MATCH_FOUND ]; then
+        echo "No flow file found for $k" 1>&2
+        printf "\n" # Create empty line for log to represent no match found.
     fi
-done
+done > "$OUTFILE"

+ 11 - 0
data/match-packets.sh

@@ -0,0 +1,11 @@
+#!/bin/sh
+
+readonly BASE_DIR="$(dirname $0)"
+readonly MATCHER="$BASE_DIR/../src/packet-matcher/packet-matcher"
+readonly MATCHFILE="$BASE_DIR/keylog-matchings.txt"
+
+grep "$1" "$MATCHFILE" |
+    grep -v ' $' |
+    while read f; do
+        "$MATCHER" $f
+    done

+ 9 - 0
ethan_data_processing_scripts/README.md

@@ -0,0 +1,9 @@
+### Using classifier.py
+
+The train(classifications: int, data: list, results: list, testdata: list, testresults: list) function should be used.
+
+Data and Testdata should be arrays of arrays of features, ex:
+
+Suppose there are 3 features, each a float from 0 to 1. Data could be: [[.3, .2, .3], [.3, .3, .3], [.3, .4, .5]...]
+
+Results should be ints in an array, each result accoring to the list of features it should represent the classification of.

+ 7 - 1
src/common/Makefile

@@ -1,4 +1,4 @@
-TARGETS=libtimecap.a
+TARGETS=liblinkedlist.a libtimecap.a
 CC=gcc
 AR=ar
 CCFLAGS=-Wall -g
@@ -14,6 +14,12 @@ libtimecap.a: $(ODIR)/timecap.o
 $(ODIR)/timecap.o: timecap.c timecap.h $(ODIR)
 	$(CC) -c $(CCFLAGS) $< -o $@
 
+liblinkedlist.a: $(ODIR)/linkedList.o
+	$(AR) $(ARFLAGS) $@ $<
+
+$(ODIR)/linkedList.o: linkedList.c linkedList.h $(ODIR)
+	$(CC) -c $(CCFLAGS) $< -o $@
+
 $(ODIR):
 	mkdir $(ODIR)
 

+ 0 - 0
src/packet-matcher/linkedList.c → src/common/linkedList.c


+ 0 - 0
src/packet-matcher/linkedList.h → src/common/linkedList.h


+ 42 - 3
src/common/timecap.c

@@ -1,26 +1,58 @@
 #include"timecap.h"
 #include<stdlib.h>
+#include<stdbool.h>
+
+static int set_filter(TimeCap* self) {
+  int err = pcap_compile(self->pcap, &self->_filter, FILTER_KEY_PKT, true,
+                         PCAP_NETMASK_UNKNOWN);
+  if (-1 == err) {
+    return -1;
+  }
+  pcap_setfilter(self->pcap, &self->_filter);
+  return 0;
+}
+
+static ssize_t count_packets(TimeCap* self) {
+  struct pcap_pkthdr* header;
+  const u_char* buf;
+  size_t i = 0;
+  if (-1 == set_filter(self)) {
+    return -1;
+  }
+  while (0 < pcap_next_ex(self->pcap, &header, &buf)) {
+    ++i;
+  }
+  pcap_freecode(&self->_filter);
+  return i;
+}
 
 TimeCap* new_timecap(char* filename, char* errBuf) {
   struct pcap_pkthdr* header;
   const u_char* buf;
   TimeCap* cap = malloc(sizeof(TimeCap));
-  if (cap == NULL) {
+  if (NULL == cap) {
     perror("malloc");
     exit(1);
   }
   cap->pcap = pcap_open_offline(filename, errBuf);
-  if (cap->pcap == NULL) {
+  if (NULL == cap->pcap) {
     free(cap);
     return NULL;
   }
-  if (pcap_next_ex(cap->pcap, &header, &buf) == -1) {
+  if (-1 == pcap_next_ex(cap->pcap, &header, &buf)) {
     sprintf(errBuf, NO_PACKET_ERR, filename, pcap_geterr(cap->pcap));
     pcap_close(cap->pcap);
     free(cap);
     return NULL;
   }
   cap->time = header->ts;
+  cap->pack_count = count_packets(cap);
+  if (-1 == cap->pack_count) {
+    pcap_perror(cap->pcap, NULL);
+    pcap_close(cap->pcap);
+    free(cap);
+    return NULL;
+  }
   pcap_close(cap->pcap);
   cap->pcap = pcap_open_offline(filename, errBuf);
   if (cap->pcap == NULL) {
@@ -28,6 +60,12 @@ TimeCap* new_timecap(char* filename, char* errBuf) {
     return NULL;
   }
   cap->name = filename;
+  if (-1 == set_filter(cap)) {
+    pcap_perror(cap->pcap, NULL);
+    pcap_close(cap->pcap);
+    free(cap);
+    return NULL;
+  }
   return cap;
 }
 
@@ -47,6 +85,7 @@ int cmp_timecap(const void* a, const void* b) {
 
 void delete_timecap(void* val) {
   TimeCap* cap = val;
+  pcap_freecode(&cap->_filter);
   pcap_close(cap->pcap);
   free(cap);
 }

+ 12 - 0
src/common/timecap.h

@@ -4,12 +4,24 @@
 #include<pcap/pcap.h>
 #include <sys/time.h>
 
+/* Filter for packets (see pcap-filter(7))
+ * 
+ * tcp[13] & 8 == 8: PSH tcp flag is set
+ * dst port 22: Going to an SSH port
+ * len % 8 == 6: Packet length is in the 8 byte intervals offset by 6.
+ * For some reason (likely for block ciphers) the length of key the packets are
+ * always this length.
+ */
+#define FILTER_KEY_PKT "tcp[13] & 8 == 8 and dst port 22 and len % 8 == 6"
+
 #define NO_PACKET_ERR "Failed to read any packets from file, \"%s\".\n\t%s"
 
 typedef struct {
+  struct bpf_program _filter;
   struct timeval time;
   pcap_t* pcap;
   char* name;
+  size_t pack_count;
 } TimeCap;
 
 TimeCap* new_timecap(char* filename, char* errBuf);

+ 36 - 0
src/feature-extractor/extractor.py

@@ -0,0 +1,36 @@
+#!/usr/bin/python3
+import typing
+import sys
+
+def parse_args():
+    import argparse
+    parser = argparse.ArgumentParser(
+        description='Extract features from pcap files.')
+    parser.add_argument('match_file', metavar='match file',
+                        type=argparse.FileType('r'), default=sys.stdin,
+                        help='File of keylog/pcaps matchings (default: stdin)')
+    # parser.add_argument('pcaps', metavar='pcaps', type=argparse.FileType('rb'),
+    #                     nargs='+', help='pcap from which to extract features')
+    parser.add_argument('-o', '--outfile', type=argparse.FileType('wb'),
+                        default="features.plo", help='Where to save the " \
+                        "extracted features (default: features.plo)')
+    return parser.parse_args()
+
+def enter_data_dir(match_file: typing.TextIO):
+    import os
+    if match_file is not sys.stdin:
+        os.chdir(os.path.dirname(match_file.name))
+
+def main():
+    args = parse_args()
+    from sample import Sample
+    enter_data_dir(args.match_file)
+    out = [Sample(*line.split(" ")) for line in args.match_file if "pcap" in line]
+    try:
+        import cPickle as pickle
+    except:
+        import pickle
+    pickle.dump(out, args.outfile)
+
+if __name__ == '__main__':
+    main()

+ 28 - 0
src/feature-extractor/sample.py

@@ -0,0 +1,28 @@
+import typing
+import pyshark
+from datetime import datetime
+
+class Sample:
+    EPOCH = datetime(1970, 1, 1)
+    TIME_FMT = '%Y-%m-%d %H:%M:%S.%f'
+    
+    def __init__(self, keylog: typing.TextIO, pcap: typing.BinaryIO):
+        self.extract_tag(keylog)
+        f = pyshark.FileCapture(pcap.strip(), only_summaries=True)
+        f.load_packets()
+        self.extract_packet_stats(f)
+
+    def extract_tag(self, keylog: typing.TextIO):
+        import os
+        dir_guided = os.path.dirname(keylog)
+        self.is_guided = os.path.basename(dir_guided) == "y"
+        dir_user = os.path.dirname(dir_guided)
+        self.user = os.path.basename(dir_user)
+        
+    def extract_packet_stats(self, pcap):
+        start = (datetime.strptime(pcap[0].time, self.TIME_FMT) - self.EPOCH)\
+              .total_seconds()
+        end = (datetime.strptime(pcap[-1].time, self.TIME_FMT) - self.EPOCH)\
+              .total_seconds()
+        self.average_iat = self.average_iat = (end - start) / len(pcap)
+            

BIN
src/flow-seperator/flow-seperator


+ 1 - 1
src/packet-matcher/Makefile

@@ -2,7 +2,7 @@ TARGET=packet-matcher
 CC=gcc
 LIBS=../common
 CCFLAGS=-Wall -g -I$(LIBS)
-LDFLAGS=-lpcap -lm -ltimecap -L$(LIBS)
+LDFLAGS=-lpcap -lm -ltimecap -llinkedlist -L$(LIBS)
 
 ODIR=obj
 

+ 37 - 55
src/packet-matcher/getPackets.c

@@ -13,14 +13,17 @@
 #define PROG_NAME "a.out"
 #endif
 
+// Seconds
+#define KEY_EPS 1
+
+#define SSH_SETUP_PACKET_COUNT 4
+
 #define FLAG_SHORT_HELP "-h"
 #define FLAG_LONG_HELP  "--help"
 
 #define FLAG_SHORT_DIFF "-d"
 #define FLAG_LONG_DIFF  "--diff"
 
-#define FILTER_KEY_PKT "tcp[13] & 8 == 8 and dst port 22"
-
 /* Format of keylog lines in log file.
  * 4 = the fd SSH reads user input from (don't know why it isn't 0 but it isn't)
  * 16384 = How many bytes SSH reads as a time
@@ -38,7 +41,7 @@ typedef struct {
   FILE* keylog;
   char* keylogName;
   struct timeval keylogStart;
-  List* timecaps;
+  TimeCap* timecap;
   struct timeval synEpsiolon;
 } Args;
 
@@ -85,20 +88,13 @@ void check_error(int cond, char* str) {
   }
 }
 
-void check_error_pcap(int cond, pcap_t* pcap, char* str) {
-  if (cond) {
-    pcap_perror(pcap, str);
-    exit(1);
-  }
-}
-
 void print_usage(int exitCode) {
-  printf("Usage: %s [options] keylog pcap [pcap ...]\n"
-         "Pair a keylog to a SSH TCP stream.\n\n"
+  printf("Usage: %s [options] keylog pcap\n"
+         "Pair each key in a keylog to a packet.\n\n"
          "Options:\n"
          "%-20s Show this help message and exit\n"
-         "%-20s The allowable difference in time (in seconds) between keylog \n"
-         "%-20s start and flow start\n",
+         "%-20s The allowable difference in time (in seconds) between client\n"
+         "%-20s and server.\n",
          PROG_NAME,
          "  "FLAG_SHORT_HELP", "FLAG_LONG_HELP,
          "  "FLAG_SHORT_DIFF", "FLAG_LONG_DIFF, "");
@@ -128,20 +124,6 @@ void parse_flag(char** argv, Args* res, size_t* i) {
   }
 }
 
-/* Assumes each pcap has at least one packet and each pcap doesn't overlap.
- */
-static void add_timecap(List* pcaps, char* filename) {
-  char* errBuf  = alloca(PCAP_ERRBUF_SIZE + strlen(filename) +
-                         sizeof(NO_PACKET_ERR));
-  TimeCap* cap = new_timecap(filename, errBuf);
-  if (cap == NULL) {
-    fprintf(stderr, "%s: %s\n", filename, errBuf);
-  }
-  else {
-    list_add_head(pcaps, cap);
-  }
-}
-
 static void read_line(FILE* keylog) {
   int c;
   while ((c = fgetc(keylog)) != '\n' && c != EOF) {
@@ -160,14 +142,20 @@ static struct timeval get_keylog_start(FILE* keylog) {
 }
 
 static Args get_args(int argc, char** argv) {
-  Args res = {NULL, NULL, {0, 0}, new_list(NULL), DEFAULT_SYN_E};
+  Args res = {NULL, NULL, {0, 0}, NULL, DEFAULT_SYN_E};
   size_t i = 1;
+  char* errBuff;
   while (argv[i]) {
     if (argv[i][0] == '-') {
       parse_flag(argv, &res, &i);
     }
     else if (res.keylog) {
-      add_timecap(res.timecaps, argv[i]);
+      errBuff = alloca(PCAP_ERRBUF_SIZE + strlen(argv[i]) +
+                         sizeof(NO_PACKET_ERR));
+      res.timecap = new_timecap(argv[i], errBuff);
+      if (res.timecap == NULL) {
+        fprintf(stderr, "%s: %s\n", argv[i], errBuff);
+      }
     }
     else {
       res.keylog = fopen(argv[i], "r");
@@ -177,14 +165,15 @@ static Args get_args(int argc, char** argv) {
     }
     ++i;
   }
-  if (res.keylog == NULL || res.timecaps == NULL) {
+  if (res.keylog == NULL || res.timecap == NULL) {
     print_usage(1);
   }
   return res;
 }
 
-void timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y)
-{
+void timeval_subtract(struct timeval *result,
+                      struct timeval *x,
+                      struct timeval *y) {
   *result = *x;
   if (result->tv_usec < y->tv_usec) {
     result->tv_sec -= 1;
@@ -196,12 +185,10 @@ void timeval_subtract(struct timeval *result, struct timeval *x, struct timeval
   result->tv_usec = abs(result->tv_usec);
 }
 
-int is_match(void* tc, void* a) {
+int timercmp_eps(struct timeval* a, struct timeval* b, struct timeval eps) {
   struct timeval diff;
-  TimeCap* cap = (TimeCap*) tc;
-  Args* args = (Args*) a;
-  timeval_subtract(&diff, &args->keylogStart, &cap->time);
-  return timercmp(&diff, &args->synEpsiolon, <);
+  timeval_subtract(&diff, a, b);
+  return timercmp(&diff, &eps, <);
 }
 
 void print_time(struct timeval ts) {
@@ -230,17 +217,21 @@ void print_keystroke(Keystroke key) {
     printf(" %hu\n", key.packetId);
 }
 
+void skip_n_packets(pcap_t* pcap, size_t n) {
+  struct pcap_pkthdr header;
+  while (n-- > 0) {
+    pcap_next(pcap, &header);
+  }
+}
+
 void match_packets_to_keys(TimeCap* cap, FILE* keylog) {
-  struct bpf_program filter;
   struct pcap_pkthdr header;
   Keystroke key = get_next_key(keylog);
   Packet* data;
-  int err = pcap_compile(cap->pcap, &filter, FILTER_KEY_PKT, true,
-                         PCAP_NETMASK_UNKNOWN);
-  check_error_pcap(err == -1, cap->pcap, NULL);
-  pcap_setfilter(cap->pcap, &filter);
+  skip_n_packets(cap->pcap, SSH_SETUP_PACKET_COUNT);
   while ((data = (Packet*) pcap_next(cap->pcap, &header)) != NULL) {
-    if (timercmp(&header.ts, &key.time, >)) {
+    if (timercmp(&header.ts, &key.time, >) ||
+        timercmp_eps(&header.ts, &key.time, double_to_timeval(KEY_EPS))) {
       key.packetTime = header.ts;
       key.packetId = data->id;
       print_keystroke(key);
@@ -248,26 +239,17 @@ void match_packets_to_keys(TimeCap* cap, FILE* keylog) {
       key = get_next_key(keylog);
     }
   }
-  pcap_freecode(&filter);
 }
 
 void cleanup(Args args) {
-  delete_list(args.timecaps, delete_timecap);
+  delete_timecap(args.timecap);
   fclose(args.keylog);
 }
 
 int main(int argc, char** argv) {
   Args args = get_args(argc, argv);
-  TimeCap* cap = (TimeCap*) list_find(args.timecaps, &args, is_match);
   int exitStatus = 0;
-  if (cap != NULL) {
-    match_packets_to_keys(cap, args.keylog);
-  }
-  else {
-    fprintf(stderr, "Failed to find matching flow file for %s.\n",
-            args.keylogName);
-    exitStatus = 1;
-  }
+  match_packets_to_keys(args.timecap, args.keylog);
   cleanup(args);
   return exitStatus;
 }

BIN
src/packet-matcher/packet-matcher


+ 19 - 0
src/pcap-matcher/Makefile

@@ -0,0 +1,19 @@
+TARGET=pcap-matcher
+CC=gcc
+LIBS=../common
+CCFLAGS=-Wall -g -I$(LIBS)
+LDFLAGS=-lm -ltimecap -lpcap -llinkedlist -L$(LIBS)
+
+ODIR=obj
+
+SOURCES = $(wildcard *.c)
+OBJECTS = $(patsubst %.c, $(ODIR)/%.o, $(SOURCES))
+HEADERS = $(wildcard *.h)
+
+default: $(TARGET)
+
+$(TARGET): $(SOURCES)
+	$(CC) $(CCFLAGS) $^ $(LDFLAGS) -o $@ -D"PROG_NAME=\"$@\""
+
+clean:
+	rm -f $(TARGET) $(ODIR)/*.o *~

+ 248 - 0
src/pcap-matcher/main.c

@@ -0,0 +1,248 @@
+#include<stdio.h>
+#include<stdlib.h>
+#include<stdbool.h>
+#include<string.h>
+#include<pcap/pcap.h>
+#include<stdbool.h>
+#include<inttypes.h>
+#include<netinet/ether.h>
+#include<arpa/inet.h>
+#include"linkedList.h"
+#include"timecap.h"
+
+#ifndef PROG_NAME
+#define PROG_NAME "a.out"
+#endif
+
+// Seconds
+#define KEY_EPS 1
+
+#define SSH_SETUP_PACKET_COUNT 4
+
+#define FLAG_SHORT_HELP "-h"
+#define FLAG_LONG_HELP  "--help"
+
+#define FLAG_SHORT_DIFF "-t"
+#define FLAG_LONG_DIFF  "--time-diff"
+
+#define FLAG_SHORT_LENG "-l"
+#define FLAG_LONG_LENG  "--len-diff"
+
+#define FLAG_SHORT_VERB "-v"
+#define FLAG_LONG_VERB  "--verbose"
+
+/* Format of keylog lines in log file.
+ * 4 = the fd SSH reads user input from (don't know why it isn't 0 but it isn't)
+ * 16384 = How many bytes SSH reads as a time
+ * The last %c is there to check that the entire string matched correctly.
+ * This does not read the entire line.  Call read_line after using this.
+ */
+#define KEYLOG_FORMAT "%lf read(%*d, \"%m[^\"]\", 16384) %c"
+
+#define S_TO_NS 1000000000
+#define S_TO_US 1000000
+
+#define DEFAULT_SYN_E {3, 0}
+
+#define DEFAULT_TIM_E 20
+
+typedef struct {
+  FILE* keylog;
+  char* keylogName;
+  struct timeval keylogStart;
+  List* timecaps;
+  struct timeval synEpsiolon;
+  size_t keyEpsilon;
+  bool verbose;
+  size_t line_count;
+} Args;
+
+void check_error(int cond, char* str) {
+  if (cond) {
+    perror(str);
+    exit(1);
+  }
+}
+
+void print_usage(int exitCode) {
+  printf("Usage: %s [options] keylog pcap [pcap ...]\n"
+         "Pair a keylog to a SSH TCP stream.\n\n"
+         "Options:\n"
+         "%-20s Show this help message and exit\n"
+         "%-20s Print reasons for rejecting pcap files\n"
+         "%-20s The allowable difference in time (in seconds) between keylog \n"
+         "%-20s start and flow start\n"
+         "%-20s The allowable difference in estimated keys between keylog and\n"
+         "%-20s flow \n",
+         PROG_NAME,
+         "  "FLAG_SHORT_HELP", "FLAG_LONG_HELP,
+         "  "FLAG_SHORT_VERB", "FLAG_LONG_VERB,
+         "  "FLAG_SHORT_DIFF", "FLAG_LONG_DIFF, "",
+         "  "FLAG_SHORT_LENG", "FLAG_LONG_LENG, "");
+  exit(exitCode);
+}
+
+struct timeval double_to_timeval(double in) {
+  struct timeval time;
+  time.tv_sec = in;
+  time.tv_usec = (in - time.tv_sec) * S_TO_US;
+  return time;
+}
+
+void parse_flag(char** argv, Args* res, size_t* i) {
+  double d;
+  char* arg = argv[*i];
+  if (strcmp(arg, FLAG_SHORT_HELP) == 0 || strcmp(arg, FLAG_LONG_HELP) == 0) {
+    print_usage(0);
+  }
+  else if (strcmp(arg, FLAG_SHORT_VERB) == 0 ||
+           strcmp(arg, FLAG_LONG_VERB) == 0) {
+    res->verbose = true;
+  }
+  else if (strcmp(arg, FLAG_SHORT_DIFF) == 0 ||
+           strcmp(arg, FLAG_LONG_DIFF) == 0) {
+    if (sscanf(argv[++*i], " %lf", &d) == 1 && d > 0) {
+      res->synEpsiolon = double_to_timeval(d);
+    }
+    else {
+      print_usage(1);
+    }
+  }
+  else if (0 == strcmp(arg, FLAG_SHORT_LENG) ||
+           0 == strcmp(arg, FLAG_LONG_LENG)) {
+    if (1 != sscanf(argv[++*i], " %zu", &res->keyEpsilon)) {
+      print_usage(1);
+    }
+  }
+}
+
+/* Assumes each pcap has at least one packet and each pcap doesn't overlap.
+ */
+static void add_timecap(List* pcaps, char* filename) {
+  char* errBuf  = alloca(PCAP_ERRBUF_SIZE + strlen(filename) +
+                         sizeof(NO_PACKET_ERR));
+  TimeCap* cap = new_timecap(filename, errBuf);
+  if (cap == NULL) {
+    fprintf(stderr, "%s: %s\n", filename, errBuf);
+  }
+  else {
+    list_add_head(pcaps, cap);
+  }
+}
+
+static int read_line(FILE* keylog) {
+  int c;
+  while ('\n' != (c = fgetc(keylog)) && EOF != c) {
+    continue;
+  }
+  return EOF == c? EOF : 0;
+}
+
+static struct timeval get_keylog_start(FILE* keylog) {
+  double seconds;
+  if (fscanf(keylog, " %lf", &seconds) != 1) {
+    fprintf(stderr, "Failed to read timestame from keylog file.\n");
+    exit(1);
+  }
+  read_line(keylog);
+  return double_to_timeval(seconds);
+}
+
+size_t count_lines(FILE* file) {
+  size_t res = 1;
+  while (EOF != read_line(file)) {
+    ++res;
+  }
+  return res;
+}
+
+static Args get_args(int argc, char** argv) {
+  Args res = {NULL, NULL, {0, 0}, new_list(NULL), DEFAULT_SYN_E, DEFAULT_TIM_E};
+  size_t i = 1;
+  while (argv[i]) {
+    if (argv[i][0] == '-') {
+      parse_flag(argv, &res, &i);
+    }
+    else if (res.keylog) {
+      add_timecap(res.timecaps, argv[i]);
+    }
+    else {
+      res.keylog = fopen(argv[i], "r");
+      res.keylogName = argv[i];
+      check_error(res.keylog == NULL, argv[i]);
+      res.keylogStart = get_keylog_start(res.keylog);
+      res.line_count = count_lines(res.keylog);
+    }
+    ++i;
+  }
+  if (res.keylog == NULL || res.timecaps == NULL) {
+    print_usage(1);
+  }
+  return res;
+}
+
+void timeval_subtract(struct timeval *result,
+                      struct timeval *x,
+                      struct timeval *y) {
+  *result = *x;
+  if (result->tv_usec < y->tv_usec) {
+    result->tv_sec -= 1;
+    result->tv_usec += 1*S_TO_US;
+  }
+  result->tv_sec -= y->tv_sec;
+  result->tv_usec -= y->tv_usec;
+  result->tv_sec = abs(result->tv_sec);
+  result->tv_usec = abs(result->tv_usec);
+}
+
+int timercmp_eps(struct timeval* a, struct timeval* b, struct timeval eps) {
+  struct timeval diff;
+  timeval_subtract(&diff, a, b);
+  return timercmp(&diff, &eps, <);
+}
+
+#define LOG(ARGS, FMT, ...) do {                \
+  if ((ARGS)->verbose) {                        \
+    fprintf(stderr, (FMT), ##__VA_ARGS__);      \
+  }                                             \
+} while (0)
+
+int is_match(void* tc, void* a) {
+  TimeCap* cap = (TimeCap*) tc;
+  Args* args = (Args*) a;
+  int res;
+  if (timercmp_eps(&args->keylogStart, &cap->time, args->synEpsiolon)) {
+    res = args->line_count <= cap->pack_count + args->keyEpsilon &&
+      args->line_count >= cap->pack_count - args->keyEpsilon;
+    if (!res) {
+      LOG(args, "PCAP %s had incompatible line numbers.\n", cap->name);
+      LOG(args, "%zu keys, %zu packets.\n", args->line_count, cap->pack_count);
+    }
+    return res;
+  }
+  else {
+    LOG(args, "PCAP %s had incompatible timestamp.\n", cap->name);
+    return 0;
+  }
+}
+
+void cleanup(Args args) {
+  delete_list(args.timecaps, delete_timecap);
+  fclose(args.keylog);
+}
+
+int main(int argc, char** argv) {
+  Args args = get_args(argc, argv);
+  TimeCap* cap = (TimeCap*) list_find(args.timecaps, &args, is_match);
+  int exitStatus = 0;
+  if (cap != NULL) {
+    printf("%s\n", cap->name);
+  }
+  else {
+    fprintf(stderr, "Failed to find matching flow file for %s.\n",
+            args.keylogName);
+    exitStatus = 1;
+  }
+  cleanup(args);
+  return exitStatus;
+}

+ 57 - 0
systems/setup.md

@@ -0,0 +1,57 @@
+# Participant Setup Procedures
+
+## Set up VM
+
+1. Open VMWare Player app
+2. Open existing Virtual Machine
+3. Navigate to and select `/opt/Thesis-mini.ova`
+4a. *14-30{1,2}* Rename directory from `/home/...` to `/tmp/...`
+4b. *14-303* Rename directory from `/home/...` to `/vm/...`
+5. Hit Retry when prompted
+6. Once the program finishes importing, Boot the Thesis-Mini
+7. Hit Continue when prompted
+
+## Using VM
+
+*Note: All SSH connections to outside the VM will take up 2 minutes to establish
+for unknown reasons.*
+
+### Direct Usage
+
+There is a terminal available as the green icon in the bottom-right corner.
+From there you can SSH into another computer as per normal.
+
+### Indirect Usage
+
+Each VM will have an IP with is accessible from the host O.S.  Usually this will be
+`172.16.98.128`.
+To be sure, from the host O.S. open a terminal and type, `ip addr` and look for
+`vmnet1`.  Replace the last octal of that interface's IP with `128` and that is
+the address of the VM.
+
+Using this IP address, you can connect to the VM with:
+```Shell
+ssh volenteer@172.16.98.128
+```
+And the password, `password123`.  Once connected participants can SSH out to other
+machines as normal.
+
+## SSHing from the VM
+
+Each SSH session will start with a prompt asking if this is a guided session.  If
+participants are not following the provided guide, they should type `n`.  If they
+are, they should type `y`.
+
+Next the shell will warn them not to enter passwords into the session.  The first
+password they enter to establish the connection will not be captured.  Importantly,
+afterwards we cannot filter out passwords afterwards.  Thus far avoiding entering
+any has not been a problem.
+
+Finally, participants must properly close the SSH session in order to complete the
+connection __(They should not hit the X to exit)__.  They can do this by typing
+`exit` or `C-d`.
+
+Once they have exited the VM will show them the log of their recorded keys.  Once
+they review the logs and feel satisfied, they can type `q` to continue.  The VM
+will then confirm that they want to submit the logs which they can respond with
+either `y` or `n` to continue or withdraw respectively.