haker.info | Etyczny hacking |

haker.info  — Etyczny hacking

 Kod relokowalny/wstrzykiwalny (x64)

20 września 2019 godz. 08:23    Dawid Farbaniec    3020 słów

1. Słowem wstępu

ażdy początkujący, który interesuje się analizą i zwalczaniem złośliwego oprogramowania (malware) powinien poznać sposób działania kodu relokowalnego (ang. relocatable code) określanego też jako kod niezależny od miejsca w pamięci (ang. position–independent code) czy wstrzykiwalny (ang. injectable). Typowe programy komputerowe są zależne od odwołań do zasobów takich jak np. funkcje interfejsu systemowego (API) czy po prostu dane potrzebne aplikacji (napisy, liczby itd.). Natomiast kod relokowalny powinien być self–contained, czyli tłumacząc opisowo: zawierać w sobie to co jest mu potrzebne do działania. Dzięki temu, gdy zostanie umieszczony gdziekolwiek w pamięci komputera, to nadal będzie działał poprawnie, a nawet miał możliwość przeniesienia swoich fragmentów w inne miejsca (ang. (self–)relocation).

2. Standardowa definicja z encyklopedii

Witryna encyclopedia.com przedstawia pojęcia position–independent code oraz relocatable code następująco:

Position-independent code — Program code that can be placed anywhere in memory, since all memory references are made relative to the program counter. Position-independent code can be moved at any time, unlike relocatable code, which can be loaded anywhere but once loaded must stay in the same position.

https://www.encyclopedia.com/computing/dictionaries-thesauruses-pictures-and-press-releases/position–independent-code

Definicja ta jest poprawna, ale sądzę, że wzięto pod uwagę typowe programy komputerowe np. na określone typy mikroprocesorów. W przypadku analizy i zwalczania malware oraz tematyki shellcode (z tego co zauważyłem) to pojęcia position–independent code oraz relocatable code są używane zamiennie.

3. Odwołania zewnętrzne są utrudnieniem

Instrukcje procesora, które nie odwołują się do funkcji API (przykładowe rozkazy: mov, add, dec, xor itp.) mogą być bez problemu wykonywane w dowolnym miejscu w pamięci.

Jednak w natywnych aplikacjach dla systemu Windows to funkcje WinAPI udostępniają główne funkcjonalności programiście. Utworzenie pliku, odczytanie pliku, połączenie sieciowe, wyświetlenie okna i wiele innych możliwości daje systemowe API. W typowych programach wywołanie funkcji WinAPI następuje (w uproszczeniu) poprzez podanie jej nazwy (i argumentów jeśli przyjmuje), która to nazwa zamieniana jest na adres (wartość liczbowa określająca miejsce funkcji w bibliotece systemowej) i wywoływana.

Kod relokowalny wstawiony do jakiegoś miejsca w pamięci musi sobie w nietypowy sposób poradzić z wywoływaniem funkcji API systemu Windows. W zwykłym programie w języku Asembler wywołanie wykonuje się poprzez rozkaz call podając mu nazwę funkcji i wymagane argumenty poprzez odpowiednie rejestry/stos.

W kodzie relokowalnym opisywanym tutaj podanie nazwy funkcji jest niemożliwe. Adresów funkcji nie można wpisać do kodu, bo są one zmienne. Standardowe pobranie adresu danej funkcji przez GetProcAddress jest również niemożliwe, ponieważ trzeba najpierw znać adres funkcji GetProcAddress.

3.1. Konwencja wywoływania funkcji Microsoft x64

Wywołanie funkcji w Asemblerze x64 nazywane też wywołaniem podprogramu przenosi sterowanie do innego miejsca w kodzie. Gdy blok kodu określany funkcją się wykona, to następuje powrót, który jest możliwy poprzez odłożony wcześniej na stosie programu adres powrotny. Funkcje wywołuje się instrukcją procesora call. To ona odkłada na stos wspomniany wcześniej adres powrotny i przekazuje kontrolę do wywoływanego podprogramu.

Nie ma jednego uniwersalnego sposobu na wywołanie funkcji. Zależne jest to od architektury, a to jak działa wywołanie i związane z nim operacje określają konwencje wywołania (ang. calling conventions).

