haehet

heap exploit 정리 본문

Pwnable

heap exploit 정리

haehet 2025. 11. 5. 12:27

지금까지 heap 관련된 공부를 해오면서 중요하다 생각한 부분을 정리하고자 한다. glibc 2.35를 기준으로 했으며 약간 내용이 두서가 없을 수도 있다. 

 

1. tcache key && safe-linking

	if (__glibc_unlikely (e->key == tcache_key))
	  {
	    tcache_entry *tmp;
	    size_t cnt = 0;
	    LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
	    for (tmp = tcache->entries[tc_idx];
		 tmp;
		 tmp = REVEAL_PTR (tmp->next), ++cnt)
	      {
		if (cnt >= mp_.tcache_count)
		  malloc_printerr ("free(): too many chunks detected in tcache");
		if (__glibc_unlikely (!aligned_OK (tmp)))
		  malloc_printerr ("free(): unaligned chunk detected in tcache 2");
		if (tmp == e)
		  malloc_printerr ("free(): double free detected in tcache 2");
		/* If we get here, it was a coincidence.  We've wasted a
		   few cycles, but don't abort.  */
	      }
	  }

tcache에는 fd옆에 key라는 변수가 있다. 만약 free할 때 fd 다음 위치에 key변수가 있으면 double free로 처리한다. 따라서 tcache dup을 하기 위해서는 key를 조금이라도 바꿔야한다.

#define PROTECT_PTR(pos, ptr) \
  ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr)  PROTECT_PTR (&ptr, ptr)

tcache는 fd를 저장할 때 다음과 같이 현재 현재주소 >> 12와 주소를 xor해서 저장한다. 따라서 우리가 원하는 주소에 할당을 받기 위해서는 xor된 값을 넣어줘야 하므로 heap주소를 leak해야 한다.

 

위에서 설명한 것 말고도 tcache counts라는게 있어서 free된 개수를 넘어서 할당을 요청하면 free 목록에서 가져오지 않을 수 있다.

2. unsorted bin

어려운 힙 문제일수록 이 unsortedbin을 이용해야 해야하는게 많은 것 같다. 

struct malloc_chunk {
    size_t prev_size;    
    size_t size;          
    struct malloc_chunk* fd; 
    struct malloc_chunk* bk;
    ...
};

unsorted bin은 다음과 같은 구조를 가지는데 이때 fd, bk에는 main arena의 주소가 들어가므로 이 주소를 읽으면 libc leak이 가능해진다. 많은 heap 문제에서 libc leak을 할 때 이것을 이용한다. (간접적으로 size filed를 조작 후 free 해서 leak하는 방식도 많이 쓰인다.) 

2-1. unsortedbin division

          if (in_smallbin_range (nb) &&
              bck == unsorted_chunks (av) &&
              victim == av->last_remainder &&
              (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
            {
              /* split and reattach remainder */
              remainder_size = size - nb;
              remainder = chunk_at_offset (victim, nb);
              unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
              av->last_remainder = remainder;
              remainder->bk = remainder->fd = unsorted_chunks (av);
              if (!in_smallbin_range (remainder_size))
                {
                  remainder->fd_nextsize = NULL;
                  remainder->bk_nextsize = NULL;
                }

              set_head (victim, nb | PREV_INUSE |
                        (av != &main_arena ? NON_MAIN_ARENA : 0));
              set_head (remainder, remainder_size | PREV_INUSE);
              set_foot (remainder, remainder_size);

              check_malloced_chunk (av, victim, nb);
              void *p = chunk2mem (victim);
              alloc_perturb (p, bytes);
              return p;
            }

또한 unsorted bin이 있는 경우에는 unsorted bin에서 분할하여 청크를 나눠준다. 이를 이용하면 heap feng shui를 할 수 있다. 

2-2. malloc consolidate

malloc consolidate는 다음 두가지 상황에서 일어난다.

  // largebin 이상의 크기가 할당 되었을 때
  else
    {
      idx = largebin_index (nb);
      if (atomic_load_relaxed (&av->have_fastchunks))
        malloc_consolidate (av);
    }
// top chunk 확장 직후 0x10000이상 크기
    if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
      if (atomic_load_relaxed (&av->have_fastchunks))
	malloc_consolidate(av);

malloc consolidate가 중요한 이유는 fast bin이 있을 때 이걸 호출하면 fast bin을 small bin, large bin으로 보낼 수 있다. 이때 이 fd 값은 libc 주소 또는 다른 heap 주소(safe-linking X)를 담고 있어서 leak하기 쉽다. 특정 상황에서 fd 위치에 문자열을 넣고 출력을 할 때 오염이 안된 주소를 leak하고 싶을 때 좋다. 또한 free 시 모든 chunk 중 unsorted bin을 먼저 확인하기 때문에 unsorted bin을 오염시킨 상황(fd 조작 등)이라면 빨리 복구하는 게 좋다.

 

2-3. large bin attack

large bin의 bk next size를 조작가능하면 원하는 주소에 큰 값을 넣을 수 있다.

https://github.com/shellphish/how2heap/blob/master/glibc_2.35/large_bin_attack.c

 

how2heap/glibc_2.35/large_bin_attack.c at master · shellphish/how2heap

A repository for learning various heap exploitation techniques. - shellphish/how2heap

github.com

 

 

3. fastbin, calloc 

3-1. fast bin dup

fastbin dup도 tcache 공격과 마찬가지로 safe-linking을 우회해야 한다. fast bin dup은 A - B - A 형식으로 하면 double free가 가능하다. 하지만 size에 대한 검증 부분이 있어서 할당 받고자 하는 곳에 size 필드가 있어야 검증에 안걸린다.

	  if (__glibc_likely (victim != NULL)) // size 체크
	    {
	      size_t victim_idx = fastbin_index (chunksize (victim));
	      if (__builtin_expect (victim_idx != idx, 0))
		malloc_printerr ("malloc(): memory corruption (fast)");
	      check_remalloced_chunk (av, victim, nb);

또한 malloc를 이용해 할당을 하고 있다면 fastbin에서 tcache로 가져오기 때문에 fast bin dup 대신 Tcache Stashing Unlink Attack을 사용하면된다. https://github.com/shellphish/how2heap/blob/master/glibc_2.35/tcache_stashing_unlink_attack.c

fast bin dup을 사용하기 위해서는 calloc 함수를 사용해야 가능하다.

 

3-2. braking calloc

calloc는 is mapped bit가 설정되어 있으면 해당 청크를 0으로 초기화 하지 않는다. 이를 이용하면 leak을 쉽게 할 수 있다.

 

 

 

 

이번 글에서는 heap에 대한 다양한 기법에 대해 알아보았다. 이 기법 외에도 다른 기법, 알아야 할 사항이 많으니 더 공부를 하도록 하자.

references: https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4420, https://github.com/shellphish/how2heap

 

 

'Pwnable' 카테고리의 다른 글

MIPS Architecture Exploitation  (0) 2026.05.01
tcache_perthread_struct overwriting  (0) 2026.01.26
FSOP 정리  (0) 2025.11.08
side channel attack(with assembly)  (0) 2025.10.02
프로그램 시작, 종료 과정 및 관련 취약점 분석  (0) 2025.08.23