在glibc_heap的利用中,很可能会遇到没有show功能的程序,从而泄露不了地址,我们可以通过控制stdio,在通过puts等io操作输出之内的函数泄露libc地址
源码分析 _IO_puts() 首先我们看puts()函数的源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int _IO_puts (const char *str) { int result = EOF; _IO_size_t len = strlen (str); _IO_acquire_lock (_IO_stdout); if ((_IO_vtable_offset (_IO_stdout) != 0 || _IO_fwide (_IO_stdout, -1 ) == -1 ) && _IO_sputn (_IO_stdout, str, len) == len && _IO_putc_unlocked ('\n' , _IO_stdout) != EOF) result = MIN (INT_MAX, len + 1 ); _IO_release_lock (_IO_stdout); return result; }
这里我们要关注的是_IO_sputn
,因为只有它调用了str
参数,跳转到定义
1 #define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
AI的解释:
在glibc中,#define _IO_sputn(__fp, __s, __n)
是一个宏定义,它的作用是将一个函数或操作的调用转换为另一个函数或操作的调用。在这个宏中,_IO_XSPUTN
是实际执行操作的函数,__fp
、__s
和 __n
是传递给该函数的参数。
具体来说,这个宏的定义可以被分解为以下几点:
宏定义 :#define
是C语言中的宏定义指令,用于定义一个宏。这个宏在预处理阶段会被展开。
参数 :__fp
、__s
和__n
是这个宏接受的三个参数:
__fp
:通常是一个指向文件流的指针(FILE *
),表示要写入的目标文件流。
__s
:通常是一个指向字符数组的指针,表示要写入的数据。
__n
:表示要写入的字符数。
展开 :当在代码中调用_IO_sputn(fp, s, n)
时,预处理器会将其展开为_IO_XSPUTN(fp, s, n)
,从而调用_IO_XSPUTN
函数。
_IO_XSPUTN
通常是一个更底层的实现,用于处理输出到文件流的具体细节。它可能会处理缓冲、字符编码等。
假设你在代码中有如下调用:
1 _IO_sputn(fp, buffer, size);
在预处理阶段,这将被转换为:
1 _IO_XSPUTN(fp, buffer, size);
这样,实际的输出操作将由_IO_XSPUTN
函数执行,而_IO_sputn
宏提供了一个更高级的接口。
总结
_IO_sputn
宏用于将对输出操作的高级抽象映射到底层实现,便于代码的维护和可读性,同时也可能涉及到优化和条件编译等特性。
这也就说明了这个_IO_putn(__fp, __s, __n)
是一个虚表函数(网上好多是这样说的),虽然#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN(__fp, __s, __n)
本身是一个宏定义,并不涉及C++的虚表机制,但可以通过理解多态性来认识它的作用和设计理念。在C++中,宏可以简化接口,同时背后可能调用一些更为复杂的底层实现,类似于如何通过虚表和虚函数来实现动态绑定。这种设计使得代码更加灵活和可维护。
_IO_new_file_xsputn() 这个_IO_sputn()
最终调用的是_IO_new_file_xsputn(
)函数,在glibc/libio/fileops.c/1200行左右,这里我们要关注的是_IO_OVERFLOW()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 _IO_size_t _IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n) { const char *s = (const char *) data; _IO_size_t to_do = n; int must_flush = 0 ; _IO_size_t count = 0 ; if (n <= 0 ) return 0 ; if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING)) { count = f->_IO_buf_end - f->_IO_write_ptr; if (count >= n) { const char *p; for (p = s + n; p > s; ) { if (*--p == '\n' ) { count = p - s + 1 ; must_flush = 1 ; break ; } } } } else if (f->_IO_write_end > f->_IO_write_ptr) count = f->_IO_write_end - f->_IO_write_ptr; if (count > 0 ) { if (count > to_do) count = to_do; #ifdef _LIBC f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count); #else memcpy (f->_IO_write_ptr, s, count); f->_IO_write_ptr += count; #endif s += count; to_do -= count; } if (to_do + must_flush > 0 ) { _IO_size_t block_size, do_write; if (_IO_OVERFLOW (f, EOF) == EOF)
这个_IO_OVERFLOW
也是虚表函数,它实际调用的是_IO_new_file_overflow
_IO_new_file_overflow 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 int _IO_new_file_overflow (_IO_FILE *f, int ch) { if (f->_flags & _IO_NO_WRITES) { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF; } if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL ) { if (f->_IO_write_base == NULL ) { _IO_doallocbuf (f); _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base); } 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 ) 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_new_do_write() 跳转到_IO_new_do_write
,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 int _IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do) { return (to_do == 0 || (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF; } libc_hidden_ver (_IO_new_do_write, _IO_do_write) 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) fp->_offset = _IO_pos_BAD; else if (fp->_IO_read_end != fp->_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; }
test 随便运行一个程序,用gdb调试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 pwndbg> x/32gx &_IO_2_1_stdout_ 0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007ffff7dd26a3 0x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd26a4 0x0000000000000000 0x7ffff7dd2670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007ffff7dd18e0 0x7ffff7dd2690 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff 0x7ffff7dd26a0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007ffff7dd3780 0x7ffff7dd26b0 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000 0x7ffff7dd26c0 <_IO_2_1_stdout_+160>: 0x00007ffff7dd17a0 0x0000000000000000 0x7ffff7dd26d0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd26e0 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000 0x7ffff7dd26f0 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007ffff7dd06e0 0x7ffff7dd2700 <stderr>: 0x00007ffff7dd2540 0x00007ffff7dd2620 0x7ffff7dd2710 <stdin>: 0x00007ffff7dd18e0 0x00007ffff7a2db80 pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd2620 $1 = { file = { _flags = -72537977, _IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "" , _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>, _fileno = 1, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000' , _shortbuf = "\n" , _lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times > }, vtable = 0x7ffff7dd06e0 <_IO_file_jumps> }
查看一下_IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pwndbg> x/32gx 0x7ffff7dd26a3 0x7ffff7dd26a3 <_IO_2_1_stdout_+131>: 0xdd3780000000000a 0xffffff00007ffff7 0x7ffff7dd26b3 <_IO_2_1_stdout_+147>: 0x000000ffffffffff 0xdd17a00000000000 0x7ffff7dd26c3 <_IO_2_1_stdout_+163>: 0x00000000007ffff7 0x0000000000000000 0x7ffff7dd26d3 <_IO_2_1_stdout_+179>: 0x0000000000000000 0xffffff0000000000 0x7ffff7dd26e3 <_IO_2_1_stdout_+195>: 0x00000000000000ff 0x0000000000000000 0x7ffff7dd26f3 <_IO_2_1_stdout_+211>: 0xdd06e00000000000 0xdd254000007ffff7 0x7ffff7dd2703 <stderr+3>: 0xdd262000007ffff7 0xdd18e000007ffff7 0x7ffff7dd2713 <stdin+3>: 0xa2db8000007ffff7 0x00000000007ffff7 0x7ffff7dd2723 <map+3>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2733 <__printf_arginfo_table+3>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2743 <buf+3>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2753 <buffer+3>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2763 <buffer+3>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2773 <buffer+3>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2783 <buffer+3>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2793 <ttyname_buf+3>: 0x0000000000000000 0x0000000000000000
若我们通过低字节覆盖,将它低字节覆盖为\x00
,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pwndbg> x/32gx 0x7ffff7dd2600 0x7ffff7dd2600 <_IO_2_1_stderr_+192>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2610 <_IO_2_1_stderr_+208>: 0x0000000000000000 0x00007ffff7dd06e0#这里包含了libc中的值 0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007ffff7dd26a3 0x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd26a4 0x0000000000000000 0x7ffff7dd2670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007ffff7dd18e0 0x7ffff7dd2690 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff 0x7ffff7dd26a0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007ffff7dd3780 0x7ffff7dd26b0 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000 0x7ffff7dd26c0 <_IO_2_1_stdout_+160>: 0x00007ffff7dd17a0 0x0000000000000000 0x7ffff7dd26d0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd26e0 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000 0x7ffff7dd26f0 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007ffff7dd06e0 pwndbg>
它就会根据f->_IO_write_ptr - f->_IO_write_base
来输出从<_IO_2_1_stderr_+192>到 <_IO_2_1_stdout_+131>中间的内容
总结: 所以我们要泄露地址,首先要将 io_file
的 _IO_CURRENTLY_PUTTING
置 1,_IO_IS_APPENDING
0x1000 置 1,以及 _IO_NO_WRITES
0x8 不要为 1,就可以进行地址泄露了,一般可以覆盖为 0xfbad1800
,就可以泄露 write_base
和 write_ptr
之间的值,随便运行个程序,我们可以看到这里,可以看到 write_base
和 write_ptr
都是 libc
。
deepseek解释0xfbad1800:
在 _flags
字段 0xfbad1800
中,0x1000
和 0x8
对应的二进制位如下:
标志位解析 :
0xfbad1800
的二进制分解 :
将 0xfbad1800
转换为 32 位二进制:
1 2 3 4 5 1111 1011 1010 1101 0001 1000 0000 0000 ^ ^ | | | +-- 第 3 位 (0x8):0 +-- 第 12 位 (0x1000):1
其他关键标志 :
总结 :
0xfbad1800
的位设置 :
第 12 位(0x1000) :1(允许追加模式)。
第 3 位(0x8) :0(允许写入操作)。
第 11 位(0x800) :1(正在写入)。
通过覆盖 _flags
为 0xfbad1800
,可以满足以下条件:
启用 _IO_IS_APPENDING
和 _IO_CURRENTLY_PUTTING
。
禁用 _IO_NO_WRITES
,从而允许地址泄露。
调试 随便找一个输出菜单有puts的堆题,直接gdb运行起来
第一步,通过x/32gx &_IO_2_1_stdout_
来查看 stdout 的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 pwndbg> x/32gx &_IO_2_1_stdout_ 0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007ffff7dd26a3 0x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd26a4 0x0000000000000000 0x7ffff7dd2670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007ffff7dd18e0 0x7ffff7dd2690 <_IO_2_1_stdout_+112>: 0x0000000000000001 0xffffffffffffffff 0x7ffff7dd26a0 <_IO_2_1_stdout_+128>: 0x000000000a000000 0x00007ffff7dd3780 0x7ffff7dd26b0 <_IO_2_1_stdout_+144>: 0xffffffffffffffff 0x0000000000000000 0x7ffff7dd26c0 <_IO_2_1_stdout_+160>: 0x00007ffff7dd17a0 0x0000000000000000 0x7ffff7dd26d0 <_IO_2_1_stdout_+176>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd26e0 <_IO_2_1_stdout_+192>: 0x00000000ffffffff 0x0000000000000000 0x7ffff7dd26f0 <_IO_2_1_stdout_+208>: 0x0000000000000000 0x00007ffff7dd06e0 0x7ffff7dd2700 <stderr>: 0x00007ffff7dd2540 0x00007ffff7dd2620 0x7ffff7dd2710 <stdin>: 0x00007ffff7dd18e0 0x00007ffff7a2db80
第二步,通过p *(struct _IO_FILE_plus*)0x7ffff7dd2620
查看它的结构体信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 pwndbg> p *(struct _IO_FILE_plus*)0x7ffff7dd2620 $1 = { file = { _flags = -72537977, _IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "" , _IO_save_base = 0x0, _IO_backup_base = 0x0, _IO_save_end = 0x0, _markers = 0x0, _chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>, _fileno = 1, _flags2 = 0, _old_offset = -1, _cur_column = 0, _vtable_offset = 0 '\000' , _shortbuf = "\n" , _lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>, _offset = -1, _codecvt = 0x0, _wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>, _freeres_list = 0x0, _freeres_buf = 0x0, __pad5 = 0, _mode = -1, _unused2 = '\000' <repeats 19 times > }, vtable = 0x7ffff7dd06e0 <_IO_file_jumps> }
第三步,设置flag值
1 pwndbg> set *0x7ffff7dd2620 = 0xfbad1800
1 2 3 4 5 6 7 8 pwndbg> x/32gx &_IO_2_1_stdout_ 0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad1800 0x00007ffff7dd26a3 0x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd26a4 0x0000000000000000 0x7ffff7dd2670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007ffff7dd18e0
第四步,设置_IO_write_base低位为\x00
1 2 3 4 5 6 7 8 9 pwndbg> set *0x7ffff7dd2640 = 0x7ffff7dd2600 pwndbg> x/32gx &_IO_2_1_stdout_ 0x7ffff7dd2620 <_IO_2_1_stdout_>: 0x00000000fbad1800 0x00007ffff7dd26a3 0x7ffff7dd2630 <_IO_2_1_stdout_+16>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2640 <_IO_2_1_stdout_+32>: 0x00007ffff7dd2600 0x00007ffff7dd26a3 0x7ffff7dd2650 <_IO_2_1_stdout_+48>: 0x00007ffff7dd26a3 0x00007ffff7dd26a3 0x7ffff7dd2660 <_IO_2_1_stdout_+64>: 0x00007ffff7dd26a4 0x0000000000000000 0x7ffff7dd2670 <_IO_2_1_stdout_+80>: 0x0000000000000000 0x0000000000000000 0x7ffff7dd2680 <_IO_2_1_stdout_+96>: 0x0000000000000000 0x00007ffff7dd18e0
再看结构体:
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> p *(struct _IO_FILE_plus*)0x7ffff7dd2620 $4 = { file = { _flags = -72542208, _IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_write_base = 0x7ffff7dd2600 <_IO_2_1_stderr_+192> "" , _IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "\n" , _IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "" ,
最终,如下:
1 2 3 4 pwndbg> c Continuing. 1 ����&��&��&�&��&��&��&��&����������size:
可以看到在size: 前面多了一些东西,若我们在gdb的debug模式下就可以看到输出的是什么了。
例题 可以参考largebin_attack中的starctf_2019_heap_master
在exp中
1 2 3 4 5 print (p.recv(1 ))print (p.recv(24 ))data = p.recv(6 ) libc_base = uu64(data) - libc.sym['_IO_file_jumps' ] lg('libc_base' ,libc_base)
实现了接收泄露出的_IO_file_jumps
的libc地址,从而计算出libc_base