[Heap] Security Check
System/System Hacking

[Heap] Security Check

728x90

_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를 조작해 에러를 발생시키는 코드

SMALL

'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