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