[Pwnable] 15. Memory Corruption: Use After Free

Wonder_Land🛕·2022년 11월 9일
0

[Pwnable]

목록 보기
15/21
post-thumbnail

[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.


  1. 서론
  2. Use After Free
  3. Q&A
  4. 마치며

1. 서론

'Use After Free'는 메모리 참조에 사용한 포인터를 메모리 해제 후에 적절히 초기화하지 않아서, 또는 해제한 메모리를 초기화하지 않고 다음 청크에 재할당해주면서 발생하는 취약점입니다.

이 취약점은 현재까지도 브라우저 및 커널에서 자주 발견되고 있으며,
익스플로잇 성공률도 다른 취약점에 비해 높아 상당히 위험하다고 알려져 있습니다.


2. Use After Free

1) Dangling Pointer

  • Dangling Pointer
    : 컴퓨터 과학에서, 유효하지 않은 메모리 영역을 가리키는 포인터

malloc 함수는 할당한 메모리의 주소를 반환합니다.

일반적으로, 메모리를 동적할당할 때는

  1. 포인터를 선언
  2. 그 포인터에 malloc함수가 할당한 메모리의 주소 저장
  3. 그 포인터를 참조하여 할당한 메모리에 접근

합니다.

메모리를 해제할 때는 free함수를 사용합니다.

그런데 free함수는 청크를 ptmalloc에 반환하기만 할 뿐, 청크의 주소를 담고 있는 포인터를 초기화하지는 않습니다.

따라서, free의 호출 이후에 프로그래머가 포인터를 초기화하지 않는다면,
포인터는 해제된 청크를 가리키는 Dangling Pointer가 됩니다.

Dangling Pointer가 생긴다고 해서 보안적으로 취약한 것은 아닙니다.
그러나, 프로그램이 예상치 못한 동작을 할 가능성을 키우며, 경우에 따라서는 공격 수단으로 활용될 수도 있습니다.

다음 예제는 Dangling Pointer의 위험성을 보여주는 예제입니다.
청크를 해제한 후에 청크를 가리키던 ptr변수를 초기화하지 않았습니다.

따라서, ptr은 이전에 할당한 청크의 주소를 가리키는 DangLing Pointer가 됩니다.

// Name: dangling_ptr.c
// Compile: gcc -o dangling_ptr dangling_ptr.c

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

int main() {
  char *ptr = NULL;
  int idx;

  while (1) {
    printf("> ");
    scanf("%d", &idx);
    switch (idx) {
      case 1:
        if (ptr) {
          printf("Already allocated\n");
          break;
        }
        ptr = malloc(256);
        break;
      case 2:
        if (!ptr) {
          printf("Empty\n");
        }
        free(ptr);
        break;
      default:
        break;
    }
  }
}
$ ./dangling_ptr
> 1
> 2

ptr이 해제된 청크의 주소를 가리키고 있으므로, 이를 다시 해제할 수 있습니다.

$ ./dangling_ptr
> 1
> 2
> 2

이를 'Double Free Bug'라고 합니다.


2) Use After Free

  • Use After Free(UAF)
    : 해제된 메모리에 접근할 수 있을 때 발생하는 취약점

이는 Dangling Pointer 외에, 새롭게 할당한 영역을 초기화하지 않고 사용하면서도 발생합니다.

mallocfree함수는 할당 / 해제할 메모리의 데이터들을 초기화하지 않습니다.

그래서 새롭게 할당한 청크를 프로그래머가 명시적으로 초기화하지 않는다면, 메모리에 남아있던 데이터가 유출되거나 사용될 수 있습니다.

// Name: uaf.c
// Compile: gcc -o uaf uaf.c -no-pie

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

struct NameTag {
  char team_name[16];
  char name[32];
  void (*func)();
};

struct Secret {
  char secret_name[16];
  char secret_info[32];
  long code;
};

int main() {
  int idx;

  struct NameTag *nametag;
  struct Secret *secret;
  
  secret = malloc(sizeof(struct Secret));
  
  strcpy(secret->secret_name, "ADMIN PASSWORD");
  strcpy(secret->secret_info, "P@ssw0rd!@#");
  secret->code = 0x1337;
  
  free(secret);
  secret = NULL;
  
  nametag = malloc(sizeof(struct NameTag));
  
  strcpy(nametag->team_name, "security team");
  memcpy(nametag->name, "S", 1);
  
  printf("Team Name: %s\n", nametag->team_name);
  printf("Name: %s\n", nametag->name);
  
  if (nametag->func) {
    printf("Nametag function: %p\n", nametag->func);
    nametag->func();
  }
}

