利用 _IO_2_1_stdout_泄露libc
1. FILE 結(jié)構(gòu)體
pwndbg> p stdout
$1 = (struct _IO_FILE *) 0x7ffff7dd0760 <_IO_2_1_stdout_>
pwndbg> ptype stdout
type = struct _IO_FILE {
int _flags;
char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_base; // 本質(zhì)上是通過修改這個(gè)結(jié)構(gòu)題泄露
char *_IO_write_ptr; // 這兩個(gè)指針地址之間的內(nèi)容
char *_IO_write_end;
char *_IO_buf_base;
char *_IO_buf_end;
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
__off_t _old_offset;
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
__off64_t _offset;
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
size_t __pad5;
int _mode;
char _unused2[20];
} *
結(jié)構(gòu)體中的flags
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000
_IO_2_1_stdout_一般是這樣的,也就是 0xfbad2887:
_IO_MAGIC|_IO_IS_FILEBUF|_IO_CURRENTLY_PUTTING|_IO_LINKED|_IO_NO_READS | _IO_UNBUFFERED |_IO_USER_BUF
如何leak
為了泄露處一些libc上的地址,我們需要修改 _IO_2_1_stdout_的_flags、_IO_write_base、_IO_write_ptr。步驟如下:
- 一般我們將
_flags設(shè)置為0xfbad18**。目的是為了設(shè)置_IO_CURRENTLY_PUTTING=0x800,_IO_IS_APPENDING=0x1000,IO_MAGIC=0xFBAD0000(后續(xù)看puts實(shí)現(xiàn)就會(huì)知道為什么要這樣設(shè)置) - 設(shè)置
_IO_write_base指向想要泄露的地方;_IO_write_ptr指向泄露結(jié)束的地址。 - 之后遇到puts或printf,就會(huì)將_IO_write_base指向的內(nèi)容打印出來。
常見payload
p64(0xfbad1800)+p64(0x0)*3+'\x00'
至于為什么將以下三個(gè)指針設(shè)置為0,原因在put函數(shù)的執(zhí)行流程中可以一探究竟。
char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
2. put函數(shù)執(zhí)行流程
IO_sputn
- 實(shí)際最終調(diào)用的是
_IO_2_1_stdout_的vtable中的__xsputn,也就是_IO_new_file_xsputn函數(shù)。
最終會(huì)調(diào)用到_IO_OVERFLOW,也就是_IO_new_file_overflow,可以看到這里要求 #define _IO_NO_WRITES 8 /* Writing not allowd */標(biāo)志位不為1,否則就會(huì)返回錯(cuò)誤。
int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
// 將 #define _IO_CURRENTLY_PUTTING 0x800置為1 繞過這個(gè)if判斷,通常沒有輸出過的話是0,程序輸出過就為1
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
{
/* Allocate a buffer if needed. */
if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
/* Otherwise must be currently reading.
If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
logically slide the buffer forwards one block (by setting the
read pointers to all point at the beginning of the block). This
makes room for subsequent output.
Otherwise, set the read pointers to _IO_read_end (leaving that
alone, so it can continue to correspond to the external position). */
if (__glibc_unlikely (_IO_in_backup (f)))
{
size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
_IO_free_backup_area (f);
f->_IO_read_base -= MIN (nbackup,
f->_IO_read_base - f->_IO_buf_base);
f->_IO_read_ptr = f->_IO_read_base;
}
if (f->_IO_read_ptr == f->_IO_buf_end)
f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
f->_IO_write_ptr = f->_IO_read_ptr;
f->_IO_write_base = f->_IO_write_ptr;
f->_IO_write_end = f->_IO_buf_end;
f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;
f->_flags |= _IO_CURRENTLY_PUTTING;
if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
f->_IO_write_end = f->_IO_write_ptr;
}
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base);
if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return EOF;
*f->_IO_write_ptr++ = ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (_IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
return EOF;
return (unsigned char) ch;
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)
IO_do_write
- 會(huì)跳進(jìn)
new_do_write函數(shù)
static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING) // 所以這里要設(shè)置 _IO_IS_APPENDING 為1,否則不會(huì)有輸出
/* On a system without a proper O_APPEND implementation,
you would need to sys_seek(0, SEEK_END) here, but is
not needed nor desirable for Unix- or Posix-like systems.
Instead, just indicate that offset (before and after) is
unpredictable. */
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base) // 或者設(shè)置stdout->_IO_read_end == stdout->_IO_write_base
{
_IO_off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);
if (fp->_cur_column && count)
fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}
3. 利用
? 一般都是利用unsorted bin的在tcache或fastbin的fd上main_arena的地址(同一個(gè)chunk既在unsortbin上,也在tcache上),配合UAF之類的漏洞覆蓋低位爆破stdout的地址,然后分配到stdout結(jié)構(gòu)體處,達(dá)到泄露的目的。
? 對(duì)于沒有tcache的 glibc 版本,使用 fastbin attack就好,因?yàn)?code>_IO_2_1_stdout_上面就是_IO_2_1_stderr_, stderr->__pad2一般是指向_IO_wide_data_2的指針,而_IO_wide_data_2是在libc中的,所以我們可以用其來偽造size。