mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-13 06:42:54 +00:00
503 lines
24 KiB
Markdown
503 lines
24 KiB
Markdown
|
# 힙
|
||
|
|
||
|
## 힙 기초
|
||
|
|
||
|
힙은 프로그램이 **`malloc`**, `calloc` 등의 함수를 호출하여 데이터를 요청할 때 데이터를 저장할 수 있는 공간입니다. 더 이상 필요하지 않은 메모리는 **`free`** 함수를 호출하여 사용 가능하게 만듭니다.
|
||
|
|
||
|
힙은 메모리에 로드된 이진 파일 바로 뒤에 위치하며 ( `[heap]` 섹션을 확인하세요):
|
||
|
|
||
|
<figure><img src="../../.gitbook/assets/image (1241).png" alt=""><figcaption></figcaption></figure>
|
||
|
|
||
|
### 기본 청크 할당
|
||
|
|
||
|
힙에 데이터를 저장하도록 요청하면 힙의 일부 공간이 해당 데이터에 할당됩니다. 이 공간은 bin에 속하며 요청된 데이터 + bin 헤더 공간 + 최소 bin 크기 오프셋만 청크에 예약됩니다. 각 청크가 어디에 있는지 찾기 어렵지 않게 최소한의 메모리만 예약하는 것이 목표입니다. 이를 위해 메타데이터 청크 정보를 사용하여 각 사용 중/해제된 청크가 어디에 있는지 알 수 있습니다.
|
||
|
|
||
|
사용된 bin에 따라 공간을 예약하는 방법이 다양하지만 일반적인 방법은 다음과 같습니다:
|
||
|
|
||
|
* 프로그램은 일정량의 메모리를 요청하여 시작합니다.
|
||
|
* 청크 목록에서 요청을 충족할 수 있는 충분히 큰 공간이 있는 경우 사용됩니다.
|
||
|
* 이는 심지어 사용 가능한 청크의 일부가 이 요청에 사용되고 나머지는 청크 목록에 추가될 수 있음을 의미할 수 있습니다.
|
||
|
* 목록에 사용 가능한 청크가 없지만 할당된 힙 메모리에 여전히 공간이 있는 경우, 힙 관리자는 새로운 청크를 생성합니다.
|
||
|
* 새로운 청크를 할당할 힙 공간이 충분하지 않은 경우, 힙 관리자는 커널에게 힙에 할당된 메모리를 확장하도록 요청한 다음 이 메모리를 사용하여 새로운 청크를 생성합니다.
|
||
|
* 모든 것이 실패하면 `malloc`은 null을 반환합니다.
|
||
|
|
||
|
요청된 **메모리가 임계값을 초과하면**, **`mmap`**이 요청된 메모리를 매핑하는 데 사용됩니다.
|
||
|
|
||
|
## 아레나
|
||
|
|
||
|
**다중 스레드** 응용 프로그램에서 힙 관리자는 충돌로 인한 충돌을 방지해야 합니다. 초기에는 **전역 뮤텍스**를 사용하여 한 번에 한 스레드만 힙에 액세스할 수 있도록 보장했지만, 이는 **성능 문제**를 일으켰습니다.
|
||
|
|
||
|
이를 해결하기 위해 ptmalloc2 힙 할당기는 "아레나"를 도입했는데, **각 아레나**는 **자체** 데이터 **구조** 및 **뮤텍스**를 가진 **별도의 힙** 역할을 하여 서로 간섭하지 않고 여러 스레드가 힙 작업을 수행할 수 있도록 합니다.
|
||
|
|
||
|
기본 "main" 아레나는 단일 스레드 응용 프로그램을 위한 힙 작업을 처리합니다. **새 스레드**가 추가되면 힙 관리자는 경합을 줄이기 위해 그들에게 **보조 아레나**를 할당합니다. 먼저 각 새 스레드를 사용하지 않은 아레나에 연결하려고 시도하며 필요한 경우 새로운 것을 만들어 32비트 시스템의 CPU 코어 수의 2배 한도까지, 64비트 시스템의 경우 8배까지 제한됩니다. 한도에 도달하면 **스레드는 아레나를 공유**해야 하므로 잠재적인 경합이 발생합니다.
|
||
|
|
||
|
주 아레나가 `brk` 시스템 호출을 사용하여 확장하는 반면, 보조 아레나는 `mmap` 및 `mprotect`를 사용하여 "서브힙"을 생성하여 힙 동작을 모방하여 다중 스레드 작업에 대한 메모리 관리를 유연하게 할 수 있습니다.
|
||
|
|
||
|
### 서브힙
|
||
|
|
||
|
서브힙은 다중 스레드 응용 프로그램의 보조 아레나에 대한 메모리 예비로 작동하여 메인 힙과 별도로 자체 힙 영역을 성장하고 관리할 수 있습니다. 서브힙이 초기 힙과 어떻게 다르며 작동하는지에 대한 내용은 다음과 같습니다:
|
||
|
|
||
|
1. **초기 힙 대 서브힙**:
|
||
|
* 초기 힙은 메모리에 로드된 프로그램 바로 뒤에 위치하며 `sbrk` 시스템 호출을 사용하여 확장됩니다.
|
||
|
* 보조 아레나에서 사용되는 서브힙은 `mmap`을 통해 생성되며 지정된 메모리 영역을 매핑하는 시스템 호출입니다.
|
||
|
2. **`mmap`을 사용한 메모리 예약**:
|
||
|
* 힙 관리자가 서브힙을 생성하면 `mmap`을 통해 큰 메모리 블록을 예약합니다. 이 예약은 즉시 메모리를 할당하지 않고 다른 시스템 프로세스나 할당이 사용하지 않아야 하는 영역을 지정합니다.
|
||
|
* 기본적으로 32비트 프로세스의 서브힙에 대한 예약 크기는 1MB이며, 64비트 프로세스의 경우 64MB입니다.
|
||
|
3. **`mprotect`를 사용한 점진적 확장**:
|
||
|
* 예약된 메모리 영역은 처음에 `PROT_NONE`으로 표시되어 있어 커널이 이 공간에 물리적 메모리를 할당할 필요가 없음을 나타냅니다.
|
||
|
* 서브힙을 "확장"하기 위해 힙 관리자는 `mprotect`를 사용하여 페이지 권한을 `PROT_NONE`에서 `PROT_READ | PROT_WRITE`로 변경하여 커널이 이전에 예약된 주소에 물리적 메모리를 할당하도록 합니다. 이 단계별 접근 방식을 통해 서브힙은 필요에 따라 확장될 수 있습니다.
|
||
|
* 서브힙 전체가 고갈되면 힙 관리자는 계속해서 할당하기 위해 새로운 서브힙을 생성합니다.
|
||
|
|
||
|
### heap\_info <a href="#heap_info" id="heap_info"></a>
|
||
|
|
||
|
이 구조체는 힙의 관련 정보를 할당합니다. 또한 더 많은 할당 후 힙 메모리가 연속적이지 않을 수 있으므로 이 구조체는 해당 정보도 저장합니다.
|
||
|
```c
|
||
|
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/arena.c#L837
|
||
|
|
||
|
typedef struct _heap_info
|
||
|
{
|
||
|
mstate ar_ptr; /* Arena for this heap. */
|
||
|
struct _heap_info *prev; /* Previous heap. */
|
||
|
size_t size; /* Current size in bytes. */
|
||
|
size_t mprotect_size; /* Size in bytes that has been mprotected
|
||
|
PROT_READ|PROT_WRITE. */
|
||
|
size_t pagesize; /* Page size used when allocating the arena. */
|
||
|
/* Make sure the following data is properly aligned, particularly
|
||
|
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
|
||
|
MALLOC_ALIGNMENT. */
|
||
|
char pad[-3 * SIZE_SZ & MALLOC_ALIGN_MASK];
|
||
|
} heap_info;
|
||
|
```
|
||
|
### malloc\_state
|
||
|
|
||
|
**각 힙**(주 힙 또는 다른 스레드의 힙)에는 **`malloc_state` 구조체**가 있습니다.\
|
||
|
중요한 점은 **주 힙의 `malloc_state`** 구조체가 **libc의 전역 변수**임을 알아야 합니다(따라서 libc 메모리 공간에 위치함).\
|
||
|
스레드의 힙에 있는 **`malloc_state`** 구조체의 경우, **해당 스레드의 "힙" 내부에 위치**합니다.
|
||
|
|
||
|
이 구조체에서 주목할 만한 몇 가지 사항이 있습니다(아래의 C 코드 참조):
|
||
|
|
||
|
* `__libc_lock_define (, mutex);`는 이 힙의 구조체가 한 번에 1개의 스레드에 의해 액세스되도록 보장합니다.
|
||
|
* 플래그:
|
||
|
* ```c
|
||
|
#define NONCONTIGUOUS_BIT (2U)
|
||
|
|
||
|
#define contiguous(M) (((M)->flags & NONCONTIGUOUS_BIT) == 0)
|
||
|
#define noncontiguous(M) (((M)->flags & NONCONTIGUOUS_BIT) != 0)
|
||
|
#define set_noncontiguous(M) ((M)->flags |= NONCONTIGUOUS_BIT)
|
||
|
#define set_contiguous(M) ((M)->flags &= ~NONCONTIGUOUS_BIT)
|
||
|
```
|
||
|
* `mchunkptr bins[NBINS * 2 - 2];`는 작은, 큰 및 정렬되지 않은 **bins**의 **첫 번째와 마지막 청크**를 가리키는 **포인터**를 포함합니다(-2는 인덱스 0이 사용되지 않기 때문입니다).
|
||
|
* 따라서 이러한 bins의 **첫 번째 청크**는 **이 구조체로의 역방향 포인터**를 가지고 있고, 이러한 bins의 **마지막 청크**는 **이 구조체로의 순방향 포인터**를 가지고 있습니다. 이것은 기본적으로 주 힙에서 이러한 주소를 **유출**할 수 있다면 **libc**의 구조체로의 포인터를 얻을 수 있다는 것을 의미합니다.
|
||
|
* `struct malloc_state *next;` 및 `struct malloc_state *next_free;` 구조체는 아레나의 연결 리스트입니다.
|
||
|
* `top` 청크는 마지막 "청크"로, 기본적으로 **힙에 남은 모든 공간**입니다. top 청크가 "비어" 있으면 힙이 완전히 사용되었으며 더 많은 공간을 요청해야 합니다.
|
||
|
* `last reminder` 청크는 정확한 크기의 청크가 없는 경우에 나타나며, 따라서 더 큰 청크가 분할되고 포인터가 남은 부분이 여기에 배치됩니다.
|
||
|
```c
|
||
|
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/malloc/malloc.c#L1812
|
||
|
|
||
|
struct malloc_state
|
||
|
{
|
||
|
/* Serialize access. */
|
||
|
__libc_lock_define (, mutex);
|
||
|
|
||
|
/* Flags (formerly in max_fast). */
|
||
|
int flags;
|
||
|
|
||
|
/* Set if the fastbin chunks contain recently inserted free blocks. */
|
||
|
/* Note this is a bool but not all targets support atomics on booleans. */
|
||
|
int have_fastchunks;
|
||
|
|
||
|
/* Fastbins */
|
||
|
mfastbinptr fastbinsY[NFASTBINS];
|
||
|
|
||
|
/* Base of the topmost chunk -- not otherwise kept in a bin */
|
||
|
mchunkptr top;
|
||
|
|
||
|
/* The remainder from the most recent split of a small request */
|
||
|
mchunkptr last_remainder;
|
||
|
|
||
|
/* Normal bins packed as described above */
|
||
|
mchunkptr bins[NBINS * 2 - 2];
|
||
|
|
||
|
/* Bitmap of bins */
|
||
|
unsigned int binmap[BINMAPSIZE];
|
||
|
|
||
|
/* Linked list */
|
||
|
struct malloc_state *next;
|
||
|
|
||
|
/* Linked list for free arenas. Access to this field is serialized
|
||
|
by free_list_lock in arena.c. */
|
||
|
struct malloc_state *next_free;
|
||
|
|
||
|
/* Number of threads attached to this arena. 0 if the arena is on
|
||
|
the free list. Access to this field is serialized by
|
||
|
free_list_lock in arena.c. */
|
||
|
INTERNAL_SIZE_T attached_threads;
|
||
|
|
||
|
/* Memory allocated from the system in this arena. */
|
||
|
INTERNAL_SIZE_T system_mem;
|
||
|
INTERNAL_SIZE_T max_system_mem;
|
||
|
};
|
||
|
```
|
||
|
### malloc\_chunk
|
||
|
|
||
|
이 구조체는 메모리의 특정 청크를 나타냅니다. 다양한 필드는 할당된 청크와 할당되지 않은 청크에 대해 다른 의미를 가지고 있습니다.
|
||
|
```c
|
||
|
// https://github.com/bminor/glibc/blob/master/malloc/malloc.c
|
||
|
struct malloc_chunk {
|
||
|
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk, if it is free. */
|
||
|
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
|
||
|
struct malloc_chunk* fd; /* double links -- used only if this chunk is free. */
|
||
|
struct malloc_chunk* bk;
|
||
|
/* Only used for large blocks: pointer to next larger size. */
|
||
|
struct malloc_chunk* fd_nextsize; /* double links -- used only if this chunk is free. */
|
||
|
struct malloc_chunk* bk_nextsize;
|
||
|
};
|
||
|
|
||
|
typedef struct malloc_chunk* mchunkptr;
|
||
|
```
|
||
|
이전에 설명한 대로, 이러한 청크들은 메타데이터를 가지고 있으며, 이 이미지에서 잘 표현되어 있습니다:
|
||
|
|
||
|
<figure><img src="../../.gitbook/assets/image (1242).png" alt=""><figcaption><p><a href="https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png">https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png</a></p></figcaption></figure>
|
||
|
|
||
|
메타데이터는 일반적으로 현재 청크 크기를 나타내는 0x08B로 표시되며, 마지막 3비트를 사용하여 다음을 나타냅니다:
|
||
|
|
||
|
* `A`: 1이면 하위 힙에서 오는 것이고, 0이면 주 힙에 있습니다.
|
||
|
* `M`: 1이면 이 청크는 mmap으로 할당된 공간의 일부이며 힙의 일부가 아닙니다.
|
||
|
* `P`: 1이면 이전 청크가 사용 중입니다.
|
||
|
|
||
|
그런 다음 사용자 데이터를 위한 공간이 있고, 마지막으로 청크가 사용 가능할 때 이전 청크 크기를 나타내는 0x08B가 있습니다(또는 할당될 때 사용자 데이터를 저장하는 데 사용됩니다).
|
||
|
|
||
|
또한 사용 가능한 경우 사용자 데이터는 몇 가지 데이터를 포함하기도 합니다:
|
||
|
|
||
|
* **`fd`**: 다음 청크를 가리키는 포인터
|
||
|
* **`bk`**: 이전 청크를 가리키는 포인터
|
||
|
* **`fd_nextsize`**: 자신보다 작은 첫 번째 청크를 가리키는 포인터
|
||
|
* **`bk_nextsize`:** 자신보다 큰 첫 번째 청크를 가리키는 포인터
|
||
|
|
||
|
|
||
|
|
||
|
<figure><img src="../../.gitbook/assets/image (1243).png" alt=""><figcaption><p><a href="https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png">https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png</a></p></figcaption></figure>
|
||
|
|
||
|
{% hint style="info" %}
|
||
|
리스트를 이렇게 구성하면 모든 청크가 등록되어 있는 배열이 필요하지 않아도 됨을 주목하세요.
|
||
|
{% endhint %}
|
||
|
|
||
|
### 청크 포인터
|
||
|
|
||
|
malloc을 사용하면 쓰기 가능한 내용을 가리키는 포인터가 반환됩니다(헤더 바로 뒤), 그러나 청크를 관리할 때는 헤더(메타데이터)의 시작 부분을 가리키는 포인터가 필요합니다.\
|
||
|
이러한 변환에는 다음 함수가 사용됩니다:
|
||
|
```c
|
||
|
// https://github.com/bminor/glibc/blob/master/malloc/malloc.c
|
||
|
|
||
|
/* Convert a chunk address to a user mem pointer without correcting the tag. */
|
||
|
#define chunk2mem(p) ((void*)((char*)(p) + CHUNK_HDR_SZ))
|
||
|
|
||
|
/* Convert a user mem pointer to a chunk address and extract the right tag. */
|
||
|
#define mem2chunk(mem) ((mchunkptr)tag_at (((char*)(mem) - CHUNK_HDR_SZ)))
|
||
|
|
||
|
/* The smallest possible chunk */
|
||
|
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))
|
||
|
|
||
|
/* The smallest size we can malloc is an aligned minimal chunk */
|
||
|
|
||
|
#define MINSIZE \
|
||
|
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
|
||
|
```
|
||
|
### 정렬 및 최소 크기
|
||
|
|
||
|
해당 청크의 포인터와 `0x0f`는 0이어야 합니다.
|
||
|
```c
|
||
|
// From https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/sysdeps/generic/malloc-size.h#L61
|
||
|
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
|
||
|
|
||
|
// https://github.com/bminor/glibc/blob/a07e000e82cb71238259e674529c37c12dc7d423/sysdeps/i386/malloc-alignment.h
|
||
|
#define MALLOC_ALIGNMENT 16
|
||
|
|
||
|
|
||
|
// https://github.com/bminor/glibc/blob/master/malloc/malloc.c
|
||
|
/* Check if m has acceptable alignment */
|
||
|
#define aligned_OK(m) (((unsigned long)(m) & MALLOC_ALIGN_MASK) == 0)
|
||
|
|
||
|
#define misaligned_chunk(p) \
|
||
|
((uintptr_t)(MALLOC_ALIGNMENT == CHUNK_HDR_SZ ? (p) : chunk2mem (p)) \
|
||
|
& MALLOC_ALIGN_MASK)
|
||
|
|
||
|
|
||
|
/* pad request bytes into a usable size -- internal version */
|
||
|
/* Note: This must be a macro that evaluates to a compile time constant
|
||
|
if passed a literal constant. */
|
||
|
#define request2size(req) \
|
||
|
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
|
||
|
MINSIZE : \
|
||
|
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
|
||
|
|
||
|
/* Check if REQ overflows when padded and aligned and if the resulting
|
||
|
value is less than PTRDIFF_T. Returns the requested size or
|
||
|
MINSIZE in case the value is less than MINSIZE, or 0 if any of the
|
||
|
previous checks fail. */
|
||
|
static inline size_t
|
||
|
checked_request2size (size_t req) __nonnull (1)
|
||
|
{
|
||
|
if (__glibc_unlikely (req > PTRDIFF_MAX))
|
||
|
return 0;
|
||
|
|
||
|
/* When using tagged memory, we cannot share the end of the user
|
||
|
block with the header for the next chunk, so ensure that we
|
||
|
allocate blocks that are rounded up to the granule size. Take
|
||
|
care not to overflow from close to MAX_SIZE_T to a small
|
||
|
number. Ideally, this would be part of request2size(), but that
|
||
|
must be a macro that produces a compile time constant if passed
|
||
|
a constant literal. */
|
||
|
if (__glibc_unlikely (mtag_enabled))
|
||
|
{
|
||
|
/* Ensure this is not evaluated if !mtag_enabled, see gcc PR 99551. */
|
||
|
asm ("");
|
||
|
|
||
|
req = (req + (__MTAG_GRANULE_SIZE - 1)) &
|
||
|
~(size_t)(__MTAG_GRANULE_SIZE - 1);
|
||
|
}
|
||
|
|
||
|
return request2size (req);
|
||
|
}
|
||
|
```
|
||
|
### 청크 데이터 가져오기 및 메타데이터 변경
|
||
|
|
||
|
이러한 함수들은 청크에 대한 포인터를 받아들여 메타데이터를 확인/설정하는 데 유용합니다:
|
||
|
|
||
|
* 청크 플래그 확인
|
||
|
```c
|
||
|
// From https://github.com/bminor/glibc/blob/master/malloc/malloc.c
|
||
|
|
||
|
|
||
|
/* size field is or'ed with PREV_INUSE when previous adjacent chunk in use */
|
||
|
#define PREV_INUSE 0x1
|
||
|
|
||
|
/* extract inuse bit of previous chunk */
|
||
|
#define prev_inuse(p) ((p)->mchunk_size & PREV_INUSE)
|
||
|
|
||
|
|
||
|
/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
|
||
|
#define IS_MMAPPED 0x2
|
||
|
|
||
|
/* check for mmap()'ed chunk */
|
||
|
#define chunk_is_mmapped(p) ((p)->mchunk_size & IS_MMAPPED)
|
||
|
|
||
|
|
||
|
/* size field is or'ed with NON_MAIN_ARENA if the chunk was obtained
|
||
|
from a non-main arena. This is only set immediately before handing
|
||
|
the chunk to the user, if necessary. */
|
||
|
#define NON_MAIN_ARENA 0x4
|
||
|
|
||
|
/* Check for chunk from main arena. */
|
||
|
#define chunk_main_arena(p) (((p)->mchunk_size & NON_MAIN_ARENA) == 0)
|
||
|
|
||
|
/* Mark a chunk as not being on the main arena. */
|
||
|
#define set_non_main_arena(p) ((p)->mchunk_size |= NON_MAIN_ARENA)
|
||
|
```
|
||
|
* 다른 청크들의 크기와 포인터들
|
||
|
```c
|
||
|
/*
|
||
|
Bits to mask off when extracting size
|
||
|
|
||
|
Note: IS_MMAPPED is intentionally not masked off from size field in
|
||
|
macros for which mmapped chunks should never be seen. This should
|
||
|
cause helpful core dumps to occur if it is tried by accident by
|
||
|
people extending or adapting this malloc.
|
||
|
*/
|
||
|
#define SIZE_BITS (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
|
||
|
|
||
|
/* Get size, ignoring use bits */
|
||
|
#define chunksize(p) (chunksize_nomask (p) & ~(SIZE_BITS))
|
||
|
|
||
|
/* Like chunksize, but do not mask SIZE_BITS. */
|
||
|
#define chunksize_nomask(p) ((p)->mchunk_size)
|
||
|
|
||
|
/* Ptr to next physical malloc_chunk. */
|
||
|
#define next_chunk(p) ((mchunkptr) (((char *) (p)) + chunksize (p)))
|
||
|
|
||
|
/* Size of the chunk below P. Only valid if !prev_inuse (P). */
|
||
|
#define prev_size(p) ((p)->mchunk_prev_size)
|
||
|
|
||
|
/* Set the size of the chunk below P. Only valid if !prev_inuse (P). */
|
||
|
#define set_prev_size(p, sz) ((p)->mchunk_prev_size = (sz))
|
||
|
|
||
|
/* Ptr to previous physical malloc_chunk. Only valid if !prev_inuse (P). */
|
||
|
#define prev_chunk(p) ((mchunkptr) (((char *) (p)) - prev_size (p)))
|
||
|
|
||
|
/* Treat space at ptr + offset as a chunk */
|
||
|
#define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s)))
|
||
|
```
|
||
|
* 비트 논리
|
||
|
```c
|
||
|
/* extract p's inuse bit */
|
||
|
#define inuse(p) \
|
||
|
((((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size) & PREV_INUSE)
|
||
|
|
||
|
/* set/clear chunk as being inuse without otherwise disturbing */
|
||
|
#define set_inuse(p) \
|
||
|
((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size |= PREV_INUSE
|
||
|
|
||
|
#define clear_inuse(p) \
|
||
|
((mchunkptr) (((char *) (p)) + chunksize (p)))->mchunk_size &= ~(PREV_INUSE)
|
||
|
|
||
|
|
||
|
/* check/set/clear inuse bits in known places */
|
||
|
#define inuse_bit_at_offset(p, s) \
|
||
|
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size & PREV_INUSE)
|
||
|
|
||
|
#define set_inuse_bit_at_offset(p, s) \
|
||
|
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size |= PREV_INUSE)
|
||
|
|
||
|
#define clear_inuse_bit_at_offset(p, s) \
|
||
|
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size &= ~(PREV_INUSE))
|
||
|
```
|
||
|
* 헤더와 푸터 설정 (chunk 번호 사용 중일 때)
|
||
|
```c
|
||
|
/* Set size at head, without disturbing its use bit */
|
||
|
#define set_head_size(p, s) ((p)->mchunk_size = (((p)->mchunk_size & SIZE_BITS) | (s)))
|
||
|
|
||
|
/* Set size/use field */
|
||
|
#define set_head(p, s) ((p)->mchunk_size = (s))
|
||
|
|
||
|
/* Set size at footer (only when chunk is not in use) */
|
||
|
#define set_foot(p, s) (((mchunkptr) ((char *) (p) + (s)))->mchunk_prev_size = (s))
|
||
|
```
|
||
|
* 청크 내부의 실제 사용 가능한 데이터 크기를 얻습니다.
|
||
|
```c
|
||
|
#pragma GCC poison mchunk_size
|
||
|
#pragma GCC poison mchunk_prev_size
|
||
|
|
||
|
/* This is the size of the real usable data in the chunk. Not valid for
|
||
|
dumped heap chunks. */
|
||
|
#define memsize(p) \
|
||
|
(__MTAG_GRANULE_SIZE > SIZE_SZ && __glibc_unlikely (mtag_enabled) ? \
|
||
|
chunksize (p) - CHUNK_HDR_SZ : \
|
||
|
chunksize (p) - CHUNK_HDR_SZ + (chunk_is_mmapped (p) ? 0 : SIZE_SZ))
|
||
|
|
||
|
/* If memory tagging is enabled the layout changes to accommodate the granule
|
||
|
size, this is wasteful for small allocations so not done by default.
|
||
|
Both the chunk header and user data has to be granule aligned. */
|
||
|
_Static_assert (__MTAG_GRANULE_SIZE <= CHUNK_HDR_SZ,
|
||
|
"memory tagging is not supported with large granule.");
|
||
|
|
||
|
static __always_inline void *
|
||
|
tag_new_usable (void *ptr)
|
||
|
{
|
||
|
if (__glibc_unlikely (mtag_enabled) && ptr)
|
||
|
{
|
||
|
mchunkptr cp = mem2chunk(ptr);
|
||
|
ptr = __libc_mtag_tag_region (__libc_mtag_new_tag (ptr), memsize (cp));
|
||
|
}
|
||
|
return ptr;
|
||
|
}
|
||
|
```
|
||
|
## 예시
|
||
|
|
||
|
### 빠른 힙 예시
|
||
|
|
||
|
[https://guyinatuxedo.github.io/25-heap/index.html](https://guyinatuxedo.github.io/25-heap/index.html)의 빠른 힙 예시를 arm64로 구현한 것:
|
||
|
```c
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
void main(void)
|
||
|
{
|
||
|
char *ptr;
|
||
|
ptr = malloc(0x10);
|
||
|
strcpy(ptr, "panda");
|
||
|
}
|
||
|
```
|
||
|
메인 함수 끝에 중단점을 설정하고 정보가 저장된 위치를 찾아봅시다:
|
||
|
|
||
|
<figure><img src="../../.gitbook/assets/image (1239).png" alt=""><figcaption></figcaption></figure>
|
||
|
|
||
|
문자열 'panda'가 `0xaaaaaaac12a0`에 저장되었음을 확인할 수 있습니다 (이 주소는 `x0` 내부의 malloc에 의해 반환된 주소였습니다). 그 앞의 0x10바이트를 확인하면 `0x0`은 **이전 청크가 사용되지 않음**을 나타내고, 이 청크의 길이는 `0x21`임을 알 수 있습니다.
|
||
|
|
||
|
추가로 예약된 공간(0x21-0x10=0x11)은 **추가된 헤더** (0x10)에서 나오며, 0x1은 0x21B가 예약되었다는 것을 의미하는 것이 아니라 현재 헤더의 길이의 마지막 3비트가 특별한 의미를 가지고 있음을 나타냅니다. 길이는 항상 16바이트로 정렬되므로(64비트 머신에서), 이 비트들은 실제로는 숫자로 사용되지 않을 것입니다.
|
||
|
```
|
||
|
0x1: Previous in Use - Specifies that the chunk before it in memory is in use
|
||
|
0x2: Is MMAPPED - Specifies that the chunk was obtained with mmap()
|
||
|
0x4: Non Main Arena - Specifies that the chunk was obtained from outside of the main arena
|
||
|
```
|
||
|
### 멀티스레딩 예제
|
||
|
|
||
|
<details>
|
||
|
|
||
|
<summary>멀티스레드</summary>
|
||
|
```c
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <pthread.h>
|
||
|
#include <unistd.h>
|
||
|
#include <sys/types.h>
|
||
|
|
||
|
|
||
|
void* threadFuncMalloc(void* arg) {
|
||
|
printf("Hello from thread 1\n");
|
||
|
char* addr = (char*) malloc(1000);
|
||
|
printf("After malloc and before free in thread 1\n");
|
||
|
free(addr);
|
||
|
printf("After free in thread 1\n");
|
||
|
}
|
||
|
|
||
|
void* threadFuncNoMalloc(void* arg) {
|
||
|
printf("Hello from thread 2\n");
|
||
|
}
|
||
|
|
||
|
|
||
|
int main() {
|
||
|
pthread_t t1;
|
||
|
void* s;
|
||
|
int ret;
|
||
|
char* addr;
|
||
|
|
||
|
printf("Before creating thread 1\n");
|
||
|
getchar();
|
||
|
ret = pthread_create(&t1, NULL, threadFuncMalloc, NULL);
|
||
|
getchar();
|
||
|
|
||
|
printf("Before creating thread 2\n");
|
||
|
ret = pthread_create(&t1, NULL, threadFuncNoMalloc, NULL);
|
||
|
|
||
|
printf("Before exit\n");
|
||
|
getchar();
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
```
|
||
|
</details>
|
||
|
|
||
|
이전 예제를 디버깅하면 처음에는 아레나가 하나만 있는 것을 볼 수 있습니다:
|
||
|
|
||
|
<figure><img src="../../.gitbook/assets/image (1).png" alt=""><figcaption></figcaption></figure>
|
||
|
|
||
|
그런 다음, 첫 번째 스레드를 호출하는 스레드, malloc을 호출하는 스레드를 호출한 후에 새로운 아레나가 생성됩니다:
|
||
|
|
||
|
<figure><img src="../../.gitbook/assets/image (1) (1).png" alt=""><figcaption></figcaption></figure>
|
||
|
|
||
|
그리고 그 안에 일부 청크를 찾을 수 있습니다:
|
||
|
|
||
|
<figure><img src="../../.gitbook/assets/image (2).png" alt=""><figcaption></figcaption></figure>
|
||
|
|
||
|
## Bins & 메모리 할당/해제
|
||
|
|
||
|
다음에서 bins가 무엇이며 어떻게 구성되어 있는지, 그리고 메모리가 할당되고 해제되는 방법을 확인하십시오:
|
||
|
|
||
|
{% content-ref url="bins-and-memory-allocations.md" %}
|
||
|
[bins-and-memory-allocations.md](bins-and-memory-allocations.md)
|
||
|
{% endcontent-ref %}
|
||
|
|
||
|
## 힙 함수 보안 검사
|
||
|
|
||
|
힙에 관여하는 함수는 힙이 손상되지 않았는지 확인하기 위해 작업을 수행하기 전에 특정 검사를 수행할 것입니다:
|
||
|
|
||
|
{% content-ref url="heap-memory-functions/heap-functions-security-checks.md" %}
|
||
|
[heap-functions-security-checks.md](heap-memory-functions/heap-functions-security-checks.md)
|
||
|
{% endcontent-ref %}
|
||
|
|
||
|
## 참고 자료
|
||
|
|
||
|
* [https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/](https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/)
|
||
|
* [https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/](https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/)
|