Back to SECURITY

DLL Spoofing

by gynvael.coldwind//vx
mailto:
gynvael@vexillium.org
www:
http://gynvael.lunarii.org
http://gynvael.vexillium.org

1. Co to jest DLL Spoofing.

"spoof v, [...] kant, szachrajstwo, okantować" [1]

DLL Spoofing jest to jedna z najmniej inwazyjnych metod modyfikacji działania aplikacji pod systemem Microsoft Windows. Polega ona na "podłożeniu" aplikacji odpowiedniej, sfabrykowanej przez nas, biblioteki DLL, w taki sposób, aby skłonić aplikacje do użycia jej zamiast oryginalnej biblioteki DLL. Pozwala to na uruchomienie swojego kodu w kontekscie aplikacji, podmianę funkcji importowanych przez aplikację, lub modyfikację kodu samej aplikacji, a w niektórych przypadkach pozwala to nawet na podniesienia poziomu uprawnień użytkownika.

W niniejszym artykule postaram się wytłumaczyć mechanizm działania DLL Spoofingu, oraz zaprezentować metodę tworzenia fałszywych bibliotek DLL, wraz z przykładem użycia.

Co będzie potrzebne:

Wszystkie informacje zawarte w tym artykule, dotyczą systemu Windows XP i na nim zostały przetestowane. Prawdopodobnie jednak są prawdziwe również dla innych wersji systemu Windows.

Nazwy struktur oraz pól pochodzą z pliku nagłówkowego "winnt.h", będącego częścią Windows API (Platform API). Plik powinien znajdować się w katalogu "include\" zainstalowanego kompilora języka C lub C++.

Poniższy artykuł ma charakter edukacyjny, a jego autor nie odpowiada za ewentualne szkody powstałe w wyniku nieumiejętnego użycia wiedzy pozyskanej z ninejszego artykułu.

2. Dynamiczne biblioteki w systemie Windows.

Zanim przejdę do samego DLL Spoofingu, postaram się przybliżyć tematykę bibliotek DLL w systemie Windows.

(Osoby które wiedzą co to jest DLL mogą spokojnie opuścić ten akapit)

Załóżmy że programujemy w języku C. Stworzyliśmy 10 programów, i w każdym z nich musieliśmy mieć funkcję "Avg", ktora oblicza średnią z podanej tablicy float'ów. Programy skompilowaliśmy, i wszystko ładnie działa. Ale pewnego dnia okazuje się że funkcja "Avg" w sumie działa źle. Więc po kolei edytujemy każdy z 10 programów i poprawiamy tą funkcję. Następnie kompilujemy i testujemy. Uff. Trochę to trwało. Wyciągamy odpowiednie wnioski, i tworzymy statyczną bibliotekę ("avg.a") z funkcją "Avg", a następnie zmieniamy kod tak żeby korzystał z tej statycznej biblioteki. Kompilujemy wszystkie 10 programów, podając kompilatorowi bibliotekę, żeby ją włączył do pliku wykonywalnego. Wszystko Działa. Mija trochę czasu, mamy już 100 programów które korzystają z "avg.a". I się znowu okazuje że mamy błąd. Poprawiamy bibliotekę "avg.a", po czym rekompilujemy 100 programów. Uff. Trochę to trwało. A gdyby tak nie linkować statycznie bilbioteki z funkcją "Avg" ? Można by stworzyć bibliotekę ładowaną dynamicznie i umieścić w niej funkcję "Avg". Dzięki takiemu rozwiązaniu nie dość że nie będzie trzeba rekompilować 100 programów w razie poprawki w funkcji "Avg", ponieważ biblioteka dynamiczna zostanie "przyłączona" do programu dopiero przy jego uruchomieniu, a nie podczas kompilacji, to jeszcze wielkości plików wykonywalnych (*.exe) zmaleją, gdyż wywalimy z nich funkcję "Avg". A wszystko to kosztem wydłużenia czasu uruchamiania programu o parę milisekund. Biblioteki DLL (Dynamic Link Library) są właśnie takimi bibliotekami ładowanymi dynamicznie, podczas uruchamiania program, lub już w czasie jego działania (jeśli program uzna że potrzebuje dodatkową funkcjonalność). Oprócz funkcji, biblioteki DLL mogą przechowywać również dane, oraz różnego rodzaju zasoby (patrz Windows Resources), takie jak ikony, pliki graficzne, skrypty wyglądu okien czy pliki HTML.

