In Windows 9x, NT, 2000, all executable files are a new file format portable executable file format based on Microsoft design, that is, PE format. There are some time, we need to modify these executables, the following text attempts to describe the format of the PE file and modifications to PE format files. 1, PE file frame composition DOS MZ Header DOS Stub PE Header Section Table Section 1 Section 2 Section N
The table is the overall level distribution of the PE file structure. All PE files (even 32-bit DLLs) must start with a simple DOS MZ Header, "MZ Signs" in DOS under DOS at offset 0, with it, once the program is executed under DOS, DOS It can identify this is an effective actuator and then runs closely with the DOS STUB after MZ HEADER. DOS Stub is actually a valid EXE. In an operating system that does not support PE file format, it will simply display an error prompt, similar to string "this program cannot run in dos mode" or programmer can implement according to your intent Complete DOS code. Usually DOS Stub is automatically generated by the assembler / compiler, not very large for our use, it simply calls the interrupt 21h service 9 to display the string "this Program Cannot Run in dos mode". The DOS Stub is PE Header. PE Header is an abbreviation for the PE-related structure image_nt_headers, which contains many important domains used in the PE loader. When executed in the operating system that supports the PE file structure, the PE loader finds the start offset from the DOS MZ HEADER offset 3ch. Therefore, the DOS STUB is jumped directly to the real file header PE Header.
The true content of the PE file is divided into a block, called Sections (section). Each section is a data with common attributes, such as ".text" section, then, what is the content of each section? In fact, PE format files put the contents of the same properties into the same section, without having to care about ".text", ". Data" named, naming is just to facilitate identification, all, if we are formatted to PE format The file is modified. Theoretically, it can be written to any festival and adjust the properties of this section. PE Header Next Array Structure Section Table. Each structure contains the attributes, file offset, virtual offset, and the like of the corresponding section. If there are 5 festivals in the PE file, there are 5 members in this structure.
The above is the physical distribution of the PE file format. He summarizes the main steps of the load of a PE file: 1. The PE file is executed, the PE loader checks the PE Header offset in the DOS MZ HEADER. If you find it, you jump to the pe header. 2, the PE loader checks the effectiveness of the Pe header. If it is effective, jump to the tail of Pe Header. 3, keeping with Pe Header is a knot. The PE loader reads the section information, and uses the file mapping method to map these sections to the memory, and pay the feature specified in the section table. 4. When the PE file is mapped into memory, the PE loader will process the Logical part similar to the Import Table in the PE file. The above steps are the results of some seniors analysis. 2, PE Full Overview We can find the definition of the PE file header in Winnt.h: type_Headers {DWORD SIGNATURE; // PE file header mark: "PE / 0/0". In the DOS header start address offset of the start point to the premises 3CH IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader // PE file information of physical distribution; // PE file information distribution logic} IMAGE_NT_HEADERS32, * PIMAGE_NT_HEADERS32; typedef struct _IMAGE_FILE_HEADER {WORD Machine; // This file runs the CPU required, for the Intel platform is the number of days DWORD TIMEDATAMP; // file creation date and time DWORD POINTOSYMBOLTABLE; / / The symbols are used to debug DWORD NUMBEROFSYMBOLS; // Symbol table Number Word SizeOfoptionalHeader; // OptionalHeader Size Word Characteristics; // File Information Tag, distinguishing file is EXE or DLL} image_file_header, * pimage_file_header;
typedef struct _IMAGE_OPTIONAL_HEADER {WORD Magic; // Flag Word (always 010bh) BYTE MajorLinkerVersion; // connector version number BYTE MinorLinkerVersion; // DWORD SizeOfCode; // code segment size DWORD SizeOfInitializedData; // initialized data block size DWORD SizeOfUninitializedData // Not initialized the data block size DWord AddressOfEntryPoint; // PE loader The RVA of the first instruction of the PE file is prepared, to change the entire execution process, you can specify this value to the new RVA, so new RVA The instructions are first performed. (Many articles have an introduction of RVA, please go to know) DWORD baseofcode; // code segment start RVA DWORD baseofData; // Data segment start RVA DWORD ImageBase; // PE file load address DWORD sectionalignment; // block Dword FileAlignment; // file block alignment WORD MajorOperatingSystemVersion; // required operating system version number WORD MinorOperatingSystemVersion; // WORD MajorImageVersion; // custom version WORD MinorImageVersion; // WORD MajorSubsystemVersion; // win32 subsystem-version. If the PE file is a Word MinorsubsystemVersion designed for Win32; / / The subsystem version must be 4.0 otherwise the dialog does not have 3-dimensional sense DWORD WIN32VERSIONVALUE; // Reserved DWORD SIZEOFIMAGE; // The size of the entire PE image in the memory DWORD SIZEOFHERS; / / The size of the head segment table DWORD checksum; // check and Word subsystem; // NT is used to identify which subsystem of the PE file belongs to Word DllCharacteristics; // dword sizeofstackReServe; // DWORD SIZEOFSTACKCOMMIT; // DWORD SizeOfHeapReserve; // DWORD SizeOfHeapCommit; // DWORD LoaderFlags; // DWORD NumberOfRvaAndSizes; // IMAGE_DATA_DIRECTORY DataDirectory [IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // IMAGE_DATA_DIRECTORY array of structures. Each structure is given a RVA important data structures, such as the introduction of the address tables, etc.} IMAGE_OPTIONAL_HEADER32, * PIMAGE_OPTIONAL_HEADER32; typedef struct _IMAGE_DATA_DIRECTORY {DWORD VirtualAddress; // table address RVA DWORD Size; // Size} IMAGE_DATA_DIRECTORY, * PIMAGE_DATA_DIRECTORY;
After the PE file head is a quotation, in Winnt.h, the TypedEf struct _Image_section_Header {byte name [//// segment table name, such as ".text" union {dWord PhysicalAddress; // physical address dword virtualsize; // Real length} misc; dword virtualaddress; // rva dword sizeofrawdata; // physical length DWORD POINTORAWDATA; / / T1 Based on file offset DWORD POINTORELOCATIONS; // Relocated offset DWORD POINTERTOLINENUMBERS; // line number Move Word NumberOfrelocations; // Relocate Number Word Numberoflinenumbers; // Bax Table Number DWord Characteristics; // Section Attribute} Image_SECTION_HEADER, * PIMAGE_SECTION_HEADER; The above structure is in Winnt.h's definition of PE file headers, how to With C / C , the PE executable operation is used to use all of the above structures, which describes the structure of the PE file header.
3, modify the PE executive Now let's write a code to the executable file in any PE format, the code is as follows: - Test.asm - .386p .Model flat, stdcall Option Casemap: None
Include /masm32/include/windows.inc include /masm32/include/user32.inc includelib /masm32/lib/user32.lib
.code
Start: Invoke MessageBoxa, 0,0,0, MB_ICONITIONFORMATION or MB_OK RET End START
The above code shows only one Messagebox box, and the binary code is obtained after compiling: unsigned char WriteLine [18] = {0x6a, 0x40, 0x6a, 0x0, 0x6a, 0x0, 0x6a, 0x0, 0xe8, 0x01, 0x0,0x0,0x0,0 xe9 0x0, 0x0, 0x0, 0x0};
Ok, let's take a look at the code that. Now use TDUMP.EXE to display a PE format to execute file information, you can find the following description: Object Table: # Name Virtsize RVA Physize Phys Off Flags - ---------------- -------------- -------- -------- 01 .Text 0000ccc0 00001000 0000CE00 000000600 60000020 [CER] 02 .DATA 00004628 0000E000 00002C00 0000D400 C0000040 [Irw] 03.RSRC 000003C8 00013 000400 00010000 40000040 [IR]
Key to Section Flags: C - Contains Code E - EXECUTABLE I - Contains Initialized Data R - Readable W - WRITEABLE
Above this file has three sections and each segment information, in fact, our code can write to any piece, here I choose ".text" segment.
Use the following code to get the header information of a PE format executable file:
//Writepe.cpp
#include
DWORD ENTRYADDRESS; DWORD EntryWrite; DWORD NEWENTRYADDRESS; DWORD NEWENTRYADDRESS; DWORD CODEEOFFSET; DWORD PEADDRESS; DWORD FLAGADDRESS; DWORD FLAGADI
DWORD VIRTSIZE; DWORD Physize; DWORD MESSAGEBOXAADDRESS;
void * basepointer;; FILETIME * Createtime; FILETIME * Accesstime; FILETIME * Writetime; Createtime = new FILETIME; Accesstime = new FILETIME; Writetime = new FILETIME main (int argc, char * * argv) {HANDLE hFile, hMapping int;
if ((hFile = CreateFile (argv [1], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE) // Open the modified files {puts ( "(could not open)" ); return exit_failure;} if (! getfiletime (hardfiletime) {printf ("/ nerror getfiletime:% d / n", getLastError ());} // Get creation, modification to modify files Time IF (! (Hmapping = cretefilemapping (hfile, 0, page_readonly | sec_commit, 0, 0, 0))) {PUTS ("(Mapping Failed)); CloseHandle (HFILE); Return EXIT_FAILURE;} IF (! BasepoInter = MapViewoffile (hmapping, file_map_read, 0, 0, 0))) {PUTS ("(" ("(View failed)); CloseHandle (HMApping); CloseHandle (HFILE); Return EXIT_FAILURE;} // Put the file header image Baseointer CloseHandle (Hmapping); CloseHandle (HFILE); MAP_EXE (Basepointer); // Get associated address unmapViewoffile (basepointer); PrintAddress (); printf ("/ n / n"); if (Space <50) {printf (" / N gap is too small, data cannot be written ./N ");} else {writefile (); // write file}
if ((hFile = CreateFile (argv [1], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE) {puts ( "(could not open)"); return EXIT_FAILURE;} if (! SetFiletime, Accesstime, WriteTime) {Printf ("Error Settime:% D / N", getLastError ());} // Restore DELETE CREATETIME for the establishment of files after the modification; delete Accesstime; Delete WriteTime CloseHandle (HFILE); RETURN 0;}
void map_exe (const void * base) {IMAGE_DOS_HEADER * dos_head; dos_head = (IMAGE_DOS_HEADER *) base; #include
IF (DOS_HEAD-> E_MAGIC! = Image_dos_signature) {PUTS ("Unknown Type of File"); Return;}
Peheader * Header; Header = (PEHAR *) DOS_HEAD DOS_HEAD-> E_LFANEW); // Get PE file header if (isbadreadptr (header, sizeof (* header)) {PUTS ("(No Pe Header, Probably dos executable) ")") ")"); Return;
DWORD MODS; CHAR TMPSTR [4] = {0}; DWORD TMPADDRESS; DWORD TMPADDRESS1;
IF (strstr (const char *) header-> section_header [0] .name, ". Text")! = null) {virtsize = header-> section_header [0] .misc.virtualsize; // This paragraph real length Physaddress = header-> section_header [0] .pointertorawdata; // This segment is physically shifted physize = header-> section_header [0] .sizeofrawData; // This section of physical length peaddress = dos_head-> e_lfanew; // Get PE The start offset of the file header
peHeader peH; tmpaddress = (unsigned long) & peH; // offset is obtained tmpaddress1 structure = (unsigned long) & (peH.section_header [0] .Characteristics); // get a variable offset flagaddress = tmpaddress1-tmpaddress 2 ; // Get the relative offset of attributes FLAGS = 0x8000; // In general, ". Text" segment is unreadable, if we want to write data to this paragraph to change its properties, actually this program is not Write the data ".text" segment, so do not need to be changed, but if you realize complex features, you must need data, there is definitely to change this value, Space = physize-virtsize; // Get available space of code segment, use To determine that we can write our code // Use this physical length to reduce the real length of this paragraph, you can get prograv = header-> opt_head.imagebase; // Get the program's loading address, general to 400000 codeoffset = header -> opt_head.baseofcode-physaddress; // Get code offset, starting RVA with code segment to subtract this segment of physical offset // should be a relatively offset address, the calculation formula is: // Code write address codeoffset
Entrywrite = header-> section_header [0] .pointertorawData header-> section_Header [0] .Misc.virtualsize; // code write physical offset MODS = entrywrite% 16; // Align Boundary IF (MODS! = 0) {entryWrite = (16-MODS);} OldEntryaddress = header-> opt_head.addressofentryPoint; // Save the old program entry address newryaddress = entrywrite codeoffset; // Calculate new program entry address return;}
void printaddress () {HINSTANCE gLibMsg = NULL; DWORD funaddress; gLibMsg = LoadLibrary ( "user32.dll"); funaddress = (DWORD) GetProcAddress (gLibMsg, "MessageBoxA"); MessageBoxAadaddress = funaddress; gLibAMsg = LoadLibrary ( "kernel32.dll "); // Get the address of MessageBox in memory so that we use}
Void writefile () {int Ret; long retf; dword address; int TMP; unsigned char waddress [4] = {0};
Ret = _open (filename, _o_rdwr | _o_creat | _o_binary, _s_iread | _s_iwrite); if (! Ret) {Printf ("Error Open / N"); Return;}
RETF = _lseek (RET, (long) peaddress 40, seek_set); // The entry address of the program starts at 40 at the PE file header (RETF == - 1) {Printf ("ERROR SEEK / N"); Return } address = newntryaddress; TMP = address >> 24; Waddress [3] = TMP; TMP = address << 8; TMP = TMP >> 24; Waddress [2] = TMP; TMP = address << 16; tmp = TMP >> 24; Waddress [1] = TMP; TMP = address << 24; TMP = TMP >> 24; WADDRESS [0] = TMP; RETF = _Write (RET, WADDRESS, 4); // Put new entry Address writes IF (RETF == - 1) {Printf ("Error Write:% D / N", getLastError ()); return;} RETF = _LSeek (RET, (long) Entrywrite, Seek_set); if (Retf == - 1) {Printf ("ERROR SEEK / N"); return;} RETF = _Write (RET, WRITELINE, 18); if (RETF == - 1) {Printf ("Error Write:% D / N" GetLastError ()); return;} // write WriteLine to our calculated space
RETF = _LSeek 9, seek_set); // Change the MessageBox function address, its binary code in WriteLine [10] f (RETF == - 1) {Printf ("Error SEEK / N" Return;}
Address = messageBoxaaddress- (Program NewEntryAddress 9 4); // Reconscent the address of the MessageBox function, the original address of the Messagebox function minus the load address of the program plus the new portal address plus 9 (its binary code relative offset) Add 4 (address length) TMP = address >> 24; Waddress [3] = TMP; TMP = address << 8; TMP = TMP >> 24; WADDRESS [2] = TMP; TMP = address << 16; TMP = TMP >> 24; WADDRESS [1] = TMP; TMP = address << 24; TMP = TMP >> 24; Waddress [0] = TMP; RETF = _Write (RET, WADDRESS, 4); // Write Recalculate MessageBox address if (RETF == - 1) {Printf ("Error Write:% D / N", getLastError ()); return;}
RETF = _LSeek (RET, (long) entrywrite 14, seek_set); // Chang change the return address, return to the original program entry address with JPM, other binary code at WriteLine [15] f (RETF == - 1) {Printf ("ERROR SEEK / N"); return; address = 0- (NewEntryAddress-OldEntryAddress 4 15); // Returning the address calculation method is the new entry address minus the old entrance address plus 4 (address length) Adding 15 (binary code relative offset) Take the anti-TMP = Address >> 24; Waddress [3] = TMP; TMP = address << 8; TMP = TMP >> 24; WADDRESS [2] = TMP; TMP = Address << 16; TMP = TMP >> 24; WADDRESS [1] = TMP; TMP = address << 24; TMP = TMP >> 24; Waddress [0] = TMP; RETF = _Write (RET, WADDRESS, 4); // Write return address if (RETF == - 1) {Printf ("Error Write:% D / N", getLastError ()); return;}
_close (re); printf ("/ nall done ... / n"); return;}
// end
Since all addresses use the RVA address in the PE format file, some function calls and return addresses must be calculated, and the above is my experience in practice, if you have a better way, sincere I hope you can tell me.
If there is an error, please let me not mislead the person who read this article. Write a chaotic, please forgive me.