Inline hook s využitím trampolíny

V jednom z předchozích článků jsem popisoval inline hook, který pro hookování využívá opakovaného přepisování prvních pěti bajtů funkce (většinou prologu) a rovněž jsem zmínil, že tato technika má své mouchy. V dnešním článku popíšu další možnost implementace inline hooku, tentokrát s využitím trampolíny. Výhodou proti předchozí implementaci je fakt, že hook je implementován pouze jednou a dále již není kód funkce při každém volání upravován.

Teoretický přístup
Při inline hooku dochází k přepisování prvních několika bajtů funkce pomocí odskoku do vlastní funkce. Tímto odskokem si zajistíme možnost pracovat se vstupem a výstupem originální funkce. Můžeme měnit hodnoty v argumentech nebo upravovat návratovou hodnotu funkce tak, aby vracela přesně to, co potřebujeme. Možností je přesně tolik, kolik jich autora hooku napadne.
V prvním kroku potřebujeme zjistit adresu funkce, kterou budeme hookovat. Ve chvíli, kdy známe adresu naší funkce, můžeme začít se samotným hákováním. Protože se většina exportovaných funkcí nachází v sekci paměti bez příznaku/oprávnění WRITABLE, musíme nejprve této části paměti přidělit práva pro zápis. Následně si uložíme potřebný počet původních bajtů do nějakého dočasného úložiště (budeme je dále potřebovat), a přepíšeme paměť instrukcí JMP (případně PUSH/RET nebo podobně) s adresou naší funkce (v případě instrukce JMP se nejedná o přímou adresu, ale o rozdíl mezi adresou, kam budeme chtít skákat a adresou námi zapisované instrukce JMP). Tímto jsme si zajistili přesměrování do naší hookované funkce. Na závěr vrátíme původní přístupová práva pro danou část paměti.
V dalším kroku si v paměti alokujeme prostor pro trampolínu. Trampolína není nic jiného než část paměti s původní bajty, které jsme si uložili před přepisováním doplněné o skok na původní/hookovanou funkci, kde však bude adresa funkce posunuta o tolik bajtů, kolik jsme jich přepsali (u JMP je to standardně 5 bajtů). Na závěr přidáme naší trampolíně příznak pro EXECUTE, aby bylo možné kód trampolíny vykonat.
Jako poslední věc si vytvoříme naši verzi hookované funkce, do níž umístíme volání originální funkce (lépe řečeno trampolíny, protože tam originální funkce začíná). To, co provedeme se vstupem/výstupem naší funkce, je nyní plně v naší režii.

Kód nehookované funkce MessageBoxA

Kód nehookované funkce MessageBoxA

Praktický přístup
Dejme tomu, že je naším cílem měnit titulek Windows API funkce MessageBox. Jako první krok potřebujeme získat adresu této funkce v DLL knihovně user32.dll. Nejjednodušší možnost je využití Windows API funkce GetProcAddress. Ta jako první parametr bere handle modulu, ze které se exportuje požadovaná funkce a jako druhý parametr název požadované funkce. Ve chvíli, kdy známe adresu funkce MessageBox, můžeme začít hookovat. Nejprve změníme příznak pro oblast přepisované paměti tak, aby měla rovněž příznak WRITABLE. K tomu se hodí Windows API funkce VirtualProtect. Je dobré zazálohovat původní práva přepisované paměti, aby je bylo možné zpětně nastavit po tom, co ukončíme editaci paměti. Nyní si zazálohujeme přepisované bajty. Zvolíme metodu JMP, protože si na ní popíšeme, jakým způsobem se počítá hodnota odskoku. Obecně platí, že je možné přepsat tolik kódu, kolik potřebujete. Vždy je ale důležité ‚neponičit‘ funkčnost kódu. Jinými slovy: Přepsat vždy a za všech okolností 5 bajtů není možné, protože každá instrukce má jinou délku a ta není vždy právě požadovaných 5 bajtů. Buď musíme pomocí reverzního inženýrství dohledat pozici funkce a přepočítat si počet bajtů k přepsání, nebo vybavit aplikaci disassemblovacím enginem. Ten nám zjistí potřebnou délku k přepsání sám a my se tak o tuto část starat nemusíme. Jak si napsat vlastní disassemblovací engine je na samostatný článek. Nechám si tedy tohle téma zatím ‚v šuplíku‘. Pouhým pohledem do kódu funkce MessageBox vidíme, že zde můžeme přepsat prvních pět bajtů naprosto s klidem. Na začátku funkce je totiž klasický prolog o délce 5 bajtů. Instrukce JMP používá opcode 0xE9. Tuto hodnotu zkopírujeme na první bajt. Teď to hlavní! Funkce JMP nebere přímo adresu, ale pouze hodnotu rozdílu dvou adres. První adresou je adresa naší funkce, druhou adresou je součet počtu přepisovaných bajtů (v našem případě 5) s adresou přepisované funkce. Obecně můžeme tuto část zapsat následovně:

JMP_ADDR = (FakeFunction – (OriginalFunction + 5))

Kód hookované funkce MessageBoxA

Kód hookované funkce MessageBoxA