Przypomnijmy sobie, że w Asemblerze MASM32 dla architektury x86-64 korzysta się z konwencji stdcall, która jest domyślna dla API systemu Windows. Wyczyszczenie stosu programu jest obowiązkiem funkcji, która jest wywoływana (ang. callee), czyli programista korzystający z takiej funkcji ma spokój z czyszczeniem stosu. Argumenty (nazywane też parametrami) przekazywane są poprzez stos „od końca”, czyli od prawej do lewej strony. Jeśli funkcja zwraca jakiś rezultat, to znajdzie się on w rejestrze akumulatora EAX. Niektóre funkcje, gdy wynik jest większy niż 32-bity zwracają wynik w parze rejestrów EDX:EAX. W konwencji stdcall, jeśli chcemy (w naszej funkcji) modyfikować wartości rejestrów ESI, EDI, EBP i EBX, to powinniśmy zachować ich wartości np. na stosie, a następnie je przywrócić przed powrotem do Windows.

Listing 3.1. Wywołanie funkcji CreateFile w Visual C++

hFile = CreateFile(szFileName, GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);

Listing 3.2. Wywołanie funkcji CreateFile w Asemblerze MASM32 (konwencja stdcall)

push 0 ;bez szablonu atrybutów push FILE_ATTRIBUTE_NORMAL ;atrybuty pliku push CREATE_NEW ;utwórz nowy plik push 0 ;atrybuty bezpieczeństwa domyślne push 0 ;sharing mode domyślny push GENERIC_WRITE ;otwarcie do zapisu push offset szFileName ;nazwa tworzonego pliku call CreateFileA ;wywołanie funkcji WinAPI mov hFile, eax

Nowoczesny Asembler MASM64 dla architektury x86-64 (w skrócie x64) korzysta z konwencji wywoływania funkcji nazwanej Microsoft x64. Wyczyszczenie stosu programu jest obowiązkiem funkcji wywołującej (ang. caller). Argumentów nie przekazuje się tylko przez stos, ale przez wybrane rejestry takie jak: R9, R8, RDX, RCX. Ze stosu korzysta się, gdy argumentów jest więcej niż cztery i odkłada się je „od końca”, czyli od prawej do lewej strony. Rezultat funkcji, jeśli ma rozmiar mniejszy niż 64-bity, zwracany jest w rejestrze akumulatora RAX. W konwencji Microsoft x64, jeśli chcemy (w naszej funkcji) modyfikować wartości rejestrów RBP, RBX, RDI, RSI, RSP, R12, R13, R14 i R15, to powinniśmy zachować ich wartości np. na stosie, a następnie je przywrócić przed powrotem do Windows. Należy również pamiętać o wyrównaniu stosu do okrągłych 16 bajtów. Ilość miejsca rezerwowanego na stosie wraz z adresem powrotnym powinna być podzielna przez 16 bez reszty.

Listing 3.3. Wywołanie funkcji CreateFile w Asemblerze MASM64 (konwencja Microsoft x64)

sub rsp, 38h ;alokacja miejsca na stosie mov qword ptr [rsp+30h], 0 ;bez szablonu atrybutów mov qword ptr [rsp+28h], FILE_ATTRIBUTE_NORMAL ;atrybuty pliku mov qword ptr [rsp+20h], CREATE_NEW ;utwórz nowy plik mov r9, 0 ;atrybuty bezpieczeństwa domyślne mov r8, 0 ;sharing mode domyślny mov rdx, GENERIC_WRITE ;otwarcie do zapisu mov rcx, offset szFileName ;nazwa tworzonego pliku call CreateFileA ;wywołanie funkcji WinAPI mov hFile, rax add rsp, 38h ;zwolnienie miejsca na stosie

4. Thread Environment Block (TEB)

Punktem zaczepienia do pobrania adresów funkcji WinAPI w tworzonym kodzie relokowalnym jest zdobycie adresu bazowego systemowej biblioteki kernel32.dll. Pierwszym krokiem jest poznanie bloku TEB (nazywanego też TIB).

