Vytvoření nové sekce PE souboru (Last Section Appender)

V předchozím článku jsem popsal způsob, jakým je možné rozšířit poslední sekci Portable Executable (dále jen PE) souboru. Kromě techniky rozšíření poslední sekce existuje i technika vytvoření nové poslední sekce. Tento článek popíše, jak takovou sekci vytvořit, jak do ní zkopírovat vlastní kód a jak zajistit jeho spuštění před tím, než dojde ke spuštění legitimní části kódu upravené aplikace. Teoreticky se přístup od toho minulého příliš neliší. Prakticky jsou zde slepá místa, jenž můžou člověku řádně znechutit práci na celém kódu, pokud o nich neví.

Teoretický přístup
Jak bylo řečeno v předchozím článku: „Každý PE soubor je soustava struktur s pevně danou hierarchií.“. Struktura PE souboru je poměrně kvalitně popsána, a proto není třeba se příliš pitvat v detailech.

Aby bylo možné vytvořit novou poslední sekci, musíme nejprve zjistit, zda náš pokus bude splňovat několik kritérií. První kritérium předpokládá existenci maximálně šestadevadesáti sekcí souboru (struktura IMAGE_SECTION_HEADER). Počet sekcí si pohodlne ověříme ve struktuře IMAGE_FILE_HEADER obsahující pole NumberOfSections, které udává právě počet sekcí PE souboru. Pokud je hodnota nižší než 96, můžeme přejít k další kontrole: Máme dostatečný prostor pro novou hlavičkovou strukturu sekce? Prostě a jednoduše: Součet hodnoty e_lfanew (IMAGE_DOS_HEADER), velikosti IMAGE_NT_HEADERS a velikosti všech hlaviček sekcí musí být větší než je velikost struktury IMAGE_SECTION_HEADER. Pokud by hodnota byla menší, došlo by k přepsání dat za poslední původní sekcí. Z vlastních zkušeností postavených na poměrně rozsáhlém počtu vzorků můžu s klidem konstantovat, že místo pro hlavičku nové sekce bude téměř vždy. I přesto doporučuji tuto kontrolu provádět. Pokud jsou splněny obě tyto podmínky, nic nebrání v přidání nové poslední sekce. Zjistíme si adresu poslední sekce a využijeme hodnoty z hlavičkové struktury k výpočtu umístění nové sekce. Jedná se o hodnoty VirtualAddress, VirtualSize, PointerToRawData, SizeOfRawData a Characteristics. Jako doplněk je možné vytvořit jméno nové sekce, jehož maximální velikost může být 8 bajtů bez koncového NULL bajtu. Navýšíme počet sekcí o jedna, upravíme velikost obrazu, adresu vstupního bodu a nakopírujeme náš kód.

