Analiza statyczna kodu wykonywalnego ELF — tutorial

Niniejszy dokument jest praktycznym przedstawieniem dzia�a� opisanych w artykule In�ynieria odwrotna kodu wykonywalnego ELF w analizie pow�amaniowej (Hakin9 01/2005).
Analizowany obiekt: netc. Przed przyst�pieniem do badania pliku nale�y go skopiowa� do katalogu domowego.

Przygotowania

Oczywi�cie Hakin9 Live posiada wszystkie potrzebne do przeprowadzenia analizy narz�dzia. Je�li jednak z niego nie korzystamy, do przeprowadzenia wszystkich opisanych dzia�a� konieczny jest system Linux i nast�puj�ce programy:

Programy z pakietu binutils Skrypty i programy z pakietu fenris Pozosta�e narz�dzia:

Wst�pne rozpoznanie analizowanego obiektu

[01] Za pomoc� narz�dzia file uzyskujemy podstawowe informacje o analizowanym pliku

# file netc
netc: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, statically linked, stripped

Jak wida�, analizowany plik zosta� skompilowany statycznie i przeszed� proces strippingu.

[02] Wyszukujemy w pliku interesuj�ce �a�cuchy znak�w

# strings -a netc > strings.out

Analizuj�c otrzyman� zawarto�� pliku odnajdziemy nast�puj�ce informacje, kt�re b�d� pomocne w dalszych dzia�aniach:

Innym sposobem na odszukanie interesuj�cych �a�cuch�w znak�w mo�e by� przeszukanie zawarto�ci wybranych sekcji analizowanego pliku. Na przyk�ad informacje o systemie i wersji kompilatora, kt�ra zosta�a u�yta do kompilacji pliku, standardowo przechowywane s� w sekcji .comment.

Zawarto�� wskazanej sekcji pliku mo�emy odczyta� stosuj�c polecenie:

# objdump -j .comment -s nazwa_programu > comment.out

lub

# objdump -h nazwa_programu > sections.out
i podejrzenie dowolnym edytorem lub przegl�dark� zawarto�ci pliku pod wskazanym offsetem.


Pr�by odtworzenia tablicy symboli

[03] Wyszukujemy i pobieramy z Internetu biblioteki, kt�re mog�y by� u�yte w trakcie kompilacji

Z wyniku dzia�ania polecenia strings w kroku [02] wiemy, �e plik zosta� skompilowany w systemie Mandrake Linux 10.0 przy zastosowaniu kompilatora GCC 3.3.2. W naszym przypadku ograniczymy si� do pr�by odtworzenia symboli zwi�zanych z bibliotek� libc, pochodz�c� z systemu Mandrake 10.0. Ze wzgl�du na przeznaczenie biblioteki libc mo�na z du�ym prawdopodobie�stwem za�o�y� jej wykorzystanie w analizowanym pliku. Inn� bibliotek�, kt�r� r�wnie� mo�na by�oby standardowo wykorzysta� w procesie odtwarzania tablicy symboli, jest biblioteka libgcc.a. My jednak ograniczymy si� wy��cznie do biblioteki libc, poniewa� elementy biblioteki libgcc.a rozpoznamy w dalszej cz�ci, poprzez por�wnanie z przyk�adowo skompilowanym programem.

Bibliotek� libc.a zapisujemy w katalogu ~/analysis/libc_components/

Co zrobi�, je�li nie posiadamy informacji, kt�re mog�yby nam pom�c w ustaleniu wersji bibliotek zastosowanych w trakcie kompilacji? W tej sytuacji mo�na pobra� kilka r�nych wersji bibliotek nawet dla kilku r�nych dystrybucji systemu, wykona� poni�sze kroki procesu odtwarzania tablicy symboli oraz oceni� otrzymane wyniki pod wzgl�dem skuteczno�ci i ilo�ci trafie�.

[04] Rozpakowujemy obiekty biblioteki

# ar x libc.a

[05] Sprawdzamy, czy w analizowanym programie zawarty jest kod poszczeg�lnych obiekt�w biblioteki

# search_static netc ~/analysis/libc_components > obj_file

W tym kroku nale�y r�wnie� zwr�ci� uwag� na kolizje, kt�re mog�y si� pojawi� w trakcie dokonanej weryfikacji. Wykryte kolizje znajduj� si� w ko�cowej cz�ci pliku obj_file. Jakie znaczenie praktyczne i jaki wp�yw na analiz� maj� kolizje? Takie, �e bez szczeg�owej analizy kodu funkcji, dla kt�rych wyst�pi�y kolizje nie daje si� jednoznacznie okre�li�, kt�ra z funkcji w rzeczywisto�ci zosta�a w programie zastosowana. Przyk�ad wykrytej kolizji:

# Possible conflict below requiring manual resolution:
# ----------------------------------------------------
# /analysis/libc_components/getsrvbynm.o - match at 0x08057580 (0x000000ea bytes)
# /analysis/libc_components/getsrvbypt.o - match at 0x08057580 (0x000000ea bytes)

[06] Generujemy list� odnalezionych odniesie� symbolicznych z poszczeg�lnych obiekt�w biblioteki

# gensymbols obj_file > symbols_db

Wynikiem dzia�ania skryptu jest lista symboli wraz z adresami, pod kt�rymi mo�na znale�� ich kod.

[07] Przeprowadzamy dessassemblacj� analizowanego programu

# gendump netc > out1

[08] Usuwamy kod odnalezionych funkcji biblioteki z pliku out1

