TUCoPS :: Malware :: badtra~1.txt

A little source code to parse the BadTrans worm's pilfered data

#!/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();



TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH