Uruchomienie programu wewnątrz programu, czyli piszemy PE loader.

Artykuł pochodzi ze strony http:\\grzonu.pl

Do zrozumienia artykułu wymagana jest znajomość budowy plików PE z którą możemy zapoznać się na MSDN lub po prostu googlując.
Nie będę jej tutaj omawiał szczegółowo bo temu tematowi można poświęcić cały dodatkowy artykuł. A jest to omówione w bardzo wielu miejscach(mogę polecić ze swej strony video arty
Gynvaela Coldwinda (jeśli źle odmieniam to poprawcie mnie))

Zacznijmy od tego czym jest PE loader.
Jest to program który ładuje inny program. Kiedy klikamy 2x na aplikacji to właśnie PE loader włącza naszą aplikacje.

Używane terminy:
Image base- Adres pod który ładowany jest nasz program.
Entry point – Adres w którym zaczyna się wykonanie naszego kodu.
VA – Virtual Address ;
RVA – Relative Virtual Address ; Jest to adres względem jakiegos innego adresu(zwykle Image base)
Offset / RAW – Miejsce w pliku.

Wszystkie te adresy przeliczymy przy pomocy klasy dołączonej do arta.(klasa.cpp , klasa.h)

Do czego mozemy tego użyc:
-do napisania packera/cryptera
-do aplikacji monitorujacej plik
-wiele innych (zalezy od pomoyslowosci)



Teraz ustalimy co musimy zrobić aby poprawnie załadować taki program.

-Znaleść Image base i wielkość potrzebna do zaalokowania.
-Zaalokowac potrzebna pamiec (VirtualAlloc)
-Skopiować odpowiednie nagłówki w odpowiednie miejsca
-Skopiować odpowiednie sekcje w odpowiednie miejsca
-Zrealizować importy
-Zmienić Image Base w PEB (Process Environment Block) na taki jaki potrzebuje aplikacja
-Wykonać skok do Entry Point

Uwaga:
Pamiętajmy żeby skompilowac nasz program z takim Image base żeby nie pokrywał się z uruchamiana aplikacja (ja uzywam 0x10000000)
w VC++ 2008 Project -> [project name] properties -> linker -> advanced ->base addres


Tyle teorii , przejdźmy do praktyki.

Na początku rezerwujemy pamieć w adresach 0x003C0000 i 0x01000000 gdyż jeśli tego nie zrobie to te adresy zostana zajęte przez pamiec ladowanych bibliotek
a adresy 0x00400000 i 0x01000000 to 2 najczęstrze Image Base.

Kod:
VirtualAlloc((LPVOID)0x003C0000,0x00540000,MEM_RESERVE,PAGE_EXECUTE_READWRITE);
VirtualAlloc((LPVOID)0x01000000,0x00540000,MEM_RESERVE,PAGE_EXECUTE_READWRITE);
Teraz otwórzmy plik który chcemy uruchomić aby pobrać najważniejsze dla nas informacje o tym pliku.
Kod:
PE_file PE;
PE.open(argv[1],0); //pierwszy parametr to nazwa piku który chcemy odpalic
Pobierzmy odpowiednie naglowki

Kod:
IMAGE_DOS_HEADER* dos=PE.GetDosHeader();**
IMAGE_FILE_HEADER* fhead=PE.GetFileHeader();
IMAGE_OPTIONAL_HEADER* opt_head=PE.GetOptionalHeader();
IMAGE_SECTION_HEADER* sect_head=PE.GetSectHeader(0);
Teraz możemy juz zwolnic zarezerwowana wczesniej pamiec.

Kod:
VirtualFree((LPVOID)0x003C0000,0,MEM_RELEASE);
VirtualFree((LPVOID)0x01000000,0,MEM_RELEASE);
I zaalokowac pamiec potrzebna na uruchomienie programu
Kod:
DWORD memory=(DWORD)VirtualAlloc((LPVOID)opt_head->ImageBase,
opt_head->SizeOfImage,MEM_RESERVE|MEM_COMMIT,PAGE_EXECUTE_READWRITE);

if(memory==0)
{
printf("Allocation error!!!");
return 1;
}

DWORD head_size=(DWORD)(dos->e_lfanew+sizeof(fhead)+4+
fhead->SizeOfOptionalHeader);****//wielkosc naglowkow
memcpy((LPVOID)opt_head->ImageBase,PE.buf,head_size);//kopiujemy naglowki

int i=0;
while(i<PE.Get_Section_Count())********//kopiujemy naglowki sekcji
{
memcpy((LPVOID)(opt_head->ImageBase+dos->e_lfanew+sizeof(fhead)+fhead->SizeOfOptionalHeader+(sizeof(IMAGE_SECTION_HEADER)*i)),PE.GetSectHeader(i),sizeof(IMAGE_SECTION_HEADER));
i++;
}

/*kopiujemy kod i inne potrzebne rzeczy na ich miejsce*/

i=0;
while(i<PE.Get_Section_Count())**
{
sect_head=PE.GetSectHeader(i);
memcpy((LPVOID)(opt_head->ImageBase+sect_head->VirtualAddress),(LPVOID)(PE.buf+sect_head->PointerToRawData),sect_head->SizeOfRawData);
i++;
}

Teraz kilka słów na temat IAT.
Musimy go przwrócic aby program zadziałał.
Każdy adres funkcji musimy znalesc i zapisac w odpowiednie miejsce.
Niektóre funkcje są zapisane tak ze możemy je odnalesc po nazwie(LoadLibrary -> GetProcAddress) , lub po numerze Ordinal i w tym drugim przypadku musimy pogrzebać w EAT danej biblioteki aby znalesc ten adres.