Thread Environment Block to struktura danych, która zawiera informacje o aktualnie wykonywanym wątku. Dostęp do niej można uzyskać poprzez rejestr segmentowy GS w trybie 64-bitowym oraz rejestr FS w trybie 32-bitowym.

Pobranie adresu bazowego struktury TEB w Asemblerze MASM64 można wykonać np. tak:

mov rax, gs:[30h] ;RAX = TEB structure

czy poprzez użycie rozkazu procesora rdgsbase:

rdgsbase rax ;RAX = TEB structure

Dodatkowo wspomnę, że w Visual C++ adres TEB można pobrać funkcją wewnętrzną (ang. intrinsic):

//x86-64 (64-bit) PVOID teb_x64 = (PVOID) __readgsqword(0x30); //x86 (32-bit) PVOID teb_x86 = (PVOID) __readfsdword(0x18);

Istotną sprawą jest, że pomimo lekkiego owiania tajemnicą tej struktury (jest to wewnętrzna struktura systemowa), to możliwe jest zdobycie jej budowy bez dokonywania jakiejś inżynierii wstecznej (RCE). W pliku nagłówkowym winternl.h dla Visual C++ w środowisku Visual Studio można znaleźć definicję tej struktury:

//plik winternl.h (Visual Studio 2019) typedef struct _TEB { PVOID Reserved1[12]; PPEB ProcessEnvironmentBlock; PVOID Reserved2[399]; BYTE Reserved3[1952]; PVOID TlsSlots[64]; BYTE Reserved4[8]; PVOID Reserved5[26]; PVOID ReservedForOle; // Windows 2000 only PVOID Reserved6[4]; PVOID TlsExpansionSlots; } TEB, *PTEB;

Należy pamiętać, że są to wewnętrzne API i struktury, które mogą ulec zmianie. O czym informuje początek komentarza w pliku winternl.h:

/******************************************************************* * * * winternl.h -- This module defines the internal NT APIs and data * * structures that are intended for the use only by internal core * * Windows components. These APIs and data structures may change * * at any time. * * * * These APIs and data structures are subject to changes from one * * Windows release to another Windows release. To maintain the * * compatiblity of your application, avoid using these APIs and * * data structures. * * * * (...) * * * *******************************************************************/

Jako podsumowanie tego podrozdziału muszę zaznaczyć w jaki sposób poznanie tej struktury przybliża nas do celu.

Otóż poprzez strukturę TEB możliwe jest uzyskanie dostępu do innej struktury: PEB.

5. Process Environment Block (PEB)

Blok PEB podobnie jak wcześniej opisany TEB jest używaną wewnętrznie przez system strukturą danych.

W pliku nagłówkowym winternl.h dla Visual C++ można znaleźć definicję tej struktury:

//plik winternl.h (Visual Studio 2019) typedef struct _PEB { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; PVOID Reserved3[2]; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; PVOID Reserved4[3]; PVOID AtlThunkSListPtr; PVOID Reserved5; ULONG Reserved6; PVOID Reserved7; ULONG Reserved8; ULONG AtlThunkSListPtr32; PVOID Reserved9[45]; BYTE Reserved10[96]; PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; BYTE Reserved11[128]; PVOID Reserved12[1]; ULONG SessionId; } PEB, *PPEB;

W strukturze PEB w kodzie powyżej należy zwrócić uwagę na pole Ldr (od Loader).

Jest to struktura _PEB_LDR_DATA, która w pliku nagłówkowym winternl.h prezentuje się następująco:

typedef struct _PEB_LDR_DATA { BYTE Reserved1[8]; PVOID Reserved2[3]; LIST_ENTRY InMemoryOrderModuleList; } PEB_LDR_DATA, *PPEB_LDR_DATA;

W powyższej strukturze (_PEB_LDR_DATA) znajduje się pole InMemoryOrderModuleList, które jest listą modułów, którą należy przeszukać, aby uzyskać adres bazowy kernel32.dll.

Struktura _LIST_ENTRY prezentuje element wyżej wspomnianej listy, a jego definicja w winnt.h wygląda następująco:

typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

6. Adres bazowy modułu kernel32.dll