Funkcje udostępniane przez bibliotekę DLL, to tzw. funkcje EKSPORTOWANE. Analogicznie, jeśli program wymaga jakiejś funkcji z biblioteki, to mówimy o IMPORTOWANIU funkcji.

Patrząc od strony technicznej, biblioteka DLL to plik PE (Portable Executable) z rozszerzeniem ".dll" (rozszerzenie to nie jest wymagane, można użyć innego). Adres oraz wielkość listy eksportów umieszczony jest w OptionalHeader (IMAGE_OPTIONAL_HEADER), który jest częścią nagłówka PE (IMAGE_NT_HEADERS), w tablicy DataDirectory pod indeksem IMAGE_DIRECTORY_ENTRY_EXPORT (0). Pod tym adresem znaleźć można strukturę IMAGE_EXPORT_DIRECTORY:

typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; DWORD AddressOfNames; DWORD AddressOfNameOrdinals; } IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;

Trzy ostatnie pola to adresy tablicy adresów eksportowanych funkcji (AddressOfFunctions), ich nazw (AddressOfNames) i numerów identyfikacyjnych (AddressOfNameOrdinals). Gdy aplikacja pobiera adres funkcji, funkcja jest wyszukiwana po nazwię lub numerze identyfikacyjnym właśnie w tych tablicach.
Co ciekawe, dana funkcja wcale nie musi się znajdować w danej bibliotece DLL. Loader PE pytający o funkcję eksportowaną przez daną bibliotekę DLL może zostać przekierowany (tzw. forwarding) do innej biblioteki DLL. Bardzo dobrym przykładem w tym miejscu, jest biblioteka wsock32.dll (WinSock 1), która większość funkcji eksportowanych przez siebie przekierowuje do biblioteki ws2_32.dll (WinSock 2).

Jak sprawdzić co dana bilbioteka eksportuje? Możemy użyć w tym celu na przykład "dumppe" (lub PEView lub CFF Explorer). Dla przykładu wylistujmy wszystkie eksportowane funkcje z wymienionej już wcześniej biblioteki "wsock32.dll".

cmd.exe >dumppe c:\windows\system32\wsock32.dll [...] Exp Addr Hint Ord Export Name by WSOCK32.dll - Wed Aug 4 08:14:51 2004 -------- ---- ----- --------------------------------------------------------- 0000286E 0 1141 AcceptEx (forwarder -> MSWSOCK.AcceptEx) 0000287F 1 1111 EnumProtocolsA (forwarder -> MSWSOCK.EnumProtocolsA) 00002896 2 1112 EnumProtocolsW (forwarder -> MSWSOCK.EnumProtocolsW) 000028AD 3 1142 GetAcceptExSockaddrs (forwarder -> MSWSOCK.GetAcceptExSockaddrs) 000028CA 4 1109 GetAddressByNameA (forwarder -> MSWSOCK.GetAddressByNameA) 000028E4 5 1110 GetAddressByNameW (forwarder -> MSWSOCK.GetAddressByNameW) 000028FE 6 1115 GetNameByTypeA (forwarder -> MSWSOCK.GetNameByTypeA) 00002915 7 1116 GetNameByTypeW (forwarder -> MSWSOCK.GetNameByTypeW) 0000292C 8 1119 GetServiceA (forwarder -> MSWSOCK.GetServiceA) 00002940 9 1120 GetServiceW (forwarder -> MSWSOCK.GetServiceW) [...] 00002DBE 43 1108 s_perror (forwarder -> MSWSOCK.s_perror) 00002DCF 44 18 select (forwarder -> ws2_32.select) 00002DDD 45 19 send (forwarder -> ws2_32.send) 00002DE9 46 20 sendto (forwarder -> ws2_32.sendto) 00002DF7 47 1105 sethostname (forwarder -> MSWSOCK.sethostname) 00002E30 48 21 setsockopt 00002E0B 49 22 shutdown (forwarder -> ws2_32.shutdown) 00002E1B 4A 23 socket (forwarder -> ws2_32.socket)

