Online: 0x029 (41)
haker.info  — Etyczny hacking_
Spreading knowledge like a virus.

Kod relokowalny/wstrzykiwalny (x64)

   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鈥搃ndependent 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鈥揷ontained, 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鈥搃ndependent 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鈥搃ndependent-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鈥搃ndependent 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 鈥瀘d 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 鈥瀘d 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鈥搈odyfikuj膮cy si臋 (x64).

Dawid Farbaniec

Wykaz literatury (bibliografia)

  • Advanced Micro Devices Inc., 2017 鈥 AMD64 Architecture Programmer's Manual
  • Intel Corporation, 2019 鈥 Intel 64 and IA-32 Architectures Software Developer's Manual
  • Microsoft Corporation, 2019 鈥 https://docs.microsoft.com/pl-pl/cpp/assembler/masm/masm-for-x64-ml64-exe (dost臋p: 28-07-2020)
  • Microsoft Corporation, 2018 鈥 https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb (dost臋p: 28-07-2020)

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 oraz polityk臋 prywatno艣ci.