Po zerknięcie na rozmiar w bajtach poszczególnych pól wyżej opisanych struktur możliwe jest przemieszczanie się po nich. Całość polega na wpisaniu odpowiednich wartości przesunięć (ang. offset) oraz dereferencji pamięci poprzez operator ptr (dla wartości 64-bitowych qword ptr).

Listing 6.1. Uzyskanie adresu bazowego modułu kernel32.dll poprzez strukturę PEB (Asembler MASM64)

;Znajdź adres kernel32.dll ;poprzez przeszukanie struktury ;ProcessEnvironmentBlock (PEB) ;R10 = TEB.ProcessEnvironmentBlock mov r10, gs:[60h] ;R10 = ProcessEnvironmentBlock->Ldr mov r10, qword ptr [r10 + 18h] ;R11 = Ldr->InMemoryOrderModuleList mov r11, qword ptr [r10 + 20h] ;R10 = InMemoryOrderModuleList.Flink mov r10, qword ptr [r11] ;R11 = InMemoryOrderModuleList.Flink->Flink mov r11, qword ptr [r10] ;R10 = InMemoryOrderModuleList.Flink->Flink->Flink ;R10 = kernel32.dll base address! mov r10, qword ptr [r11 + 20h]

Powyższy kod w Asemblerze MASM64 zwraca w rejestrze R10 adres bazowy modułu kernel32.dll. Po uzyskaniu dostępu do listy InMemoryOrderModuleList następuje przejście do jej trzeciego elementu. Jest tak, gdyż to właśnie trzeci moduł to szukany kernel32.dll. Pierwszy moduł to uruchomiony plik wykonywalny, a drugi to ntdll.dll.

Poniżej przedstawiono dodatkowo aplikację, która znaleziony w strukturze PEB adres bazowy modułu kernel32.dll porównuje z adresem pobranym zwykłą metodą tj. przez wywołanie funkcji LoadLibrary. Oczywiście to sprawdzenie jest tylko w celach debugowania/nauki i tego fragmentu nie będzie w tworzonym kodzie relokowalnym.

Listing 6.2. Wersja rozszerzona kodu z listingu 6.1

; Get kernel32.dll base address ; through Process Environment Block ; haker.info / A.D. 2019 extrn ExitProcess : proc extrn LoadLibraryA : proc extrn MessageBoxA : proc .code Main proc ;Znajdź adres kernel32.dll ;poprzez przeszukanie struktury ;ProcessEnvironmentBlock (PEB) ;R10 = TEB.ProcessEnvironmentBlock mov r10, gs:[60h] ;R10 = ProcessEnvironmentBlock->Ldr mov r10, qword ptr [r10 + 18h] ;R11 = Ldr->InMemoryOrderModuleList mov r11, qword ptr [r10 + 20h] ;R10 = InMemoryOrderModuleList.Flink mov r10, qword ptr [r11] ;R11 = InMemoryOrderModuleList.Flink->Flink mov r11, qword ptr [r10] ;R10 = InMemoryOrderModuleList.Flink->Flink->Flink ;R10 = kernel32.dll base address! mov r10, qword ptr [r11 + 20h] ;Poniżej sprawdzenie poprawności, ;czyli porównanie znalezionego ;adresu bazowego kernel32.dll ;z adresem pobranym przez LoadLibraryA ;zachowaj znaleziony ;adres kernel32.dll ;na stosie push r10 ;pobierz adres kernel32.dll ;w sposób zwykły sub rsp, 28h mov rcx, offset kernel32dll call LoadLibraryA add rsp, 28h ;zdejmij adres kernel32.dll ;ze stosu programu pop rcx ;porównaj adresy cmp rcx, rax jne _bad         _good: mov rdx, offset szFound jmp _msgbox         _bad: mov rdx, offset szNotFound ;wyświetl stosowny komunikat         _msgbox: sub rsp, 28h xor r9, r9 xor r8, r8 ;rdx ustawione wcześniej xor rcx, rcx call MessageBoxA add rsp, 28h         _exit: sub rsp, 8h xor rcx, rcx call ExitProcess kernel32dll db "kernel32.dll", 0 szFound db "kernel32.dll address found.", 0 szNotFound db "kernel32.dll address NOT found.", 0 Main endp end

7. Kod relokowalny w Asemblerze MASM64

Jako, że zrozumienie poniższych terminów jest niezbędne do analizy przedstawionego dalej kodu, to postanowiłem umieścić tutaj te krótkie wyjaśnienia.

Przesunięcie w pliku (ang. file offset) — liczba wyznaczająca położenie określonych danych od ustalonego miejsca w pliku. Bardzo często miejscem startowym jest początek pliku, który ma offset równy zero.

Adres wirtualny (ang. Virtual Address, VA) — jest to adres do danych po załadowaniu ich do pamięci. Prawie zawsze określa się go po prostu adresem. Można spotkać też określenia: liniowy lub efektywny. Adres wirtualny zawiera w sobie tzw. ImageBase, czyli miejsce w pamięci pod które ładowany jest plik.

Relatywny adres wirtualny (ang. Relative Virtual Address, RVA) — ten adres można otrzymać odejmując ImageBase danego modułu od adresu wirtualnego (VA) w tym module.

We wcześniejszym podrozdziale zaprezentowane zostało znalezienie adresu bazowego modułu kernel32.dll. Kolejny krok w przód to przeszukanie tego modułu w celu znalezienia adresu funkcji WinAPI o nazwie GetProcAddress, która pozwala pobierać adresy innych funkcji Windows API.

Po załadowanym do pamięci module kernel32.dll porusza się jak po pliku PE/COFF, dlatego artykuł Format plików PE/PE32+ dla początkujących jak i inne dokumenty o strukturze plików PE będą przydatne.

Ogólny schemat działania prezentuje się następująco:

  • (adres bazowy kernel32.dll jest pobrany poprzez PEB)
  • Przejść do nagłówka PE (przesunięcie 3Ch)
  • Przejść do nagłówka opcjonalnego PE (przesunięcie 18h)
  • Przejść do sekcji eksportowanych funkcji (przesunięcie 70h)
  • Odczytać liczbę eksportowanych funkcji (PIMAGE_EXPORT_DIRECTORY->NumberOfNames)
  • Odnaleźć nazwę funkcji (PIMAGE_EXPORT_DIRECTORY->AddressOfNames)
  • Przejść do struktury zawierającej numery kolejne funkcji (AddressOfNameOrdinals, przesunięcie 024h)
  • Pobrać numer określający kolejność funkcji
  • Na podstawie numeru kolejnego uzyskać adres funkcji z AddressOfFunctions (przesunięcie 01Ch)
  • Pobraną funkcją GetProcAddress uzyskać adres funkcji LoadLibraryA
  • Załadować moduł user32.dll za pomocą LoadLibraryA
  • Pobrać adres funkcji MessageBoxA z user32.dll za pomocą GetProcAddress
  • Wywołać funkcję MessageBoxA. Jeśli wszystko się udało powinien pojawić się komunikat.

Kompletny kod przykładu w Asemblerze MASM64 (ML64.EXE) znajduje się na listingu poniżej.

Listing 7.1. Przykładowy kod relokowalny wyświetlający proste okno komunikatu w Asemblerze MASM64