Jak widać każda exportowana funkcja jest zcharakteryzowana przez swój adres (jest to adres tzw. RVA, Relative Virtual Address), Hint (jest to pole z tablicy nazw funkcji, które pomaga określić lokalizację funkcji w tablicy, przez co przyspiesza wyszukiwanie; należy zaznaczyć jednak, że Windows nie opiera się na tym polu, traktuje je jedynie jako podpowiedź), Ord (numer identyfikacyjny), oraz nazwę wraz z uwagami. W uwagach zaznaczone jest czy funkcja jest forwardowana (forwarder), a jeśli tak, to do jakiej bilbioteki oraz do jakiej funkcji w tej bibliotece (np. "ws2_32.send" oznacza funkcję "send" w bibliotece "ws2_32.dll"). Jak się łatwo można domyślić, wiele funkcji może mieć taki sam adres, tj. jedna funkcja może być aliasem drugiej.

Pliki wykonywalne (PE) "*.exe" zawierają listę importowanych bibliotek oraz importowanych funkcji z tych bibliotek. Adres listy importowanych bibliotek znajduję się w strukturze OptionalHeader (IMAGE_OPTIONAL_HEADER) w nagłówku PE (IMAGE_NT_HEADERS), w tablicy DataDirectory pod indeksem IMAGE_DIRECTORY_ENTRY_IMPORT (1). Lista importów składa się ze struktur IMAGE_IMPORT_DESCRIPTOR.

typedef struct _IMAGE_IMPORT_DESCRIPTOR { _ANONYMOUS_UNION union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;

Najważniejszym polem w tej strukturze jest Name, które jest adresem (RVA) nazwy biblioteki (np. "wsock32.dll"). Oprócz tego, w tej strukturze są dwa niemniej ważne pola, mianowicie FirstThunk oraz OriginalFirstThunk. Zawierają one adresy tablic struktur IMAGE_THUNK_DATA32. Obie tablice (OriginalFirstThunk i FirstThunk) są równoliczne. Po jednym wpisie w obu tablicach dla każdej importowanej funkcji.

typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; DWORD Function; DWORD Ordinal; DWORD AddressOfData; } u1; } IMAGE_THUNK_DATA32,*PIMAGE_THUNK_DATA32;

Przed uruchomieniem aplikacji, obie tablice zawierają numery identyfikacyjne funkcji (jeśli u1.Oridinal & IMAGE_ORDINAL_FLAG32 jest prawdziwy) lub adresy nazw funkcji. Należy tutaj dodać że niektóre kompilatory nie wypełniają FirstThunk przy kompilacji (co w niczym nie przeszkadza). Nazwy funkcji przechowywane są w strukturach IMAGE_IMPORT_BY_NAME.

typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;

Pierwsze pole w tej strukturze to, wspomniana wczesniej, podpowiedź (Hint). Natomiast drugie to nazwa w ASCIIZ (ASCII Zero-terminated).
W momencie kiedy uruchamiamy program, Loader PE zaczyna ładować biblioteki DLL, oraz wypełnia tablicę FirstThunk adresami funkcji z biblioteki (nazwy funkcji można odczytać w OriginalFirstThunk), skąd następnie aplikacja może je sobie zczytać i uruchomić daną funkcję.

Pliki PE oferują jeszcze dwie ciekawe możliwości jeśli chodzi o importy. Mianowicie podczas kompilacji, kompilator może wstawić adresy funkcji z bibliotek DLL orazu do kompilowanego pliku do tablicy importów, dzięki czemu (jeśli wersja biblioteki się zgadza), Loader PE nie musi wyszukiwać adresów funkcji w bibliotece DLL, wystarczy że ją wrzuci do pamięci. Mówimy wtedy o tzw. "Bound Imports". [7]
Drugą sprawą jest opóźnione ładowanie biblioteki DLL. Mianowicie biblioteka przy uruchomieniu programu nie jest ładowana (mimo iż widnieje w tablicy importów). Ładowana jest dopiero w momencie próby użycia przez aplikację którejś z jej funkcji. [8]

Aplikacja może również użyć funkcji LoadLibrary/FreeLibrary oraz GetProcAddress do załadowania biblioteki DLL podczas działania programu, oraz pobrania adresów funkcji z załadowanej biblioteki.

Dodać należy jeszcze że biblioteka DLL posiada funkcję DllMain, która jest uruchamiana przy załadowaniu biblioteki DLL do pamięci, przy utworzeniu nowego wątku, zabiciu wątku oraz przy uwolnieniu biblioteki. Funkcja przyjmuje dwa parametry (oraz jeden nieużywany). Pierwszym z nich jest adres (VA - Virtual Address) pod który biblioteka została załadowana, a drugim jest powód wywołania DllMain.

