关于缓冲区的一点学习
(pwn)出题的时候没什么特殊需求都会对stdout
,stdin
设置:
setvbuf(stdout,0,2,0);
setvbuf(stdin,0,2,0);
但是一直对缓冲区只有个模糊的概念..这次完整地学一遍…
缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。
总的来说就是提高计算机速度。
定义在glibc/libio/iosetvbuf.c
中
//https://code.woboq.org/userspace/glibc/libio/iosetvbuf.c.html#_IO_setvbuf
#define _IOFBF 0 /* Fully buffered. */
#define _IOLBF 1 /* Line buffered. */
#define _IONBF 2 /* No buffering. */
一共有三种类型:全缓冲,行缓冲,无缓冲
我们常用的setvbuf(stdout,0,2,0)
中的2就是代表无缓冲输入输出直接写入文件
全缓冲的话除了强制刷新缓冲区之外只有缓冲区填满才会刷新。
行缓冲是在全缓冲上增加了遇到换行符也刷新缓冲区。
常见的stdout
初始就是行缓冲缓冲区大小为8192bit
在glibc/libio/stdio.h
中有定义#define BUFSIZ 8192
可以通过以下程序验证。
#include<stdio.h>
char buf[0x123];
int main()
{
puts("n132");
printf("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI");//"A"*0x400+"I"
read(0,buf,0x132);
}
程序会输出1024个”A”但是”I”会留在缓冲区中
输出结束后stdout
情况如下
n132>>> p *stdout
$1 = {
_flags = 0xfbad2a84,
_IO_read_ptr = 0x602010 "I", 'A' <repeats 14 times>...,
_IO_read_end = 0x602010 "I", 'A' <repeats 14 times>...,
_IO_read_base = 0x602010 "I", 'A' <repeats 14 times>...,
_IO_write_base = 0x602010 "I", 'A' <repeats 14 times>...,
_IO_write_ptr = 0x602011 'A' <repeats 15 times>...,
_IO_write_end = 0x602010 "I", 'A' <repeats 14 times>...,
_IO_buf_base = 0x602010 "I", 'A' <repeats 14 times>...,
_IO_buf_end = 0x602410 "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
}
这里我们可以发现缓冲区在heap上,一个0x410size的chunk
fllush
用于清空文件缓冲区
//https://code.woboq.org/userspace/glibc/libio/iofflush.c.html
int
_IO_fflush (FILE *fp)
{
if (fp == NULL)
return _IO_flush_all ();
else
{
int result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
result = _IO_SYNC (fp) ? EOF : 0;
_IO_release_lock (fp);
return result;
}
}
libc_hidden_def (_IO_fflush)
weak_alias (_IO_fflush, fflush)
利用setvbuf
事实上我们可以设置文件的缓冲模式和缓冲区大小.
//https://code.woboq.org/userspace/glibc/libio/iosetvbuf.c.html#_IO_setvbuf
int
_IO_setvbuf (FILE *fp, char *buf, int mode, size_t size)
{
int result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
switch (mode)
{
case _IOFBF:
fp->_flags &= ~(_IO_LINE_BUF|_IO_UNBUFFERED);
if (buf == NULL)
{
if (fp->_IO_buf_base == NULL)
{
/* There is no flag to distinguish between "fully buffered
mode has been explicitly set" as opposed to "line
buffering has not been explicitly set". In both
cases, _IO_LINE_BUF is off. If this is a tty, and
_IO_filedoalloc later gets called, it cannot know if
it should set the _IO_LINE_BUF flag (because that is
the default), or not (because we have explicitly asked
for fully buffered mode). So we make sure a buffer
gets allocated now, and explicitly turn off line
buffering.
A possibly cleaner alternative would be to add an
extra flag, but then flags are a finite resource. */
if (_IO_DOALLOCATE (fp) < 0)
{
result = EOF;
goto unlock_return;
}
fp->_flags &= ~_IO_LINE_BUF;
}
result = 0;
goto unlock_return;
}
break;
case _IOLBF:
fp->_flags &= ~_IO_UNBUFFERED;
fp->_flags |= _IO_LINE_BUF;
if (buf == NULL)
{
result = 0;
goto unlock_return;
}
break;
case _IONBF:
fp->_flags &= ~_IO_LINE_BUF;
fp->_flags |= _IO_UNBUFFERED;
buf = NULL;
size = 0;
break;
default:
result = EOF;
goto unlock_return;
}
result = _IO_SETBUF (fp, buf, size) == NULL ? EOF : 0;
unlock_return:
_IO_release_lock (fp);
return result;
}
libc_hidden_def (_IO_setvbuf)
weak_alias (_IO_setvbuf, setvbuf)
函数流程:
_flags
_IO_SETBUF(fp,buf,siez)
call fp->_IO_doallocate_t
,设置_flags
_flags
_IO_SETBUF(fp,buf,siez)
_flags
_IO_SETBUF(fp,0,0)
其中 _IO_SETBUF(fp,buffer_address,size)
表示设置fp
文件的缓冲区地址为buffer_address
大小为size
为了这几种mode
的_flags
先贴上各种_flags
宏定义
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED 0x0002
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
#define _IO_EOF_SEEN 0x0010
#define _IO_ERR_SEEN 0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close. */
#define _IO_LINKED 0x0080 /* In the list of all open files. */
#define _IO_IN_BACKUP 0x0100
#define _IO_LINE_BUF 0x0200
#define _IO_TIED_PUT_GET 0x0400 /* Put and get pointer move in unison. */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
/* 0x4000 No longer used, reserved for compat. */
#define _IO_USER_LOCK 0x8000
2个涉及到的flag:
三种case中对_flags
的操作依次分别是:
tip:感觉看了这个对_IO_LEAK
的理解更加深入了
在上一次的探索中得知leak条件是_flags&0x1800=0x1800
其中包括了_IO_LINE_BUF
一般ctf比赛中的binary
都有设置stdout
为无缓冲所以改写是必要的,虽然另一个条件是_IO_IS_APPENDING
显然不是stdout
具备的.
这个函数会清空所有fp的缓冲区之后会单独开篇文章研究。