Info
As usual, the vulnerable file is here
gef➤ !file ./feap
./feap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=67b9e845e43f9d9b32307836545c649d0390c822, stripped
gef➤ checksec
[+] checksec for '/home/vagrant/feap'
Canary: Yes
NX Support: Yes
PIE Support: No
No RPATH: Yes
No RUNPATH: Yes
Partial RelRO: Yes
Full RelRO: No
feap is a binary that allows you to add/edit/delete a list of notes. The notes are stored in a table locate in the .bss at 0x6020a8. This table can hold 20 notes. A note has the following structure:
00000000 struct __notes
00000000 name db 64 dup(?) # 64 bytes
00000040 body dq # sizeof(malloc-ed block)-64
00000048 __notes ends
For each note at offset i, the total size of the malloc-block can be found at the offset i of the table notes_sizes located at 0x6020b0, i.e. notes_sizes[i] = sizeof(notes[i]).
To manipulate the notes, the program offers several options via a simple menu with different choices:
- function at 0x400D2B allows to add new notes (later called
add_note()), - function at 0x400C5A allows to delete new notes (later called
del_note()), - function at 0x400AFC allows to edit a note (later called
edit_note()), - function at 0x40096D allows to print all notes (later called
print_all_notes()), - function at 0x0400A1D allows to print one note (later called
print_note()), exit()the program
Vulnerability
Two vulnerabilities were found:
- a lack of boundary check in
print_note()function allows to read at arbitrary address of the memory space; - a heap overflow in the
edit_note()function allows to overwrite adjacent chunks.
Memory leak
When printing a specific note, the user is prompted for a note index. However, this index is not checked, meaning that any submitted value outside the boundary of the notes table (i.e. between [0, 19]) will leak the process memory.
print_note()
{
int result; // rax@2
unsigned int v1; // [sp+Ch] [bp-4h]@1
printf("Please enter note id to print: ");
__isoc99_scanf("%d", &v1);
if ( notes[v1] ) {
printf("ID: %d\n", v1);
printf("Title: %s\n", notes[v1]);
printf("Body: %s\n", &notes[v1]->body);
result = 0LL;
} else
[...]
notes and notes_sizes are 2 adjacently allocated chunks of size 160 bytes. This implies that attempting to reach notes[20] will land in notes_sizes prev_size section, and notes[21] in notes_sizes size section. So by accessing notes[22] we will attempt to read the content pointed by notes_sizes[0]. So to read at address ADDR in the memory layout, we must:
- create a note of size
ADDR-64 - print the note at offset 22
(as implemented in leak_memory function)
This leak not only allows us to dump heap addresses, but also GOT addresses (such as puts@got, free@got, printf@got) etc. which defeats the library randomization.
Heap overflow
The function edit_note(), allows to edit the content of a note, by editing its name of its body.
When editing the body, the function calls fgets() on note[i]->body, but with a size to read of the entire chunk (name + body).
So we have 64 bytes (i.e. sizeof(note.name) ) that we can overwrite in the next chunk.
Exploitation
On top of the vulnerabilities mentioned earlier, we have total control over the size of a call to malloc() in the add_note() function (at 0x400DAE). This is the perfect scenario for a House of Force exploitation.
The House of Force technique has already been used in previous post so I won’t detail it as much (no pretty ascii art this time ). But the idea stays the same:
- allocate a first note;
- allocate a second note, whose malloc-ed size will drop us into the GOT;
- overwrite
notes[1]chunk headers by editingnotes[0]; - create a last chunk that will actually overwrite the desired address.
free@got.plt at 0x602018 is a good candidate to be overwritten, so we need to create a size of free_location - notes_location. With a few adjustments we get:
sz = free_location - notes_location - 512
s.read_until("Enter note body size: ")
s.write("{}\n".format(sz))
Now we know that free@got.plt will be overwritten by the note->name of the next note created.
With the memory leak explained above, we can dynamically get several addresses in the GOT. Using libcdb on those 2 addresses we can know the libc version and therefore the offset of the system() function:
system_addr = libc_base + 0x00046640
All we have left to do, is to write "/bin/sh" as the note->name of the note we will want to delete.
ok("Create 1st note")
s.read_until("> ")
s.write("1\n")
s.read_until("Enter note body size: ")
s.write("2\n")
s.read_until(": ")
s.write("/bin/sh\0\n")
Put it all together in the complete exploit and you get the flag: