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

Kod samo鈥搈odyfikuj膮cy si臋 (x64)

   Dawid Farbaniec    1190 s艂贸w

1. S艂owem wst臋pu

rogram, kt贸ry sam modyfikuje sw贸j kod, gdy jest ju偶 uruchomiony na pewno wzbudzi ciekawo艣膰 podczas analizy w debuggerze. Technika ta polega na zamienianiu przez program swoich instrukcji podczas wykonywania. U偶ywa si臋 jej np. w zabezpieczeniach antypirackich. Ot贸偶 tak, modyfikacja w艂asnego kodu przez proces aplikacji pozwala utrudni膰 osobom niepowo艂anym rozpracowanie, a nast臋pnie np. z艂amanie zabezpiecze艅 plik贸w programu. Kod samo鈥搈odyfikuj膮cy si臋 znajdzie te偶 zastosowanie w innych miejscach. Inny przyk艂ad u偶ycia to aplikacje do kompresji plik贸w wykonywalnych. Narz臋dzie typu packer po uruchomieniu rozpakowuje skompresowany kod w pami臋ci i dopiero wtedy rozpoczyna si臋 wykonanie go. W tym artykule poznasz w艂a艣nie technik臋 modyfikacji w艂asnego kodu przez program. Mi艂ej lektury.

2. Teoria

Na pocz膮tek troch臋 potrzebnej teorii. Kompilator lub je艣li programujemy w Asemblerze to bezpo艣rednio asembler (z ma艂ej litery 鈥 narz臋dzie) buduje kod 藕r贸d艂owy do pliku wykonywalnego (np. *.exe). Instrukcje procesora w pliku *.exe s膮 reprezentowane poprzez kod maszynowy. Rozkazy i ich operandy s膮 zakodowane jako ci膮g bajt贸w.

Sytuacja pocz膮tkowa przedstawia si臋 tak, 偶e po uruchomieniu programu mamy w pami臋ci rozkazy procesora jako kod maszynowy (bajty). Spos贸b t艂umaczenia instrukcji w formie mnemonik贸w (np. ADD, MOV czy XOR) na ich kody operacyjne (opkody) jest opisany dok艂adnie w dokumentacji procesor贸w x86-64. Modyfikacja kodu aplikacji w pami臋ci polega w skr贸cie na zamianie opkod贸w.
Na przyk艂ad instrukcja xor rcx, rcx (zerowanie rejestru licznika RCX) jest zakodowana jako ci膮g bajt贸w 48 33 C9. Chc膮c zmodyfikowa膰 kod w pami臋ci nale偶y podmieni膰 te bajty. Np. ustawienie im warto艣ci 90 90 90 spowoduje, 偶e otrzymanymi instrukcjami b臋d膮 trzy rozkazy NOP.

Poni偶ej na rysunku 2.1 przedstawiono adresy, opkody (bajty) i mnemoniki instrukcji z fragmentu kodu uruchomionego w debuggerze x64dbg.


Rysunek 2.1. Adresy, kody operacyjne i mnemoniki w debuggerze x64dbg

W tym momencie pr贸ba nadpisania instrukcji zako艅czy si臋 b艂臋dem naruszenia ochrony pami臋ci (Exception Access Violation). Ju偶 t艂umacz臋 dlaczego tak jest. Okre艣lone bloki pami臋ci maj膮 ustawione odpowiednie atrybuty. I tak np. sekcji z danymi nie mo偶na domy艣lnie wykonywa膰 (ang. execute). A do sekcji kodu nie mo偶na domy艣lnie pisa膰 (ang. write ).

Zatem kolejnym krokiem b臋dzie poznanie funkcji, kt贸ra pozwoli zmienia膰 atrybuty stron pami臋ci w uruchomionej aplikacji. Jest to funkcja z Windows® API o nazwie VirtualProtect.
Jej sk艂adnia przedstawia si臋 nast臋puj膮co:

BOOL VirtualProtect( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect );

Funkcja posiada cztery parametry:

  • lpAddress 鈥 wska藕nik, kt贸ry okre艣la pocz膮tek strony (ze zbioru stron pami臋ci) od kt贸rego b臋d膮 zmieniane atrybuty dost臋pu
  • dwSize 鈥 rozmiar obszaru w bajtach do kt贸rego b臋d膮 modyfikowane atrybuty dost臋pu
  • flNewProtect 鈥 sta艂a okre艣laj膮ca atrybuty dost臋pu.
    Podstawowe sta艂e:
    PAGE_EXECUTE (0x10) » zbi贸r stron mo偶e by膰 wykonywany.
    PAGE_EXECUTE_READ (0x20) » zbi贸r stron mo偶e by膰 wykonywany i odczytywany.
    PAGE_EXECUTE_READWRITE (0x40) » zbi贸r stron mo偶e by膰 wykonywany, odczytywany i zapisywany.
    PAGE_READONLY (0x02) » zbi贸r stron mo偶e by膰 odczytywany.
    PAGE_READWRITE (0x04) » zbi贸r stron mo偶e by膰 odczytywany i zapisywany.
    (...)
  • lpflOldProtect 鈥 wska藕nik na zmienn膮 o rozmiarze podw贸jnego s艂owa, kt贸ra b臋dzie przechowywa膰 stare atrybuty strony (przed zmian膮).

Na koniec tego rozdzia艂u wspomn臋 o sposobie adresowania w architekturze x86-64 nazywanym RIP-relative addressing, czyli adresowanie wzgl臋dne do wska藕nika rozkaz贸w. Je艣li by艂aby potrzeba stworzenia, aby kod by艂 position independent, czyli m贸g艂 by膰 uruchomiony w r贸偶nych miejscach pami臋ci, to dzi臋ki tej metodzie mo偶liwe b臋dzie zachowanie poprawnych odwo艂a艅 do danych poprzez u偶ycie zwyk艂ych etykiet j臋zyka Asembler. W architekturze x86 by艂o ju偶 wsparcie dla wzgl臋dnego odwo艂ywania si臋 do okre艣lonych miejsc w pami臋ci (np. w instrukcji JMP). Jednak w x86-64 mo偶liwy jest dodatkowo tak偶e zapis i odczyt pami臋ci poprzez adresowanie wzgl臋dne do wska藕nika rozkaz贸w. Mo偶e to by膰 u偶yteczne przy samo鈥搈odyfikuj膮cym si臋 shellcode.

3. Nadpisywanie kolejnych instrukcji

Rozpocznijmy od mniej z艂o偶onego przyk艂adu. Jest to nadpisanie dw贸ch instrukcji. Kod 藕r贸d艂owy tego przyk艂adu przedstawiono na listingu 3.1. Analiza i opis przyk艂adu znajduje si臋 poni偶ej listingu 3.1.

Listing 3.1. Prosty przyk艂ad samo鈥搈odyfikuj膮cego si臋 kodu w Asemblerze MASM64
;http://haker.info/ extrn ExitProcess : proc extrn VirtualProtect : proc .const PAGE_EXECUTE_READWRITE equ 040h .data oldProtect dd 0 .code Main proc ;zmiana atrybut贸w bloku pami臋ci na: ;wykonanie, odczyt i zapis sub rsp, 28h mov r9, offset oldProtect mov r8, PAGE_EXECUTE_READWRITE mov rdx, 15h mov rcx, offset _mutable call VirtualProtect add rsp, 28h _mutable: ;wpisanie do rejestru RBX adresu etykiety _mutable mov rbx, offset _mutable ;nadpisanie wskazywanych instrukcji czterema rozkazami NOP mov dword ptr [rbx+011h], 090909090h ;poni偶ej instrukcje do nadpisania xor rcx, rcx ret _exit: sub rsp, 8h xor rcx, rcx call ExitProcess Main endp end

Skrypt budowania kodu MASM64 do pliku wykonywalnego EXE (build.bat)
@echo off ml64.exe prog1.asm /link /entry:Main /subsystem:windows /defaultlib:"kernel32.Lib" /defaultlib:"user32.Lib" /LARGEADDRESSAWARE:NO pause

W funkcji g艂贸wnej Main na pocz膮tku jest ustawienie blokowi pami臋ci atrybut贸w PAGE_EXECUTE_READWRITE co pozwala, opr贸cz wykonywania, tak偶e dokonywa膰 zapisu i odczytu kodu aplikacji. Od etykiety _mutable rozpoczyna si臋 g艂贸wny kod, kt贸ry dokonuje samo鈥搈odyfikacji.

Najpierw do rejestru RBX kopiowany jest adres etykiety _mutable. Nast臋pnie instrukcja mov dokonuje wpisania czterech bajt贸w (podw贸jnego s艂owa) o warto艣ci 090909090h pod adres rbx+11h. W rejestrze RBX jest adres etykiety _mutable do kt贸rego dodawane jest 17 bajt贸w (011h). Jest tak, gdy偶 17 bajt贸w od etykiety _mutable znajduje si臋 pocz膮tek kodu, kt贸ry b臋dzie modyfikowany.

