Modify PE executable files (1)

Source: Internet
Author: User
Tags filetime
In Windows 9x, NT, and 2000, All executable files are based on a new file format portable Executable File designed by Microsoft. Format(Portable execution body), that is, PE format. In some cases, we need to modify these executable files. The following text attempts to describe the PE file format and modify the PE format file in detail.
1. PE file framework
Dos mz Header
Dos Stub
PE Header
Section Table
Section 1
Section 2
Section...
Section N
The above table shows the overall hierarchical distribution of the PE file structure. All PE files (or even 32-bit DLLs) must start with a simple dos MZ header. The offset 0 contains the "MZ mark" of the executable file under DOS, once the program is executed in DOS, DOS can identify this as a valid execution body and then run the Dos Stub following the MZ header. Dos Stub is actually a valid exe. In the * operating system that does not support the PE file format, it will simply display an error message, similar to the string "this program cannot run in DOS mode", or programmers can implement the complete dos code according to their own intentions. Generally, Dos Stub is automatically generated by the assembler/compiler, which is not very useful to us. It simply calls to interrupt 21h Service 9 to display the string "this program cannot run in DOS mode ".
Followed by the Dos Stub is the PE Header. The PE Header is short for the image_nt_headers of the pe-related structure. It contains important fields used by many pe loaders. When an executable file is executed in the * operating system that supports the PE file structure, the PE Loader finds the start offset of the PE Header from the offset 3ch of the dos mz header. Therefore, the real file header PE Header is located directly without Dos Stub.
The real content of a PE file is divided into blocks, which are called sections ). Each section is a piece of data with common attributes, such as the ". Text" section. What is the content of each section? In fact, files in PE format put the content with the same attributes into the same section, without having to worry about ". text ",". the name of data is only for easy identification. If we modify the file in PE format, we can theoretically write it into any section, and adjust the attributes of this section.
The following Array Structure Section Table (Section Table) of PE Header ). Each structure contains the attributes, file offset, and virtual offset of the corresponding section. If the PE file contains five sections, there are five members in the array.
The above is the physical distribution of the PE file format. The following describes the main steps for loading a PE file:
1. the PE file is executed. The PE Loader checks the PE Header offset in the dos mz header. If it is found, it will jump to the PE Header.
2. the PE Loader checks the validity of the PE Header. If valid, it will jump to the end of the PE Header.
3. The section table that follows the PE Header is followed. The PE Loader reads the section information, maps these sections to the memory using the file ing method, and pays the section attributes specified in the preceding section table.
4. After the PE file is mapped to the memory, the PE Loader will process the logic section similar to the import table in the PE file.
The above steps are a brief description of the analysis results of some predecessors.
2. Overview of PE file headers
We can find the PE file header definition in the WINNT. h file:
Typedef struct _ image_nt_headers {
DWORD signature;
// PE Header flag: "PE/0/0 ". Start at the address pointed to by the START dos header offset 3ch
Image_file_header fileheader; // physical distribution of PE files
Image_optional_header32 optionalheader; // information about the Logical Distribution of PE files
} Image_nt_headers32, * pimage_nt_headers32;

Typedef struct _ image_file_header {
Word machine; //CPUThe Intel Platform is 14ch
Word numberofsections; // number of file sections
DWORD timedatestamp; // file creation date and time
DWORD pointertosymboltable; // used for debugging
DWORD numberofsymbols; // Number of symbols in the symbol table
Word sizeofoptionalheader; // size of the optionalheader Structure
Word characteristics; // mark the file information to identify whether the file is exe or DLL
} Image_file_header, * pimage_file_header;

Typedef struct _ image_optional_header {
Word magic; // flag (always 010bh)
Byte majorlinkerversion; // connector version
Byte minorlinkerversion ;//
DWORD sizeofcode; // code segment size
DWORD sizeofinitializeddata; // size of the initialized data block
DWORD sizeofuninitializeddata; // uninitialized data block size
DWORD addressofentrypoint; // RVA of the first instruction of the PE file to be run by the PE Loader. To change the entire execution process, you can specify this value to the new RVA, in this way, the commands at the new RVA are executed first. (For details about RVA, refer to many articles)
DWORD baseofcode; // code segment start RVA
DWORD baseofdata; // The starting RVA of the Data Segment
DWORD imagebase; // address for loading PE files
DWORD sectionalignment; // block alignment
DWORD filealignment; // file block alignment
Word majoroperatingsystemversion; // The System Version Number * required
Word minoroperatingsystemversion ;//
Word majorimageversion; // The custom version number.
Word minorimageversion ;//
Word majorsubsystemversion; // Win32 subsystem version. If the PE file is specially designed for Win32
Word minorsubsystemversion; // This subsystem version must be 4.0. Otherwise, the dialog box will not have a three-dimensional stereoscopic effect.
DWORD win32versionvalue; // Reserved
DWORD sizeofimage; // size of the entire PE image in memory
DWORD sizeofheaders; // size of all headers + section tables
DWORD checksum; // checksum
Word subsystem; // NT is used to identify the subsystem of the PE file.
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 structure array. Each structure provides an important data structure RVA, such as the introduction of address tables.
} Image_optional_header32, * pimage_optional_header32;