Praktický přístup
Po namapování souboru do paměti si uložíme hodnoty SectionAlignment a FileAlignment a opět soubor odmapujeme. Znovu soubor namapujeme, ale tentokrát s velikostí, jenž je součtem původní velikosti souboru a velikosti přidávaného kódu zaokrouhleno na velikost FileAlignment. Poslední sekci získáme jako součet adresy začátku PE souboru, velikosti PE struktury IMAGE_NT_HEADERS a velikosti všech struktur hlaviček mínus jedna struktura. Nová sekce tedy bude poslední sekce + velikost struktury IMAGE_SECTION_HEADER. Jméno sekce zvolíme například .test a nakopírujeme do nové sekce. Hodnota VirtualSize je rovna velikosti vkládaného kódu. SizeOfRawData obsahuje hodnotu velikosti vkládaného kódu zaokrouhlenou na násobek hodnoty FileAlignment. Pro hodnotu VirtualAddress nové sekce sečteme hodnoty VirtualAddress a VirtualSize poslední sekce a zaokrouhlíme na násobek honodty SectionAlignment. Stejně budeme postupovat u hodnoty PointerToRawData, jen sečteme hodnoty PointerToRawData a SizeOfRawData poslední sekce zaokrouhlenou na násobek hodnoty FileAlignment. Jako hodnotu Characteristics zvolíme IMAGE_SCN_MEM_EXECUTE a IMAGE_SCN_MEM_READ, aby bylo možné z dané sekce číst a zároveň vykonávat její kód. Tím bychom měli zajištěnu hlavičku pro novou sekci. Nyní upravíme pár stávajících hodnot ve strukturách IMAGE_FILE_HEADER a IMAGE_OPTIONAL_HEADER. Jako první zvedneme počet sekcí o jedna, což provedeme inkrementováním hodnoty pole NumberOfSections ve struktuře IMAGE_FILE_HEADER. Ve struktuře IMAGE_OPTIONAL_HEADER změníme hodnotu SizeOfImage udávající velikost souborového obrazu na hodnotu součtu původní hodnoty SizeOfImage a hodnoty velikosti přidávaného kódu zaokrouhlenou na násobek hodnoty FileAlignment. Celou tuto hodnotu zaokrouhlíme na násobek SectionAlignment (není to nejčistší řešení, ale jak jsem psal v předchozím článku – proč si prostě nepomoci, když se tak můžeme vyhnout problémům? 🙂 ). Na závěr nastavíme AddressOfEntryPoint na hodnotu RVA adresy začátku našeho kódu a máme vyhráno 🙂

Při pokusu vytvářet nové sekce v souborech se můžete setkat se stavem, kdy proces proběhne bez problémů, ale soubor nebude možné spustit. Bude docházet k vypisování ‚nesmyslné‘ hlášky o chybějící knihovně .dll. V takovém případě se při vytváření hlavičkového souboru podařilo přepsat tzv. Bound Import Directory. Pro rychlé odstranění problému stačí ‚odstranit‘ adresu Bound Import Directory z pole DataDirectory ve struktuře IMAGE_OPTIONAL_HEADER tak, že tuto adresu jednoduše vynulujeme společně s velikostí. Takových podobných problémů se může při editaci PE souboru objevit několik. Tento je ale asi nejčastější, a proto na něj upozorňuji přímo.

Ukázkový kód
Přidávaný kód má pouze ten účel, že přesměruje vykonávání z našeho kódu na původní Entry Point:

PUSHAD
POPAD
MOV EAX, OEP    ;OldEntryPoint Address
JMP EAX

Tímto velice jednoduchým kódem se dá upravit většina PE aplikací. Existují určité vyjímky, ale o těch až někdy příště.

#include <Windows.h>
#include <stdio.h>
 
DWORD Align(DWORD dwVal, DWORD dwAlign);
 
