Rozbudowa | .cpl , .exe , .dll , .ocx , .sys , .scr , .drv , .efi |
---|---|
Typ MIME | application/vnd.microsoft.portable-executable, application/efi |
PUID | x-fmt / 411 |
Podpisy |
4D 5A( hexa ) 4D5A*50450000( PRONOM regex ) |
Typ formatu | Binarne , wykonywalne , kod obiektu , DLL |
Oparte na |
Plik wykonywalny MZ COFF |
Format PE ( przenośny plik wykonywalny , wykonywalny laptop ) to pliki wykonywalne i biblioteki w systemie operacyjnym Windows 32-bit i 64-bit .exe (programy), .ocx ( OLE i ActiveX ), .dll i. Cpl ( Windows Element panelu sterowania ).
Jest to również format używany dla plików wykonywalnych UEFI (.efi).
Jest to format wywodzący się z COFF .
Microsoft przyjął format PE wraz z wprowadzeniem systemu Windows NT 3.1 . Wszystkie kolejne wersje systemu Windows, w tym Windows 95/98 / ME, obsługują ten format. Wcześniej „wykonywalne” pliki znajdowały się w NE - New Executable File Format , „ New ” w odniesieniu do CP / M i * .com - w plikach .
Utworzenie formatu PE było spowodowane chęcią Microsoftu do zaprojektowania struktury plików, która mogłaby być dostosowana do różnych maszyn z systemem Windows NT, który początkowo był w stanie obsługiwać architektury inne niż Intel x86 ( na przykład Power PC i Motorola 68000 ). Pomysł polegał zatem na stworzeniu struktury podobnej do tych architektur .
Plik wykonywalny PE ma następującą strukturę:
Pierwsze dwa bajty pliku reprezentują znaki MZ .
Nagłówek MZ umożliwia systemowi operacyjnemu rozpoznanie pliku jako prawidłowego pliku wykonywalnego, jeśli jest uruchamiany z systemu MS-DOS , aby mógł wykonać swój segment DOS. Oto struktura nagłówka w języku C :
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;Rozpoznajemy na przykład:
Segment DOS jest wykonywany, gdy system Windows nie rozpoznaje pliku jako w formacie PE lub jeśli jest wykonywany w systemie MS-DOS . Zwykle wyświetla komunikat taki jak Ten program nie może być uruchomiony w trybie DOS , dosłownie przetłumaczony, Ten program nie może być uruchomiony w trybie DOS .
Nagłówek PE jest zbiorem obiektów , zgrupowanych w jednej strukturze o nazwie IMAGE_NT_HEADER , tutaj jest jego prototyp w C języku .
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER OptionalHeader; } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;Wyświetl (en) http://msdn.microsoft.com/en-us/library/windows/desktop/ms680313%28v=vs.85%29.aspx
Zobacz (en) http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339%28v=vs.85%29.aspx, aby uzyskać szczegółowe informacje
Adresy fizyczne, wirtualne i pamięciPierwszą rzeczą, którą należy wiedzieć, jest to, że plik wykonywalny jest ładowany do pamięci pod adresem ImageBase (obecnym w OptionalHeader ), jeśli ten adres jest dostępny. W przeciwnym razie jest ładowany pod inny adres, który będzie nową wartością ImageBase .
W nagłówku i treści pliku PE znajdują się trzy różne mapowania:
Katalogi to części pliku, które są używane podczas jego ładowania. Pozycja i rozmiar danych tych katalogów są wyświetlane w polu DataDirectory elementu OptionalHeader, które jest tablicą struktur IMAGE_DATA_DIRECTORY opisanych w następujący sposób:
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;VirtualAddress będący adresem początku zawartości katalogu po załadowaniu sekcji do pamięci, względem ImageBase (obecnego w OptionalHeader ). I zmień jego rozmiar.
Chociaż NumberOfRvaAndSizes field of OptionalHeader wskazuje na ich liczbę, istnieją zwykle 16:
Pozycja | Nazwisko | Opis |
0 | IMAGE_DIRECTORY_ENTRY_EXPORT | Tabela eksportu |
1 | IMAGE_DIRECTORY_ENTRY_IMPORT | Importuj tabelę |
2 | IMAGE_DIRECTORY_ENTRY_RESOURCE | Tabela zasobów |
3 | IMAGE_DIRECTORY_ENTRY_EXCEPTION | Tabela wyjątków |
4 | IMAGE_DIRECTORY_ENTRY_SECURITY | Tabela certyfikatów |
5 | IMAGE_DIRECTORY_ENTRY_BASERELOC | Tabela relokacji |
6 | IMAGE_DIRECTORY_ENTRY_DEBUG | Informacje debugowania |
7 | IMAGE_DIRECTORY_ENTRY_COPYRIGHT / IMAGE_DIRECTORY_ENTRY_ARCHITECTURE | Dane dotyczące praw autorskich lub architektury |
8 | IMAGE_DIRECTORY_ENTRY_GLOBALPTR | wskaźniki globalne |
9 | IMAGE_DIRECTORY_ENTRY_TLS | Lokalna tabela magazynu wątków (TLS) |
10 | IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG | Załaduj tabelę konfiguracji |
11 | IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT | Tabela powiązanego importu |
12 | IMAGE_DIRECTORY_ENTRY_IAT | Tabela adresów importu |
13 | IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT | Deskryptor odroczonego importu |
14 | IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR | Nagłówek środowiska uruchomieniowego COM + |
15 | - | zarezerwowane: musi być puste. |
Tabela sekcji znajduje się tuż za nagłówkiem PE. To jest tablica zawierająca wiele struktur IMAGE_SECTION_HEADER .
Struktury te zawierają informacje o sekcjach pliku binarnego, który ma zostać załadowany do pamięci.
Pole NumberOfSections struktury IMAGE_FILE_HEADER wskazuje, ile wpisów znajduje się w tej tabeli. Maksymalna obsługiwana przez system Windows 96 sekcji.
Tabela sekcji jest prototypowana w następujący sposób:
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;Każda sekcja ma nazwę składającą się z 8 znaków, ta nazwa nie ma znaczenia, ale zwykle możemy znaleźć następujące:
Nazwisko | Opis |
.tekst | Kod (instrukcje) programu |
.bss | Niezainicjowane zmienne |
.reloc | Tabela relokacji (szósty katalog) |
.dane | Zainicjowane zmienne |
.rsrc | Zasoby pliku (trzeci katalog: kursory, dźwięki, menu itp.) |
.rdata | Dane tylko do odczytu |
.idata | Tabela importu (drugi katalog) |
.upx | Znak kompresji UPX, charakterystyczny dla oprogramowania UPX |
.aspack | Znak pakietu ASPACK, specyficzny dla oprogramowania ASPACK |
.adata | Znak pakietu ASPACK, specyficzny dla oprogramowania ASPACK |
Tabela relokacji znajduje się w szóstym katalogu „Podstawowa tabela relokacji”. Wskazuje wirtualne adresy wartości reprezentujących adres pamięci. Pozwala to w przypadku, gdy plik wykonywalny nie został załadowany do pamięci pod adresem ImageBase , na zastąpienie wszystkich odniesień do pamięci odpowiadających nowej wartości ImageBase .
Tabela relokacji to seria struktur o różnych rozmiarach. Każdy składa się z nagłówka typu IMAGE_BASE_RELOCATION, a następnie tablicy wartości typu WORD (o rozmiarze 16 bitów). Czasami ostatnią wartością jest 0, w którym to przypadku jest używana tylko do wyrównania 4-bajtowego.
typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; } IMAGE_BASE_RELOCATION,*PIMAGE_BASE_RELOCATION;Nie znamy liczby struktur obecnych w tablicy relokacji, ale znamy jej rozmiar i dlatego zatrzymujemy się, gdy dotarliśmy do końca. Podobnie nie wiemy, ile wartości następuje po każdym nagłówku, ale wywnioskujemy to z rozmiaru.
Każda wartość składa się z 4 bitów informacji i 12 bitów danych. Część informacyjna może mieć jedną z następujących wartości:
Wartość | Opis |
IMAGE_REL_BASED_ABSOLUTE | |
IMAGE_REL_BASED_HIGH | |
IMAGE_REL_BASED_LOW | |
IMAGE_REL_BASED_HIGHLOW | Dane to adres odnoszący się do bazy (adres wirtualny = VirtualAddress + dane) |
IMAGE_REL_BASED_HIGHADJ | |
IMAGE_REL_BASED_MIPS_JMPADDR |
Najczęściej używany jest tylko IMAGE_REL_BASED_HIGHLOW .
Obliczona w ten sposób wartość adresu pamięci zostanie przesunięta zgodnie z różnicą między oryginalnym ImageBase a adresem początku pamięci przydzielonej programowi.
IAT, co oznacza Import Address Table , zawarty w drugim katalogu Import table (zwykle w sekcji .idata lub .rdata ), wskazuje adresy interfejsów API zaimportowanych przez oprogramowanie, a także nazwy bibliotek DLL, które je importują. Funkcje. Interfejsy API zawarte w tych bibliotekach DLL umożliwiają poprawne działanie oprogramowania.
Jego istnienie wynika z faktu, że interfejsy API są różnie adresowane w zależności od systemu operacyjnego.
Po pierwsze, powinieneś wiedzieć, że struktura o nazwie IMAGE_IMPORT_DESCRIPTOR jest używana dla każdej wywoływanej biblioteki DLL; plus ostatni z 5 wyzerowanych DWORDów, które definiują zakończenie.
Dla każdej zaimportowanej biblioteki DLL zostanie użyta struktura o nazwie IMAGE_THUNK_DATA dla każdego interfejsu API tej biblioteki DLL; będzie zatem tyle IMAGE_THUNK_DATA, ile jest funkcji wyeksportowanych przez bibliotekę DLL, a także ostatni DWORD określający zakończenie tej biblioteki DLL.
Trzecia struktura, IMAGE_IMPORT_BY_NAME , definiuje nazwę API oraz ich numer ORDINAL (16-bitowy numer identyfikujący funkcję w bibliotece DLL). Jest tyle interfejsów API zaimportowanych przez bibliotekę DLL. Na przykład, w powyższym przykładzie obrazu, istnieją trzy IMAGE_IMPORT_BY_NAME zdefiniowane dla advapi32.dll, bo tylko trzy z jego API wykorzystywane są przez program.
typedef struct _IMAGE_IMPORT_DESCRIPTOR { _ANONYMOUS_UNION union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // It points to the first thunk IMAGE_THUNK_DATA } DUMMYUNIONNAME; DWORD TimeDateStamp; // 0 if not bound DWORD ForwarderChain; // -1 if no forwarders DWORD Name; // RVA of DLL Name. DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses) } IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR; typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; //Ordinal Number BYTE Name[1]; //Name of function } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; typedef union _IMAGE_THUNK_DATA { PDWORD Function; PIMAGE_IMPORT_BY_NAME AddressOfData; } IMAGE_THUNK_DATA, *PIMAGE_THUNK_DATA;EAT = Eksportuj tabelę adresów
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; /* 0x00 */ DWORD TimeDateStamp; /* 0x04 */ WORD MajorVersion; /* 0x08 */ WORD MinorVersion; /* 0x0a */ DWORD Name; /* 0x0c */ DWORD Base; /* 0x10 */ DWORD NumberOfFunctions; /* 0x14 */ DWORD NumberOfNames; /* 0x18 */ DWORD AddressOfFunctions; // 0x1c RVA from base of image DWORD AddressOfNames; // 0x20 RVA from base of image DWORD AddressOfNameOrdinals; // 0x24 RVA from base of image } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;Moduł ładujący PE to część systemu Windows, która umożliwia rozpoznawanie i ładowanie plików PE do pamięci. To dzięki niemu Windows może wykonywać instrukcje takiego pliku.
Inżynieria odwrotna, czasami skracana do odwracania , to technika analizy oprogramowania, która umożliwia znajdowanie „błędów”, usterek, projektowanie exploitów lub pęknięć lub naprawianie problemów w oprogramowaniu. Jest to technika mająca na celu zrozumienie, jak działa oprogramowanie, bez konieczności sięgania do jego kodu źródłowego.
Innym zastosowaniem odwracania jest analiza złośliwego oprogramowania w celu zrozumienia jego zachowania i struktury w celu stworzenia szczepionki przeciwko niemu. Te tak zwane szczepionki można następnie zintegrować z oprogramowaniem antywirusowym w celu ochrony systemu w przyszłości. Należy pamiętać, że większość wirusów / antywirusów koncentruje się na systemie Windows, który jest zdecydowanie najczęściej używanym / sprzedawanym systemem operacyjnym w społeczeństwie. Chociaż istnieje złośliwe oprogramowanie wykorzystujące inne infrastruktury, takie jak Stuxnet, które atakowały systemy SCADA.
Ta praktyka polega na dogłębnej analizie kodu bajtowego programu (niezależnie od formatu), aby ujawnić jego działanie, zachowanie. Wydobywa rozległą wiedzę o systemie operacyjnym ( FreeBSD , Windows , Linux , Mac ), programowaniu ( ASM , C / C ++ ) i architekturze ( x86 , SPARC ) ze strony systemu tam działającego, zwanego potocznie Reverser lub Krakers.
W tym artykule omówiono format Portable Executable (PE), więc zawarte w nim informacje będą dotyczyły tego formatu plików.
Zauważ, że możliwe jest oczywiście wykonanie wszystkich poniższych kroków w formatach takich jak ELF (format wykonywalny i łączony) lub Mach-O (format pliku obiektów Mach). Z drugiej strony, zmieniając format, zmienią się narzędzia, a nie zasada, ale same programy; wyczuwalne będą również niuanse dotyczące zabezpieczeń przed cofaniem.
Statyczna analiza oprogramowania składa się z czytania kodu bajtowego , ponieważ istnieje na dysku twardym, to znaczy, nie będąc w realizacji.
Odczytanie kodu bajtowego programu nie jest niczym więcej, ani mniej niż transformacją sekwencji bajtów występujących w programie na język ASM, język mnemoniczny pozwalający człowiekowi zrozumieć, co program musi podjąć.
Narzędzia zostały specjalnie opracowane do tego celu, takie jak Win32Dasm lub Pydasm ( moduł Python ).
Analiza dynamiczna składa się z dwóch faz: odczyt kodu - wykonanie kodu.
Aby zastosować takie podejście, konieczne jest posiadanie narzędzia programowego o nazwie Debugger , takiego jak OllyDbg lub GDB (GNU Debugger).
Zasadą jest rozmontowanie programu w trakcie jego wykonywania, zwane też analizą krok po kroku lub krok po kroku , tak aby móc modyfikować jego zachowanie w czasie rzeczywistym, aby bardziej szczegółowo zrozumieć jego działanie i możliwe modyfikacje.
Większość skanów jest wykonywana w Ring 3 , czyli w trybie użytkownika. Powyżej dochodzimy do Ring 0 lub Kernel , ale ten rodzaj dogłębnej analizy jest rzadki i zarezerwowany dla poszukiwaczy przygód (rozwój sterowników, hakowanie itp.).
Inną metodą, nieco nietypową (ale funkcjonalną), jest wirtualizacja sprzętu. Damien Aumaitre przedstawił swój VirtDbg narzędzie w SSTIC 2010 .
Zobacz (fr) http://esec-lab.sogeti.com/dotclear/public/publications/10-sstic-virtdbg_slides.pdf
Patrz Pierścień ochronny
Odwracając się, będąc postrzeganą jako inwazyjna technika w odniesieniu do oprogramowania o zamkniętym źródle (zastrzeżony i nieujawniony kod źródłowy), programiści wpadli zatem na pomysł włączenia określonych procedur do swoich programów, które będą miały tylko jeden cel: zapobieganie informacjom o zachowanie oprogramowania w jak największym stopniu.
Aby to zrobić, istnieje wiele metod, w tym użycie Packera ( szyfrowanie / kompresja kodu), kody detektorów debuggera, wstawianie sumy kontrolnej itp.
PakowaczeProgram pakujący to mały program, którego celem jest kompresja oprogramowania w celu zmniejszenia jego początkowego rozmiaru, przy jednoczesnym zachowaniu jego wykonywalnego aspektu.
Jednak z biegiem czasu pojawiły się nowe pakery; mają one teraz możliwość szyfrowania kodu programu docelowego, co skutkuje zmniejszeniem zarówno jego wagi, jak i jednoczesną modyfikacją jego kodu wykonawczego. Znany algorytm , a jeden z najprostszych do ominięcia, ponieważ klucz jest ogólnie napisane w programie, jest niewątpliwie XOR (Exclusive-OR).
Oczywiście część związana z deszyfrowaniem - wykonywana podczas uruchamiania spakowanego / zaszyfrowanego oprogramowania - będzie wykonywana w sposób niewidoczny dla użytkownika. Ale na odwrocie zobaczysz to inaczej, wizualizując kod ASM inny niż normalny.
UPX jest przykładem pakera; umożliwia zmniejszenie rozmiaru pliku wykonywalnego o prawie 50%.
Wykrywanie punktów przerwaniaDetekcja punktu przerwania (Breakpoint) polega na wykryciu w trakcie wykonywania binarnego wywołania funkcji INT 3 , czyli 0xCC w systemie szesnastkowym . Kod podobny do tego wykonałby tę operację:
if(byte XOR 0x55 == 0x99){ // Si Byte = 0xCC -> 0xCC XOR 0x55 == 0x99 printf("Breakpoint trouvé !!"); } Fałszywy punkt przerwaniaTechnika fałszywego punktu przerwania polega na symulowaniu punktu przerwania w kodzie oprogramowania tak, jak robiłby to debugger, używając instrukcji INT 3 lub 0xCC w systemie szesnastkowym . Gdy debugger napotka tę funkcję, zatrzyma się sam, otrzymując sygnał SIGTRAP .
Domyślnie proces kończy pracę, gdy otrzyma sygnał SIGTRAP . Sztuczka polega na zmianie tego domyślnego zachowania za pomocą funkcji signal () zastosowanej do SIGTRAP.
Oto przykład kodu źródłowego w języku C dla systemów Linux:
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> void sighandler(int signal) { printf("Je suis une fonction ordinaire... "); exit(0); } int main(void) { signal(SIGTRAP,sighandler); // On place le sighandler __asm__("int3"); // On place un faux Breakpoint printf("Pris au piège..."); // Le débogueur arrive ici return EXIT_FAILURE; }W praktyce istnieją różne sposoby wykrywania debugera. Najprościej, jak to możliwe, dzięki funkcji IsDebuggerPresent (void) w systemie Windows:
#include <stdio.h> #include <windows.h> int main(int argc, char* argv[]) { // Attention: cette fonction vaut 0 s'il n'y a pas de débogueur. if(!IsDebuggerPresent()) { printf("Helloworld !"); } else { printf("Debogueur detecte !"); } return 0; }Następnie istnieją różne sztuczki do wykrywania debugera. Na przykład:
#include <stdio.h> #include <windows.h> #include <time.h> int main(int argc, char* argv[]) { int start = clock(); Sleep(100); int DeltaTemps = abs(clock() - start - 100); printf("deltaTemps: %d\n", DeltaTemps); if(DeltaTemps < 4) { printf("Helloworld !"); } else { printf("Debogueur detecte !"); } return 0; }Ten kod oblicza różnicę czasu. Przy uśpieniu wynoszącym 100 ms zwykle różnica wynosi 100 (w ciągu kilku milisekund). Więc obliczamy różnicę, odejmujemy 100 i otrzymujemy wynik. Stosujemy funkcję abs, aby zawsze uzyskać wartość dodatnią. W trybie krokowym program zostanie znacznie spowolniony, a co za tym idzie, różnica będzie większa i program się zawiesi.