Wiemy już że biblioteki eksportują funkcje, a aplikacje je importują. Musimy jeszcze wiedzieć gdzie system szuka bibliotek. Okazuje się że to zależy.

W rejestrze systemu Windows jest lista znanych systemowi bibliotek DLL (mówimy o Systemowych DLL).

HKLM\SYSTEM\ControlSet001\Control\Session Manager\KnownDLLs

Wszystkie biblioteki DLL znajdujące się na tej liście, oraz wszystkie biblioteki DLL importowane przez biblioteki z listy są uznawane za Systemowe DLL.

Jeśli aplikacja importuje systemowy DLL, jest on szukany w następujących lokacjach (kolejność jest ważna):

Jeśli natomiast aplikacja nie znajduje się na liście, czyli nie jest biblioteką systemową (mówimy o Application DLL albo User DLL), to kolejność przeszukiwania jest następująca:

Specjalne traktowanie ma biblioteka twain_32.dll, która jest odrazu wyszukiwana w katalogu Windowsa (C:\Windows\System32). [9] [10]

W rejestrze na liście systemowych DLL jest standardowo około 20 pozycji, co razem z ich zależnościami daje około 38 pozycji. Są to:

lz32.dll, olesvr32.dll, olethk32.dll, tsappcmp.dll, ntdll.dll, advapi32.dll, comdlg32.dll, imagehlp.dll, oleaut32.dll, rpcrt4.dll, url.dll, version.dll, wldap32.dll, comctl32.dll, msvcrt.dll, mpr.dll, wow32.dll, crypt32.dll, userenv.dll, msasn1.dll, wintrust.dll, apphelp.dll, imm32.dll, cryptui.dll, netapi32.dll, lpk.dll, wininet.dll, usp10.dll, kernel32.dll, urlmon.dll, shlwapi.dll, shdocvw.dll, shell32.dll, user32.dll, olecnv32.dll, olecli32.dll, ole32.dll, gdi32.dll [11]

W katalogu systemowym jest około 1200 bibliotek, stąd wniosek, że jest bardzo dużo bibliotek które są najpierw poszukiwane w katalogu aplikacji, a dopiero potem w katalogu systemowym. Można to wykorzystać do własnych celów, i w katalogu aplikacji umieścić bibliotekę DLL która wykona nasz kod w kontekscie odpalonej aplikacji. Jest to właśnie DLL Spoofing.

3. DLL Spoofing.

3.1. Zapobieganie uruchomieniu.

Zacznijmy od bardzo prostej rzeczy, mianowicie zapobiegania uruchomienia programu. Czemu mieli byśmy niechcieć żeby program się uruchomił? Wyboraźmy sobię (całkiem realną zresztą) sytuację że nasz komputer zainfekował worm, który wrzucił się do katalogu "C:\Windows\Update\" jako plik "updmgr.exe". Proces "updmgr.exe" jest cały czas odpalony, a jeśli go zabijamy, odrazu odpala się ponownie. Jak sprawić żeby się już nie odpalił ? Przyjrzyjmy się importom "updmgr.exe":

cmd.exe >dumppe c:\Windows\Update\updmgr.exe | find "Import Name" DumpPE v1.23 (c) Copyright Tenth Planet Software Intl., C Turvey 1995-1999. All rights reserved. Non-Commercial use only. Imp Addr Hint Import Name from WSOCK32.dll - Not Bound Imp Addr Hint Import Name from VERSION.dll - Not Bound Imp Addr Hint Import Name from KERNEL32.dll - Not Bound Imp Addr Hint Import Name from USER32.dll - Not Bound Imp Addr Hint Import Name from SHELL32.dll - Not Bound >