Instrukcje xor rcx, rcx oraz ret po przet艂umaczeniu na kod maszynowy (opkody) maj膮 rozmiar 4 bajt贸w. Z tego powodu wstawiane s膮 cztery rozkazy nop, kt贸re maj膮 razem r贸wnie偶 4 bajty.

Kod z poprzedniego listingu 3.1. uruchomiony w debuggerze x64dbg przedstawiono wraz z dodatkowym wyja艣nieniem na rysunku 3.1. poni偶ej.


Rysunek 3.1. Prosty przyk艂ad samo鈥搈odyfikuj膮cego si臋 kodu pod debuggerem x64dbg

Na wideo poni偶ej przedstawiono uruchomienie kodu z poprzedniego listingu 3.1. pod narz臋dziem typu debugger (x64dbg).


4. Rozszyfrowanie i uruchomienie kodu w pami臋ci

Technika modyfikacji w艂asnego kodu mo偶e by膰 u偶ywana na r贸偶ne sposoby. W poprzednim rozdziale by艂o to nadpisywanie instrukcji. Tutaj zaprezentowane zostanie rozszyfrowywanie kodu, a nast臋pnie uruchomienie go. Artyku艂 ten nie przedstawia oczywi艣cie wszystkich mo偶liwo艣ci. Z innych metod to spotyka si臋 te偶 np. modyfikacj臋 warto艣ci argument贸w w kodzie, wymazywanie kodu z pami臋ci po wykonaniu i inne.

W przyk艂adzie zaprezentowanym poni偶ej na listingu 4.1. zaszyfrowany kod do wykonania znajduje si臋 w tablicy bajt贸w. Jest to sekcja danych. System Windows® posiada zabezpieczenie o nazwie Data Execution Prevention (DEP), co oznacza w j臋zyku polskim zabezpieczenie przed wykonywaniem danych. Z tego powodu dane nie mog膮 zosta膰 wykonane jako instrukcje. Chyba, 偶e zmieni si臋 im atrybuty pami臋ci. Zatem tak jak w poprzednim rozdziale zostanie u偶yta funkcja VirtualProtect.

Przedstawiaj膮c w punktach dzia艂anie programu z listingu 4.1., lista wygl膮da艂by tak jak poni偶ej:

  • Rozszyfrowanie kodu z tablicy bajt贸w w p臋tli for.
  • Ustawienie pami臋ci (tablicy) atrybutu wykonywalno艣ci (ang. execute) za pomoc膮 funkcji VirtualProtect.
  • Rzutowanie (konwersja) tablicy bajt贸w na wska藕nik na funkcj臋.
  • Wywo艂anie kodu z tablicy bajt贸w (funkcja func()).
Listing 4.1. Prosty przyk艂ad samo鈥搈odyfikuj膮cego si臋 kodu w Visual C++
#include <Windows.h> BYTE encryptedCode[] = { 0x97, //0x97 xor 7 = 0x90 = NOP opcode 0x97, //j.w. 0x97, //j.w. 0xC4 //0xC4 xor 7 = 0xC3 = RET opcode }; int wmain(int argc, TCHAR* argv[]) { UINT codeLength = sizeof(encryptedCode); DWORD oldProtect = NULL; typedef int (*ExecuteFunc)(); for (auto &byte : encryptedCode) { byte = byte xor 7; } VirtualProtect(encryptedCode, codeLength, PAGE_EXECUTE_READWRITE, &oldProtect); //wywo艂anie zmodyfikowanego (rozszyfrowanego) kodu ExecuteFunc func = (int(*)())&encryptedCode; func(); #if _DEBUG system("pause"); #endif return EXIT_SUCCESS; }

5. Podsumowanie

Wpis ten jest tylko wprowadzeniem do tworzenia kodu samo鈥搈odyfikuj膮cego si臋. Nie musz臋 chyba pisa膰, 偶e technika ta daje na prawd臋 du偶e mo偶liwo艣ci. Dzi臋kuj臋 za czas po艣wi臋cony na przeczytanie wpisu i pozdrawiam.

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)

Tagi:  reverse-engineering  masm64  visual-cpp  security 

Komentarze czytaj膮cych

Czytelnik napisa艂:
A do sekcji kodu nie mo偶na domy艣lnie pisa膰 (ang. read).

write :)
 ponad miesi膮c temu (03 lipca 2020 godz. 00:58)
Dawid (haker.info) napisa艂:
Dzi臋kuj臋 za zwr贸cenie uwagi. Poprawi艂em teraz.
 ponad miesi膮c temu (03 lipca 2020 godz. 01:06)



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.