int main(){
  char *szFile = "C:\\calc.exe";
  HANDLE hFile = NULL, hFileMapping = NULL;
  DWORD dwFileSize = 0, dwCodeSize = 400;
  DWORD dwFileAlign = 0, dwSectionALign = 0;
  DWORD dwInfectable = 0, dwOEP = 0;
  PDWORD pRet = NULL;
  LPVOID lpFileMapped = NULL;
  PIMAGE_DOS_HEADER pDosHeader = NULL;
  PIMAGE_NT_HEADERS pNtHeaders = NULL;
  PIMAGE_SECTION_HEADER pLastSection = NULL;
  PIMAGE_SECTION_HEADER pNewSection = NULL;
 
  hFile = CreateFileA(szFile, GENERIC_READ | GENERIC_WRITE, 
                      FILE_SHARE_READ | FILE_SHARE_WRITE,
                      NULL, OPEN_EXISTING, 
                      FILE_ATTRIBUTE_NORMAL, NULL);
 
  if(hFile != INVALID_HANDLE_VALUE){
    dwFileSize = GetFileSize(hFile, NULL);
 
    hFileMapping = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, 0, dwFileSize, NULL);
 
    if(hFileMapping != INVALID_HANDLE_VALUE){
      lpFileMapped = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
 
      if(lpFileMapped != NULL){
        pDosHeader = (PIMAGE_DOS_HEADER)lpFileMapped;
 
        if(pDosHeader->e_magic == IMAGE_DOS_SIGNATURE){
          pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
 
          if(pNtHeaders->Signature == IMAGE_NT_SIGNATURE){
            if((pNtHeaders->OptionalHeader.SizeOfHeaders - (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + 
              pNtHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER))) >= 
              sizeof(IMAGE_SECTION_HEADER)){
                dwFileAlign = pNtHeaders->OptionalHeader.FileAlignment;
                dwSectionALign = pNtHeaders->OptionalHeader.SectionAlignment;
                dwInfectable = 1;
            }else{
              printf("[-] Too few spaces for ");
            }
          }else{
            printf("[-] NT Signature not found!\n");
          }
        }else{
          printf("[-] DOS Signature not found!\n");
        }
 
        UnmapViewOfFile(lpFileMapped);
      }
 
      CloseHandle(hFileMapping);
    }
 
    if(dwInfectable == 1){
      hFileMapping = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, 0, dwFileSize + Align(dwCodeSize, dwFileAlign), NULL);
 
      if(hFileMapping != INVALID_HANDLE_VALUE){
        lpFileMapped = MapViewOfFile(hFileMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
 
        if(lpFileMapped != NULL){
          pDosHeader = (PIMAGE_DOS_HEADER)lpFileMapped;
          pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
 
          pLastSection = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeaders + (sizeof(IMAGE_NT_HEADERS) + 
                              (pNtHeaders->FileHeader.NumberOfSections - 1) * 
                              sizeof(IMAGE_SECTION_HEADER)));
 
          pNewSection = pLastSection + 1;
          memset(pNewSection, 0, sizeof(IMAGE_SECTION_HEADER));
 
          pNtHeaders->FileHeader.NumberOfSections++;
          pNtHeaders->OptionalHeader.SizeOfHeaders = Align((pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + 
                  pNtHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)), dwFileAlign);
          pNtHeaders->OptionalHeader.SizeOfImage = Align(pNtHeaders->OptionalHeader.SizeOfImage + Align(dwCodeSize, dwFileAlign), dwSectionALign);
 
          pNewSection->Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;
          strcpy((char *)pNewSection->Name, ".test");
          pNewSection->Misc.VirtualSize = dwCodeSize;
          pNewSection->PointerToRawData = Align(pLastSection->PointerToRawData + pLastSection->SizeOfRawData, dwFileAlign);
          pNewSection->SizeOfRawData = Align(dwCodeSize, dwFileAlign);
          pNewSection->VirtualAddress = Align(pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize, dwSectionALign);
 
          pNtHeaders->OptionalHeader.DataDirectory[11].Size = 0;
          pNtHeaders->OptionalHeader.DataDirectory[11].VirtualAddress = 0;
 
          strcpy((char *)((DWORD)pDosHeader + pNewSection->PointerToRawData), "\x60\x61\xB8");
          dwOEP = pNtHeaders->OptionalHeader.AddressOfEntryPoint;
          pNtHeaders->OptionalHeader.AddressOfEntryPoint = pNewSection->VirtualAddress;
          pRet = (PDWORD)((DWORD)pDosHeader + pNewSection->PointerToRawData + 3);
          *pRet = pNtHeaders->OptionalHeader.ImageBase + dwOEP;
          *((PWORD)((PBYTE)pRet + 4)) = 0xE0FF;
        }
      }
 
    }
 
    CloseHandle(hFile);
  }
  getchar();
  return 0;
}
 
DWORD Align(DWORD dwVal, DWORD dwAlign){
  return (((dwVal + dwAlign - 1) / dwAlign) * dwAlign);
}
Entry Point upravené aplikace

Entry Point upravené aplikace

Napsat komentář

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