Nyní jsme si zajistili možnost skoku do naší funkce. V ní provedeme změnu titulku okna a zavoláme originální funkci. Tohle volání ale neprovedeme přímo, protože nám chybí na začátku funkce 5 bajtů. Těchto 5 bajtů musíme uložit někam do paměti a doplnit je o instrukci JMP ukazující za námi přepsaných 5 bajtů ve funkci MessageBox. A právě tahle část kódu je označovaná jako trampolína, protože nám umožňuje skočit do původní funkce bez toho, aby došlo k jejímu poškození. Pomocí funkce VirtualAlloc si alokujeme potřebné místo pro trampolínu a zkopírujeme do ní původních 5 bajtů funkce MessageBox (tedy prolog funkce). Za prolog přidáme instrukci JMP. Tentokrát bude hodnota odskoku rovna rozdílu adresy originální funkce a adresy trampolíny. Navíc musíme odečíst velikost přepisovaného kodu, tedy 5 bajtů. Pokud budeme nyní v naší fake funkci chtít zavolat Windows API funkci MessageBox, budeme místo ní volat adresu trampolíny. Ta přesměruje tok programu do originální funkce. Když originální funkce skončí, dojde k návratu do naší fake funkce a následnému návratu do hlavní funkce programu. To je vše.

Trampolína inline hooku funkce MessageBoxA

Trampolína inline hooku funkce MessageBoxA

Ukázkový kód
Zahákujeme funkci MessageBoxA a změníme její titulek na popisek ‚Hooked!‘. Pro názornost se první MessageBox provede před hookováním a druhý hned po hookování.

#include <stdio.h>
#include <windows.h>
 
#define OLDCODESIZE 5
 
typedef int (WINAPI *hMessageBox) (HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
 
typedef struct HookFuncStruct{
  DWORD dwOrigFuncAddr;
  DWORD dwRwSize;
  DWORD dwFakeFuncAddr;
  LPVOID lpTrampoline;
}HookFuncStruct, *pHookFuncStruct;
 
struct HookFuncStruct msgBox;
 
int HookedMessageBox(HWND hWnd, LPCSTR lpText, 
                     LPCSTR lpCaption, UINT uType) {
  int iRet = 0;
  hMessageBox hOriginal = NULL;
 
  hOriginal = (hMessageBox)msgBox.lpTrampoline;
  iRet = hOriginal(hWnd, lpText, "Hooked!", uType);
 
  return iRet;
}
 
void Hook(HookFuncStruct *msgBox){
  DWORD oldProtect = 0;
  DWORD hookedAddress = 0;
  DWORD dwTrampolineJump = 0;
  HANDLE hCurrProc = NULL;
 
  BYTE jmpHook[5] = {0xE9, 0x00, 0x00, 0x00, 0x00};
  BYTE jmpOrig[5] = {0xE9, 0x00, 0x00, 0x00, 0x00};
  BYTE oldBytes[msgBox->dwRwSize];
 
  hCurrProc = GetCurrentProcess();
 
  hookedAddress = ((DWORD)msgBox->dwFakeFuncAddr - (msgBox->dwOrigFuncAddr + 5));
  memcpy(&jmpHook[1], &hookedAddress, 4);
 
  VirtualProtect((LPVOID)msgBox->dwOrigFuncAddr, msgBox->dwRwSize, 
                 PAGE_EXECUTE_READWRITE, &oldProtect);
  ReadProcessMemory(hCurrProc, (LPCVOID)msgBox->dwOrigFuncAddr, 
                    oldBytes, msgBox->dwRwSize, 0);
  WriteProcessMemory(hCurrProc, (LPVOID)msgBox->dwOrigFuncAddr, 
                     jmpHook, 5, 0);
  VirtualProtect((LPVOID)msgBox->dwOrigFuncAddr, msgBox->dwRwSize, 
                 oldProtect, 0);
 
  msgBox->lpTrampoline = VirtualAlloc(NULL, 20, MEM_RESERVE | MEM_COMMIT, 
                                      PAGE_EXECUTE_READWRITE);
  WriteProcessMemory(hCurrProc, msgBox->lpTrampoline, 
                     oldBytes, msgBox->dwRwSize, 0);
 
  dwTrampolineJump = (msgBox->dwOrigFuncAddr - (DWORD)msgBox->lpTrampoline - msgBox->dwRwSize);
  memcpy(&jmpOrig[1], &dwTrampolineJump, 4);
 
  WriteProcessMemory(hCurrProc, (LPVOID)((DWORD)msgBox->lpTrampoline + msgBox->dwRwSize), 
                     jmpOrig, msgBox->dwRwSize, 0);
  VirtualProtect(msgBox->lpTrampoline, 20, 
                 PAGE_EXECUTE_READ, 0);
}
 
int main(int argc, char **argv) {
  msgBox.dwRwSize = OLDCODESIZE;
  msgBox.dwOrigFuncAddr = (DWORD)GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
  msgBox.dwFakeFuncAddr = (DWORD)HookedMessageBox;
 
  MessageBoxA(NULL, "UNICODE verze funkce MessageBox", 
              "Nehookovano", MB_OK);
  Hook(&msgBox);
  MessageBoxA(NULL, "UNICODE verze funkce MessageBox", 
              "Nehookovano", MB_OK);
 
  return 0;
}
Zahookovaná funkce MessageBoxA se změněným titulkem okna

Zahookovaná funkce MessageBoxA se změněným titulkem okna

1 komentář u „Inline hook s využitím trampolíny

  1. Pingback: Formgrabbing | Security Cave

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *