예제를 통해 알아보는 Use-After-Free 발생 원인

Use After Free 취약점은 IE나 Chrome 과 같은 브라우저 버그 헌팅시 자주 언급되곤 한다.

구글링을 통해 검색해본 결과 한글어 검색 결과는 대부분 IE취약점과 관련된 내용이었으며,
영어 검색 결과 역시 IE관련 내용이 많아, 대부분의 예제가 자바스크립트로 작성되어 있다.

불행하게도 자바스크립트 까막눈인지라, 기존에 공개되어 있는 코드 분석은 쉽지 않을 것 같아
C언어 자료구조를 공부하면서 정리한 Use-After-Free 예제를 소개하고자 한다.

예제 코드 설명

코드 수준은 매우 간단하므로, C코드를 읽을 수 있다면 이해하는데 큰 어려움은 없을 것이다.

먼저 예제 코드는 아래와 같다.

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

typedef struct _MEMBER
{
	char * m_pName;
	char * m_pAddr;
	char * m_Telno;
	char * m_temp;

}MEMBER;
// 구조체를 이용해 포인터 변수 선언//
void main()
{

	MEMBER m1;

	char temp[1024]; // 스택에 1024바이트 버퍼 생성 //

	printf("이름 : ");
	gets(temp);
	m1.m_pName = (char *)malloc(strlen(temp)+1);
	strcpy (m1.m_pName, temp);

	printf("--------------------\n\n");

	printf("주소 : ");
	gets(temp);
	m1.m_pAddr = (char *)malloc(strlen(temp)+1);
	strcpy (m1.m_pAddr, temp);

	printf("--------------------\n\n");

	printf("전화 : ");
	gets(temp);
	m1.m_Telno = (char *)malloc(strlen(temp)+1);
	strcpy (m1.m_Telno, temp);

	printf("--------------------\n\n");

	printf ("이름  :  %s\n\n", m1.m_pName);
	printf ("주소  :  %s\n\n", m1.m_pAddr);
	printf ("전화  :  %s\n\n", m1.m_Telno);

	free(m1.m_pAddr);
	
	gets(temp);
	m1.m_temp = (char *) malloc(strlen(temp)+1);
	strcpy (m1.m_temp, temp);

	printf ("이름  :  %s\n\n", m1.m_pName);
	printf ("주소  :  %s\n\n", m1.m_pAddr);
	printf ("전화  :  %s\n\n", m1.m_Telno);


	/* Change Third Object Test */

	printf ("Input Explotable Payloads ...... \n\n");
	gets(m1.m_pAddr);
	printf ("전화  :  %s\n\n", m1.m_Telno);
	
}

코드 흐름은 다음과 같다.

- MEMBER 구조체를 통해 Char형 포인터를 선언한다.

- 길이가 1024바이트인 'temp' 배열을 생성한다.

- gets() 를 이용해 첫번째 사용자 입력값 ("이름")을 받은 후, 
  malloc 할당된 힙메모리 주소를 m1.m_pAddr에 저장한다.

- 두번째 입력값과 세번째 입력값도 위 방법으로 malloc을 통해 힙영역에 저장한다.

- 각각의 결과 값을 출력한다.  // 여기까지는 별다른 문제가 발생하지 않는다.  //

- 두번째 힙메모리 영역을 해제(free) 시킨다.

- 새로운 값을 입력받은 후 ,  새로운 힙메모리 영역을 할당받는다. 이때 주소는 m1.m_temp에 저장한다.

- 각각의 결과를 다시 출력해본다.

- 새로운 값을 다시 입력 받는다. 이때 Parameter 값의 길이를 100바이트 정도로 길게 넣어본다.

- 결과를 출력해본다.

일단 44 라인까지 실행하면 아래와 같이 정상적으로 값이 출력되는 것을 볼 수 있다.

Cap 2014-07-30 23-24-30-296

Struct내 선언되어 있는 각각의 포인터가 정상적인 값을 가리키고 있는 것으로 보인다.

디버깅을 통해 힙메모리 주소를 좀 더 자세하게 들여다 보도록 하자.

44번 라인까지 실행된 상태에서의 힙 메모리 구조는 아래와 같다.

Cap 2014-07-30 23-32-04-199

구조체 내 각각의 포인터 (m1.m_pAddr, m1.m_pName, m1.m_pTelno) 가
힙 메모리 영역의 “AAAA” / “BBBB” / “CCCC”를 가리키고 있으며 정상적으로 스트링이 카피 되어있다.

 

페이로드에 따른 힙 메모리 재사용 문제

이제 문제가 되는 다음 코드 영역을 살펴보도록 하자.

	free(m1.m_pAddr);
	
	gets(temp);
	m1.m_temp = (char *) malloc(strlen(temp)+1);
	strcpy (m1.m_temp, temp);

