diff --git a/bin/Makefile b/bin/Makefile
new file mode 100644
index 0000000..c064bdb
--- /dev/null
+++ b/bin/Makefile
@@ -0,0 +1,8 @@
+pibs: pibs.o
+ gcc -Wall -o pibs pibs.o -lwiretap `pkg-config --libs glib-2.0` -lpcap -lhiredis -ggdb
+pibs.o: pibs.c
+ gcc -D HASHDEBUG=0 -Wall -c pibs.c `pkg-config --cflags glib-2.0` -I /usr/include/wireshark/wiretap -I /usr/include/wireshark/wsutil -I /usr/include/wireshark `pkg-config --libs glib-2.0` -I /usr/local/include/hiredis -ggdb
+
+clean:
+ -rm pibs
+ -rm *.o
diff --git a/bin/pibs.c b/bin/pibs.c
new file mode 100644
index 0000000..7a1e482
--- /dev/null
+++ b/bin/pibs.c
@@ -0,0 +1,468 @@
+/*
+* pibs - Passive Identification of BackScatter
+*
+* Copyright (C) 2019 Gerard Wagener
+* Copyright (C) 2019 CIRCL Computer Incident Response Center Luxembourg
+* (SMILE gie).
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see .
+*/
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+//TODO test other values
+#define NBINS 1024 //Number of bins
+#define NBINITEMS 255 //Number of items per bin
+#define SZBIN 4
+#define NBINSCALE 2 // Scaling factor of the entire datastructure
+
+#define HDBG(...) if (HASHDEBUG) fprintf(stderr, __VA_ARGS__)
+
+#define ERR_ATTACH_NOT_EMPTY 11
+#define ERR_NO_SHMID_FILE 12
+
+typedef struct pibs_header_s {
+ uint8_t magic [4];
+ uint8_t version;
+ //Put some useful stuff here
+ uint32_t next_item;
+ uint32_t bin_offset;
+ uint64_t data_size;
+ uint32_t max_item;
+ uint8_t padding [3];
+} pibs_header_t;
+
+
+/* TODO This can squezed. Timestamp can be expressed on 8 bits i.e. relative
+ * minutes
+ * IP can be represented with 16 bits ipaddr = ip / bin_size
+ * Not sure if space can be saved in usual cases
+ */
+typedef struct item_s {
+ uint32_t timestamp;
+ uint32_t next_item;
+ uint32_t ipaddr;
+} item_t;
+
+/* Need to hash source IP addresses and record first seen and flags */
+typedef struct pibs_s {
+ int errno_copy;
+ int errno_pibs;
+ char *filename;
+ int should_dump_table;
+ int show_backscatter;
+ int show_stats;
+ int should_create_shm;
+ int should_attach;
+ //TODO use self contained data structure that can be easily serialized
+ //Put data structure in an entire block to easier serialize
+ uint8_t *data;
+ uint32_t next_block;
+ uint32_t next_item;
+ uint32_t bin_offset;
+ uint64_t data_size;
+ uint32_t* bin_table;
+ uint32_t max_item;
+ item_t* items;
+ int shmid;
+ char shmid_file [FILENAME_MAX];
+} pibs_t;
+
+int load_shmid_file(pibs_t* pibs)
+{
+ FILE* fp;
+ if (pibs->shmid_file[0]) {
+ fp = fopen(pibs->shmid_file,"r");
+ if (fp) {
+ //FIXME check file
+ fscanf(fp, "%d", &pibs->shmid);
+ return pibs->shmid;
+ }
+ } else {
+ pibs->errno_pibs = ERR_NO_SHMID_FILE;
+ }
+ return -1;
+}
+
+//TODO when attaching the next_item must be recovered if results
+//of previous runs need to be increased
+int pibs_shmat(pibs_t* pibs)
+{
+ /* FIXME init function needs to break up in two functions. One that
+ * initializes internal pibs structures as cli options etc
+ * a second one for describing the data itself, size of bin_table
+ * number of items etc.
+ */
+ if (pibs->data) {
+ free(pibs->data);
+ pibs->data = NULL;
+ }
+ if (pibs->data) {
+ pibs->errno_pibs = ERR_ATTACH_NOT_EMPTY;
+ printf("TEST Data is not null\n");
+ return -1;
+ }
+ if (!pibs->shmid_file[0]) {
+ pibs->errno_pibs = ERR_NO_SHMID_FILE;
+ return -1;
+ }
+ if (load_shmid_file(pibs) > 0) {
+ pibs->data = shmat(pibs->shmid, NULL, SHM_RND);
+ if ( (int) pibs->data == -1) {
+ pibs->errno_copy = errno;
+ } else {
+ return 1;
+ }
+ }
+ // Something did not work
+ return -1;
+}
+
+int pibs_shmget(pibs_t* pibs)
+{
+ FILE* fp;
+ pibs->shmid = shmget(IPC_PRIVATE, pibs->data_size, IPC_CREAT | 0600);
+ if (pibs->shmid < 0) {
+ pibs->errno_copy = errno;
+ }
+
+ if (pibs->shmid_file[0]){
+ fp = fopen(pibs->shmid_file, "w");
+ if (fp) {
+ fprintf(fp,"%d",pibs->shmid);
+ fclose(fp);
+ }
+ //TODO error handling
+ }
+ //TODO attach to it and bzero it
+ //setup the tables
+ return pibs->shmid;
+}
+
+/*
+ * Returns -1 if not found
+ * returns last timestamp if found
+ */
+int_fast64_t get_last_timestamp(pibs_t* pibs, uint32_t ip)
+{
+ uint32_t idx;
+ uint32_t i;
+ //TODO explore alternative hashing functions
+ //https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key/12996028#12996028
+ idx = ip % NBINS;
+ HDBG("[TS] Checking for IP %x at index = %d\n", ip, idx);
+ i = pibs->bin_table[idx];
+ while (i){
+ if (pibs->items[i].ipaddr == ip) {
+ HDBG("[TS] Found item %x at position %d\n", ip , i);
+ return pibs->items[i].timestamp;
+ }
+ i = pibs->items[i].next_item;
+ }
+ HDBG("[TS] IP: %x was not found return -1\n",ip);
+ return -1;
+}
+
+void insert_ip(pibs_t* pibs, uint32_t ip, uint32_t ts)
+{
+ uint32_t idx;
+ uint32_t i;
+ uint32_t parent;
+
+ idx = ip % NBINS;
+ HDBG("[INS] Lookup IP address %x. Hashed value: %d\n", ip, idx);
+ parent = 0;
+ if (pibs->bin_table[idx]){
+ // There is already someone in the bin
+ i = pibs->bin_table[idx];
+ HDBG("[INS] Starting searching at position %d\n", i);
+ do {
+ HDBG("[INS] Iterating items at index %d. Current position: %d.\
+ Next position = %d\n",
+ idx,i,pibs->items[i].next_item);
+ HDBG("[INS] Checking IP at address %p\n",&pibs->items[i]);
+ if (pibs->items[i].ipaddr == ip) {
+ HDBG("[INS] Found item %x at position %d\n", ip , i);
+ HDBG("[INS] New timestamp for ip %x is %d\n",ip,ts);
+ pibs->items[i].timestamp = ts;
+ return;
+ }
+ parent = i;
+ i = pibs->items[i].next_item;
+ } while (i != 0 );
+ HDBG("[INS] The IP %x was not found in the item list, last parent %d\n",
+ ip, parent);
+ }
+ // The IP was not found in an item list or the hashed value wsa not present
+ // in the bin table, so create a new item
+ pibs->next_item++;
+ if (pibs->next_item > pibs->max_item) {
+ printf("FIXME run out of memory. Do something better than abort\n");
+ //Go through old timestamps and keep linked list of stuff that can be
+ //reused or do kind of defragmentation
+ abort();
+ }
+ if (pibs->bin_table[idx] == 0) {
+ pibs->bin_table[idx] = pibs->next_item;
+ }
+ HDBG("[INS] Insert ip %x at position %d, parent = %d\n", ip,
+ pibs->next_item,parent);
+ pibs->items[pibs->next_item].ipaddr = ip;
+ pibs->items[pibs->next_item].timestamp = ts;
+ if (parent) {
+ pibs->items[parent].next_item = pibs->next_item;
+ }
+}
+
+void process_frame(pibs_t* pibs, wtap *wth,
+ uint8_t *buf, size_t length)
+{
+ struct ip* ipv4;
+ uint32_t ip;
+ struct tcphdr* tcp;
+ int_fast64_t lastseen;
+
+ if (length < sizeof(struct ip)) {
+ return;
+ }
+
+
+ ipv4 = (struct ip*)buf;
+ // Focus only on TCP packets
+ if (ipv4->ip_p != 6)
+ return;
+
+ tcp = (struct tcphdr*)(buf+sizeof(struct ip));
+
+ memcpy(&ip, &ipv4->ip_src, 4);
+ // Record only source ips where syn flag is set
+ // TODO check other connection establishment alternatives
+ if (tcp->th_flags == 2 ){
+ insert_ip(pibs, ip, wth->rec.ts.secs);
+ return;
+ }
+
+ lastseen = get_last_timestamp(pibs, ip);
+
+ if (lastseen > 0){
+ HDBG("IP %x %s was already seen before at %ld. Time difference %ld.\n"
+ , ip, inet_ntoa(ipv4->ip_src), lastseen, wth->rec.ts.secs-lastseen);
+ return;
+ }
+ // TODO keep these IPs in a hashtable and rank them
+ if (pibs->show_backscatter) {
+ printf("Potential backscatter. IP. %s. TCP flags: %d. Source port:%d \n",
+ inet_ntoa(ipv4->ip_src), tcp->th_flags, ntohs(tcp->th_sport));
+ }
+ //TODO relative time
+ //Purge old ips?
+}
+
+void process_file(pibs_t* pibs)
+{
+ wtap *wth;
+ int err;
+ char *errinfo;
+ gint64 data_offset;
+ int ethertype;
+ guint8 *buf;
+
+ fprintf(stderr,"Processing %s\n",pibs->filename);
+ wth = wtap_open_offline ( pibs->filename, WTAP_TYPE_AUTO, (int*)&err,
+ (char**)&errinfo, FALSE);
+ if (wth) {
+ /* Loop over the packets and adjust the headers */
+ while (wtap_read(wth, &err, &errinfo, &data_offset)) {
+ if (wth->rec.rec_type == REC_TYPE_PACKET) {
+ if (wth->rec.tsprec == WTAP_TSPREC_USEC){
+ if (wth->rec.rec_header.packet_header.caplen < 14) {
+ fprintf(stderr,"Packet too small, skip\n");
+ continue;
+ }
+ }
+ buf = wth->rec_data->data;
+ ethertype = buf[12] << 8 | buf[13];
+ // TODO Focus on IPv4 only
+ if (ethertype == 0x0800) {
+ process_frame(pibs, wth, buf+14, wth->rec.rec_header.packet_header.caplen -14);
+ }
+ }
+ }
+ wtap_close(wth);
+ fprintf(stderr,"[INFO] Processing of filename %s done\n",pibs->filename);
+ }else{
+ fprintf(stderr, "[ERROR] Could not open filename %s,cause=%s\n",pibs->filename,
+ wtap_strerror(err));
+ }
+}
+
+pibs_t* init(void)
+{
+ pibs_t *pibs;
+
+ wtap_init(FALSE);
+ pibs=calloc(sizeof(pibs_t),1);
+ //TODO check if size is correct
+ pibs->data_size = sizeof(pibs_header_t) + NBINSCALE * NBINS * SZBIN * NBINITEMS * sizeof(item_t);
+ pibs->data = calloc(pibs->data_size,1);
+ pibs->filename = calloc(FILENAME_MAX,1);
+ printf("#Internal look up structure size in bytes: %ld\n", pibs->data_size);
+ // Build header
+ pibs->data[0]='P';
+ pibs->data[1] = 'I';
+ pibs->data[2] = 'B';
+ pibs->data[3] = 'S';
+ pibs->data[4] = 1; //version 1
+ pibs->next_block = sizeof(pibs_header_t);
+ pibs->bin_offset = pibs->next_block;
+ printf("#data address is %p\n",pibs->data);
+ pibs->bin_table = (uint32_t*)(pibs->data+pibs->bin_offset);
+ printf("#bin_table address is %p\n", pibs->bin_table);
+ // Create bins
+ pibs->next_block+=SZBIN * NBINS;
+ printf("#next block %d\n", pibs->next_block);
+ pibs->items = (item_t*)(pibs->data+pibs->next_block);
+ pibs->next_item = 0;
+ printf("#items are address %p\n", pibs->items);
+ pibs->max_item = NBINS * NBINITEMS;
+ printf("#max_item: %d\n", pibs->max_item);
+ return pibs;
+}
+
+void pibs_dump_raw(pibs_t* pibs)
+{
+ int i;
+ printf("#RAW table dump\n");
+ printf("#Index next_item\n");
+ printf("#BINs\n");
+ for (i=0; i< NBINS; i++) {
+ printf("%d %d\n", i, pibs->bin_table[i]);
+ }
+ printf("#ITEMS\n");
+ printf("#Index next_item, timestamp, ipaddr\n");
+ for (i=0; i < NBINITEMS * NBINS; i++) {
+ printf("%d %d %d %x\n", i, pibs->items[i].next_item,
+ pibs->items[i].timestamp,
+ pibs->items[i].ipaddr);
+ }
+}
+
+void pibs_dump_stats(pibs_t* pibs)
+{
+ int i;
+ int j;
+ int cnt;
+ uint64_t sum;
+ sum = 0;
+ printf("#Bin table\n");
+ printf("#Bin number, Item offset, number of items\n");
+ for (i=0; i < NBINS; i++) {
+ j= pibs->bin_table[i];
+ cnt = 0;
+ while (j) {
+ cnt++;
+ j=pibs->items[j].next_item;
+ }
+ sum+=cnt;
+ printf("%d %d %d\n", i, pibs->bin_table[i], cnt);
+ }
+ printf("#Number of unique IP addresses: %ld\n", sum);
+}
+
+int main(int argc, char* argv[])
+{
+
+ int opt;
+ pibs_t* pibs;
+
+ pibs = init();
+
+ fprintf(stderr, "[INFO] pid = %d\n",(int)getpid());
+
+ while ((opt = getopt(argc, argv, "r:dbsni:a")) != -1) {
+ switch (opt) {
+ case 'r':
+ strncpy(pibs->filename, optarg, FILENAME_MAX);
+ break;
+ case 'd':
+ pibs->should_dump_table = 1;
+ break;
+ case 'b':
+ pibs->show_backscatter = 1;
+ break;
+ case 's':
+ pibs->show_stats = 1;
+ break;
+ case 'n':
+ pibs->should_create_shm = 1;
+ break;
+ case 'i':
+ strncpy(pibs->shmid_file, optarg, FILENAME_MAX);
+ break;
+ case 'a':
+ pibs->should_attach = 1;
+ break;
+
+ default: /* '?' */
+
+ fprintf(stderr, "[ERROR] Invalid command line was specified\n");
+ }
+ }
+ if (pibs->should_create_shm) {
+ pibs_shmget(pibs);
+ if (pibs->shmid >0){
+ printf("Create a new shared memory segment %d\n", pibs->shmid);
+ } else {
+ printf("Failed to get shared memory segment. Cause = %s\n",
+ strerror(pibs->errno_copy));
+ }
+ }
+ if (pibs->should_attach) {
+ if (pibs_shmat(pibs) > 0 ) {
+ printf("Attached to shared memory segment %d\n", pibs->shmid);
+ } else {
+ printf("Failed to attach to shared memory segment. System error:%s\n",
+ strerror(pibs->errno_copy));
+ return EXIT_FAILURE;
+ }
+ }
+ if (pibs->filename[0]) {
+ process_file(pibs);
+ }
+ if (pibs->should_dump_table){
+ pibs_dump_raw(pibs);
+ }
+ if (pibs->show_stats){
+ pibs_dump_stats(pibs);
+ }
+ return EXIT_FAILURE;
+}