# decomp_strip obj_file < out1 > out2

[09] Dla u�atwienia analizy dodajemy nazwy odnalezionych funkcji w miejsca ich wywo�a�

# decomp_insert_symbols symbols_db < out2 > out3

[10] Dla zwi�kszenia czytelno�ci kodu w miejsca odniesie� do �a�cuch�w znak�w wstawimy ich tre��

# decomp_xref_data netc < out3 > out4

Do odtworzenia zawarto�ci tablicy symboli mo�na r�wnie� zastosowa� narz�dzia z pakietu fenris.

Kolejne kroki:
[a] Otwieramy do edycji skrypt getfprints
[a] W parametrze TRYLIBS wpisujemy �cie�ki oraz nazwy bibliotek, z kt�rych chcemy utworzy� baz� sygnatur

# getfprints

[b] Zmieniamy nazw� pliku wynikowego na domy�ln� nazw� pliku sygnatur

# mv NEW-fnprints.dat fnprints.dat

[c] Wykorzystuj�c program dress odtwarzamy usuni�te symbole

# dress -F ./fnprints.dat nazwa_programu > lista_odtworzonych symboli
lub
# dress -F ./fnprints.dat nazwa_programu nazwa_programu_z_dodana_lista_symboli

Ustalenie funkcji do��czonych przez kompilator

Je�eli znana jest wersja kompilatora zastosowanego do kompilacji analizowanego programu (a my j� znamy) lub mo�emy si� domy�la� tej informacji, mo�na podj�� pr�b� okre�lenia lokalizacji funkcji dodanych przez kompilator (podobny efekt powinni�my uzyska� wykorzystuj�c bibliotek� libgcc.a w procesie odtwarzania tablicy symboli). Do przeprowadzenia tego dzia�ania wykorzystamy por�wnanie przyk�adowego programu skompilowanego tym samym kompilatorem co analizowany plik.

[11] Tworzymy przyk�adowy program sample.c

int main(int argc, char **argv[])
{
return 0;
}

[12] Kompilujemy przyk�adowy program

# gcc —static —o sample sample.c

[13] Por�wnujemy elementy skompilowanego programu z kodem analizowanego pliku - out4

W wyniku por�wnania uda�o si� ustali� nast�puj�ce funkcje:

Funkcja _start() - Adres = 08048100


Funkcja call_gmon_start() - Adres = 08048124


Funkcja __do_global_dtors_aux() - Adres = 08048150


Funkcja frame_dummy() - Adres = 080481b0


Lokalizacj� funkcji _start() uzyskamy r�wnie� odczytuj�c warto�� entrypoint nag��wka ELF


Ustalenie lokalizacji funkcji main()

[14] Por�wnuj�c budow� funkcji _start() z przyk�adowego programu sample z kodem tej funkcji odnalezionej w analizowanym pliku ustalamy lokalizacj� funkcji main().

08048100: xor %ebp,%ebp
08048102: pop %esi
08048103: mov %esp,%ecx
08048105: and $0xfffffff0,%esp
08048108: push %eax
08048109: push %esp
0804810a: push %edx
0804810b: push $0x804aa90
08048110: push $0x804aa30
08048115: push %ecx
08048116: push %esi
08048117: push $0x804994f
0804811c: call 0x0804a3b0 <__libc_start_main>
08048121: hlt


Ustalenie funkcji u�ytkownika

[15] Ustalamy funkcje u�ytkownika (dla �cis�o�ci - funkcje, kt�re nie zosta�y rozpoznane jako obiekty biblioteki)

# grep 'call 0x' out4 | grep -v '<' > user_f.out

[16] Poniewa� w analizowanym kodzie wiele wywo�ywanych funkcji si� powtarza, spr�bujemy uzyska� wy��cznie unikalne adresy funkcji

# grep 'call 0x' out4 | grep -v '<' | awk '{print $3}' | sort -u

0x0804812d
0x080481bd
0x08048204
0x080482a5
0x08048303
0x0804834b
0x080483b5
0x080483fd
0x080486b8
0x080488ab
0x08048951
0x080489b3
0x08048a3b
0x08048d9d
0x08049235
0x08049311
0x0804950b
0x0804aed0
0x0804bf70
0x0804bf90
0x08057370
0x08057580


Jak si� okazuje, cz�� z uzyskanych adres�w mo�e nie istnie� w naszym kodzie out4. Ich brak wynika z istnienia kolizji sygnatur funkcji, kt�re w kroku [8] zosta�y usuni�te.


W�a�ciwa analiza dzia�a� realizowanych przez program

[17] W kolejnych krokach, zaczynaj�c od funkcji main() powinni�my dokona� analizy przep�ywu kontroli pomi�dzy funkcjami u�ytkownika oraz realizowanych przez nich dzia�a�. Przechodz�c ten krok powinni�my uzyska� odpowied� na pytania typu: jak� rol� pe�ni� analizowany obiekt w systemie oraz jakie mechanizmy wykorzystuje. Do w�a�ciwej interpretacji dzia�a� realizowanych przez poszczeg�lne funkcje wymagana jest znajomo�� j�zyka Assembler prznajmniej w stopniu podstawowym.

Funkcja main()
0x0804812d
0x080481bd
0x08048204
0x080482a5
0x08048303
0x0804834b
0x080483b5
0x080483fd
0x080486b8
0x080488ab
0x08048951
0x080489b3
0x08048a3b
0x08048d9d
0x08049235
0x08049311
0x0804950b