;+------------------------------------+ ;| Relocatable code example in MASM64 | ;| https://haker.info/ | A.D. 2019 | ;| Coded by Dawid Farbaniec | ;+------------------------------------+ extern ExitProcess : proc ;debug only .code Main proc ;v--- CUT HERE ---v ;Zachowaj rejestry nieulotne ;na stosie programu ;(ang. nonvolatile registers) push rbp push rbx push rdi push rsi push rsp push r12 push r13 push r14 push r15 ;Znajdź adres kernel32.dll ;poprzez przeszukanie struktury ;ProcessEnvironmentBlock (PEB) ;R10 = TEB.ProcessEnvironmentBlock mov r10, gs:[60h] ;R10 = ProcessEnvironmentBlock->Ldr mov r10, qword ptr [r10 + 18h] ;R11 = Ldr->InMemoryOrderModuleList mov r11, qword ptr [r10 + 20h] ;R10 = InMemoryOrderModuleList.Flink mov r10, qword ptr [r11] ;R11 = InMemoryOrderModuleList.Flink->Flink mov r11, qword ptr [r10] ;RBX = InMemoryOrderModuleList.Flink->Flink->Flink ;RBX = kernel32.dll base address! mov rbx, qword ptr [r11 + 20h] ;Dodaj do adresu bazowego przesunięcie 3Ch, ;aby odczytać pole e_lfanew, które zawiera ;pozycję nagłówka PE w pliku modułu ;R9D = przesunięcie (ang. offset) w pliku ;pod którym znajduje się nagłówek PE mov r9d, dword ptr [rbx + 3Ch] ;Do odczytanego przesunięcia dodaj adres bazowy modułu ;Uzyskamy wtedy VA z RVA ;R9 = PIMAGE_NT_HEADERS64 add r9, rbx ;Dodaj przesunięcie 18h, aby znaleźć się ;w nagłówku opcjonalnym (PIMAGE_OPTIONAL_HEADER64) ;Dodaj przesunięcie 70h, aby znaleźć ;się w PIMAGE_DATA_DIRECTORY, które ;zawiera informacje o eksportowanych symbolach add r9, 18h + 70h ;Pobierz adres wirtualny (VA) PIMAGE_EXPORT_DIRECTORY mov r11d, dword ptr [r9] ;R8 = adres struktury PIMAGE_EXPORT_DIRECTORY lea r8, qword ptr [rbx + r11] ;ECX = PIMAGE_EXPORT_DIRECTORY->NumberOfNames mov ecx, dword ptr [r8 + 18h] ;R12D = PIMAGE_EXPORT_DIRECTORY->AddressOfNames ;(tablica adresów relatywnych wirtualnych - RVA) mov r12d, dword ptr [r8 + 20h] ;Uzyskaj adres VA z adresu relatywnego RVA ;poprzez dodanie adresu bazowego modułu add r12, rbx         _search_loop: ;Pobierz kolejny element z listy ;PIMAGE_EXPORT_DIRECTORY->AddressOfNames lea r10, qword ptr [r12 + rcx * sizeof dword] ;Pobierz adres relatywny wirtualny (RVA) ;danego elementu (nazwy funkcji) mov edi, dword ptr [r10] ;Dodaj adres bazowy modułu (otrzymamy VA) add rdi, rbx ;RSI = nazwa szukanej funkcji (będzie porównywana) lea rsi, qword ptr [szGetProcAddress]         _compare_str: ;porównaj bajt z rejestru RSI z bajtem z RDI ;(porównywanie nazw funkcji znak po znaku) cmpsb jne _function_not_found ;Pobierz bajt spod adresu w RSI mov al, byte ptr [rsi] ;Sprawdź czy rejestr AL równy zero test al, al jz _function_found jmp _compare_str         _function_not_found: loop _search_loop jmp _exit         _function_found: ;R8 zawiera adres PIMAGE_EXPORT_DIRECTORY ;Dodając przesunięcie 24h znajdujemy się ;w polu AddressOfNameOrdinals struktury. ;Zawiera ono wartości określające kolejność ;funkcji zgodne z wcześniejszym polem AddressOfNames mov r10d, dword ptr [r8 + 24h] ;Poprzednia wartość to adres relatywny (RVA) ;dlatego dodajemy adres bazowy modułu (imagebase) add r10, rbx ;Pobierz wartość określającą kolejność ;z listy na którą wskazuje pole ;AddressOfNameOrdinals mov cx, word ptr [r10 + rcx * sizeof word] ;R8 zawiera adres PIMAGE_EXPORT_DIRECTORY ;Dodając przesunięcie 1Ch znajdujemy się ;w polu AddressOfFunctions struktury. ;AddressOfFunctions zawiera adresy ;punktów wejścia eksportowanych funkcji mov r10d, dword ptr [r8 + 1Ch] ;Poprzednia wartość to adres relatywny (RVA) ;dlatego dodajemy adres bazowy modułu (imagebase), ;aby otrzymać VA add r10, rbx ;Pobierz adres RVA funkcji ;z listy AddressOfFunctions w R10 ;korzystając z numeru indeksu w RCX mov eax, dword ptr [r10 + rcx * sizeof dword] ;Dodaj adres bazowy modułu, aby z RVA ;otrzymać adres VA znalezionej funkcji add rax, rbx ;R15 = adres do funkcji GetProcAddress mov r15, rax ;GetProcAddress("kernel32.dll", "LoadLibraryA"); ;kolejność bajtów Little endian na procesorach x86(-64) ;wymaga tutaj wpisywania napisów od końca (odwrotnie) mov rcx, "Ayra" push rcx mov rcx, "rbiLdaoL" push rcx mov rdx, rsp mov rcx, rbx sub rsp, 30h ;alokacja miejsca na stosie call rax ;call GetProcAddress add rsp, 30h + 10h ;zwolnienie miejsca na stosie ;RDI = adres do funkcji LoadLibraryA mov rdi, rax ;LoadLibraryA("user32.dll"); mov rcx, "ll" push rcx mov rcx, "d.23resu" push rcx mov rcx, rsp sub rsp, 30h ;alokacja miejsca na stosie call rdi ;call LoadLibraryA add rsp, 030h + 10h ;zwolnienie miejsca na stosie ;R14 = uchwyt modułu user32.dll mov r14, rax ;GetProcAddress("user32.dll", "MessageBoxA"); mov rcx, "Axo" push rcx mov rcx, "BegasseM" push rcx mov rdx, rsp mov rcx, r14 sub rsp, 028h ;alokacja miejsca na stosie call r15 add rsp, 28h + 10h ;zwolnienie miejsca na stosie ;R13 = adres funkcji MessageBoxA mov r13, rax ;MessageBoxA(0, "haker.info", 0, 0); mov rdx, "of" push rdx mov rdx, "ni.rekah" push rdx mov rdx, rsp xor r9, r9 xor r8, r8 sub rsp, 30h ;alokacja miejsca na stosie xor rcx, rcx call r13 ;call MessageBoxA add rsp, 40h ;zwolnienie miejsca na stosie ;Odtwórz wartość rejestrów ;nieulotnych (ang. nonvolatile) pop r15 pop r14 pop r13 pop r12 pop rsp pop rsi pop rdi pop rbx pop rbp ;przeskocz dane (napis) ;akurat tych danych ;nie chcemy wykonywać jmp _exit szGetProcAddress db "GetProcAddress", 0         _exit: ;ExitProcess call should be here. ;^--- CUT HERE ---^ ;debug only sub rsp, 8h xor rcx, rcx call ExitProcess Main endp end

