[CS] Heap

Magnolia·2026년 3월 15일

Heap

Heap은 프로그램 실행 중 동적으로 메모리를 할당하기 위한 영역이다.
프로그램이 실행되기 전에는 정확한 크기를 알 수 없는 데이터를 저장하기 위해 사용된다.

대표적으로 다음 함수들을 통해 Heap 메모리를 사용한다.

malloc
calloc
realloc
free

ex)

int *p = malloc(sizeof(int));

이 코드는 실행 중에 Heap 영역에 4바이트 메모리를 할당하고 그 주소를 반환한다.

Heap은 프로그램이 실행되는 동안 필요할 때마다 메모리를 할당하고 해제할 수 있기 때문에 매우 유연한 메모리 영역이다.

하지만 메모리를 직접 관리해야 하기 때문에 다음과 같은 문제가 발생할 수 있다.

  • Memory Leak
  • Use After Free
  • Double Free
  • Heap Overflow

이러한 취약점들이 Heap Exploit의 주요 공격 포인터가 된다.


메모리 구조

프로그램이 실행되면 프로세스의 가상 메모리는 일반적으로 다음과 같은 구조를 가진다.

High Address
+------------------+
| Stack            |
|                  |	//함수 호출 시 자동 할당
|                  |
+------------------+
| Heap             |
|                  |	//동적 할당
|                  |
+------------------+
| BSS              |	//정적 변수 및 전역 변수
+------------------+
| Data             |	//초기화된 전역 변수
+------------------+
| Text             |	//프로그램 코드 영역
+------------------+
Low Address

각 영역의 특징은 다음과 같다.

Text 영역

프로그램의 실행 코드가 저장되는 영역이다.
일반적으로 읽기 전용으로 설정되어 있어 코드 변조를 방지한다.

ex)

main()
printf()
system()

Data 영역

초기화된 전역 변수와 정적 변수가 저장되는 영역이다.

ex)

int a = 10;

프로그램 시작 시 메모리에 로드된다.


BSS 영역

초기화되지 않은 전역 변수와 정적 변수가 저장되는 영역이다.

ex)

int a;

이 변수는 실행 시 자동으로 0으로 초기화된다.


Heap 영역

프로그램 실행 중 동적으로 메모리를 할당하기 위해 사용되는 영역이다.

Heap은 낮은 주소에서 높은 주소 방향으로 증가한다.

ex)

malloc()
new

Heap은 프로그램이 실행되는 동안 계속 크기가 변할 수 있으며 메모리 관리 라이브러리(glibc)가 이를 관리한다.


Stack 영역

함수 호출과 관련된 정보가 저장되는 영역이다.

대표적으로 다음 정보들이 저장된다.

  • 지역 변수
  • 함수 매개변수
  • 리턴 주소
  • Saved Register

Stack은 높은 주소에서 낮은 주소 방향으로 증가한다.


스택과 힙의 차이

구분StackHeap
할당 방식자동 할당동적 할당
관리컴파일러가 관리프로그래머가 관리
속도빠름상대적으로 느림
크기제한적비교적 큼
해제함수 종료 시 자동 해제free() / delete 필요
저장 데이터지역 변수, 함수 호출 정보동적으로 생성된 데이터

malloc 동작 원리

리눅스에서 malloc은 대부분 glibc allocator(ptmalloc)를 사용한다.

Heap 메모리는 단순히 계속 할당되는 것이 아니라
Chunk라는 단위로 관리된다.

기본 동작 흐름

malloc -> heap chunk 생성 -> free list에 저장 -> 재사용

즉, free()된 메모리는 바로 OS로 반환되는 것이 아니라 재사용을 위해 allocator 내부에 저장된다.

이 때문에 Heap Exploit에서는 free된 chunk 구조를 조작하는 공격이 많이 사용된다.


Heap Chunk 구조

Heap 메모리는 Chunk 단위로 관리된다.

glibc 내부 구조

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

각 필드의 의미는 다음과 같다.


prev_size

이전 Chunk의 크기를 저장한다.

이전 Chunk가 사용중인 경우(PREV_INUSE) 면 이 값은 사용되지 않는다.


size

현재 Chunk의 크기를 저장한다.

단순히 크기만 저장되는 것이 아니라 하위 비트에는 flag 정보가 포함된다.

대표 flag

PREV_INUSE
IS_MMAPPED
NON_MAIN_ARENA

ex)

0x21

실제 의미

0x20 = chunk size
0x1  = PREV_INUSE

fd (forward pointer)

free된 chunk가 bin에 들어갈 때 다음 chunk를 가리키는 포인터


bk (backward pointer)

free된 chunk가 이전 chunk를 가리키는 포인터


Chunk 구조

+------------------+
| prev_size        |
+------------------+
| size             |
+------------------+
| user data        |
|                  |
|                  |
+------------------+

사용자가 malloc()으로 받은 메모리는 실제로는 metadata(prev_size, size) 뒤에 위치한 user data 영역이다.

이 metadata를 overwrite하면 Heap exploit이 가능하다.


Metadata

malloc()을 호출하면 사용자는 단순히 메모리 주소만 받지만, 실제로 Heap에서는 metadata와 user data가 함께 존재하는 구조로 관리된다.

+------------------+
| prev_size        |
+------------------+
| size             |
+------------------+
| user data        | ← malloc이 반환하는 주소
|                  |
|                  |
+------------------+

여기서 prev_sizesize 두 영역이 metadata다.


예를 들어 이러한 코드가 있다고 하자.

char *p = malloc(0x20);

메모리 구조는 다음과 같다.

0x555555559000  prev_size
0x555555559008  size
0x555555559010  user data ← p

p = metadata + 0x10

이 된다.

[metadata][user data]
   ↑           ↑
 prev_size     malloc이 반환하는 주소
 size

이런 느낌


Heap allocator(glibc)는 메모리를 관리하기 위해 각 chunk에 대한 정보를 저장해야 한다.

대표적으로 다음 정보를 관리한다.

  • chunk 크기
  • 이전 chunk 상태
  • free list 연결 정보

그래서 metadata에 다음 값들이 들어간다.

prev_size
size
fd
bk

Heap Exploit과 metadata

Heap exploit의 핵심은 이 metadata를 조작하는 것이다.

예를 들어 Heap Overflow가 발생하면

[chunk1 user data] → overflow
                     ↓
                 chunk2 metadata overwrite

size 조작, fd 조작, bk 조작이 가능해지고 여러 공격이 가능해진다.


0개의 댓글