ASIS CTF 2016 - feap write-up

hugsy 9 May 2016

Reading time: 5 min

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]). image_alt

To manipulate the notes, the program offers several options via a simple menu with different choices:

  1. function at 0x400D2B allows to add new notes (later called add_note()),
  2. function at 0x400C5A allows to delete new notes (later called del_note()),
  3. function at 0x400AFC allows to edit a note (later called edit_note()),
  4. function at 0x40096D allows to print all notes (later called print_all_notes()),
  5. function at 0x0400A1D allows to print one note (later called print_note()),
  6. 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:

  1. create a note of size ADDR-64
  2. 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). edit_note_overflow

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:

  1. allocate a first note;
  2. allocate a second note, whose malloc-ed size will drop us into the GOT;
  3. overwrite notes[1] chunk headers by editing notes[0];
  4. 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: flag