드디어 malloc의 분석을 시작해본다. tcache 2 가 적용된 최신 버전의 libc로 분석을 한다.
기본적으로 설명은 어느정도 malloc의 동작 원리에 대해 알고있다는 가정하에 설명할 생각이다.
Arena
먼저 간단하게 집고 넘어가야 할 것이 있다. 바로 Arena에 관해서이다.
Arena는 스레드당 할당된 heap 부분을 관리하는 곳 이라고 할 수 있다. 그래서 메인스레드에서 할당되었으면
main arena
라고 부르는 것이다. 아마도 이는 속도 때문이라고 생각한다. 분석하면서도 atomic 계산의 정확도에 대한 말이 나오는 것 보면 동기화 관련한 문제 때문인 것 같다. 그러나 스레드가 많아지게 되면 공간적인 낭비가 이루어지게 때문에 최대로 할당받는 아레나의 수는 64bit에서는 8*core 개수로 정해져있고, 이를 넘어가게 되면 이전에 사용되었던 arena를 재사용한다고 한다. 그리고 우리가 흔히 heap을 공부하며 main arena
에서 fastbin
등을 관리한다고 했는데, 이런것들이 저장되있는 곳은 arena header로 다음 구조체 부분이다.
struct malloc_state
{
/* Serialize access. */
__libc_lock_define (, mutex); //뮤텍스 관련 부분
/* Flags (formerly in max_fast). */
int flags;
/*fastbin chunk가 최근에 들어왔으면 세팅된다 */
/* bool 형식이지만 모든 시스템에서 atomic연산이 지원되지는 않는다 */
int have_fastchunks;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* 탑청크 */
mchunkptr top;
/* 분할되고 남은청크 */
mchunkptr last_remainder;
/* unsorted, small, largebin 영역 */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* 다음 아레나가 연결되있음 */
struct malloc_state *next;
/* free된 아레나. 이 영역에 접근하려면 arena.c의 free_list_lock을 이용해 동기화
필요함*/
struct malloc_state *next_free;
/* 이 아레나에 연결된 thread의 개수. 0이면 free된 아레나. 위와 동일하게
접근해야함.*/
INTERNAL_SIZE_T attached_threads;
/* 이 아레나에 할당된 시스템 메모리 */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
main arena
는 전역변수로 할당되어있지만, 만약 다른 스레드에서 새롭게 동적할당을 받게 된다면 새로운 arena가 생기게 되고 새로운 heap영역을 받게된다. 그리고 이 arena는 heap부분에 동적으로 할당되어있다. 마치 tcache arena
와 비슷하다.
간단하게 예제를 만들어보았다.
#include <stdio.h>
#include <pthread.h>
#include <time.h>
#include <stdlib.h>
// 쓰레드 동작시 실행될 함수
void *firstThreadRun()
{
int*p=malloc(0x80);
printf("thread : %p\n",p);
}
int main()
{
pthread_t firstThread;
int threadErr;
int*m=malloc(0x80);
printf("main : %p\n",m);
if(threadErr = pthread_create(&firstThread,NULL,firstThreadRun,NULL))
{
// 에러시 에러 출력
printf("Thread Err = %d",threadErr);
}
pthread_join(firstThread,(void**)&threadErr);
getchar();
}
main thread에서 heap을 할당받자 0x602000
에 할당이 되었다.
thread에서 할당을 받자. mmap으로 매핑된 메모리가 생겼다.
arena에 next도 설정이 되었다.
대강의 원리를 알아보는 것은 이정도면 충분 할 것 같다.
Libc Malloc
먼저 malloc_chunk
구조체이다.
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if 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 free. */
struct malloc_chunk* bk_nextsize;
};
prev_size
와 size
는 size_t
(8byte) 형식으로 이루어져있고 fd,bk 등은 자기참조 구조체 형식으로 이루어져 있다. 이 구조체의 크기는 0x30 byte 이다. fd_nextsize
와 bk_nextsize
는 largebin에서 쓰이는 요소들로 정렬의 편의를 위해 사용된다. fastbin
, smallbin
, unsortedbin
에서는 메타데이터이다.
일단 malloc
을 호출하면 먼저 __libc_malloc
이 호출된다.
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr; //아레나 헤더로
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);//__malloc_hoo 값을 읽어오기
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0)); //jmp __malloc_hook
#if USE_TCACHE // libc >= 2.26이상이면 tcache 사용
size_t tbytes;
/*유효한 사이즈인지 검사하고 패딩처리, 유효하지 않으면(int넘어가거나) errno 세팅*/
checked_request2size (bytes, tbytes);
size_t tc_idx = csize2tidx (tbytes); //사이즈로 몇번째 tcache bin인지 알아낸다.
MAYBE_INIT_TCACHE (); //tcache가 init 되지 않았으면 init 해준다.
DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins //요청사이즈가 tcache 사이즈에 포함될시에
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache //그리고 tcache가 init 되었을 때에
&& tcache->entries[tc_idx] != NULL) //그리고 해당 사이즈의 bin이 존재할시
{
return tcache_get (tc_idx); //tcache bin에서 할당받는다
}
DIAG_POP_NEEDS_COMMENT;
#endif
if (SINGLE_THREAD_P)// single thread인 경우
{
victim = _int_malloc (&main_arena, bytes); //_int_malloc 호출
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
&main_arena == arena_for_chunk (mem2chunk (victim))); //에러검출용
return victim; //반환
}
/*arena.c에 정의되있음. arena를 탐색하면서 비어있는 arena를 가져오거나,
새롭게 할당받는다*/
arena_get (ar_ptr, bytes);
/*받은 아레나를 통해서 malloc*/
victim = _int_malloc (ar_ptr, bytes);
/* Retry with another arena only if we were able to find a usable arena
before. */
/*밑에는 각종 오류 검출용 코드*/
if (!victim && ar_ptr != NULL)
{
LIBC_PROBE (memory_malloc_retry, 1, bytes);
ar_ptr = arena_get_retry (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
return victim; //결국에는 victim을 반환하는 것은 같다.
}
libc_hidden_def (__libc_malloc)
__libc_malloc
이 하는 일은 단순하다.
1. __malloc_hook
에 값이 존재하면 그곳으로 점프한다.
2. tcache 사용시에는 tcache 영역에 대한 검증을 하고 tcache에서 바로 청크를 가져온다. 이 과정에서 사이즈에 패딩처리를 해주는데, 이게 뭔말이냐 하면 우리가 13바이트를 할당 요청했을 때 실제로 16바이트가 할당되는 것을 말한다. 처리하기 쉽게 정렬해주는 것이다.
3. 싱글스레드인 경우 _int_malloc
함수에 main arena와 요청크기를 인자로 넘겨준다. 그리고 할당받은 영역을 반환한다.
4. 멀티스레드인 경우 새롭게 arena를 할당받고 _int_malloc
에 할당받은 arena와 요청크기를 인자로 넘겨준다. 그리고 할당받은 영역을 반환한다.
다음 포스팅은 int malloc에 대한 분석을 차근차근 해보겠다.
'Security > 개인 연구' 카테고리의 다른 글
tcache smallbin 취약점 (0) | 2020.12.28 |
---|---|
malloc.c 분석 [2] _int_malloc (fastbin, smallbin) (2) | 2020.12.28 |
fclose() 를 이용한 leak (1) | 2020.12.27 |
Ubuntu 16.04 Exploit 테크닉 정리 (0) | 2020.12.17 |
_call_tls_dtors exploit (0) | 2020.11.26 |