생각보다 코드가 그렇게 길지는 않다.
sysmalloc은 나중에 다룰 함수인데, 처음 할당을 받을 때 heap의 주소를 결정해주는 함수 이다. (initialize)
아무래도 다중아레나의 경우 사용되는 remove fb 같은 함수들은 뭔지 잘 모르겠어서 찾아봐야겠다.
static void*
_int_malloc(mstate av, size_t bytes)
{
INTERNAL_SIZE_T nb; /* 패딩처리된 사이즈 */
unsigned int idx; /* 인접한 bin의 인덱스 */
mbinptr bin; /* 인접한 bin */
mchunkptr victim;
INTERNAL_SIZE_T size;
int victim_index;
mchunkptr remainder; /* 분할되고 남은 청크 */
unsigned long remainder_size;
unsigned int block; /* bit map traverser */
unsigned int bit; /* bit map traverser */
unsigned int map; /* current word of binmap */
mchunkptr fwd; /* misc temp for linking */
mchunkptr bck; /* misc temp for linking */
#if USE_TCACHE
size_t tcache_unsorted_count; /* count of unsorted chunks processed */
#endif
checked_request2size(bytes, nb); //실제 할당받는 크기로 변환
/*사용가능한 아레나가 없는 경우 sysmalloc으로 할당받는다. mmap 사용*/
if (__glibc_unlikely(av == NULL))
{
void* p = sysmalloc(nb, av);
if (p != NULL)
alloc_perturb(p, bytes);
return p;
}
#define REMOVE_FB(fb, victim, pp) \
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) \
!= victim); \
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) //fastbin 사이즈일시
{
idx = fastbin_index(nb); //사이즈를 통해서 몇번째 bin인지 알아낸다.
mfastbinptr* fb = &fastbin(av, idx); //아레나 헤더에서 해당 fastbin을 가져옴.
mchunkptr pp;
victim = *fb; //fastbin의 첫번째 청크를 가져온다.
if (victim != NULL) //청크가 존재할시에
{
if (SINGLE_THREAD_P) //싱글스레드
*fb = victim->fd; //하나 꺼냈으니까 리스트의 다음 청크를 해당 빈의 top으로 설정
else
REMOVE_FB(fb, pp, victim);
if (__glibc_likely(victim != NULL))
{
size_t victim_idx = fastbin_index(chunksize(victim)); //fastbin의 사이즈 검사
if (__builtin_expect(victim_idx != idx, 0)) //꺼내온 청크의 size가 할당요청한 size와 다를시에
malloc_printerr("malloc(): memory corruption (fast)"); //malloc(): memory corruption (fast) 에러를 출력
check_remalloced_chunk(av, victim, nb); //여기서 할당 받은 청크가 잘 정렬되었는지 등을 체크하는데, assert 함수가 비활성화 되있어서 죽지 않는다.
#if USE_TCACHE //tcache 사용시에
size_t tc_idx = csize2tidx(nb); //tcache 인덱스 가져온다.
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* tcache의 해당 bin에 청크들이 0~7사이에 존재하면 fastbin에 있는 청크들을
tcache로 옮긴다.*/
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (SINGLE_THREAD_P)
*fb = tc_victim->fd;
else
{
REMOVE_FB(fb, pp, tc_victim);
if (__glibc_unlikely(tc_victim == NULL))
break;
}
tcache_put(tc_victim, tc_idx); //tcache에 하나씩 집어넣는다.
}
}
#endif
void* p = chunk2mem(victim); //실제 할당받은 청크는 헤더부터 시작하기 때문에 사용자가 사용할 공간으로 변환
alloc_perturb(p, bytes); //테스트 기능이라고 적혀있음
return p; //할당받은 청크를 반환
}
}
}
/*
먼저 fastbin에 관한 내용이다. 크게 우리가 알고 있던 내용과 다른 것은 없다. 한가지 신기한점은 check_remaloced_chunk라는 함수가 할당받은 청크가 정렬됬는지 ( 할당받은 주소가 0x10단위) 를 검사를 하는데, assert 함수로 검사가 실행되지만 #ifndef NDEBUG 때문에 assert 동작이 하지 않는다. 만약 이게 없었더라면 malloc_hook등을 fastbin으로 받으면 종료되야 한다.
Fastbin의 동작방식은 다음과 같이 정리 할 수 있겠다. (싱글스레드 기준)
1. 요청 사이즈를 통해서 해당 bin을 찾는다.
2. 해당 bin이 비어있는지 검사하고 아니면 가장 마지막에 들어온 청크를 할당받는다.
3. 해당청크의 사이즈가 요청한 사이즈와 다르면 malloc() : memory corruption (fast) 오류를 출력한다.
4. tcache를 사용할 경우, 해당사이즈의 tcache bin이 가득 차있지 않으면 fastbin에 있는 다음 할당시 반환될 청크를 tcache에 집어넣는다.
5. 할당받은 청크를 반환한다.
다음은 smallbin 관련 내용이다.
if (in_smallbin_range(nb)) //0~0x400까지의 요청
{
idx = smallbin_index(nb); //smallbin index로 바꾼다.
bin = bin_at(av, idx); //malloc_state구조체에서 smallbin의 인덱스를 가져온다.
if ((victim = last(bin)) != bin) //victim=해당bin->bk
{
bck = victim->bk;
if (__glibc_unlikely(bck->fd != victim)) //꺼낼때 bk의 fd가 할당받는 청크인지 검사
malloc_printerr("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset(victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena(victim);
check_malloced_chunk(av, victim, nb);
#if USE_TCACHE
size_t tc_idx = csize2tidx(nb); //해당사이즈에 해당하는 tcache index 계산
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
while (tcache->counts[tc_idx] < mp_.tcache_count //해당 tcache에 자리에 있으면, smallbin이 빌 때까지
&& (tc_victim = last(bin)) != bin) //tcache로 옮긴다.
{
if (tc_victim != 0) //bin->bk가 null인지 검사한다. 에러메세지는 없음
{
bck = tc_victim->bk; //bin->bk->bk
set_inuse_bit_at_offset(tc_victim, nb); //inuse bit 설정(tcache라서)
if (av != &main_arena)
set_non_main_arena(tc_victim);
bin->bk = bck; //연결 끊고 unlink
bck->fd = bin; //bin->bk->bk->fd=bin
//그런데 여기서 재밌는게 가능할 것 같다.
tcache_put(tc_victim, tc_idx);
}
}
}
#endif
void* p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}
}
house of lore라는 공격방식이 어떻게 보안검사를 우회하는지 알 수 있는 부분이다. 기본적인 것은 fastbin과 비슷하지만 smallbin은 원형 double linked list 이다.
smallbin의 작동방식은 다음과 같다.
1. 요청 사이즈를 통해서 해당 smallbin을 찾는다.
2. 해당 bin->bk != bin인지 검사한다. (bin->bk==bin이면 아무것도 청크가 없는 것)
3. bin->bk->fd 가 bin->bk 인지 검사한다. 만약 아닐경우
malloc(): smallbin double linked list corrupted 메세지를 출력하고 종료한다.
4. tcache의 해당사이즈가 비어있으면 남은 청크를 tcache로 이동시킨다.
5. 할당받은 청크를 반환한다.
그런데 재밌는게 가능할 것 같다. smallbin chunk에서 tcache로 옮기는 과정에서 fake chunk를 넣어주고
smallbin의 원상복구가 가능할 것 같다..!! 이에 대해서는 자세히 분석해서 올리도록 하겠다.
'Security > 개인 연구' 카테고리의 다른 글
malloc 함수 분석 (0) | 2021.01.09 |
---|---|
tcache smallbin 취약점 (0) | 2020.12.28 |
malloc.c 분석 [1] __libc_malloc (feat. Arena) (0) | 2020.12.28 |
fclose() 를 이용한 leak (1) | 2020.12.27 |
Ubuntu 16.04 Exploit 테크닉 정리 (0) | 2020.12.17 |