Libc Heap

힙 기초

힙은 프로그램이 malloc, calloc 등의 함수를 호출하여 데이터를 요청할 때 데이터를 저장할 수 있는 공간입니다. 더 이상 필요하지 않은 메모리는 free 함수를 호출하여 사용 가능하게 만듭니다.

힙은 메모리에 로드된 이진 파일 바로 뒤에 위치하며 ( [heap] 섹션을 확인하세요):

기본 청크 할당

힙에 데이터를 저장하도록 요청하면 힙의 일부 공간이 해당 데이터에 할당됩니다. 이 공간은 bin에 속하며 요청된 데이터 + bin 헤더 공간 + 최소 bin 크기 오프셋만 청크에 예약됩니다. 각 청크가 어디에 있는지 찾기 어렵지 않게 최소한의 메모리만 예약하는 것이 목표입니다. 이를 위해 메타데이터 청크 정보를 사용하여 각 사용 중/해제된 청크가 어디에 있는지 알 수 있습니다.

사용된 bin에 따라 공간을 예약하는 방법이 다양하지만 일반적인 방법은 다음과 같습니다:

  • 프로그램은 일정량의 메모리를 요청하여 시작합니다.

  • 청크 목록에서 요청을 충족할 수 있는 충분히 큰 공간이 있는 경우 사용됩니다.

  • 이는 심지어 사용 가능한 청크의 일부가 이 요청에 사용되고 나머지는 청크 목록에 추가될 수 있음을 의미할 수 있습니다.

  • 목록에 사용 가능한 청크가 없지만 할당된 힙 메모리에 여전히 공간이 있는 경우, 힙 관리자는 새로운 청크를 생성합니다.

  • 새로운 청크를 할당할 힙 공간이 충분하지 않은 경우, 힙 관리자는 커널에게 힙에 할당된 메모리를 확장하도록 요청한 다음 이 메모리를 사용하여 새로운 청크를 생성합니다.

  • 모든 것이 실패하면 malloc은 null을 반환합니다.

요청된 메모리가 임계값을 초과하면, **mmap**이 요청된 메모리를 매핑하는 데 사용됩니다.

아레나

다중 스레드 응용 프로그램에서 힙 관리자는 충돌로 인한 충돌을 방지해야 합니다. 초기에는 전역 뮤텍스를 사용하여 한 번에 한 스레드만 힙에 액세스할 수 있도록 보장했지만, 이는 성능 문제를 일으켰습니다.

이를 해결하기 위해 ptmalloc2 힙 할당기는 "아레나"를 도입했는데, 각 아레나자체 데이터 구조뮤텍스를 가진 별도의 힙 역할을 하여 서로 간섭하지 않고 여러 스레드가 힙 작업을 수행할 수 있도록 합니다.

기본 "main" 아레나는 단일 스레드 응용 프로그램을 위한 힙 작업을 처리합니다. 새 스레드가 추가되면 힙 관리자는 경합을 줄이기 위해 그들에게 보조 아레나를 할당합니다. 먼저 각 새 스레드를 사용하지 않은 아레나에 연결하려고 시도하며 필요한 경우 새로운 것을 만들어 32비트 시스템의 CPU 코어 수의 2배 한도까지, 64비트 시스템의 경우 8배까지 제한됩니다. 한도에 도달하면 스레드는 아레나를 공유해야 하므로 잠재적인 경합이 발생합니다.

주 아레나가 brk 시스템 호출을 사용하여 확장하는 반면, 보조 아레나는 mmapmprotect를 사용하여 "서브힙"을 생성하여 힙 동작을 모방하여 다중 스레드 작업에 대한 메모리 관리를 유연하게 할 수 있습니다.

서브힙

서브힙은 다중 스레드 응용 프로그램의 보조 아레나에 대한 메모리 예비로 작동하여 메인 힙과 별도로 자체 힙 영역을 성장하고 관리할 수 있습니다. 서브힙이 초기 힙과 어떻게 다르며 작동하는지에 대한 내용은 다음과 같습니다:

  1. 초기 힙 대 서브힙:

  • 초기 힙은 메모리에 로드된 프로그램 바로 뒤에 위치하며 sbrk 시스템 호출을 사용하여 확장됩니다.

  • 보조 아레나에서 사용되는 서브힙은 mmap을 통해 생성되며 지정된 메모리 영역을 매핑하는 시스템 호출입니다.

  1. mmap을 사용한 메모리 예약:

  • 힙 관리자가 서브힙을 생성하면 mmap을 통해 큰 메모리 블록을 예약합니다. 이 예약은 즉시 메모리를 할당하지 않고 다른 시스템 프로세스나 할당이 사용하지 않아야 하는 영역을 지정합니다.

  • 기본적으로 32비트 프로세스의 서브힙에 대한 예약 크기는 1MB이며, 64비트 프로세스의 경우 64MB입니다.

  1. mprotect를 사용한 점진적 확장:

  • 예약된 메모리 영역은 처음에 PROT_NONE으로 표시되어 있어 커널이 이 공간에 물리적 메모리를 할당할 필요가 없음을 나타냅니다.

  • 서브힙을 "확장"하기 위해 힙 관리자는 mprotect를 사용하여 페이지 권한을 PROT_NONE에서 PROT_READ | PROT_WRITE로 변경하여 커널이 이전에 예약된 주소에 물리적 메모리를 할당하도록 합니다. 이 단계별 접근 방식을 통해 서브힙은 필요에 따라 확장될 수 있습니다.

  • 서브힙 전체가 고갈되면 힙 관리자는 계속해서 할당하기 위해 새로운 서브힙을 생성합니다.