Typedef struct _ image_data_directory {
DWORD virtualaddress; // The RVA address of the table
DWORD size; // size
} Image_data_directory, * pimage_data_directory;

The PE file header is followed by a section table, which is defined in winnt. h as follows:
Typedef struct _ image_section_header {
Byte name [image_sizeof_short_name]; // The Name Of The section table, such as ". Text"
Union {
DWORD physicaladdress; // physical address
DWORD virtualsize; // the actual length.
} MISC;
DWORD virtualaddress; // RVA
DWORD sizeofrawdata; // physical length
DWORD pointertorawdata; // the offset of the section based on the file
DWORD pointertorelocations; // relocation offset
DWORD pointertolinenumbers; // offset of the row number table
Word numberofrelocations; // Number of relocation items
Word numberoflinenumbers; // Number of row number tables
DWORD characteristics; // section attributes
} Image_section_header, * pimage_section_header;

The above structure is in winnt. in H, we need to use all the above structures to define PE file headers and how to use C/C ++ for PE executable files, it describes in detail the structure of the PE file header.

3. Modify the PE Executable File
Now let's write a piece of code into any executable file in PE format. The Code is as follows:
-- Test. ASM --
. 386 P
. 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, mb_iconinformation or mb_ OK
RET
End start

The above Code only shows a MessageBox box. After compilation, the binary code is as follows:
Unsigned char writeline [18] = {
0x6a, 0x40, 0x6a, 0x0, 0x6a, 0x0, 0x6a, 0x0, 0xe8, 0x01,0x0 0x0 0x0, 0xe9, 0x0 0x0 0x0 0x0
};

Okay. Now let's take a look at how to write the code. When tdump.exe is used to display Executable File Information in PE format, the following description can be found:
Object table:
# Name should size RVA physsize phys off flags
--------------------------------------------------
01. Text collate ccc0 00001000 0000ce00 00000600 60000020 [CER]
02. Data 00004628 10000e000 00002c00 0000d400 c0000040 [irw]
03. rsrc 000003c8 00013000 00000400 00010000 40000040 [ir]

Key to section flags:
C-contains code
E-executable
I-contains initialized data
R-readable
W-writeable

As described above, this file contains three segments and the information of each segment. In fact, our code can write any segment. Here I select the ". Text" segment.

Use the following code to obtain the header information of an executable file in PE format:

// Writepe. cpp

# Include
# Include
# Include
# Include
# Include
# Include

Unsigned char writeline [18] = {
0x6a, 0x40, 0x6a, 0x0, 0x6a, 0x0, 0x6a, 0x0, 0xe8, 0x01,0x0 0x0 0x0, 0xe9, 0x0 0x0 0x0 0x0
};

DWORD space;
DWORD entryaddress;
DWORD entrywrite;
DWORD prograv;
DWORD oldentryaddress;
DWORD newentryaddress;
DWORD codeoffset;
DWORD peaddress;
DWORD flagaddress;
DWORD flags;

DWORD quota size;
DWORD physaddress;
DWORD physsize;
DWORD messageboxaadaddress;

