_int_malloc, _int_free는 heap이 정상동작 하지 못하는 경우를 방지하기 위해 검증 코드가 존재한다. 이 검증 코드를ㄹ 이해하고 있어야 익스플로잇 할 때 우회해서 공격할 수 있다.
_int_malloc
malloc(): memory corruption (fast)
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
idx = fastbin_index (nb);
if (victim != 0)
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
}
△ _int_malloc 에서 fastbin 크기의 heap이 할당될 때 호출되는 검증 코드
할당하려는 fastbin의 크기와 할당될 영역의 크기를 구한 후 두 크기가 같은 bin에 속하는지 검증
같은 bin에 속하는 크기라면 정상적으로 할당
아니라면 "malloc(): memory corruption (fast)" 오류를 출력하고 비정상 종료
m_error1
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr,*ptr2;
ptr = malloc(0x20);
ptr2 = malloc(0x20);
free(ptr);
free(ptr2);
fprintf(stderr,"ptr2 fd: %p: %p\n", ptr2, ptr2);
*(uint64_t *)ptr2 = *(uint64_t *)ptr2 + 0x100;
fprintf(stderr,"ptr2 fd: %p: %p\n", ptr2, ptr2);
malloc(0x20);
malloc(0x20);
}
△ m_error1.c
ptr2의 fd를 heap chunk가 존재하지 않는 영역의 주소로 조작해 검증에러를 발생시킨다.
△ 메모리의 모습
해제된 ptr2의 fd가 올바르지 않은 0x602100 주소로 조작되어있다.
0x602100 주소는 같은 bin의 크기를 가지고 있지 않기 때문에 오류가 발생한다.
malloc(): memory corruption
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
unsorted bin heap의 main_arena의 unsorted bin 주소인지 확인
다른 값일 때, 할당하는 heap의 크기가 2*SIZE_SZ 보다 작거나 av->system_mem 보다 크면 오류를 출력하고 비정상 종료
m_error2
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr,*ptr2,*ptr3;
ptr = malloc(0x100);
ptr2 = malloc(0x100);
free(ptr);
fprintf(stderr, "BK: %p\n", ptr[1]);
ptr[1] += 0x40;
fprintf(stderr, "Corrupted BK: %p\n", ptr[1]);
malloc(0x21000);
}
△ malloc():memory corruption 검증 에러를 발생시키는 코드
△ 메모리의 모습
unsorted bin의 bk를 다른 값으로 조작하고 av->system_mem 보다 큰 값을 할당했다.
unsorted bin의 bk가 smallbin의 주소를 가리키고 있기 때문에 오류가 발생한다.
_int_free
free(): invalid pointer
#define misaligned_chunk(p) \
((uintptr_t)(MALLOC_ALIGNMENT == 2 * SIZE_SZ ? (p) : chunk2mem (p)) \
& MALLOC_ALIGN_MASK)
size = chunksize (p);
if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
|| __builtin_expect (misaligned_chunk (p), 0))
{
errstr = "free(): invalid pointer";
errout:
if (!have_lock && locked)
(void) mutex_unlock (&av->mutex);
malloc_printerr (check_action, errstr, chunk2mem (p), av);
△ heap을 해제할 때 호출되는 검증 코드
p > -size 혹은 misaligned_chunk (p) 조건을 검증한다.
해제하려는 chunk의 크기를 음수로 변환하여 chunk 주소랑 비교한다. 이 때, 작거나 해제하려는 chunk의 align이 맞지 않을 경우 "free(): invalid pointer" 오류를 출력하고 비정상 종료한다.
f_error1
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr;
// p > -size
ptr = malloc(0x100);
ptr[-1] = -0x100;
free(ptr);
}
△ free(): invalid pointer 검증 에러를 발생시키는 코드
△ 메모리의 모습
heap의 size를 음수로 바꾸고 해제하면서 오류가 발생한다.
f_error2
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr;
ptr = malloc(0x100);
free(ptr+0x100);
}
△ heap chunk가 존재하지 않는 영역을 해제하는 코드
△ 메모리의 모습
해제하려는 영역은 malloc_chunk 구조체가 존재하지 않기 때문에 오류가 발생한다.
free(): invalid size
#define MIN_CHUNK_SIZE (offsetof(struct malloc_chunk, fd_nextsize))
#define MINSIZE \
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
{
errstr = "free(): invalid size";
goto errout;
}
△ heap을 해제할 때 호출되는 검증 코드
해제하려는 heap의 size가 MINSIZE (malloc_chunk의 크기)보다 크고 heap의 align이 맞는지 검증한다.
malloc()을 통해 heap을 할당하면 malloc_chunks 구조체가 함께 할당되기 때문에 최소크기는 0x20이다.
f_error3
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr;
// size < MINSIZE
ptr = malloc(0x10);
ptr[-1] = 0x19;
free(ptr);
}
△ free(): invalid size 검증 에러를 발생시키는 코드
△ 메모리의 모습
heap의 size를 최소 크기(0x20)보다 작은 0x19로 조작하고 해제하면서 size가 MINSIZE보다 작아지기 때문에 오류가 발생한다.
free(): invalid next size (fast), free(): invalid next size (normal)
#define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s)))
if (have_lock
|| ({ assert (locked == 0);
mutex_lock(&av->mutex);
locked = 1;
chunk_at_offset (p, size)->size <= 2 * SIZE_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem;
}))
{
errstr = "free(): invalid next size (fast)";
goto errout;
}
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0))
{
errstr = "free(): invalid next size (normal)";
goto errout;
}
△ heap을 해제할 때 호출되는 검증 코드
chunk_at_offset (p, size)->size <= 2 * SIZE_SZ 와 chunksize (chunk_at_offset (p, size)) >= av->system_mem 조건을 검증한다
chunk_at_offset 매크로는 해제하려는 chunk의 size를 가져와 해당 푕ㄴ터에서 size를 더한 주소를 반환한다.
→ 해제하려는 heap 뒤에 위치하는 또 다른 heap의 size를 2*SIZE_SZ, av->system_mem과 비교한다.
(fast) 와 (normal)의 차이: 해제하려는 heap의 크기가 fastbin인지 samllbin 크기인지
f_error4
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr,*ptr2;
ptr = malloc(0x10);
ptr2 = malloc(0x10);
ptr2[-1] = 0x22001;
free(ptr);
}
△ free(): invalid next size 검증 에러를 발생시키는 코드
△ 메모리의 모습
ptr 뒤에 위치하는 ptr2의 size를 av->system_mem 크기보다 크게 설정하고 ptr을 해제하였기 때문에 에러가 발생한다.
double free or corruption (fasttop)
mchunkptr old = *fb, old2;
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
△ heap을 해제할 때 호출되는 검증 코드
old == p 조건을 검증한다. (old 포인터: 이전에 해제한 포인터를 가리킴, p: 현재 해제할 포인터)
old == p 조건이 성립한다면 "double free or corruption (fasttop)" 에러를 출력하고 비정상 종료한다.
f_error5
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr;
ptr = malloc(0x10);
free(ptr);
free(ptr);
}
△ double free or corruption 검증 에러를 발생시키는 코드
두 번째 free 함수에서 ptr을 해제할 때 old와 p가 같은 포인터를 가지기 때문에 에러가 발생한다.
double free or corruption (top)
if (__glibc_unlikely (p == av->top))
{
errstr = "double free or corruption (top)";
goto errout;
}
p == av->top 조건을 검증한다.
해제하려는 heap이 꼭대기(top)에 존재해 top chunk 값을 가지면 "double free or corruption (top)"에러를 출력하고 비정상 종료한다.
f_error6
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr,*ptr2;
ptr = malloc(0x100);
ptr2 = malloc(0x100);
free(ptr2);
free(ptr2);
}
△ 위의 검증 에러를 발생시키는 코드
두 번 해제하는 ptr2가 av->top이기 때문에 에러가 발생한다.
double free or corruption (out)
nextchunk = chunk_at_offset(p, size);
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
{
errstr = "double free or corruption (out)";
goto errout;
}
△ 검증 코드
해제하려는 chunk 뒤에 위치하는 포인터와 av->top + chunksize(av->top) 을 검증한다.
heap chunk를 해제했을 때 해제된 chunk의 크기 뒤에 힙이 존재하지 않으면 "double free or corruption(out)" 에러 메시지가 출력되고 비정상 종료된다.
f_error7
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr,*ptr2;
ptr = malloc(0x100);
ptr2 = malloc(0x100);
ptr2[-1] = 0x21001;
free(ptr2);
}
△ 위 검증에러를 발생시키는 코드
size를 큰 값으로 만들고 해제해 에러가 발생한다.
double free or corruption (!prev)
if (__glibc_unlikely (!prev_inuse(nextchunk)))
{
errstr = "double free or corruption (!prev)";
goto errout;
}
△ 검증 코드
해제하려는 heap 뒤에 위치하는 heap의 size에 PREV_INUSE 플래그가 설정되어 있는지 확인한다.
PREV_INUSE가 설정되어 있지 않다면 "double free or corruption (!prev)"에러를 출력하고 비정상 종료한다.
f_error8.c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr,*ptr2;
ptr = malloc(0x100);
ptr2 = malloc(0x100);
ptr2[-1] = 0x110;
free(ptr);
}
△ ptr2 heap chunk의 PREV_INUSE를 0으로 조작해 에러를 발생시키는 코드
unlink
unlink 매크로: 두 개의 heap chunk가 연속적으로 해제되었을 때 두 chunk를 병합하기 위해 사용
corrupted double-linked list
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
FD->bk = BK; \
BK->fd = FD;
△ smallbin을 처리하는 unlink 매크로 코드
해제하려는 chunk의 FD->bk와 BK->fd가 해제하려는 주소인지 검증한다.
FD->bk와 BK->fd가 해제하려는 heap의 주소와 다르다면 "corrupted double-linked list"에러를 출력하고 비정상 종료한다.
unlink1
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr,*ptr2;
ptr = malloc(0x100);
ptr2 = malloc(0x100);
free(ptr);
free(ptr2);
}
△ 두 개의 인접한 heap chunk를 해제하여 unlink 매크로를 호출하는 코드
오류가 출력되지 않는다. = FD->bk와 BK->fd가 해제하려는 heap의 주소와 같다
unlink2
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr,*ptr2;
ptr = malloc(0x100);
ptr2 = malloc(0x100);
free(ptr);
ptr[0] = ptr+0x100;
ptr[1] = ptr+0x100;
free(ptr2);
}
△ 연결리스트를 조작해 에러를 발생시키는 코드
corrupted double-linked list (not small)
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
else { \
FD->bk = BK; \
BK->fd = FD; \
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV);
△ smallbin에서 허용하는 크기보다 큰 heap이 연속적으로 해제되었을 때 호출되는 unlink 매크로
in_smallbin_range (P->size) 코드로 호출됨
P->fd_nextsize->bk_nextsize != P와 P->bk_nextsize->fd_nextsize != P 조거능로 검증한다.
large bin은 FD, BK, fd_nextsize, bk_nextsize를 관리해 해당 조건을 만족하지 못하면 "corrupted double-linked list (not small)" 에러가 출력되고 비정상 종료한다.
unlink3
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
int main()
{
uint64_t *ptr,*ptr2;
ptr = malloc(0x1000);
ptr2 = malloc(0x1000);
free(ptr);
ptr[2] = ptr+0x100;
ptr[3] = ptr+0x100;
free(ptr2);
}
△ fd_nextsize와 bk_nextsize를 조작해 에러를 발생시키는 코드
'System > System Hacking' 카테고리의 다른 글
[Heap] UAF(Use-After-Free) (0) | 2020.09.19 |
---|---|
[Heap] Double Free 취약점 (0) | 2020.09.17 |
[Heap] ptmalloc2 (0) | 2020.09.12 |
Return to csu (ft. Return-to-vuln, Just-In-Time Code Reuse) (0) | 2020.08.15 |
Return to csu (ft. JIT ROP) (0) | 2020.08.14 |