heap_info

이 구조체는 힙의 관련 정보를 할당합니다. 또한 더 많은 할당 후 힙 메모리가 연속적이지 않을 수 있으므로 이 구조체는 해당 정보도 저장합니다.

// 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개의 스레드에 의해 액세스되도록 보장합니다.

  • 플래그:

#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

이 구조체는 메모리의 특정 청크를 나타냅니다. 다양한 필드는 할당된 청크와 할당되지 않은 청크에 대해 다른 의미를 가지고 있습니다.

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

이전에 설명한 대로, 이러한 청크들은 메타데이터를 가지고 있으며, 이 이미지에서 잘 표현되어 있습니다:

메타데이터는 일반적으로 현재 청크 크기를 나타내는 0x08B로 표시되며, 마지막 3비트를 사용하여 다음을 나타냅니다:

  • A: 1이면 하위 힙에서 오는 것이고, 0이면 주 힙에 있습니다.

  • M: 1이면 이 청크는 mmap으로 할당된 공간의 일부이며 힙의 일부가 아닙니다.

  • P: 1이면 이전 청크가 사용 중입니다.

그런 다음 사용자 데이터를 위한 공간이 있고, 마지막으로 청크가 사용 가능할 때 이전 청크 크기를 나타내는 0x08B가 있습니다(또는 할당될 때 사용자 데이터를 저장하는 데 사용됩니다).

또한 사용 가능한 경우 사용자 데이터는 몇 가지 데이터를 포함하기도 합니다:

  • fd: 다음 청크를 가리키는 포인터

  • bk: 이전 청크를 가리키는 포인터

  • fd_nextsize: 자신보다 작은 첫 번째 청크를 가리키는 포인터

  • bk_nextsize: 자신보다 큰 첫 번째 청크를 가리키는 포인터

리스트를 이렇게 구성하면 모든 청크가 등록되어 있는 배열이 필요하지 않아도 됨을 주목하세요.

청크 포인터

malloc을 사용하면 쓰기 가능한 내용을 가리키는 포인터가 반환됩니다(헤더 바로 뒤), 그러나 청크를 관리할 때는 헤더(메타데이터)의 시작 부분을 가리키는 포인터가 필요합니다. 이러한 변환에는 다음 함수가 사용됩니다:

// 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이어야 합니다.

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

청크 데이터 가져오기 및 메타데이터 변경

이러한 함수들은 청크에 대한 포인터를 받아들여 메타데이터를 확인/설정하는 데 유용합니다:

  • 청크 플래그 확인

// 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)
  • 다른 청크들의 크기와 포인터들

/*
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)))
  • 비트 논리

/* 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 번호 사용 중일 때)

/* 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))
  • 청크 내부의 실제 사용 가능한 데이터 크기를 얻습니다.

#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의 빠른 힙 예시를 arm64로 구현한 것:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(void)
{
char *ptr;
ptr = malloc(0x10);
strcpy(ptr, "panda");
}

메인 함수 끝에 중단점을 설정하고 정보가 저장된 위치를 찾아봅시다:

문자열 '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

멀티스레딩 예제

멀티스레드

```c #include #include #include #include #include

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가 무엇이며 어떻게 구성되어 있는지, 그리고 메모리가 할당되고 해제되는 방법을 확인하십시오:

<div data-gb-custom-block data-tag="content-ref" data-url='bins-and-memory-allocations.md'>

[bins-and-memory-allocations.md](bins-and-memory-allocations.md)

</div>

## 힙 함수 보안 검사

힙에 관여하는 함수는 힙이 손상되지 않았는지 확인하기 위해 작업을 수행하기 전에 특정 검사를 수행할 것입니다:

<div data-gb-custom-block data-tag="content-ref" data-url='heap-memory-functions/heap-functions-security-checks.md'>

[heap-functions-security-checks.md](heap-memory-functions/heap-functions-security-checks.md)

</div>

## 참고 자료

* [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/)

Last updated