Int main (INT argc, char ** argv)
{
Handle hfile, hmapping;
Void * basepointer;
Filetime * createtime;
Filetime * accessTime;
Filetime * writetime;
Createtime = new filetime;
AccessTime = new filetime;
Writetime = new filetime;

If (hfile = createfile (argv [1], generic_read | generic_write, file_1__read | file_1__write, 0, open_existing, expiration, 0) = invalid_handle_value) // open the file to be modified
{
Puts ("(cocould not open )");
Return exit_failure;
}
If ('getfiletime (hfile, createtime, accessTime, writetime ))
{
Printf ("/nerror getfiletime: % d/N", getlasterror ());
}
// Obtain the creation and modification time of the file to be modified.
If ('(hmapping = createfilemapping (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;
}
// Save the file header image to baseointer
Closehandle (hmapping );
Closehandle (hfile );
Map_exe (basepointer); // obtain the address.
Unmapviewoffile (basepointer );
Printaddress ();
Printf ("/n ");
If (space <50)
{
Printf ("/n gap is too small, data cannot be written into./N ");
}
Else
{
Writefile (); // write a file
}

If (hfile = createfile (argv [1], generic_read | generic_write, file_pai_read | file_pai_write, 0, open_existing, file_flag_sequential_scan, 0) = invalid_handle_value)
{
Puts ("(cocould not open )");
Return exit_failure;
}

If ('setfiletime (hfile, createtime, accessTime, writetime ))
{
Printf ("error settime: % d/N", getlasterror ());
}
// Restore the file creation time after modification.
Delete createtime;
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
Typedef struct pe_header_map
{
DWORD signature;
Image_file_header _ head;
Image_optional_header opt_head;
Image_section_header section_header [];
} Peheader;
# Include

If (dos_head-> e_magic '= image_dos_signature)
{
Puts ("unknown type of file ");
Return;
}

Peheader * Header;
Header = (peheader *) (char *) dos_head + dos_head-> e_lfanew); // obtain the PE File Header
If (isbadreadptr (header, sizeof (* Header ))
{
Puts ("(No PE Header, probably dos executable )");
Return;
}

Dword mod;
Char tmpstr [4] = {0 };
DWORD tmpaddress;
DWORD tmpaddress1;

If (strstr (const char *) header-> section_header [0]. Name, ". Text") '= NULL)
{
Required size = header-> section_header [0]. Misc. virtualsize;
// The actual length of this segment
Physaddress = header-> section_header [0]. pointertorawdata;
// The physical offset of this segment
Physsize = header-> section_header [0]. sizeofrawdata;
// The physical length of this segment
Peaddress = dos_head-> e_lfanew;
// Get the start offset of the PE File Header

Peheader Peh;
Tmpaddress = (unsigned long) & Peh;
// Get the structure offset
Tmpaddress1 = (unsigned long) & (Peh. section_header [0]. characteristics );
// Get the offset of the Variable
Flagaddress = tmpaddress1-tmpaddress + 2;
// Get the relative offset of the attribute
Flags = 0x8000;
// In general, ". text segments cannot be read or written. If we want to write data into this segment, we need to change its attributes. In fact, this program does not write data into this segment. text, so it does not need to be changed. But if you implement complicated functions, you must definitely need data and change the value,

Space = physsize-maximum size;
// Obtain the available space of the code segment to determine whether the code can be written to us.
// Use the physical length of this segment minus the actual length of this segment.
Prograv = header-> opt_head.imagebase;
// Obtain the Assembly address of the program, which is generally 400000
Codeoffset = header-> opt_head.BaseOfCode-physaddress;
// Get the code offset, and subtract the physical offset from the beginning RVA of the code segment
// The calculation formula for the program entry is a relative offset address. The calculation formula is:
// Code write address + codeoffset

Entrywrite = header-> section_header [0]. pointertorawdata + header-> section_header [0]. Misc. virtualsize;
// Physical offset of code writing
MoD = entrywrite % 16;
// Align the Boundary
If (MODS '= 0)
{
Entrywrite + = (16-mods );
}
Oldentryaddress = header-> opt_head.addressofentrypoint;
// Save the old program entry address
Newentryaddress = entrywrite + codeoffset;
// Calculate the 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 ");
// Obtain the address of MessageBox in the memory so that we can 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 is 40 at the beginning of the PE File Header
If (retf =-1)
{
Printf ("error seek/N ");
Return;
}
Address = newentryaddress;
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 the new entry address to the file
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 the space we calculated

Retf = _ lseek (Ret, (long) entrywrite + 9, seek_set );
// Change the MessageBox function address. Its binary code is at writeline [10 ].
If (retf =-1)
{
Printf ("error seek/N ");
Return;
}

Address = messageboxaadaddress-(prograv + newentryaddress + 9 + 4 );
// Recalculate the address of the MessageBox function. The original address of the MessageBox function minus the loading address of the program plus the new entry address plus 9 (the relative offset of its binary code) plus 4 (the 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 the recalculated MessageBox address
If (retf =-1)
{
Printf ("error write: % d/N", getlasterror ());
Return;
}

Retf = _ lseek (Ret, (long) entrywrite + 14, seek_set );
// Change the return address and use JPM to return the original program entry address. Other binary code is at writeline [15 ].
If (retf =-1)
{
Printf ("error seek/N ");
Return;
}

Address = 0-(newentryaddress-oldentryaddress + 4 + 15 );
// The return address is calculated using the new entry address minus the old entry address plus 4 (address length) and 15 (relative offset of binary code ).
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 (RET );
Printf ("/Nall done.../N ");
Return;
}

// End
Because all the addresses in files in PE format use the RVA address, some function calls and return addresses must be calculated before they can be obtained. These are my experiences in practice, if you have a better solution, I sincerely hope you can tell me.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.