|
#!/bin/perl #-------------------------------------------------------------------- # https://badtrans.monkeybrains.net/ for more information. # # Parse the badtrans keylogger emails. # http://securityresponse.symantec.com/avcenter/venc/data/w32.badtrans.b@mm.html # Version 1.0 # # Grab Subject line from header -- subject line is an identifier for # the hosts machine. Usually, the users email address. # Next, rip out passwords and then sort keystroked info. # Passwords come like this: # Res: _RESOURCE_, Pas: _PASSWORD_ # Info comes as keystokes from programs in this format: # Title: "_TITLE_", _TIMESTAMP_\n_MESSAGE_ # Some login thing (currently discarding this info): # _TIMESTAMP_, Computer: "_COMPUTER_" User: "_USER_" # # From the Password lines, there types of data are gleaned: # 1) website passwords # 2) Remote Network Access passwords # 3) WebPost info (used by the Microsoft OS) # # Project Methodolgy: # 1) parse data and store in db. # 2) classify data (eg title types: email, sms, document, etc) # 3) create frontend for viewing data # # Notes: # need to update MySQL to use the FULLTEXT text search feature. #-------------------------------------------------------------------- #-------------------------------------------------------------------- # INSTRUCTIONS: # To use this software, you need to set the message_separator variable, # and set up a mysql database to load the parsed data. To use the # parsed data, you need a frontend for viewing the data, or you need # to be proficient in command line sql statements. Here is the sql desc: # CREATE TABLE res ( # id int(9) unsigned DEFAULT '0' NOT NULL auto_increment PRIMARY KEY, # kind enum('rna','webpost','webpost2','ftp','basicauth') NOT NULL, # username varchar(64) DEFAULT '' NOT NULL, # password varchar(64) DEFAULT '' NOT NULL, # realm varchar(64) DEFAULT '' NOT NULL, # owner_id int(9) unsigned DEFAULT '0' NOT NULL, # KEY byRealm (realm), # KEY byKind (kind) # ); # # CREATE TABLE message ( # id int(9) unsigned DEFAULT '0' NOT NULL auto_increment PRIMARY KEY, # kind enum('document','browser','email','chat','excel','na') NOT NULL, # title varchar(64) DEFAULT '' NOT NULL, # body text DEFAULT '' NOT NULL, # owner_id int(9) unsigned DEFAULT '0' NOT NULL, # KEY byKind (kind), # #FULLTEXT (body) # ); # # CREATE TABLE owner ( # id int(9) unsigned DEFAULT '0' NOT NULL auto_increment PRIMARY KEY, # email varchar(64) DEFAULT '' NOT NULL, # KEY (email) # ); # Read this link: http://www.mysql.com/doc/F/u/Fulltext_Search.html # Adding the index after entering the data is faster than having an # index during the INSERTs. #-------------------------------------------------------------------- $filename = shift; if ($filename =~ /\.gz$/) { open IN, "/usr/bin/gunzip -c $filename |" or die "they say...\nUsage: $0 filename\n"; } else { open IN, "<$filename" or die "they say...\nUsage: $0 filename\n"; } #-------------------------------------------------------------------- # every message begins with a spiffy record separator. Set it. #-------------------------------------------------------------------- $DEBUG = undef; my $message_separator = 'From suck_my_prick@ijustgotfired.com'; $/ = $message_separator; # We are going to process one email at a time <IN> eq $message_separator or die "Bad start, I quit!\n"; #-------------------------------------------------------------------- # connect to database and grab handle. #-------------------------------------------------------------------- use DBI; # pulls in all the code from the DBI module. my ($dbh, $sql_resource, $sql_message, $sql_owner, $sth_owner_select); { my %db = qw (info dbi:mysql:smp:localhost user smp pass TruthSetFree); $dbh = DBI->connect($db{'info'}, $db{'user'}, $db{'pass'} ) or die "No db connection"; } $sql_resource = "INSERT into res VALUES (NULL,?,?,?,?,?) ;"; $sql_message = "INSERT into message VALUES (NULL,?,?,?,?) ;"; $sql_owner = "INSERT into owner VALUES (NULL,?) ;"; $sth_owner_select = $dbh->prepare(q{ SELECT id FROM owner WHERE email = ? }); open UNKNOWN_RES,'>unknown_resources.txt' or die "Fark!"; #-------------------------------------------------------------------- # start reading in the STDIN #-------------------------------------------------------------------- $| = 666; my $total_messages; while (<IN>) { $total_messages++; $total_messages =~ /00$/o and (print '.'); $total_messages =~ /000$/o and (print 'o'); my ($passwords_found,$subject,$oid); # id owner - this is not anonymous ;) /Subject: (.*)/o or do { print "no Subject line!\n" if $DEBUG; next; }; $subject = $1 || 'NA'; #print STDERR "Subject: $subject\n" if $DEBUG; $sth_owner_select->execute($subject); $oid = ($sth_owner_select->fetchrow_array())[0] or do { $dbh->do($sql_owner,undef,($subject)); $sth_owner_select->execute($subject); $oid = ($sth_owner_select->fetchrow_array())[0] or 0; }; # grab passwords my ($webpost_site, $webpost_username, $webpost_password); while (s/^Res: (.*), Pas: (.*)$//om) { my ($type, $user, $password, $realm); my ($res, $pas) = ($1, $2); ($res eq 'MAPI' or $res eq 'NNTP') and next; #useless info? if ($res =~ /^\*Rna\\([^\\]*)\\(.*)$/o) { ($type, $user, $password, $realm) = ('rna', $2, $pas, $1); } elsif ($res =~ /^\*WebPost\\(.*)$/o) { if (/^\*WebPost\\HKCU\\Software\\Microsoft\\WebPost\\Sites\\(.*)\\UserName$/o) { if ($webpost_site eq $1) { ($type, $user, $password, $realm) = ('webpost', $pas, $webpost_password, $webpost_site); undef $webpost_site; } else { $webpost_site = $1; $webpost_username = $pas; next; } } elsif ($res =~ /^\*WebPost\\HKCU\\Software\\Microsoft\\WebPost\\Sites\\(.*)\\Password$/o) { if ($webpost_site eq $1) { ($type, $user, $password, $realm) = ('webpost', $webpost_username, $pas, $webpost_site); undef $webpost_site; } else { $webpost_site = $1; $webpost_password = $pas; next; } } else { ($type, $user, $password, $realm) = ('webpost2', undef, $pas, $1); undef $webpost_site; #probably not needed } } elsif ($pas =~ /^(.*):(.*)$/o) { ($1 and $2) or next; $res =~ s!^http://!!; ($type, $user, $password, $realm) = ('basicauth', $1, $2, $res); } elsif ($res =~ /^ftp:\/\/(.+)\@(.+)$/o) { ($type, $user, $password, $realm) = ('ftp', $1, $pas, $2); } else { print UNKNOWN_RES $&; next; } $user ||= 'NA'; $password ||= 'NA'; $realm ||= 'NA'; $passwords_found++; $dbh->do($sql_resource,undef,($type, $user, $password, $realm, $oid)); } #print STDERR "Passwords found: $passwords_found\n" if $DEBUG; # ignore domain login crap (to ignore it, substitute it OUT of the message. s/[A-Z][a-z]{2}, \d\d [A-Z][a-z]{2} \d{4} \d\d:\d\d:\d\d, Computer: ".*?" User: ".*?"//og; # grab the keystroke sniffer stuff. $_ .= 'Title: "'; while (s/Title: "(.*?)", (\d\d:\d\d:\d\d)(.*?)(Title: ")/$4/os) { my ($title, $time, $body) = ($1, $2, $3); $title ||= 'NONE'; $body =~ /\w\w\w/o or next; #skip if body is emptyish $body =~ s/^\s+//o; #clean $body =~ s/\s+$//o; #clean $body =~ s/[\015\012]+/\012/og; if ($title =~ /Microsoft Word|Notepad/o) {$type = 'document';} elsif ($title =~ /^(Internet Explorer|Netscape|Mozilla)/o) {$type = 'browser';} elsif ($title =~ /(America Online|SMS|Messaggio|AOL|Instant Message|Chat|Conversation)/o) {$type = 'chat';} elsif ($title =~ /^(Fwd?|Re):/o) {$type = 'email';} elsif ($title =~ /(Outlook|Euroda)/o) {$type = 'email';} elsif ($title =~ /Microsoft Excel/o) {$type = 'excel';} else {$type = 'na';} $dbh->do($sql_message,undef,($type, $title, $body, $oid)) or (print STDERR "$DBI::errstr\n"); } #print STDERR "\n LEFT OVER CRAP [$_]\n"; } $sth_owner_select->finish(); $dbh->disconnect();