#!/usr/bin/perl -w #AIM Sniff Copyright (C) 2002 Shawn Grimes #This program is free software; you can redistribute it and/or #modify it under the terms of the GNU General Public License as #published by the Free Software Foundation; version 2 of the License. #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 General Public License for #more details. #You should have received a copy of the GNU General Public License along with #this program; if not, write to the #Free Software Foundation, Inc. #59 Temple Place, Suite 330 #Boston, MA 02111-1307 USA #You may also contact me directly with any questions at: #shawn@aimsniff.com #OR on AIM: spittingfire101 (let me know you are talking about AIM Sniff) #And of course the mailing list: aimsniff-devel@lists.sourceforge.net #And the NEW website: www.aimsniff.com #FOR SUPPORT FOLLOW THESE STEPS: #First scan the README and make sure you didn't miss anything #Second, scan the FAQ section at the http://www.aimsniff.com forums # (it's only like 3 entries to read) #Third, Do a search on the forums #Fourth and most importantly, If you use windows, don't even bother # asking me any questions I don't use windows so I can't help # you anyway. Try the forums, there is a section just for you. #Fifth, if you have done all of the above and still can't find an # answer, go ahead and email me or IM me. I will be very nice # so long as you have followed steps 1-4 use strict; use Net::Pcap; use NetPacket::Ethernet qw(:strip); use NetPacket::IP qw(:strip); use NetPacket::TCP; use NetPacket::UDP; use DBI; use Unicode::String qw(utf8 latin1 utf16); use Proc::Daemon; use Proc::Simple; use FileHandle; use Unix::Syslog; use GDBM_File; use Fcntl; autoflush STDOUT 1; ###################################################################### ######### User Defined Variables ######### ###################################################################### my $debug=0; my $debug2=1; my $debugmsg=""; my $daemonMode=0; my $driver="mysql"; my $database="aimsniff"; my $host="localhost"; my $user="root"; my $password=""; my $quiet=0; my $dev='eth0'; my $filter_str='port 5190'; my $promisc=1; my $to_ms=1000; my $handleFile; my $dumpHandles=0; my $outputFile; my $dumpfile; my $SMB = 1; my $nodb = 0; my $childCPUMaxPct=80; #Amount of cpu it can use before it restarts itself my $parentPollTimeout=10; my $useSyslog=0; my $ident="aimSniff"; my $facility=Unix::Syslog::LOG_LOCAL1; my $options=Unix::Syslog::LOG_PID | Unix::Syslog::LOG_PERROR; my $priority=Unix::Syslog::LOG_INFO; ###################################################################### ######### End User Defined Variables--Modify nothing below ######### ###################################################################### ###################################################################### # Parent Global Variables ###################################################################### my $child = 0; ###################################################################### # End parent global variables ###################################################################### ###################################################################### # Child global variables ###################################################################### my $msgCount = 0; my $loginCount = 0; my $fileCount = 0; my $chatCount = 0; my $buddyListCount = 0; my $pcap_t; my $err; my $getHandles; my $count = -1; ### These shouldn't be changed (yet) my $optimize=0; my $netmask=0; my $snaplen=50000; ###################################################################### # End child global variables ###################################################################### my($ver)='0.9d'; print "\n\n#############################\n"; print "AIM Sniff v. $ver\n"; print "Developed by: Shawn Grimes\n"; print "#############################\n\n"; #### #Get command line arguments #### &getOptions(@ARGV) || die "Could not get options\n"; sub kill_child() { if (defined($child)) { $child->kill("SIGQUIT"); while ($child->poll() == 1) { sleep(1); # wait for child to exit } } } sub ParentLeaveNow() { kill_child(); log_msg("Parent exiting"); unlink '/var/run/aimsniff_parent.pid'; exit; } sub launch_proc() { $child = Proc::Simple->new(); my $status = $child->start(\&start_AS); my $pid = $child->pid; log_msg("Started New Process, PID: $pid"); open(PID, ">/var/run/aimsniff.pid"); print PID $pid; close(PID); } sub save_parent_pid() { open(PID, ">/var/run/aimsniff_parent.pid"); print PID $$; close(PID); } sub open_syslog() { if ($useSyslog == 1) { Unix::Syslog::openlog $ident, $options, $facility || die "Couldn't open Syslog"; } } sub log_msg($) { my ($msg) = @_; if ($useSyslog == 1){ Unix::Syslog::syslog($priority, $msg); } else{ print $msg . "\n"; } } sub convert_to_sec($) { my ($time) = @_; my $result = 0; if ($time =~ /(\d+):(\d+):(\d+)/) { my ($hr, $min, $sec) = ($1, $2, $3); $result = $hr * 3600 + $min * 60 + $sec; } $result; } if($daemonMode == 1){ #Attempting to intercept ctrl-c $SIG{'QUIT'}=\&ParentLeaveNow; $SIG{'INT'}=\&ParentLeaveNow; my $status; Proc::Daemon::Init; &open_syslog(); &save_parent_pid(); log_msg("Running as daemon..."); &launch_proc(); my $lastUsage = 0; my $usageMax = $childCPUMaxPct == 0 ? 1 : $childCPUMaxPct / 100; log_msg("usageMax: $usageMax") if ($debug); while(1){ sleep($parentPollTimeout); $status = $child->poll(); if ($status == 0){ log_msg("Missing child, starting a new one"); &launch_proc(); } my $pid = $child->pid; my $time=`ps -p $pid --no-header --format time`; my $cpuUsage = &convert_to_sec($time); my $usagePct = ($cpuUsage - $lastUsage) / $parentPollTimeout; log_msg("PID: $pid CPU: $cpuUsage, last CPU: $lastUsage, Usage %: $usagePct") if ($debug); $lastUsage = $cpuUsage; if($usagePct > $usageMax ){ log_msg( "About to restart child due to excessive CPU utilization," . "PID: $pid CPU: $cpuUsage, last CPU: $lastUsage, Usage %: " . "$usagePct"); &kill_child(); &launch_proc(); $lastUsage = 0; } } } else { &start_AS; } sub start_AS { &open_syslog(); if ($daemonMode == 1) { $SIG{'QUIT'}=\&LeaveNow; $SIG{'INT'}=\&LeaveNow; $SIG{'HUP'}=\&dump_child_stats; } tieNameIp(); ### #Set counts=0 ### $msgCount=0; $loginCount=0; $fileCount=0; $chatCount=0; $buddyListCount=0; ################## if(defined($getHandles) && $getHandles == 1){ &getFromHandle || die "Could not get SMB Names\n"; ##Used to find out who messages are coming from in the database. }else{ if(defined($dumpfile)){ #work in offline mode? log_msg("Working in Offline Mode"); log_msg("nReading File: $dumpfile"); $pcap_t = Net::Pcap::open_offline($dumpfile, \$err); die "Error opening file: $err\n" if(!defined($pcap_t)); }else{ #actively sniff log_msg("Beginning Sniff...") if(!$quiet); log_msg("FILTER: $filter_str"); $pcap_t=Net::Pcap::open_live($dev, $snaplen, $promisc, $to_ms, \$err) || die "Error opening pcap: $err\n"; #start sniffing my $filter_t; my $result=Net::Pcap::compile($pcap_t, \$filter_t, $filter_str,$optimize,$netmask); #compile filter_str Net::Pcap::setfilter($pcap_t, $filter_t); #apply filter } Net::Pcap::loop($pcap_t, $count, \&process_pkt,"xyz"); #start to process the packets that are received &LeaveNow; } } #Process the packet sub process_pkt { #CHANGED #Mark Anacker's suggestion so that output can be piped through something else # STDOUT->autoflush; # STDOUT->autoflush(STDOUT); autoflush STDOUT 1; ### my($pktuser, $hdr, $pkt) = @_; if (!defined($hdr) or !defined($pkt)) { log_msg("Bad args passed to callback"); log_msg("Bad user data"), if ($user ne "xyz"); log_msg("Bad pkthdr"), if (!defined($hdr)); log_msg("Bad pkt data"), if (!defined($pkt)); log_msg("not ok"); exit; } my(%sqlAdd, $msg,$handle,$country,$language,$format); #get datetimestamp of packet my ($sec, $min, $hour, $mday, $mon, $year)=localtime($hdr->{tv_sec}); $year+=1900; $mon+=1; my $datestamp="$year-$mon-$mday $hour:$min:$sec"; #Strip Ethernet portion of packet off my $ip_obj=NetPacket::IP->decode(eth_strip($pkt)); my $srcip=$ip_obj->{src_ip}; my $dstip=$ip_obj->{dest_ip}; my $proto=$ip_obj->{proto}; my ($tcp_obj, $udp_obj, $flags, $srcport, $dstport, $dataset); if($proto==6){ $tcp_obj=NetPacket::TCP->decode(ip_strip(eth_strip($pkt))); $flags=$tcp_obj->{flags}; #make sure packet contains ACK PUSH #return if(!($flags==24 || $flags==10)); $srcport=$tcp_obj->{src_port}; $dstport=$tcp_obj->{dest_port}; $dataset=$tcp_obj->{data}; }elsif($proto==17){ $udp_obj=NetPacket::UDP->decode(ip_strip(eth_strip($pkt))); $srcport=$udp_obj->{src_port}; $dstport=$udp_obj->{dest_port}; $dataset=$udp_obj->{data}; } my ($family, $chanid)=undef; my ($destHandle, $fromHandle) = ("", ""); # if($srcport=='1863' || $dstport=='1863'){ # The start of MSN protocol analysis # &MSNparse($dataset); # }else{ #AIM Parse ($family,$dataset)=&familyFind($dataset); #get Family ID and SubID return if(!defined($family)||!defined($dataset)); # } #Have to better modularize so that plugins can be easily added #AIM Messages if ($family eq '0003000b' || $family eq '0003000c' || $family eq '0004000b' || $family eq '0004000c' || $family eq '0013000e' || $family eq '000b0002' || $family eq '0001001e' || $family eq '0001000e' ) { print("$family -- $dataset\n") if ($debug); return; } my $known=0; my @knownFamilies=qw(00040006 00040007 00170006 001700060170002 000e 000d0008 00130003 00130006 00020015 00020006); foreach my $testFamily(@knownFamilies){ $known=1 if($family=~/^$testFamily/); } if(!$known){ print "Unknown family: $family\n" if($debug); return; } if(($family=~/00040006/) or ($family=~/00040007/)){ #AIM Message $debugmsg="AIM Message"; ($chanid,$dataset)=&idFind($dataset); #Get 8 byte id before handle, not sure what it's for yet if($chanid == '0001'){ ($handle,$dataset)=&handleFind($dataset); #Get AIM Handle $msg=&msgFind($dataset); #Get the message # log_msg("\$chanid=$chanid defined(\$handle)=" . defined($handle) . # " defined(\$dataset)=" . defined($dataset) . # " defined(\$msg)=" . defined($msg)); if($family == '00040007' and $handle and $msg){ $debugmsg="Incoming Message"; #Incoming messages $destHandle=getNameIp($dstip); %sqlAdd=('table','logs','ts',$datestamp,'fromHandle',$handle,'handle',$destHandle,'direction',$family, 'message', $msg,'ip', $dstip); $msg="AIM##TYPE##Incoming Message##TS##$datestamp##FAMILY##$family##FROM##$handle##DESTHANDLE##$destHandle##DESTIP##$dstip##MESSAGE##$msg"; $msgCount++; }elsif($family=='00040006' and $handle and $msg){ $debugmsg="Outgoing Message"; #Outgoing messages $fromHandle=getNameIp($srcip); %sqlAdd=('table',"logs",'ts',$datestamp,'fromHandle',$fromHandle,'handle',$handle, 'direction',$family, 'message', $msg,'ip', $srcip) if($handle and $msg); $msg="AIM##TYPE##Outgoing Message##TS##$datestamp##FAMILY##$family##FROM##$fromHandle##DESTHANDLE##$handle##SRCIP##$srcip##MESSAGE##$msg"; $msgCount++; } }elsif($chanid == '0002'){ $debugmsg="File Xfer"; my($handle, $capString, $lang, $format, $fileMsg, $ip, $port, $filename)=&getFile($dataset); if($handle and $ip and $port and $filename){ $msg="File Xfer Detected:
\nCapabilities: $capString
\nLanguage: $lang
\nFormat:$format
\nMSG: $fileMsg
\nIP: $ip
\nPort: $port
\nFile: $filename
\n"; %sqlAdd=('table','logs','ts',$datestamp,'handle',$handle,'direction',$family,'message',$msg,'ip',$srcip); $msg="AIM##TYPE##File Xfer##TS##$datestamp##FAMILY##$family##SRCIP##$ip##PORT##$port##CAPSTRING##$capString##LANG##$lang##FORMAT##$format##HANDL##$handle##MESSAGE##$fileMsg##FILE##$filename"; $fileCount++; } } #AIM Logins }elsif($family eq '00170006'){ #AIM Login $debugmsg="AIM Login"; $handle=&getSignon($dataset); #Get AIM Handle setNameIp($srcip, $handle); if($SMB == 1){ log_msg("Getting smb info for $srcip") if ($debug); my ($SMBMachine, $SMBUser)=&SMBInfo($srcip); log_msg("SMBMachine=$SMBMachine") if ($debug && $SMBMachine); log_msg("SMBUser=$SMBUser") if ($debug && $SMBUser); log_msg("handle=$handle") if ($debug && $handle); $SMBMachine="UNKNOWN" if(!$SMBMachine); $SMBUser="UNKNOWN" if(!$SMBUser); if($handle){ %sqlAdd=('table',"handles",'ts',$datestamp,'handle',$handle, 'username',$SMBUser,'machine',$SMBMachine,'ip',$srcip); $msg="AIM##TYPE##Login##TS##$datestamp##FAMILY##$family##SRCIP##$srcip##HANDLE##$handle##SMBUser##$SMBUser##SMBMachine##$SMBMachine"; } }else{ if($handle){ %sqlAdd=('table',"handles",'ts',$datestamp,'handle',$handle,'ip',$srcip); $msg="AIM##TYPE##Login##TS##$datestamp##FAMILY##$family##SRCIP##$srcip##HANDL##$handle"; } } $loginCount++; #AIM Version Information }elsif($family eq '001700060170002'){ $debugmsg="Version Information"; my ($version, $country, $language); ($handle, $version, $country, $language)=&getVersion($dataset); $debugmsg="Got Version"; if($handle and $version){ $msg="Version Information:\n$handle is using $version with country=$country and language=$language\n\n"; %sqlAdd=('table',"versions",'ts',$datestamp,'ip',$srcip, 'handle',$handle, 'version', $version,'country', $country, 'language',$language) if($handle and $version); $msg="AIM##TYPE##Version Information##TS##$datestamp##FAMILY##$family##SRCIP##$srcip##HANDL##$handle##VERSION##$version##COUNTRY##$country##LANGUAGE##$language"; } #Chat Information }elsif($family=~/^000e/){ $debugmsg="Chat Info"; #Format a %sqlAdd statement #Format a @printOUT statement ($chanid,$dataset)=&idFind($dataset); ($handle,$format,$language,$msg)=&getChats($family,$dataset); $debugmsg="Got Chats"; my ($direction, $ip); if($handle eq ''){ $direction=$srcip . '-> Chatroom'; $ip=$srcip; }else{ $direction=$handle . '->' . $dstip; $ip=$dstip; } $msg=&msgClean($msg); if($msg){ $msg="AIM##TYPE##Chat Message##TS##$datestamp##FAMILY##$family##IP##$ip##HANDL##$handle##FORMAT##$format##LANGUAGE##$language##MESSAGE##$msg"; $chatCount++; } #Chat room join }elsif($family=~/^000d0008/){ $debugmsg="Chat join"; ($chanid,$dataset)=&idFind($dataset); my $room; ($format, $language, $room)=&getChatJoin($dataset); $debugmsg="Got chat join"; if($format and $language and $room){ $msg="***CHAT ROOM JOIN****
\n$srcip joined $room
\nFORMAT: $format
\nLANGUAGE: $language
\n\n"; %sqlAdd=('table', "logs",'ts',$datestamp,'handle',"",'direction',$family,'message',$msg,'ip',$srcip); $msg="AIM##TYPE##Chat Room Join##TS##$datestamp##FAMILY##$family##SRCIP##$srcip##FORMAT##$format##LANGUAGE##$language##ROOM##$room"; } #Buddy List }elsif($family=~/^00130003/){ $debugmsg="Get Buddies"; $dataset=~s/^([a-f0-9]{4})//; $flags=$1; $dataset=~s/^([a-f0-9]{8})//; my $SNACreqID=$1; if($dataset=~s/^.*2a02[a-f0-9]{8}00130006//){ %sqlAdd=('table','buddies','ts',$datestamp,'ip',$dstip); my %buddylist=&getBuddies($dataset); $debugmsg="Got Buddies"; $msg="AIM##TYPE##Buddy List##TS##$datestamp##FAMILY##00130006##DSTIP##$dstip"; my $x = 0; my $key; foreach $key(keys(%buddylist)){ $x++; $sqlAdd{$key}=substr($buddylist{$key},1); $msg.="##GROUP$x##$key:" . substr($buddylist{$key},1); } $buddyListCount++; } #Buddy list too (sometimes I found that the buddy list is sent with the 0013003 family as a sub packet (see above elsif statemetn) }elsif($family=~/^00130006/){ $debugmsg="Get other buddies"; %sqlAdd=('table','buddies','ts',$datestamp,'ip',$dstip); my %buddylist=&getBuddies($dataset); $debugmsg="got buddies"; $msg="AIM##TYPE##Buddy List##TS##$datestamp##FAMILY##00130006##DSTIP##$dstip"; my $x = 0; my $key; foreach $key(keys(%buddylist)){ $x++; $sqlAdd{$key}=substr($buddylist{$key},1); $msg.="##GROUP$x##$key:" . substr($buddylist{$key},1); } $buddyListCount++; }elsif($family=~/^00020015/){ if($dataset=~s/0000[a-f0-9]{4}001500000001([a-f0-9]{2})//){ my $buddySize=hex($1)*2; $dataset=~s/^([a-f0-9]{$buddySize})//; my $buddy=&convertHex($1); $msg="AIM##TYPE##Buddy Info Request##TS##$datestamp##FAMILY##00020015##SRCIP##$srcip##BUDDY##$buddy" if($buddy) ; %sqlAdd=('table','logs','ts',$datestamp,'ip',$srcip,'direction','00020015','message',"****Buddy Info Request****
\n   $buddy\n") if($buddy); } }elsif($family=~/^00020006/){ if($dataset=~s/^000000[a-f0-9]{2}0015([a-f0-9]{2})//){ my $buddySize=hex($1)*2; $dataset=~s/^([a-f0-9]{$buddySize})//; my $buddy=&convertHex($1); if($dataset=~s/.*00030004[a-f0-9]{8}0001001f[a-f0-9]{62}0002([a-f0-9]{4})//){ my $infoSize=hex($1)*2; $dataset=~s/^([a-f0-9]{$infoSize})//; my $info=&convertHex($1); %sqlAdd=('table','logs','ts',$datestamp,'ip',$dstip,'direction','00020006','message',"****Buddy Info Reply****
\n   $buddy
\n   $info\n") if($info && $buddy); $info=&msgClean($info); $msg="AIM##TYPE##Buddy Info##TS##$datestamp##FAMILY##00020006##DSTIP##$dstip##BUDDY##$buddy##INFO##$info" if($info && $buddy); } } } &mysqlAdd(%sqlAdd) if(!$nodb and $sqlAdd{'table'}); &printOUT($quiet, $msg, $outputFile) if($msg); } sub familyFind{ #Find family id my($dataset)=@_; my ($family, $dataportion); return 0 if(!(length($dataset) > 0)); #Exit if $dataset is not > 0 if (length($dataset) % 2 != 0) { $dataset .= "0"; } $dataportion=utf16($dataset); $dataportion=$dataportion->hex; $dataportion=~s/U\+//g; if($dataportion=~/^([0-9a-f]{4}\s){20}2a02/){ $dataportion=~s/^([0-9a-f]{4}\s){20}//; } if($dataportion=~/^2a02/){ if ($dataportion=~s/^2a02 [0-9a-f]{4} [0-9a-f]{4} (00[a-f0-9]{2} 00[a-f0-9]{2})//){ $family=$1; $family=~s/\s//g; } $dataportion=~s/\s//g; }else{ return 0; } return($family,$dataportion); } sub getSignon{ #Find AIM Handle my($dataset)=@_; my($handle, $lengthHandle)=""; $dataset=~s/^.*000100([0-9a-f]{2})//; $lengthHandle=hex($1); $dataset=~s/^(([a-f0-9]{2}){$lengthHandle})//; $handle=&convertHex($1); $handle=~s/\s//; return $handle; } sub SMBInfo{ #Get SMB machine name and username my($ip)=@_; my(@SMBInfo)=`nmblookup -A $ip`; my($SMBInfo, $SMBName, $username); if($SMBInfo[1]!~/ACTIVE/){ log_msg("Didn't find nmblookup entry: $SMBInfo[1]") if ($debug); $SMBName=""; $username=""; }else{ $SMBName=$SMBInfo[1]; $SMBName=~/(.*)\<.*/; $SMBName=$1; $SMBName=~s/\s//; $SMBName=~s/\<.*//; $SMBName=utf8($SMBName); $SMBName=$SMBName->hex; $SMBName=~s/U\+00//g; $SMBName=~s/\s20//g; my @digis=split(/\s/,$SMBName); $SMBName=""; my ($digi, $te); foreach $digi(@digis){ $te=chr(hex($digi)); $SMBName.=$te; } ##Get user name $count=2; if($SMBInfo){ while($SMBInfo[$SMBInfo-$count]=~/SMS/ || $SMBInfo[$SMBInfo-$count]!~/03/){ #Don't use SMS accounts $count++; } $username=$SMBInfo[$SMBInfo-$count]; $username=~/(.*)\s*\<03\>.*/; $username=$1; $username=~s/\s//; $username=utf8($username); $username=$username->hex; $username=~s/U\+00//g; $username=~s/\s20//g; @digis=split(/\s/,$username); $username=""; foreach $digi(@digis){ $te=chr(hex($digi)); $username.=$te; } } } return $SMBName, $username; } sub idFind{ #Gets 8 byte id before handle length and handle is sent my($dataset)=@_; $dataset=~s/^[0-9a-f]{28}([a-f0-9]{4})//; my $chanid=$1; return $chanid, $dataset; } sub handleFind{ #Find AIM handle my($dataset)=@_; my($handle, $lengthHandle); $handle=""; $dataset=~s/^([a-f0-9]{2})//; $lengthHandle=hex($1)*2; $dataset=~s/^([a-f0-9]{$lengthHandle})//; $handle=&convertHex($1); $handle=~s/\s//; return $handle, $dataset; } sub msgFind{ #Find AIM msg my($dataset)=@_; my $msg=""; my $hexcomb=$dataset; $hexcomb=~s/.*01010?2?010100([a-f0-9]{2})//; my $length=hex($1)-4; $hexcomb=~s/00000000(.*)[00.*]?//; $msg=$1; $msg=~s/00.*//; $msg=&convertHex($msg); return $msg } sub msgClean{ #Remove HTML tags & NULL Chars from message my($msg)=@_; my $null=pack('@',0); #Thanks to Zoli $msg=~s/<([^>]|\n)*>//g; $msg=~s/\<\/.*//; $msg=~s/$null//g; return $msg; } sub mysqlAdd{ my(%values)=@_; my($table)=delete $values{'table'}; my($sql)=""; my ($key, $value, $sth); my $dsn="DBI:$driver:database=$database:host=$host:user=$user:password=$password"; my $dbh=DBI->connect($dsn) || return 0; $dbh->{RaiseError}=0; #Needed special case for buddy list if($table eq 'buddies') { # $sql="DELETE * FROM buddies WHERE ip='$values{'ip'}'"; # $sth=$dbh->prepare($sql) || log_msg("Error preparing $DBI::errstr"); my($datestamp)=delete $values{'ts'}; my($ip)=delete $values{'ip'}; foreach $key(keys %values){ #for each buddy group, add an entry $sql="INSERT INTO $table SET ts='$datestamp', ip='$ip', "; $value=$dbh->quote($values{$key}); $key=$dbh->quote($key); $sql.= "buddygroup=$key, buddylist=$value"; $sth=$dbh->prepare($sql) || log_msg("Error preparing $DBI::errstr"); $sth->execute || log_msg("Error executing $DBI::errstr"); # log_msg("$sql"); } # log_msg("$sql"); } else { $sql="INSERT INTO $table SET "; foreach $key(keys %values){ $value=$dbh->quote($values{$key}); $value=~s/\\0//g; $sql.=" $key=$value,"; } chop($sql); $sth=$dbh->prepare($sql) || log_msg("problem preparing: ".$dbh->errstr); $sth->execute || log_msg("Error executing $DBI::errstr"); } # log_msg("SQL: $sql") if ($debug); } sub printOUT{ my($quiet, $msg, $outputFile)=@_; my($count,%printValues)=""; if(defined($outputFile)){ open(OUTPUT, ">>$outputFile") || die "Could not open file: $^E\n"; print OUTPUT "$msg\n" || die "Could not print to file\n"; close(OUTPUT); } if(!$quiet){ #parse $msg to print it in a friendly manner my($key,$value)=''; my($x)=0; #print "$msg\n"; my ($heading, $part); while($msg=~/\#\#/){ $msg=~s/\#\#(.*)//; if($x==0){ $heading=$msg; }else{ if(($x%2)==0){ $value=&msgClean($msg); $printValues{$key}=$value; }else{ $key=$msg; } } $part=$1; $msg=$part; $x++; } $printValues{$key}=&msgClean($part); print "$heading\n\tType: $printValues{'TYPE'}\n\tTimestamp: $printValues{'TS'}\n"; delete $printValues{'TS'}; delete $printValues{'TYPE'}; foreach $key(keys(%printValues)){ if($key=~/GROUP/){ $printValues{$key}=~s/(.*)://; my $buddyGroup=$1; print "\tGROUP: $buddyGroup\n"; my @members=split(/,/,$printValues{$key}); my $member; foreach $member(@members){ print "\t\t\t$member\n"; } }else{ print "\t$key: $printValues{$key}\n"; } } print "\n\n"; return; my @values=split(/\#\#/,$msg); if($values[1] eq '00040006' or $values[1] eq '00040007'){ $count=@values; if($count > 5){ #file xfer print "\n\n****FILE XFER****\n"; print "\t$values[0]\n"; print "\tFrom: $values[2]:$values[3]\n"; if($values[1]=~/0006/){ print "\tTO: $values[7]\n"; }else{ print "\tFROM: $values[7]\n"; } print "\tMSG: " . &msgClean($values[8]) . "\n"; print "\tFileName: $values[9]\n"; }else{ print "\n\n######MESSAGE#####\n$values[1]\n"; print "\t$values[0]\n"; if($values[1]=~/0006/){ print "\tFROM: $values[3]\n"; print "\tTO: $values[2]\n"; }else{ print "\tFROM: $values[2]\n"; print "\tTO: $values[3]\n"; } print "\tMSG: " . &msgClean($values[4]) . "\n"; } }elsif($values[1] eq '00170006'){ print "\n\n!!!!!!!AIM LOGIN!!!!!!!!\n"; print "\tAt $values[0]\n"; if($values[4]){ print "\t$values[4] on $values[5] ($values[2]) logged on as $values[3]\n"; }else{ print "\t$values[3] is logging on from $values[2]\n"; } }elsif($values[1] eq '00170002'){ print "\n\n@@@@@\@AIM Version@@@@@@\n"; print "\tAt $values[0]\n"; print "\t$values[3] on $values[2] is using $values[4]\n"; print "\tCountry: $values[5]\tLanguage: $values[6]\n"; }elsif($values[1]=~/000e/){ print "\n\n$$$$$\$Chat Message$$$$$\n"; print "\tAt $values[0]\n"; if($values[3] eq ''){ print "\t$values[2] sent a message to a chatroom\n"; print "\tMSG: " . &msgClean($values[6]) . "\n"; }else{ print "\t$values[3] received:\n"; print "\t" . &msgClean($values[6]) . "\n"; } }elsif($values[1] eq '000d0008'){ print "\n\n%%%%%%%\%Chatroom Login%%%%%%\n"; print "\tAt $values[0]\n"; print "\t$values[2] joined $values[5]\n"; }elsif($values[1] eq '00130006'){ print "\n\n^^^^^^^Buddy List^^^^^^^^\n"; print "\tAt $values[0]\n"; print "\t$values[2] got the following list of buddies:\n"; splice(@values,0,3); foreach $value (@values){ my @group=split(":",$value); my @buddies=split(",",$group[1]); my $buddy; print "\t\t$group[0]:\n"; foreach $buddy(@buddies){ print "\t\t\t$buddy\n"; } } }else{ print "\n\nUNKOWNK FAMILY\n"; foreach $value(@values){ print "$value\n"; } } } } sub getFromHandle{ log_msg("Beginning handle lookups"); my $dsn="DBI:$driver:database=$database:host=$host:user=$user:password=$password"; my $dbh=DBI->connect($dsn) || return 0; $dbh->{RaiseError}=0; my $sql="SELECT id, ip, ts FROM logs WHERE direction='00040006' and fromHandle is NULL or fromHandle=''"; my $sth=$dbh->prepare($sql) || die "problem preparing: ",$dbh->errstr,"\n"; my ($id, $ip, $ts, @values, $handle, $sql2, $sth2); $sth->execute || die "Error executing $DBI::errstr\n"; $sth->bind_col(1, \$id); $sth->bind_col(2, \$ip); $sth->bind_col(3, \$ts); while(@values=$sth->fetchrow_array()){ $handle=""; $sql2="SELECT handle FROM handles WHERE ip='$ip' AND ts<'$ts' order by ts desc limit 1"; $sth2=$dbh->prepare($sql2) || die "Problem preparing: ",$dbh->errstr,"\n"; $sth2->execute || die "Error executing $DBI::errstr\n"; $sth2->bind_col(1, \$handle); my @row=$sth2->fetchrow_array(); if($handle){ $sql2="UPDATE logs SET fromHandle='$handle' WHERE id='$id'"; #Get the last handle logged in from that ip and before that timestamp $sth2=$dbh->prepare($sql2) || die "Problem preparing: ",$dbh->errstr,"\n"; $sth2->execute || die "Error executing $DBI::errstr\n"; } } $sql="SELECT id, ip, ts FROM logs WHERE (direction='00040007' or direction='00020006' or direction='00020015') and handle is NULL or handle=''"; $sth=$dbh->prepare($sql) || die "problem preparing: ",$dbh->errstr,"\n"; $sth->execute || die "Error executing $DBI::errstr\n"; $sth->bind_col(1, \$id); $sth->bind_col(2, \$ip); $sth->bind_col(3, \$ts); while(@values=$sth->fetchrow_array()){ $handle=""; $sql2="SELECT handle FROM handles WHERE ip='$ip' AND ts<'$ts' order by ts desc limit 1"; $sth2=$dbh->prepare($sql2) || die "Problem preparing: ",$dbh->errstr,"\n"; $sth2->execute || die "Error executing $DBI::errstr\n"; $sth2->bind_col(1, \$handle); my @row=$sth2->fetchrow_array(); if($handle){ $sql2="UPDATE logs SET handle='$handle' WHERE id='$id'"; #Get the last handle logged in from that ip and before that timestamp $sth2=$dbh->prepare($sql2) || die "Problem preparing: ",$dbh->errstr,"\n"; $sth2->execute || die "Error executing $DBI::errstr\n"; } } log_msg("Lookups complete"); return 1; } sub getVersion{ my($dataset)=@_; my ($handle, $hlength, $version, $language, $country, $type, $value)=""; $dataset=~s/^[a-f0-9]{12}//; while( ($type!~/4a/) && ($type ne "")){ #Changed thanks to Zoli $dataset=~s/^([a-f0-9]{4})00([a-f0-9]{2})//; $type=$1; my $len=(hex($2) * 2); $dataset=~s/([a-f0-9]{$len})//; $value=$1; if($type=~/0001/){ $handle=&convertHex($value); }elsif($type=~/0003/){ $version=&convertHex($value); }elsif($type=~/000f/){ $language=&convertHex($value); }elsif($type=~/000e/){ $country=&convertHex($value); } } return $handle, $version, $country, $language; } sub getFile{ my($dataset)=@_; my($handle, $lenHandle, $ip,$msg,$filename,$lang,$format,$type,$len,$value)=""; $dataset=~s/^([a-f0-9]{2})//; $lenHandle=hex($1) * 2; $dataset=~s/^([a-f0-9]{$lenHandle})//; $handle=&convertHex($1); $dataset=~s/0005(00[a-f0-9]{2})//; #CHANGED #commented out to get rid of error message # $lenPacket=hex($1); $dataset=~s/([a-f0-9]{4})//; my $flags=$1; if($flags=='0000'){ $dataset=~s/([a-f0-9]{16})//; my $cookie=$1; my $port; #check if it is a file xfer my $capString=$1 if($dataset=~s/.*(0946[a-f0-9]*53540000)//); if($capString){ $type='1'; while($type && $dataset && (length($dataset)>8)){ $dataset=~s/^([a-f0-9]{4})([a-f0-9]{4})//; $type=$1;$len=(hex($2) * 2); #log_msg("TYPE: $type\tVALUE: $len"); #log_msg("DATA: $dataset"); #error checking to make sure that the length is not longer then what is left if($type!~/000f/ && ($len<=length($dataset))){ $dataset=~s/([a-f0-9]{$len})//; $value=$1; } if($type=~/000e/){ #Language $lang=&convertHex($value); }elsif($type=~/000d/){ #Format $format=&convertHex($value); }elsif($type=~/2711/){ #File Name $value=~s/^00010001[a-f0-9]{8}//; $value=~s/00.*//; $filename=&convertHex($value); }elsif($type=~/0005/){ #Port Number $port=hex($value); }elsif($type=~/0003/){ #IP Address my @octets=split('([a-f0-9]{2})',$value); my $octet; foreach $octet(@octets){ $octet=hex($octet) if ($octet); $ip.=$octet . "." if($octet); } $ip=~s/\.$//; }elsif($type=~/000c/){ #Message $msg=&convertHex($value); #exit out }elsif(length($dataset)<9){ $dataset=''; } } } return ($handle, $capString, $lang, $format, $msg, $ip, $port, $filename); }else{ return 0; } } #function to handle Chat events sub getChats{ my($family, $dataset)=@_; my($lenMsg,$handleset,$lenHandle,$handle,$type,$len,$value,$format,$language,$msg)=''; if($family=~/0006/){ #incoming message $dataset=~s/^0003([a-f0-9]{4})//; $lenMsg=hex($1)*2; $dataset=~s/^([a-f0-9]{$lenMsg})//; $handleset=$1; $handleset=~s/^([a-f0-9]{2})//; $lenHandle=hex($1)*2; $handleset=~s/^([a-f0-9]{$lenHandle})//; $handle=&convertHex($1); }elsif($family=~/0005/){ #outgoing message $dataset=~s/[a-f0-9]{24}//; $handle=''; }else{ return 0; #not ready for any other chat commands yet } $type=1; while($type && $dataset){ $dataset=~s/^([a-f0-9]{4})([a-f0-9]{4})//; $type=$1;$len=(hex($2)*2); if($type!~/0005/ && ($len<=length($dataset))){ $dataset=~s/([a-f0-9]{$len})//; $value=$1; } if($type=~/0002/){ $format=&convertHex($value); }elsif($type=~/0003/){ $language=&convertHex($value); }elsif($type=~/0001/){ $msg=&convertHex($value); }elsif(length($dataset)<9){ $dataset=''; } } return ($handle,$format,$language,$msg) } sub getChatJoin{ my($dataset)=@_; my($room,$language,$format,$value)=''; my $type=1; while($type && $dataset){ $dataset=~s/^([a-f0-9]{4})([a-f0-9]{4})//; $type=$1; my $len=(hex($2)*2); if($type!~/ff01/ && ($len<=length($dataset))){ $dataset=~s/([a-f0-9]{$len})//; $value=$1; } if($type=~/00d6/){ $format=&convertHex($value); }elsif($type=~/00d7/){ $language=&convertHex($value); }elsif($type=~/00d3/){ $room=&convertHex($value); }elsif(length($dataset)<9){ $dataset=''; } } if($format and $language and $room){ return ($format, $language, $room); }else{ return 1; } } #added this function to convert hex to string for me sub convertHex{ my($value)=@_; my($incr,$bit,$msg)=""; for($incr=0;$incr=32 && hex($bit)<=126){ $msg.=chr(hex($bit)); } } return $msg; } #Function to get buddy list sub getBuddies{ my($dataset)=@_; my($verSSI,$revNum,$lenItem,$nameItem,$groupID,$buddyID,$typeItem,$lenAddData,$AddData,$type,$len,$AddValue,$key,$buddy,%buddylist,%groups)=""; $dataset=~s/^[a-f0-9]{12}([a-f0-9]{2})//; $verSSI=$1; $dataset=~s/^([a-f0-9]{4})//; $revNum=$1; #Now comes list of items while(length($dataset)>4){ #Changed thanks to Zoli $dataset=~s/^([a-f0-9]{4})//; $lenItem=hex($1) * 2; if($lenItem8)){ $AddData=~s/^([a-f0-9]{4})([a-f0-9]{4})//; $type=$1;$len=hex($2) * 2; if($len<=length($AddData)){ $AddData=~s/^([a-f0-9]{$len})//; $AddValue=$1; } } } }else{ return(%buddylist); } } return(%buddylist); } sub print_config() { print "Running with:\n"; print "\tdumpfile=$dumpfile\n" if defined($dumpfile); print "\thandleFile=$handleFile\n" if defined($handleFile); print "\tdumpHandles=$dumpHandles\n" if defined($dumpHandles); print "\tpacketCount=$count\n" if defined($count); print "\tdev=$dev\n" if defined($dev); print "\tfilter=$filter_str\n" if defined($filter_str); print "\tpromisc=$promisc\n" if defined($promisc); print "\ttimeout=$to_ms\n" if defined($to_ms); print "\tSMB=$SMB\n" if defined($SMB); print "\tDaemon Mode=$daemonMode\n" if defined($daemonMode); print "\tnodb=$nodb\n" if defined($nodb); print "\tquiet=$quiet\n" if defined($quiet); print "\thost=$host\n" if defined($host); print "\tuser=$user\n" if defined($user); print "\tpassword=****\n" if defined($password); print "\tgetHandles Mode=$getHandles\n" if defined($getHandles); print "\tuseSyslog=$useSyslog\n" if defined($useSyslog); print "\tparentPollTimeout=$parentPollTimeout\n" if defined($parentPollTimeout); print "\tchildCPUMaxPct=$childCPUMaxPct\n" if defined($childCPUMaxPct); } sub getOptions{ my(@ARGV)=@_; my $arg; if(scalar(@ARGV) > 0 && $ARGV[0]=~/-C=(.*)/){ my $configfile=$1; open(CONFIG,"$configfile") || log_msg("Error opening config file, using defaults instead."); while(){ $arg=$_; if($arg!~/^\#/){ $handleFile=$1 if($arg=~/handlefile=(.*)/); $dumpHandles=$1 if($arg=~/dumpHandles=(.*)/); $dumpfile=$1 if($arg=~/dumpfile=(.*)/); $outputFile=$1 if($arg=~/outputFile=(.*)/); $count=$1 if($arg=~/packetCount=(.*)/); $dev=$1 if($arg=~/dev=(.*)/); $filter_str=$1 if($arg=~/filter='(.*)'/); $promisc=1 if ($arg=~/promisc=1/); $to_ms=$1 if ($arg=~/timeout=(.*)/); $nodb=1 if ($arg=~/nodb=1/); $quiet=1 if ($arg=~/quiet=1/); $SMB=1 if ($arg=~/SMB=1/); $daemonMode=1 if ($arg=~/daemon=1/); $host=$1 if ($arg=~/host=(.*)/); $user=$1 if ($arg=~/user=(.*)/); $debug=$1 if ($arg=~/debug=(.*)/); $password=$1 if ($arg=~/password=(.*)/); $database=$1 if($arg=~/database=(.*)/); $useSyslog=$1 if ($arg=~/useSyslog=(.*)/); $parentPollTimeout=$1 if ($arg=~/parentPollTimeout=(.*)/); $childCPUMaxPct=$1 if ($arg=~/childCPUMaxPct=(.*)/); } } if (scalar(@ARGV) > 1 && $ARGV[1] =~ /--getHandles/){ $getHandles=1; $daemonMode=0; # don't bother b/c we want to exit when we're done } close(CONFIG); print_config(); }else{ foreach $arg (@ARGV){ $getHandles=1 if($arg=~/--getHandles/); $dumpHandles=$1 if($arg=~/--dumpHandles/); $dumpfile=$1 if($arg=~/-r=(.*)/); $handleFile=$1 if($arg=~/-HF=(.*)/); $outputFile=$1 if($arg=~/-O=(.*)/); $count=$1 if($arg=~/-c=(.*)/); $dev=$1 if($arg=~/-d=(.*)/); $filter_str=$1 if($arg=~/-f=(.*)/); $promisc=1 if ($arg=~/-p/); $to_ms=$1 if ($arg=~/-to=(.*)/); $nodb=1 if ($arg=~/--nodb/); $quiet=1 if ($arg=~/--quiet/); $SMB=1 if ($arg=~/--SMB/); $daemonMode=1 if ($arg=~/-D/); if($arg=~/--h/ or $arg=~/-h/){ print "**************\nAimSniff $ver\n**************\n"; print "AIM Sniff Copyright (C) 2002 Shawn Grimes\n"; print "This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2.\n"; print "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 General Public License for more details.\n"; print "You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n"; print "You may also contact me directly with any questions at: grimessh\@users.sourceforge.net\n"; print "\n###########\nTo use:\n"; print "\t-C=filename <-Get AIM Sniff options from a config file\n"; print "\t-r=filename <-Read a PCAP file instead of doing a live capture\n"; print "\t-O=filename <-Output to a file\n"; print "\t-HF=filename <-Output handle/ip hash\n"; print "\t-c=integer <-The number of packets to read before quitting\n"; print "\t-d=dev <-The device to capture packets from\n"; print "\t-f='filter string' <-String to filter on enclosed in single quotes\n\t\t(DEFAULT: 'tcp and port 5190') -- Should only have to be specified if you think AIM is running on a different port\n"; print "\t-p <-Place the device into promiscuous mode\n"; print "\t-to=integer <-Read timeout in ms\n"; print "\t--SMB <-Turn SMB lookups 'on' to get NT domain usernames with AIM logins, Off by default\n"; print "\t--nodb <-Do not dump to a DB, only dump to STDOUT\n"; print "\t--quiet <-Do not print anything but errors to STDOUT\n"; print "\t-D <-Run as daemon\n"; print "\t--getHandles <-Do not do anything with PCAP but populate the fromHandle field in the logs table\n"; print "\t--dumpHandles <--Dumps the hash containing AIM Handles and IP Addresses to syslog\n"; print "\nDefaults:\n"; print "\t-d=eth0\n\t-f='tcp and port 5190'\n\t-p\n\t-to=1000\n"; exit; } } } $daemonMode=0 if($dumpfile); return 1; } sub MSNparse{ my($dataset)=@_; my($dataportion,@values,$type,%msnHash)=""; #Need to convert to hex to parse easier if((length($dataset) > 0) or ((length($dataset) % 2) != 0)){ $dataportion=utf16($dataset); $dataportion=$dataportion->hex; $dataportion=~s/U\+//g; $dataportion=~s/\s//g; } if($dataportion=~s/0d0a/ /g){ if($dataportion=~s/\s\s(.*)//){ $msnHash{'Message'}=&convertHex($1); } @values=split(/\s/,$dataportion); $type=shift(@values); $type=&convertHex($type); my $value; foreach $value(@values){ $value=~/(.*)3a20(.*)/; my $key=$1; $value=$2; $key=&convertHex($key); $value=&convertHex($value); $msnHash{$key}=$value; } if(!$msnHash{'TypingUser'}){ log_msg("TYPE: $type"); my $key; foreach $key (keys(%msnHash)){ log_msg("$key=$msnHash{$key}"); } log_msg("#######################################"); log_msg("#######################################"); } } # while($dataset){ # $dataset=~s/(.*) } sub dump_child_stats() { my %stats; Net::Pcap::stats($pcap_t, \%stats); log_msg("Packets Received: $stats{'ps_recv'}"); log_msg("Packets Dropped: $stats{'ps_drop'}"); log_msg("Packets Dropped by interface: $stats{'ps_ifdrop'}"); log_msg("Messages found: $msgCount"); log_msg("Logins found: $loginCount"); log_msg("Buddy List Captured: $buddyListCount"); log_msg("File Xfers: $fileCount"); log_msg("Chat Messages: $chatCount"); } sub LeaveNow{ log_msg("Child exiting"); if($debug2){ unlink("/tmp/AS.log"); sysopen(LOG,'/tmp/AS.log',O_WRONLY|O_EXCL|O_CREAT,0600); print LOG "$debugmsg\n"; close(LOG); } &dump_child_stats(); untieNameIp(); unlink '/var/run/aimsniff.pid'; exit; } my %nameIPs; sub tieNameIp() { if (defined($handleFile)) { tie(%nameIPs, 'GDBM_File', $handleFile, &GDBM_WRCREAT, 0600, ) || die "Can't tie nameIPs: $!"; } if ($dumpHandles == 1) { log_msg('Handle File Dump Follows...'); my ($key, $val); while (($key, $val) = each %nameIPs) { log_msg($key . ' = ' . $val); } } } sub untieNameIp() { untie %nameIPs if tied(%nameIPs); } sub setNameIp($$) { my ($ip, $name) = @_; $nameIPs{$ip} = $name; log_msg("Set $name for IP $ip") if ($debug); } sub getNameIp($) { my ($ip) = @_; my $name = (defined $nameIPs{$ip} ? $nameIPs{$ip} : ''); log_msg("Returning \"$name\" for $ip") if ($debug); $name; }