Jak widać worm importuje funkcje z pięciu bibliotek, z czego cztery są systemowe. Jedyną niesystemowa biblioteką jest "wsock32.dll". Jako że wiemy że ta biblioteka jest napierw szukana w katalogu programu (czyli "C:\Windows\Update\"), to możemy wrzucić tam pusty plik o nazwie "wsock32.dll". Windows przy ponownym uruchomieniu "updmgr.exe" odczyta "C:\Windows\Update\wsock32.dll", stwierdzi że jest to nieprawidłowa biblioteka, i zabije proces. Po zabiciu procesu "updmgr.exe" faktycznie pojawił by się komunikat:

msg updmgr.exe - Bad Image The application or DLL C:\Windows\Update\WSOCK32.dll is not a valid Windows image. Please check this against your installation diskette.

Po kliknięciu "OK" proces został by zabity, a my mogli byśmy wywalić "updmgr.exe" z dysku.

3.2. Forwardująca DLL.

Zajmijmy się teraz programem mIRC. Jest to bardzo przyjemny klient sieci IRC, korzystający oczywiście z w/w biblioteki "wsock32.dll". Załóżmy że naszym końcowym celem będzie logowanie przesyłanych danych, między mIRC'em a serwerem sieci IRC.
W poprzednim punkcie pokazałem jak pośrednio "zabić" proces, w tym natomiast skupimy się na stworzeniu biblioteki DLL która będzie transparentna dla mIRC'a, czyli mIRC z jej użyciem będzie normalnie działał. Pierwszym krokiem będzie stworzenie pliku "fwdWsock32.def", który będzie potrzebny programowi "dllwrap.exe" do stworzenia biblioteki DLL.
Pliki "*.def" [12] zawierają informacje o nazwie biblioteki, eksportowanych przez nią funkcjach, ewentualnych aliasach oraz forwardach. Przykładowy prosty plik "*.def" wygląda następująco:

test.def LIBRARY test.dll EXPORTS MojaExportowanaFunkcja @12 AliasNaFunkcje = PrawdziwaNazwaFunkcji ForwardowanaFunkcja = BibliotekaDll.JakasFunkcja

Gdzie "@12" to numer identyfikacyjny funkcji (Ord). Do stworzenia pliku "*.def" z "wsock32.dll" użyjemy aplikacji impdef.exe:

cmd.exe >impdef fwdWsock32.def c:\windows\system32\wsock32.dll Borland Impdef Version 3.0.22 Copyright (c) 1991, 2000 Inprise Corporation >

W ten sposób postawł plik "fwdWsock32.def". Poniżej prezentuje jego fragment:

fwdWsock32.def LIBRARY WSOCK32.DLL EXPORTS AcceptEx @1141; AcceptEx EnumProtocolsA @1111; EnumProtocolsA EnumProtocolsW @1112; EnumProtocolsW [...]

Teraz musimy wykonać kopię oryginalnej "wsock32.dll" do naszego katalogu, nazywając ją na przykład "orgWsock32.dll", a następnie przerobić uzyskany plik "fwdWsock32.def" do postaci:

cut Funkcja = orgWsock32.Funkcja

Jeśli używamy gVima sprawa ogranicza się do jednej prostej komendy:

:4,$s/^ *\([A-Za-z0-9_]\+\) *\(@[0-9]\+\)[ ;].*$/\1=orgWsock32.\1 \2/

W innych edytorach byćmoże będziemy musieli zrobić to ręcznie (w takim wypadku możemy się ograniczyć do funkcji wymaganych przez mIRC).
Uzyskany przez nas plik powinien wyglądać następująco:

fwdWsock32.def LIBRARY WSOCK32.DLL EXPORTS AcceptEx=orgWsock32.AcceptEx @1141 EnumProtocolsA=orgWsock32.EnumProtocolsA @1111 EnumProtocolsW=orgWsock32.EnumProtocolsW @1112 [...]

Następnie tworzymy wyjściową bibliotekę:

cmd.exe >dllwrap --def fwdWsock32.def -o wsock32.dll >dir wsock32.dll | find "wsock32.dll" 2006-08-28 17:59 23 447 wsock32.dll >

Wrzucamy bibliotekę do katalogu mIRC i uruchamiamy program. Program powinien uruchomić się bez problemów i normalnie działać. Jedyna różnica jest taka, że teraz korzysta z naszej spoofniętej biblioteki DLL.

3.3. Podmiana funkcji "send" i "recv".

Kolejnym krokiem jest podmienienie dwóch funkcji, "send" (która w WinSock odpowiada za wysyłanie danych), oraz "recv" (która odpowiada za odbieranie danych). W tym celu stworzymy nowy plik "spoof.c" w którym umieścimy dwie funkcje, "MySend" oraz "MyRecv", które przejmą funkcje "send" i "recv".
Aby nie było problemów z kompatybilnością funkcji, funkcje "MySend" oraz "MyRecv" musza mieć prototyp identyczny jak oryginał. Prototypy funkcji "send" i "recv" można znaleźć w "include\winsock.h".

int PASCAL recv(SOCKET,char*,int,int); int PASCAL send(SOCKET,const char*,int,int);

Na początek niech "MyRecv" i "MySend" po prostu wypisują coś na stdout. Jak wiadomo mIRC jest aplikacją okienkową (bezkonsolową), więc najlepiej stdout podczas uruchomienia po prostu przekierować do pliku.

Plik spoof.c wygląda następująco:

spoof.c #include <stdio.h> int __stdcall MyRecv(int sock,char* data,int count,int sth) { puts("recv()"); fflush(stdout); return 0; } int __stdcall MySend(int sock,const char* data,int count,int sth) { puts("send()"); fflush(stdout); return 0; }

Kompilujemy plik do obiektu:

cmd.exe >gcc spoof.c -c -Wall >dir spoof.o | find "spoof.o" 2006-08-28 18:13 698 spoof.o >

Zanim stworzymy bibliotekę DLL, musimy jeszcze usunąć przekierowanie w "fwdWsock32.def", i wstawić aliasy "send=MySend@16" oraz "recv=MyRecv@16".

fwdWsock32.def [...] recv=MyRecv@16 @16 [...] send=MySend@16 @19 [...]

Następnie tworzymy DLL, przerzucamy ją do katalogu mIRC'a, po czym uruchamiany program przekierowując stdout do pliku out.txt:

cmd.exe >dllwrap --def fwdWsock32.def -o wsock32.dll spoof.o >copy /Y wsock32.dll c:\mIRC\ >cd c:\mIRC\ >mirc > out.txt

Gdy program się uruchomi, klikamy żeby się połączył. Z uwagi na to że nie zachowaliśmy funkcjonalności "send" i "recv", program się nie połączy, natomiast w pliku "out.txt" pojawi się tekst swiadczący o tym że nasze funkcje zostały wywołane.

out.txt [...] send() send() send() send() send() send() recv()

OK. Poprawmy teraz "spoof.c" tak aby oryginalne "send" oraz "recv" były wywoływane. I dorzućmy powiedzmy logowanie. W tym celu musimy stworzyć dodatkową procedurę "DllMain", która na początku odrazu pobierze adresy oryginałów, i zapisze je gdzieś. Adresy pobierzemy za pomocą LoadLibrary oraz GetProcAddress. Zmodyfikowany "spoof.c" wygląda tak:

spoof.c #include <windows.h> #include <stdio.h> int __stdcall (*OrgRecv)(int,char*,int,int); int __stdcall (*OrgSend)(int,const char*,int,int); int __stdcall MyRecv(int sock,char* data,int count,int sth) { int ret = OrgRecv(sock, data, count, sth); if(ret > 0) { puts("\nRECIVED:"); fwrite(data, 1, ret, stdout); fflush(stdout); } return ret; } int __stdcall MySend(int sock,const char* data,int count,int sth) { puts("\nSENDING:"); fwrite(data, 1, count, stdout); fflush(stdout); return OrgSend(sock, data, count, sth); } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { static HINSTANCE hDll; if(fdwReason == DLL_PROCESS_ATTACH) { hDll = LoadLibrary("C:\\windows\\system32\\wsock32.dll"); OrgRecv = (void*)GetProcAddress(hDll, "recv"); OrgSend = (void*)GetProcAddress(hDll, "send"); return TRUE; } else if(fdwReason == DLL_PROCESS_DETACH) { FreeLibrary(hDll); return TRUE; } return TRUE; }

Kompilacja i test.

cmd.exe >gcc spoof.c -c -Wall >dllwrap --def fwdWsock32.def -o wsock32.dll spoof.o >copy /Y wsock32.dll c:\mIRC\ >cd c:\mIRC\ >mirc > out.txt >

Łączymy się z serwerem IRC. Tym razem połączenie nastąpi, a w naszym pliczku "out.txt" znajdzie się log z niego:

out.txt SENDING: NICK GynDream SENDING: USER GynDream "" "torun.ircnet.pl" :Gynvael Coldwind RECIVED: :torun.ircnet.pl NOTICE AUTH :*** Looking up your hostname... :torun.ircnet.pl NOTICE AUTH :*** Checking Ident RECIVED: :torun.ircnet.pl NOTICE AUTH :*** Found your hostname [...]

Nasza biblioteka jest na tyle uniwerslna w tym momencie, że bez problemu możemy jej użyć do logowania przesyłanych danych dowolnej aplikacji korzystającej z "wsock32.dll".

3.4. Podmiana importów innych DLL.

W poprzednim punkcie pokazałem jak podmienić funkcje spoofniętej biblioteki DLL, w tym natomiast postaram się zaprezentować metodę podmiany, z poziomu spoofniętej DLL, funkcji importowanych z innych bibliotek DLL. Z uwagi na to że podmieniać będziemy adres funkcji w tablicy importów, najlepiej żeby proces był już wpełni uruchomiony. W związku z czym podmiana nastąpi gdy z poziomiu mIRC'a wyśle do serwera string "HACKHELP". Ofiarą (jak można się ze stringa domyślić) będzie funkcja "WinHelpA" z "user32.dll".
Najpierw trochę teorii. Za pomocą funkcji GetModuleHandle możemy uzyskać adres (VA, w pamięci) głównego programu, tj. mIRC'a w tym wypadku. Z tego adresu odczytujemy najpierw IMAGE_DOS_HEADER, a potem header PE, i dobieramy się do tablicy importów. Szukamy tam odpowiedniej funkcji w OriginalFirstThunk i zmieniamy jej adres w blizniaczym wpisie w FirstThunk.
Kod "spoof.c" wygląda następująco:

spoof.c #include <windows.h> #include <stdio.h> int __stdcall (*OrgRecv)(int,char*,int,int); int __stdcall (*OrgSend)(int,const char*,int,int); void* DllSpoof_SpoofImport(const char *DllName, const char *FuncName, void *SpoofFuncAddress); WINUSERAPI BOOL WINAPI MyWinHelpA(HWND a,LPCSTR b,UINT c,DWORD d) { MessageBox(0, "Help was haxxed!", "by gynvael.coldwind//vx", 0); return TRUE; } int __stdcall MyRecv(int sock,char* data,int count,int sth) { int ret = OrgRecv(sock, data, count, sth); if(ret > 0) { puts("\nRECIVED:"); fwrite(data, 1, ret, stdout); fflush(stdout); } return ret; } int __stdcall MySend(int sock,const char* data,int count,int sth) { puts("\nSENDING:"); fwrite(data, 1, count, stdout); fflush(stdout); int i; for(i = 0; i < count - 8; i++) if(strncmp(&amp;data[i], "HACKHELP", 8) == 0) { DllSpoof_SpoofImport("user32.dll", "WinHelpA", MyWinHelpA); puts("\nHACKHELP found! Haxxing!"); fflush(stdout); return count; } return OrgSend(sock, data, count, sth); } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { static HINSTANCE hDll; if(fdwReason == DLL_PROCESS_ATTACH) { hDll = LoadLibrary("C:\\windows\\system32\\wsock32.dll"); OrgRecv = (void*)GetProcAddress(hDll, "recv"); OrgSend = (void*)GetProcAddress(hDll, "send"); return TRUE; } else if(fdwReason == DLL_PROCESS_DETACH) { FreeLibrary(hDll); return TRUE; } return TRUE; } static IMAGE_IMPORT_DESCRIPTOR *FindImportDescriptor(const char *DllName) { unsigned long MainVA = (unsigned long)GetModuleHandle(0); if(*(unsigned short*)MainVA != *(unsigned short*)"MZ") return NULL; IMAGE_DOS_HEADER *DosHeader = (IMAGE_DOS_HEADER*)MainVA; IMAGE_NT_HEADERS *NtHeaders = (IMAGE_NT_HEADERS*)(MainVA + DosHeader->e_lfanew); if(NtHeaders->Signature != IMAGE_NT_SIGNATURE) return NULL; IMAGE_DATA_DIRECTORY *ImportDirectory = &amp;NtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; IMAGE_IMPORT_DESCRIPTOR *ImportDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)(MainVA + ImportDirectory->VirtualAddress); for(; ImportDescriptor->Name; ImportDescriptor++) if(stricmp((const char*)(MainVA + ImportDescriptor->Name), DllName) == 0) break; if(ImportDescriptor->Name == 0) return NULL; return ImportDescriptor; } void* DllSpoof_SpoofImport(const char *DllName, const char *FuncName, void *SpoofFuncAddress) { unsigned long MainVA = (unsigned long)GetModuleHandle(0); IMAGE_IMPORT_DESCRIPTOR *ImportDescriptor = FindImportDescriptor(DllName); if(ImportDescriptor == NULL) return NULL; // Seek and spoof IMAGE_THUNK_DATA32 *FThunk, *OThunk; FThunk = (IMAGE_THUNK_DATA32*)(MainVA + ImportDescriptor->FirstThunk); OThunk = (IMAGE_THUNK_DATA32*)(MainVA + ImportDescriptor->OriginalFirstThunk); void *OriginalFuncAddr; for(; OThunk->u1.AddressOfData; OThunk++, FThunk++) { if(OThunk->u1.Ordinal &amp; IMAGE_ORDINAL_FLAG32) continue; IMAGE_IMPORT_BY_NAME *ImportFuncName = (IMAGE_IMPORT_BY_NAME*)(MainVA + OThunk->u1.AddressOfData); if(strcmp((const char*)ImportFuncName->Name, FuncName) != 0) continue; OriginalFuncAddr = (void*)FThunk->u1.Function; DWORD OldRight; VirtualProtect(&amp;FThunk->u1.Function, 4, PAGE_EXECUTE_READWRITE, &OldRight); FThunk->u1.Function = (DWORD)SpoofFuncAddress; DWORD Temp; VirtualProtect(&amp;FThunk->u1.Function, 4, OldRight, &Temp); break; } return OriginalFuncAddr; }

Kompilacja i uruchomienie:

cmd.exe >gcc spoof.c -c -Wall >dllwrap --def fwdWsock32.def -o wsock32.dll spoof.o >copy /Y wsock32.dll c:\mIRC\ >cd c:\mIRC\ >mirc > out.txt >

Łączymy się z jakimś serwerem, po czym wpisujemy:

mIRC /help

Help się ładnie otwiera. Teraz wysyłamy do serwera string HACKHELP, po czym ponownie próbujemy otworzyć pomoc.

mIRC /quote HACKHELP -=185859=- -> Server: HACKHELP - /help

Tym razem zamiast okienka pomocy, pojawił się nasz message box. W "out.txt" możemy dodatkowo znaleźć string "HACKHELP found! Haxxing!".

Wszystko ładnie działa. A co zrobić jeśli natrafimy na brak User DLL w importach programu? Jeśli się da, to możemy spróbować zmienić nazwę jednej z importowanych bibliotek. Na przykład zamiast "kernel32.dll" wpisać "kundel32.dll" :)

