Decoding SSL traffic in HUGE wireshark captures

I needed to analyze a problem on a server, which required me to see through network dumps. Unfortunately, there’s a firewall between clients and this server, so i couldn’t filter on client IP. Which means there are zillions of connections in my .pcapng files, with only very few of them relevant. All in all, 137 GB of capture for one day.

A few of those files were small enough to be opened by wireshark. Decrypting the SSL content went well, after i made an entry for the server key in the SSL preferences.

Now, unfortunately, wireshark has big problems with files that are larger than, approximately, 2 GB. I captured one file per hour, with the largest files being 22 GB, so there’s no chance to open them. However, i found a program to split big capture files into single connections named splitpcap.

Unfortunately, that program can’t deal with pcapng files, so i had to convert them to pcap first:

"c:\Program Files\Wireshark\editcap.exe" -F libpcap apache_on_1368_00007_20150916171730.pcapng apache_on_1368_00007_20150916171730.pcap

then split them into one file per tcp connection:

d:\Softwaredownloads\SplitCap_2-1\SplitCap.exe -r apache_on_1368_00007_20150916171730.pcap -o apache_on_1368_00007_20150916171730

Then, i started opening those files. Unfortunately, SSL decryption didn’t work anymore.

What happened? Well, SSL uses sessions, which have session keys. Creating them takes a long time, which is why the first connection from a client will create the key, and all subsequent connections will share the same key. If i split the traffic into single connections, and happen to open one that isn’t the key-creating one, wireshark won’t find the key, and can’t decrypt the traffic.

This is a standard problem, so wireshark has an option to export the keys from one capture file, and re-use them when opening a different one. Unfortunately, this requires us to open the file first. And i can’t open those 22 GB files. Also, i really didn’t want to split my 137 GB into 137 files of 1 GB, load each of them, export the keys, and repeat.

However, you can use tshark as well to export the keys. And since tshark works packet by packet, instead of loading the whole file, it can deal with much larger files.

So I created two scripts. The first one, print_sessions.sh, is more or less a copy/paste from the wireshark forum, by User Syn-bit:

grep -A6 "ssl_save_session stored session id" |\
    sed -e 's/ //g' | \
    awk -F'|' '$1 ~ "ssl_save_sessionstoredsessionid" {printf("RSA Session-ID:");next}
    $1 ~ "ssl_save_sessionstoredmastersecret" {printf(" Master-Key:");next}
    $1 == "--" {printf("\n");next}
    {printf("%s",$1)}
    END {printf("\n")}'

And the second one, xtractsess.sh, creates a named pipe, runs one or more capture files through it, and uses print_sessions.sh to collect them into one single file:

rm -f /tmp/ssl_debug.pipe
mkfifo /tmp/ssl_debug.pipe
( ./print_sessions.sh < /tmp/ssl_debug.pipe | tee sslkeys.txt ) & sleep 86400 > /tmp/ssl_debug.pipe &

for file in "$@"; do
tshark -n \
    -o "ssl.desegment_ssl_records: TRUE" \
    -o "ssl.desegment_ssl_application_data: TRUE" \
    -o "ssl.keys_list:10.250.33.213,443,http,/mnt/local/D/userdata/myname/server.key" \
    -o "ssl.debug_file:/tmp/ssl_debug.pipe" \
    -r $1 -R "(tcp.port eq 443)" > /dev/null
done

kill $!

As you can see, the tshark command line was heavily inspired by Kurt Knochner from the same forum post.

Within the loop, all tsharks will dump to the same file (/tmp/ssl_debug.pipe), which is read by print_sessions.sh. However, each tshark will open/close the file, so normally the reader terminates after the first close. This is what the sleep is for: it keeps a writer process open until the last tshark terminates, so print_sessions won’t stop until it got everything.

Another problem is timestamps – unfortunately, sslsplit doesn’t do anything to the timestamps of the generated files, which means they all have the timestamp of the split operation. If you want to match those files to some external log files, finding the correct one is quite hard. This is why i wrote a small program to set the timestamp of a pcap file to the time of the first packet within it:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>
#include <stdio.h>
#include <stdint.h>
#include <errno.h>

#pragma pack(1)

struct pcap_header {
        uint32_t magic;
        uint16_t major;
        uint16_t minor;
        uint32_t offset;
        uint32_t accuracy;
        uint32_t snaplen;
        uint32_t llheader;
};

struct packet_header {
        uint32_t timestamp;
        /* rest irrelevant for this program */
};

void settime(char *filename);

int main(int argc, char **argv) {
        while (--argc)
                settime(*++argv);
}

void settime(char *filename) {
        int fd;
        struct pcap_header pcap_header;
        struct packet_header packet_header;
        struct utimbuf utimbuf;
        uint32_t timestamp;

        if ((fd=open(filename, O_RDONLY))==-1) {
                perror(filename);
                return;
        }
        if ((read(fd, &pcap_header, sizeof pcap_header))!=sizeof pcap_header
        ||     (pcap_header.magic != 0xa1b2c3d4
             && pcap_header.magic != 0xd4c3b2a1)) {
                fprintf(stderr, "%s: no valid pcap header\n", filename);
                close(fd);
                return;
        }
        if ((read(fd, &packet_header, sizeof packet_header))!=
                                                sizeof packet_header) {
                fprintf(stderr, "%s: no first packet\n", filename);
                close(fd);
                return;
        }
        close(fd);

        timestamp=packet_header.timestamp;
        if (pcap_header.magic == 0xd4c3b2a1) {
                timestamp=
                        ((timestamp>>24)&0xff)<< 0 |
                        ((timestamp>>16)&0xff)<< 8 |
                        ((timestamp>> 8)&0xff)<<16 |
                        ((timestamp>> 0)&0xff)<<24;
        }
        utimbuf.actime=utimbuf.modtime=timestamp;
        if (utime(filename, &utimbuf)==-1) {
                fprintf(stderr, "set time of %s: %s\n",
                        filename, strerror(errno));
        }
}

Leave a Reply

Your email address will not be published. Required fields are marked *