Rozšíření poslední sekce PE souboru (Last Section Expander)

Rozšíření poslední sekce kódu spadá společně s vytvořením nové poslední sekce (Last Section Appender) k nejzákladnějším technikám úpravy binárních Portable Executable (dále PE) souborů. V praxi to znamená, že změnou velikosti poslední sekce změníme velikost celého PE souboru. Díky tomu můžeme v námi vytvořeném prostoru libovolně pracovat. Můžeme sem vložit náš kód, který například dekóduje celý zbytek aplikace a na závěr ji spustí (tzv. run-time decrypter), můžeme sem přidat kód, který upraví běh aplikace (klasicky u aplikací, které bývají tzv. patchovány za běhu, aby došlo k odstranění zabezpečení – častěji se používají loadery a nebo statické patchery), přidání nové funkcionality do aplikace atd. Nejvíce tyto manipulace se soubory proslavily viry – tzv. PE infektory.

Teortický přístup
Každý PE soubor je soustava struktur s pevně danou hierarchií. Jednotlivé hodnoty jsou buď pevně dány nebo je možné je dopočítat. A toho využívají i aplikace pracující s PE soubory. Základní pravidlo je: Nesnaž se všechno přidávat na bajt přesně. Je to možné, ale pak je celý proces zpracování mnohem náročnější. Dobrým pravidlem je přičítat aspoň 400 bajtů (ještě lépe přímo velikost FileAlignemnt ze struktury IMAGE_OPTIONAL_HEADER, kde tato zarovnávací hodnota mívá zpravidla hodnotu 200h -> 512d) k velikosti vkládaného kódu. Vyhnete se tak většině problémů

Pokud chceme zvětšit poslední sekci, musíme si nejprve najít hlavičkovou strukturu této sekce a upravit její hodnoty. Konkrétně musíme upravit hodnotu SizeOfRawData o velikost přidávaného kódu a nově vzniklou hodnotu zarovnat na násobek hodnoty FileAlignment. Na RVA původní VirtualSize budeme následně kopírovat náš nový kód. VirtualSize se změní o velikost přidávaného kódu zarovnanou na hodnotu FileAlignment. Hodnota Characteristics musí obsahovat příznak EXECUTABLE, jinak vykonávání kódu skončí chybou typu ACCESS VIOLATION. V hlavičce IMAGE_OPTIONAL_HEADER změníme hodnotu AddressOfEntryPoint na hodnotu původní hodnoty VirtualSize v součtu s hodnotou VirtualAddress poslední sekce. Timto dáváme loaderu najevo, že chceme, aby se aplikace spouštěla (měla vstupní bod) na adrese našeho kódu. SizeOfImage se změnou velikosti poslední sekce taktéž změnila, takze pro získání nové hodnoty musíme sečíst VirtualAddress a novou hodnotu VirtualSize poslendí sekce. Tím by měly být všechny podstatné změny aplikovány a měla by zůstat zachována funkčnost souboru.

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. Prakticky by stačilo pouze přičíst velikost přidávaného kódu. Proč se ale nerozšoupnout a ušetřit si čas s odlaďováním přesných výpočtů přidávaných velikostí? 🙂 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. Upravíme výše zmíněné hodnoty a nakopírujeme náš kód na původní adresu VirtualSize poslední sekce. Náš kód musí na konci obsahovat skok zpět na původní hodnotu AddressOfEntryPoint plus ImageBase (obě ze struktury IMAGE_OPTIONAL_HEADER). Tím dojde po zpracování našeho kódu k přesměrování vykonávání kódu na původní a aplikace tak bude fungovat zcela normálně.

Ukázkový kód
Ukázkový kód je záměrně rozdělen na dvě části tak, aby bylo zřejmé, jak celý proces kontroly probíhá. 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:

PUSH OEP ;OldEntryPoint Address
RET

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 = 500;
  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;
 
  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){
      dwFileSize += Align(dwCodeSize, dwFileAlign);
      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;
          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)));
 
          pLastSection->SizeOfRawData = Align(pLastSection->SizeOfRawData + Align(dwCodeSize, dwFileAlign), dwFileAlign);
          strcpy((char *)((DWORD)pDosHeader + pLastSection->PointerToRawData + pLastSection->Misc.VirtualSize), "\x68");
          pRet = (PDWORD)((DWORD)pDosHeader + pLastSection->PointerToRawData + pLastSection->Misc.VirtualSize + 1);
          dwOEP = pNtHeaders->OptionalHeader.AddressOfEntryPoint;
          *pRet = pNtHeaders->OptionalHeader.ImageBase + dwOEP;
          *((PBYTE)pRet + 4) = 0xC3;
          pNtHeaders->OptionalHeader.AddressOfEntryPoint = pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize;
 
          pLastSection->Misc.VirtualSize += Align(dwCodeSize, dwFileAlign);
          pLastSection->Characteristics |= IMAGE_SCN_MEM_EXECUTE;
 
          pNtHeaders->OptionalHeader.SizeOfImage = pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize;
        }
      }
 
    }
 
    CloseHandle(hFile);
  }
 
  MessageBoxA(NULL, "Done!", "Done!", MB_OK);
 
  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 *