8. Zakończenie

Po zbudowaniu powyższego kodu do pliku wykonywalnego EXE w celu wydzielenia samodzielnego kodu relokowalnego należy wyodrębnić bajty kodu maszynowego z pomiędzy znaczników --- CUT HERE ---. Po tej operacji kod w postaci ciągu bajtów nadaje się np. do umieszczenia w dowolnym buforze w pamięci operacyjnej i wykonania. Do wyodrębnienia samego kodu z pliku EXE można użyć edytora szesnastkowego (ang. hex editor). Podstawy obsługi edytora heksadecymalnego opisałem w artykule Podstawy edycji plików binarnych.

Na koniec polecam również mój poprzedni artykuł o podobnej tematyce: Kod samo–modyfikujący się (x64).

Dawid Farbaniec


Tagi:  reverse-engineering  masm64  security 

Komentarze czytających



Wszystkie treści umieszczone na tej witrynie są chronione prawem autorskim. Surowo zabronione jest kopiowanie i rozpowszechnianie zawartości tej witryny bez zgody autora. Wszelkie opublikowane tutaj treści (w tym kody źródłowe i inne) służą wyłącznie celom informacyjnym oraz edukacyjnym. Właściciele tej witryny nie ponoszą odpowiedzialności za ewentualne niezgodne z prawem wykorzystanie zasobów dostępnych w witrynie. Użytkownik tej witryny oświadcza, że z zamieszczonych tutaj danych korzysta na własną odpowiedzialność. Wszelkie znaki towarowe i nazwy zastrzeżone zostały użyte jedynie w celach informacyjnych i należą wyłącznie do ich prawnych właścicieli. Korzystając z zasobów witryny haker.info oświadczasz, że akceptujesz powyższe warunki.