Do grzebania w EAT wykorzystamy poniższa funkcje:
Kod:
DWORD pe_export_info(void *startp,DWORD cur_ord,char* name_buf)
{
****IMAGE_EXPORT_DIRECTORY *export;
****IMAGE_DOS_HEADER *idh;
****IMAGE_NT_HEADERS *pehead;
****ptrdiff_t start;
****char **names;
****int nfunc, ordbase, i;
****short *ords;
****void **funcs;
****
****start = (ptrdiff_t)startp;
****idh = (IMAGE_DOS_HEADER *)startp;
****pehead = (IMAGE_NT_HEADERS *)(start + idh->e_lfanew);
****export = (IMAGE_EXPORT_DIRECTORY *)
********(start + pehead->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
****
****nfunc = export->NumberOfNames;
****ordbase = export->Base;
****names = (char **)(start + export->AddressOfNames);
****ords = (short *)(start + export->AddressOfNameOrdinals);
****funcs = (void **)(start + export->AddressOfFunctions);
****
****
****for(i = 0; i < nfunc; i++)
****{
********if((ords[i]+ordbase) == cur_ord)
********{
memcpy(name_buf,(char*)(names[i] + start),256);
return (DWORD)((DWORD)funcs[ords[i]]+(DWORD)startp);
********}****
****}
****
return 0;****
}
jest to zmodyfikowana wersja funkcji którą możemy znaleść w google

Dobrze przystąpmy do zrealizowania IAT:

Kod:
if(opt_head->DataDirectory[1].VirtualAddress!=0) /*sprawdzamy czy program posiada IAT*/
{
IMAGE_IMPORT_DESCRIPTOR* import=(IMAGE_IMPORT_DESCRIPTOR*)(opt_head->ImageBase+opt_head->DataDirectory[1].VirtualAddress);

int nDLL=0;
i=0;
while(1) //sprawdzamy ilosc potrzebnych dll`i 
{
import=(IMAGE_IMPORT_DESCRIPTOR*)(opt_head->ImageBase+opt_head->DataDirectory[1].VirtualAddress+(i*sizeof(IMAGE_IMPORT_DESCRIPTOR)));
i++;
if(import->Name==0)
{
break;
}

nDLL++;
}
Teraz przejdzmy do wlasciwego realizowania IAT:
Uwaga:
Jeśli pole ITD->u1.AddressOfData >= 0x80000000 znaczy to ze musimy realizowac tą funkcje po Ordinalach.
Kod:
int x=0;
char* szFunctionName2;
std::string dll_name;
DWORD* IATentryaddress;
IMAGE_THUNK_DATA* ITD;
i=0;



while(i<nDLL)
{
import=(IMAGE_IMPORT_DESCRIPTOR*)(opt_head->ImageBase+opt_head->DataDirectory[1].VirtualAddress+(i*sizeof(IMAGE_IMPORT_DESCRIPTOR)));
ITD =(IMAGE_THUNK_DATA*)((BYTE*)opt_head->ImageBase+ import->OriginalFirstThunk);
x=0;

while(ITD->u1.Function)
{
if(!(ITD->u1.AddressOfData & 0x80000000))**// to co mowilem w uwaga :D
{
****szFunctionName2 =**(char*)((BYTE*)opt_head->ImageBase+ (DWORD)ITD->u1.AddressOfData+2);
******
IATentryaddress = (DWORD*)((BYTE*)opt_head->ImageBase +import->FirstThunk)+x;
************** 
dll_name=(char*)(opt_head->ImageBase+import->Name);


if(LoadLibrary(dll_name.c_str())==0)
{
printf("\n\nBrak biblioteki (%s)\n\n",dll_name.c_str());
ExitProcess(0);
}


*IATentryaddress = (DWORD)GetProcAddress(LoadLibrary((char*)(opt_head->ImageBase+import->Name)),szFunctionName2);
**
}//ordinal
else
{
IATentryaddress = (DWORD*)((BYTE*)opt_head->ImageBase +import->FirstThunk)+x;
DWORD c_ord=ITD->u1.AddressOfData-0x80000000; //nasz ordinal
dll_name=(char*)(opt_head->ImageBase+import->Name);
HMODULE hMod=LoadLibrary(dll_name.c_str());
char func_name[256];
*IATentryaddress=(DWORD)pe_export_info((void*)hMod,c_ord,func_name);
}
ITD =(IMAGE_THUNK_DATA*)((BYTE*)ITD+sizeof(ITD));
**x++;
}
i++;
}
}
Uff. Najtrudniejsze w zasadzie za nami teraz pozostaje nam tylko zmienić
Image base i skoczyć do kodu.

Image base przechowywany jest w strukturze PEB jej adres jest zapisany w struktutrze TEB.
A adres struktury TEB jest w rejestrze FS
Kod:
DWORD IB_hook=PE.Get_IB();

__asm mov eax, fs:[0x30]**// do eax wrzucamy adres PEB
__asm add eax, 8**********//dodajemy do eax 8 (eax = adres Image base)
__asm mov ebx, IB_hook****//do ebx, kopiujemy Image Base programu
__asm mov [eax], ebx ****** //podmieniamy Image Base
No i wykonujemy skok:
Kod:
DWORD EP=PE.Get_EP()+PE.Get_IB();

__asm call EP

return 0;

Nie jestem pewien dzialania tego na systemach innych niz Windows XP

To tyle mam nadzieje że choć troche ułatwiłem zrozumienie „jak to się dzieje że program się włącza”.


Do artykułu dostępne sa materiały w postaci kodu klas i przykładowego kodu.

KODY