구조체에서의 두번째 포인터 변수 (m1.m_pAddr / “BBBB” 값이 저장)가 가리키고 있는 힙 메모리 영역을
해제 (free) 시킨 후, m1.m_temp 포인터 변수에 힙 메모리 영역을 새로 할당하였다.

사용자로 부터 새로운 값을 입력받아 (gets()) 이곳에 Strcpy하면 힙 메모리는 어떤 모습을 가지게 될까.

디버깅 해보기 전, 나의 예상으로는 새로운 힙매니저가 완전히 새로운 힙 메모리 영역을 배정한 후
사용자 입력값을 그곳에 저장할 것이라고 생각했다.

그러나 예상과는 달리 결과가 두가지로 나뉘었다.

– 첫번째 결과 : 페이로드 크기가 큰 경우 –

구조체의 두번째 포인터 변수가 가리키는 힙 메모리 영역을 해제 시킨 후,
새로운 입력값을 받는데, 이 때 페이로드 값을 약 40바이트 정도로 크게 (기존 입력값 대비) 넣어보았다.

이때 힙 메모리 영역의 구조는 다음과 같다.

Cap 2014-07-31 00-01-49-725

Cap 2014-07-31 00-02-20-306

위 그림에서 알 수 있듯이, 구조체의 세번째 포인터 변수가 가리키는 “CCCC”값 아래쪽 힙 영역이
새로이 할당된 후, 그곳에 사용자 입력값 (“D” * 40바이트) 가 복사되어 저장되었다.

위의 경우 나의 예상과 일치하였는데, 문제는 페이로드를 작게 넣었을 경우에 발생했다.

– 두번째 결과 : 페이로드 크기가 작거나 동일한 경우 –

이번에는 p_Addr 힙 영역을 해제 한 후, 페이로드 값을 기존 값과 동일한 크기 (“DDDD” – 4바이트)로
입력해 보았다.

결과는 다음과 같다.

Cap 2014-07-31 00-17-55-246

46번째 라인에서 보다시피 m1.m_pAddr 포인터가 가리키고 있던 “BBBB”영역을 해제 한 후,
malloc 함수를 통해 새로운 힙 메모리 영역을 선언했음에도 불구하고, 기존에 잡혀 있던 힙 영역을
재사용 하는 것을 볼 수 있다.

아마도 힙매니저가 효율적인 메모리 관리를 위해, 어떤 규칙을 갖고 할당하는 것으로 생각되는데
문제는 그 53번 라인처럼, 이미 해제된 영역을 가리키고 있던 포인터를 사용하여
해제된 영역에 접근할 수 있다는 것이다.

54번 라인까지의 출력 결과는 아래와 같다.

Cap 2014-07-31 00-24-33-070

m1.m_pAddr 포인터가 가리키고 있던 값 (“BBBB”) 가 해제(free) 되었음에도 불구하고,
해제된 공간이 재사용됨으로 인해서 기존의 포인터가 해당값을 참조할 수 있게 되었다.

만약 이와 같은 상황에서 아래과 같이 힙 메모리를 해제하면 어떤 결과가 발생하게 될까?

	printf ("이름  :  %s\n\n", m1.m_pName);
	printf ("주소  :  %s\n\n", m1.m_pAddr);
	printf ("전화  :  %s\n\n", m1.m_Telno);

	free(m1.m_pAddr);
	free(m1.m_temp);

Cap 2014-07-31 00-38-08-582

위와 같이 잘못된 힙메모리 참조에 대한 에러 메시지가 표시되는 것을 볼 수 있다.
그 이유는 현재 두개의 포인터가 같은 힙 메모리 영역을 가리키고 있는 상황에서
두번의 힙 메모리 해제(free)를 시도하였기 때문이다.

m_temp 포인터가 가리키는 힙 영역이 해제되기 직전의 메모리 구조를 보면 이해가 쉽게 될 것이다.

Cap 2014-07-31 00-37-53-431

위 그림에서 확인할 수 있듯이 56번째 라인이 실행되면서 이미 힙 메모리 영역은
“feeefeee”값으로 해제가 된 상태이다.
이때 57번 라인이 실행되면 기존의 해제된 영역을 다시 해제하려고 하기 때문에 위와 같이 에러가 발생하게 된다.

 

마무리 지으며…

위에서 언급했듯이 Use-After-Free 공격은 주로 브라우저 취약점에서 많이 발견되고 있다.

아무래도 사용자 입력값 (페이로드가 유동적)에 대한 힙 메모리 컨트롤이 많이 발생하는 프로그램일수록
이러한 취약점을 이용한 공격에 많이 노출될 수 밖에 없을 것 같다.

위에서는 C코드를 예제로 하였지만 자바스크립트 역시 동일한 개념으로 접근하면 될 것으로 보인다.

마지막으로 Sweepchip 님의 블로그에 소개된 UAF취약점을 이용한 브라우저 Exploit 자료를 링크로
포스팅을 마치고자 한다.

[Sweepchip 님의 블로그 글 보러 가기]

 

Site Footer