4. Epilog.

Mam nadzieje że powyższy artykuł okazał się ciekawy ;>. Wszelkie komentarze można wysyłać mi na mejla (jest na górze podany).
Na koniec dodam jeszcze że system Windows od wersji XP ma dodatkową funkcję SetDllDirectory. Może warto się nią zainteresować?
W sumie tyle =^^=. Gritz.

Gynvael Coldwind of the Vexillium

ps. Gritzy:
Arashi Coldwind, Samlis Coldwind, moi rodzice Team Vexillium: Unavowed, furio, aps, Nekrataal, xa, Blount, Maly_ Tenmei, Lunarii, #crackscene, #uw-team, #linux, united crew, ReWolf, chomik, cauchy, ved, mag7, omeg, pixelx, g0blin, l5x, keidii, mulander, defc0n, lidia, salvation, j00ru, Nicon, fr3m3n, unknow, nemessica, Juzio, diabel, i wszyscy inni których znam i lubie ;>

5. Źródła.

  1. onet.pl, Portal Wiedzy, http://portalwiedzy.onet.pl/tlumacz.html?qs=spoof&amp;tr=ang-auto&x=0&y=0
  2. MinGW, http://www.mingw.org/download.shtml
  3. The Dev C++ Resource Site, http://bloodshed.net/dev/devcpp.html
  4. Inprise Corporation, Borland C++ Command Line Tools, http://community.borland.com/article/0,1410,20633,00.html
  5. MASM32, http://www.masm32.com/
  6. Vim, http://www.vim.org/
  7. Wikipedia, http://en.wikipedia.org/wiki/Dynamic_link_library#Symbol_resolution_and_binding
  8. Sachin R Sangoi, http://www.codeproject.com/dll/Delay_Loading_Dll.asp
  9. MSDN, http://support.microsoft.com/kb/164501/en-us
  10. MSDN, http://msdn.microsoft.com/msdnmag/issues/02/03/Loader/
  11. Gynvael Coldwind, System DLL i Application DLL, http://gynvael.vexillium.org/?stuff=sysdll.stuff
  12. Dlltool documentation, http://sourceware.org/binutils/docs-2.17/binutils/def-file-format.html

Articles

Comic