Hi everyone, this is the writeup for the challenge House-of-loop in the AceBear Security Contest 2019
You may want to checkout the exploit code
We are given a stripped ELF x64 binary which can be interacted with, our task is to get remote code execution(RCE).
The binary presents us 3 options: Create, View and Delete a note.
When creating a note, we have 3 fields: Title, Private, and Description.
We have the limits on
Private field length but with Description, we can use as much as we specified.
When viewing a note, we can view the
Description fields but not Private.
When deleting a note, we specify the
Title of the note which we want to delete and then it will be removed from the view.
I spent 1h30 to reverse and understand the binary and decompile the binary.
The fully decompiled program is at here.
EDIT: Almost forgot, I used
syms2elf plugin to get the function symbols into the executable (a.k.a de-strip) :)
When creating a note, it
malloc(144) for the note, note’s title is at offset 96 of the struct.
The title is null-terminated at offset 25 although we can write 32bytes.
Private data is written to the beginning(offset 0).
Lastly, It asks us for the description size, malloc enough data, zero it out, finally read in an exact number of bytes.
The address of the description is then saved in its field.
Then the program goes on a check if it was the first note created or loop through the singly-linked list until the
next_note field is null, it puts the address of the newly-created note there.
When deleting a note, it loops through the linked list to find the note with the matched title, unlink it from the singly-linked-list then free its data and itself.
While reversing the binary, I found out that the
next_note field is not initialized at the time of creating and clear it at the time of deletion.
So it left a dangling pointer there and also picked up the dangling pointer which is left there earlier.
So that opens us a use-after-free vulnerability.
Meanwhile, it creates a problem that if we are careless, we can put the program in an endless loop by having the
next_note field points (directly/indirectly) to itself.
After hours of trying the program, I also found a critical logic bug.
The chunk that is malloc-ed to store the Description is memset by the size we entered but not the actual chunk size.
Which means, if we create a 0-sized description, it will then
malloc(0), which gives 16 bytes, then
memset(chunk,0,0) (which is nonsense).
So, we got an information disclosure of anything that was there before :)
[+] checksec for '/root/house_of_loop/house_of_loop' Canary: Yes NX: Yes PIE: Yes Fortify: No RelRO: Full
To leak a pointer, we can use the 0-sized description to leak the FD, BK of that chunk.
FYI, FD, BK pointers are used when a chunk is freed to points to the next free one.
That’s cool, we can easily leak the heap address.
But what about libc? Where can I find it?
Since glibc 2.26 with the introduction of
tcache, we have a libc-info-leak
A chunk inside the unsorted bin will have a pointer to an libc address in the
fd if that is the last chunk and in
bk, if it was the first one.
In my exploit, at line 67, I allocate a description that is big enough to go to the unsorted-bin, then I freed it, try to re-allocate that chunk with a 0-sized description. Because it was the last chunk, there will be a pointer :)
There was a reason why I also free the last chunk allocated. If we don’t do so, the last chunk we allocated will point to that chunk, but the dangling pointer there will throw us a ∞ loop
In other words, let consider the heap currently be allocated like this
| A | B | C | D | v ^ v ^ v ^ └──────────┘ └──────────┘ └──────┘
A is that big chunk, D is the last one we allocated.
The line represents the linked list pointer to the next note. They don’t go away upon deleting.
When you free A, then reallocate it, it will stay at the same position but with the pointer.
D will the points to A(the next one allocated) then A->B->C->D => ∞ loop
By also free D after A, these things happen:
next_note(which is 0 because it is the last one) will be saved to C, which marks C as the last note.
Because of malloc’s natural of a first-fit algorithm, the reallocated one will have the struct lied on D’s location and the description at A’s description.
Because D has
next_notefield equal to 0 so it will be the last note(the previous one is C)
The address will be at the description of the last allocated note.
And that’s how you leak the libc address.
You can see the execution trace at here
From UAF to ACE
So the only field that was not initialized was
next_note, so how can we control it?
To leave there an arbitrary pointer, we need to create another note that has the description size is the same size as the note structure.
Then we can attempt to re-allocate the description(which we have full control) as the note structure.
To re-allocate it, firstly, we will need to allocate a note that has the description’s size strictly greater than the note structure.
By doing that, we can make sure that our crafted struct is not being used as the description of the other note.
Then, the next note we allocate will lie on our crafted struct
| A | data of A(144)| B | data of B(144)| | crafted |
| C | D | B | data of B(144)| Data of C(144+) |
But…, what will we put there…???
Okay, we will set up an arbitrary-write primitive.
The GOT is Fully Protected, which mean it is read-only
Now, we are introducing to you the MALLOC HOOKS
The malloc hooks are available for debugging heap purpose, and it’s executed before any malloc operations
If the data at variable
__malloc_hook is not NULL, it will be executed.
So, if we write the address we want there, and we will have ACE (Arbitrary Code Execution)
Ideally, you may want to write an address of a
one_gadget there for the sake of simplicity.
Let’s check out this piece of code:
As you can see, the program finds the note that we want to delete then does this piece of code:
prev_note->next_note = tbf_note->next_note;
It takes the address of the note that goes after the note will want to free, and then make it be the
next_note of the note that comes before the one we want to free.
| A | B | C | v ^ v ^ └──────────┘ └──────────┘
| A | freed | C | v ^ └────────────────────────┘
Basically, what it does it unlink that note from the linked list
Hey, have you learned the old-school dlmalloc unlink exploit yet?
The idea is simple,
Let’s make the A note lie on somewhere near
- Use the _use-after-free
vuln to make__free_hook
address as thenext_note`
- Use the _use-after-free
Create a new note from A(Let’s called it B)
Then we unlink B off the linked_list
Anything at B->next_note will be put in A->next_note !!!! (Isn’t that Arbitrary Write?)
=> Let’s make
__free_hook is also the
next_note field of A :)
Isn’t that the famous dlmalloc unlink exploit? ;)
Friendly Reminder: Don’t forget to fill up the heap holes you created by any stage of this exploit :)
chung96vn for creating this challenge(or the opportunity for me to learn heap exploit :))
You, Yes. You, for staying till this end of this writeup :)