위의 예제는 UAF 취약점이 있는 코드입니다.

구조체 NameTagSecret이 정의되어 있는데,
예제에서 그 중 외부에 유출되면 안되는 Secret구조체를 먼저 할당합니다.
그리고 secret_name, secret_info, code에 값을 입력하고, 해제합니다.

이후, 사원의 정보를 담고 있는 nametag를 생성합니다.
team_name, name에 각각의 값을 입력하고, 입력한 데이터를 출력합니다.

이후, 함수 포인터 funcNULL이 아니라면 포인터가 가리키는 주소를 출력하고, 해당 주소의 함수를 호출합니다.

$ ./uaf
Team Name: security team
Name: S@ssw0rd!@#
Nametag function: 0x1337
Segmentation fault (core dumped)

출력 결과를 보면, Name으로 secret_info의 문자열이 출력되고,
값을 입력한 적 없는 함수 포인터가 0x1337을 가리키는 것을 볼 수 있습니다.


3) UAF 동적 분석

ptmalloc2는 새로운 할당 요청이 들어오면,
요청된 크기와 비슷한 청크가 bin이나 tcahche에 있는지 확인합니다.

만약 있다면, 해당 청크를 재사용합니다.

위의 예제 코드 상에서, NametagSecret은 같은 크기의 구조체입니다.

그러므로 앞서 할당한 secret을 해제하고 nametag를 할당하면,
nametagsecret과 같은 메모리 영역을 사용하게 됩니다.
이 때, free는 해제한 메모리의 데이터를 초기화하지 않으므로, nametag에는 secret값이 일부 남아있습니다.

다음은 secret을 해제한 직후, secret이 사용하던 메모리 영역을 출력한 것입니다.

secret_name은 적절한 fdbk값으로 초기화됐지만, secret_info의 값은 그대로 남아있습니다.

pwndbg> heap
Free chunk (tcache) | PREV_INUSE
Addr: 0x405290
Size: 0x41
fd: 0x00

pwndbg> x/10gx 0x405290
0x405290:       0x0000000000000000      0x0000000000000041
0x4052a0:       0x0000000000000000      0x0000000000405010
0x4052b0:       0x6472307773734050      0x0000000000234021

pwndbg> x/s 0x4052b0
0x4052b0:       "P@ssw0rd!@#"

다음으로는, nametag를 할당하고, printf함수를 호출하는 시점에서 nametag 멤버 변수들의 값을 확인하겠습니다.

pwndbg> x/10gx 0x405290
0x405290:       0x0000000000000000      0x0000000000000041
0x4052a0:       0x7974697275636573      0x0000006d61657420
0x4052b0:       0x6472307773734053      0x0000000000234021
0x4052c0:       0x0000000000000000      0x0000000000000000
0x4052d0:       0x0000000000001337      0x0000000000020d31

pwndbg> x/s 0x4052a0
0x4052a0:       "security team"

pwndbg> x/s 0x4052b0
0x4052b0:       "S@ssw0rd!@#"

pwndbg> x/gx 0x4052d0
0x4052d0:       0x0000000000001337

nametag->team_name에는 "security team"이 그대로 입력되었으나,
nametag->name에는 초기화되지 않은 secret_info의 값이 존재하는 것을 확인할 수 있습니다.
또한, nametag->fuc 위치에 secret->code에 대입했던 0x1337이 남아있는 것을 알 수 있습니다.

이 값이 0이 아니므로, 예제 코드에서 nametag->func가 호출되고, Segmentation Fault가 발생합니다.

정리하자면!
동적 할당한 청크를 해제한 뒤에는,
해제된 메모리 영역에 이전 객체의 데이터가 남습니다.

이러한 특징을 공격자가 이용한다면, 초기화되지 않은 메모리의 값을 읽어내거나, 새로운 객체가 악의적인 값을 사용하게 유도하여 프로그램의 정상적인 실행을 방해할 수 있습니다.


3. Q&A

-


4. 마치며

-

[Reference] : 위 글은 다음 내용을 제가 공부한 후, 인용∙참고∙정리하여 만들어진 게시글입니다.

profile
아무것도 모르는 컴공 학생의 Wonder_Land

0개의 댓글