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.
Teraz otwórzmy plik który chcemy uruchomić aby pobrać najważniejsze dla nas informacje o tym pliku.Kod:VirtualAlloc((LPVOID)0x003C0000,0x00540000,MEM_RESERVE,PAGE_EXECUTE_READWRITE); VirtualAlloc((LPVOID)0x01000000,0x00540000,MEM_RESERVE,PAGE_EXECUTE_READWRITE);
Pobierzmy odpowiednie naglowkiKod:PE_file PE; PE.open(argv[1],0); //pierwszy parametr to nazwa piku który chcemy odpalic
Teraz możemy juz zwolnic zarezerwowana wczesniej pamiec.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);
I zaalokowac pamiec potrzebna na uruchomienie programuKod:VirtualFree((LPVOID)0x003C0000,0,MEM_RELEASE); VirtualFree((LPVOID)0x01000000,0,MEM_RELEASE);
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:
jest to zmodyfikowana wersja funkcji którą możemy znaleść w googleKod: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;**** }
Dobrze przystąpmy do zrealizowania IAT:
Teraz przejdzmy do wlasciwego realizowania 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++; }
Uwaga:
Jeśli pole ITD->u1.AddressOfData >= 0x80000000 znaczy to ze musimy realizowac tą funkcje po Ordinalach.
Uff. Najtrudniejsze w zasadzie za nami teraz pozostaje nam tylko zmienić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++; } }
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
No i wykonujemy skok: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
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



Odpowiedź z Cytatem