The guys over at abuse.ch who publish the ZeuS and SpyEye tracking lists among others have added another list, this time tracking the SSL certificates in use by various pieces of malware for C&C traffic. There are two types of lists, a list of fingerprints of observed certificates and a list of IP addresses associated with the traffic. The fingerprint list also comes as a ruleset for the Open Source (IDS/IPS) Suricata, unfortunately Snort doesn't support SSL/TLS fingerprinting so Snort users are out of luck (the IP lists would certainly be of some use). It also comes as a CSV.
This got me to thinking about other ways to do this without having a Suricata instance or doing this with historical data (maybe you run full packet capture at the gateway and don't discard ssl?). Granted for the historical use case you could just fire up Suricata and run the pcaps through it, but where's the fun in that? *nix has a utility called ssldump which looks at network data, live or pcap, and parses out the session information including the certificate. Once we have the certificate it can be fingerprinted with OpenSSL and compared to our list of known bad fingerprints.
This is still a work in progress and currently doesn't work with live traffic (something goes awry with the awk script I think) and I can't clain to have written all the code, more glued some bits and pieces together but it seems like it could be quite effective with a bit more fiddling. I'd be interested to see how it goes on a reasonable size data set. I still need to write something to grab the Source and Destination IP from the ssldump too.
ssldump -AN -r ssl.pcap | awk 'BEGIN {c=0;} { if ($0 ~ /^[ ]+Certificate$/) {c=1;}
if ($0 !~ /^ +/ ) {c=0;} if (c==1) print $0;} ' | tr "\\n" " " | sed 's/ *//g' |
perl sslbacklist.pl
The contents of the Perl script is as follows
use strict;
use warnings;
use Text::CSV;
use Data::Dumper;
my %bad_thumbprints;
my $csv = Text::CSV->new( { sep_char => ',' } );
my $file = 'sslblacklist.csv';
open( my $data, '<', $file ) or die "Could not open '$file' $!\n";
while ( my $line = <$data> ) {
chomp $line;
if ( $csv->parse($line) ) {
my @fields = $csv->fields();
if ( $fields[1] ) {
$bad_thumbprints{ $fields[1] } = $fields[2];
}
}
else {
warn "Line could not be parsed: $line\n";
}
}
my @certificates = split( /certificate\[\d+\]=/, <> );
foreach my $certificate (@certificates) {
unless ( $certificate eq 'Certificate' ) {
my $thumbprint = `echo $certificate | xxd -r -p | openssl x509 -inform der -fingerprint`;
$thumbprint = lc substr $thumbprint, 17, 59;
$thumbprint =~ s/://g;
if ( exists( $bad_thumbprints{$thumbprint} ) ) {
print
"ALERT: Bad Thumbprint ($thumbprint) detected indicating $bad_thumbprints{$thumbprint} malware \n";
}
}
}
References:
Awk script came from
http://serverfault.com/questions/313610/extracting-ssl-certificates-from-the-network-or-pcap-files
OpenSSL and xxd commands from
http://stackoverflow.com/questions/22211140/conversion-x-509-certificate-represented-as-a-hex-string-into-pem-encoded-x-509
edit: Updated Perl script to do the awk part and other cleanup, also extracts IP addresses
ssldump -ANn -r ssl.pcap | perl sslbacklist.pl
use strict;
use warnings;
use Text::CSV;
use Data::Dumper;
my %bad_thumbprints;
my $csv = Text::CSV->new( { sep_char => ',' } );
my $file = 'sslblacklist.csv';
open( my $data, '<', $file ) or die "Could not open '$file' $!\n";
while ( my $line = <$data> ) {
chomp $line;
if ( $csv->parse($line) ) {
my @fields = $csv->fields();
if ( $fields[1] ) {
$bad_thumbprints{ $fields[1] } = $fields[2];
}
}
else {
warn "Line could not be parsed: $line\n";
}
}
my $c = 0;
my $certificatestring;
my $source_ip;
my $dest_ip;
while (<>) {
chomp; # strip record separator
if ( $_ =~
m/New TCP connection #\d+: (\d+\.\d+\.\d+\.\d+)\(\d+\) <-> (\d+\.\d+\.\d+\.\d+)\(\d+\)/
)
{
$source_ip = $1;
$dest_ip = $2;
}
if ( $_ =~ /^[ ]+Certificate$/ ) {
$c = 1;
}
if ( $_ !~ /^ +/ ) {
$c = 0;
}
if ( $c == 1 ) {
$certificatestring = $certificatestring . $_;
}
}
$certificatestring =~ s/\n//g;
$certificatestring =~ s/ //g;
my @certificates = split( /certificate\[\d+\]=/, $certificatestring );
foreach my $certificate (@certificates) {
unless ( $certificate eq 'Certificate' ) {
my $thumbprint =
`echo $certificate | xxd -r -p | openssl x509 -inform der -fingerprint`;
$thumbprint = lc substr $thumbprint, 17, 59;
$thumbprint =~ s/://g;
if ( exists( $bad_thumbprints{$thumbprint} ) ) {
print
"ALERT: Bad Thumbprint ($thumbprint) detected indicating $bad_thumbprints{$thumbprint} malware. Source IP: $source_ip Dest IP: $dest_ip \n";
}
}
}
edit 2: now works when sniffing and needs a refactor...
sudo ssldump -ANn -i eth0 | perl sslbacklist.pl
use strict;
use warnings;
use Text::CSV;
my %bad_thumbprints;
my $csv = Text::CSV->new( { sep_char => ',' } );
my $file = 'sslblacklist.csv';
open( my $data, '<', $file ) or die "Could not open '$file' $!\n";
while ( my $line = <$data> ) {
chomp $line;
if ( $csv->parse($line) ) {
my @fields = $csv->fields();
if ( $fields[1] ) {
$bad_thumbprints{ $fields[1] } = $fields[2];
}
}
else {
warn "Line could not be parsed: $line\n";
}
}
my $c = 0;
my $certificatestring;
my $source_ip;
my $dest_ip;
my $connection_no;
while (<>) {
chomp; # strip record separator
if ( $_ =~
m/New TCP connection #(\d+): (\d+\.\d+\.\d+\.\d+)\(\d+\) <-> (\d+\.\d+\.\d+\.\d+)\(\d+\)/
)
{
$connection_no = $1;
$source_ip = $2;
$dest_ip = $3;
}
if ( $_ =~ /^\s+Certificate\s*$/ ) {
$c = 1;
}
if ( $_ !~ /^ +/ ) {
$c = 0;
}
if ( $c == 1 ) {
$certificatestring = $certificatestring . $_;
}
if ( $c == 0 && $certificatestring ) {
$certificatestring =~ s/\n//g;
$certificatestring =~ s/ //g;
my @certificates = split( /certificate\[\d+\]=/, $certificatestring );
foreach my $certificate (@certificates) {
unless ( $certificate eq 'Certificate' ) {
my $thumbprint =
`echo $certificate | xxd -r -p | openssl x509 -inform der -fingerprint`;
$thumbprint = lc substr $thumbprint, 17, 59;
$thumbprint =~ s/://g;
if ( exists( $bad_thumbprints{$thumbprint} ) ) {
print
"ALERT: Bad Thumbprint ($thumbprint) detected indicating $bad_thumbprints{$thumbprint} malware. Source IP: $source_ip Dest IP: $dest_ip \n";
}
}
}
$certificatestring = "";
}
}
There's now
a github for this
here