What I want to achieve is to add a new section to a second PE file (target PE) and be able to access it on runtime inside the target, so I'm looking for guidance on how to overwrite the section address table after inserting the new section.
I'm loading and parsing the PE binary from an unsigned char value with a library named libpeconv, and adding the section at the EOF, but I want to know how to overwrite the section alignment and set the characteristics to read-only because there won't be any execution inside, I'm not able to do this using the library apparently, so I will need to write the bytes directly.
Also, I'm not able to add a new section in the linking step, I need to strictly parse the target PE binary in its raw format.
Appending a new section to a PE image is rather a complex process. However, it is not that hard. It consists of roughly two steps.
IMAGE_SECTION_HEADER
structure for your section to the end of Section Headers Array
(which is an array of the structures IMAGE_SECTION_HEADER
.).All PE images include these data blocks in order:
IMAGE_DOS_HEADER
"PE00"
in the string, and 0x00004550
in integer).IMAGE_FILE_HEADER
IMAGE_OPTIONAL_HEADER
The 3, 4, and 5 form the structure IMAGE_NT_HEADERS
out. And all this is followed by the IMAGE_SECTION_HEADER
array.
Section data is placed after the headers. When PE image is on disk, the first byte of each section data is aligned with the File Alignment value. When PE image is in memory (loaded by OS image loader), the first byte of each section data is aligned with the Section Alignment value.
The fields File Alignment and Section Alignment are default 0x200
and 0x1000
, respectively.
When adding your section, some important fields of Optional Header must be updated: SizeOfInitializedData
, SizeOfHeaders
, and SizeOfImage
.
SizeOfInitializedData
: Total amount of initialized data across PE Image, in bytes.
SizeOfHeaders
: Total size of all headers in PE image, aligned with File Alignment.
SizeOfImage
: This value is the virtual address of the last section + the virtual size of the last section, aligned with Section Alignment.
You can see how you can add your section and update these fields correctly in the example program. For further algorithmic details, look at the code.
PS: To manipulate a 32-bit PE file, use 32-bit compiled version of the code and for 64-bit PE file, use 64-bit compiled version.
#include <stdio.h>
#include <Windows.h>
PIMAGE_DOS_HEADER lpDosHeader;
PIMAGE_NT_HEADERS lpNtHeaders;
PIMAGE_FILE_HEADER lpFileHeader;
PIMAGE_OPTIONAL_HEADER lpOptionalHeader;
PIMAGE_SECTION_HEADER lpImageSections;
IMAGE_SECTION_HEADER ourSection;
FILE* fileStream = NULL;
char* peFileBuffer = NULL; // A temporary buffer to be used to do neccessary work.
char* fileName = NULL; // Input file name.
char* outputFileName = NULL; // Output file name.
char* sectionName = NULL; // Store the name of new section here.
char* sectionData = NULL; // And the data of new section.
int sizeofNewSectionData = 0;
int nextArg = 1; // Current index of argument to read
// Helper function we will utilize to calculate the aligned offset.
int align(int value, int alignment);
int main(int argc, char* argv[])
{
PIMAGE_SECTION_HEADER lpLastSection = NULL;
int newFileSize = 0;
// File alignment of the pe file. The default value is mostly 0x200. But for possible exceptions, we will trust the value of file itself. (e.g. 0x400)
int fileAlignmentOfPEImage = 0;
int sectionAlignmentOfPEImage = 0;
if (argc != 5)
{
printf("Wrong usage.\n");
printf("Specify arguments as follows : AddNewPESection.exe <Full path of PE File> <output file name> <name of section to append> <sample section data in plain text> (without <> signs)\n");
return -1;
}
// Get the arguments.
fileName = argv[nextArg++];
outputFileName = argv[nextArg++];
sectionName = argv[nextArg++];
if (strlen(sectionName) > 8)
{
printf("Choose a section name whose size is less than or equal to 8 characters.\n");
return -1;
}
sectionData = argv[nextArg++];
sizeofNewSectionData = strlen(sectionData) + 1;
// Load the pe image from disk into a memory buffer.
fileStream = fopen(fileName, "rb");
fseek(fileStream, 0, SEEK_END);
size_t fileSize = ftell(fileStream);
fseek(fileStream, 0, SEEK_SET);
peFileBuffer = malloc(fileSize);
if (!peFileBuffer)
return NULL;
fread(peFileBuffer, 1, fileSize, fileStream);
fclose(fileStream);
// Parse the headers.
lpDosHeader = (PIMAGE_DOS_HEADER)peFileBuffer;
// Check the validity of file.
if (lpDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("The file is not a valid PE file.\n");
return -2;
}
lpNtHeaders = (PIMAGE_NT_HEADERS)(peFileBuffer + lpDosHeader->e_lfanew);
if (lpNtHeaders->Signature != IMAGE_NT_SIGNATURE)
{
printf("The file is not a valid PE file.\n");
return -3;
}
lpFileHeader = (PIMAGE_FILE_HEADER)&(lpNtHeaders->FileHeader);
lpOptionalHeader = (PIMAGE_OPTIONAL_HEADER)&(lpNtHeaders->OptionalHeader);
lpImageSections = (PIMAGE_SECTION_HEADER)((char*)lpOptionalHeader + lpFileHeader->SizeOfOptionalHeader);
// Get the file alignment and section alignment because they may be
// different from default values (0x200 and 0x1000, respectively.).
fileAlignmentOfPEImage = lpOptionalHeader->FileAlignment;
sectionAlignmentOfPEImage = lpOptionalHeader->SectionAlignment;
// Get the last section before we append our section.
lpLastSection = lpImageSections + lpFileHeader->NumberOfSections - 1;
fileStream = fopen(outputFileName, "wb");
// Update the number of sections because we are adding one more.
lpFileHeader->NumberOfSections++;
// Calculate the rva of our section data beforward because we
// will use it twice, both when setting the VirtualAddress field
// in our section header and when calculating new SizeOfImage.
ourSectionRVA = align(lpLastSection->VirtualAddress +
lpLastSection->Misc.VirtualSize, sectionAlignmentOfPEImage);
// Here is the most important part. We update the following three fields because we are adding a new section.
// First, since our section includes initialized data, we add the size of our data into the field SizeOfInitializedData.
// Second, we are calculating the new size of headers in the file. This field is aligned with File Aligment. So add
// sizeofNewSectionData and align it.
// Finally, calculate the new size of image. Again this field is aligned but at this time, with Section Alignment.
lpOptionalHeader->SizeOfInitializedData += sizeofNewSectionData;
lpOptionalHeader->SizeOfHeaders = align(lpDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + lpFileHeader->NumberOfSections * sizeof(IMAGE_SECTION_HEADER), fileAlignmentOfPEImage);
lpOptionalHeader->SizeOfImage = align(ourSectionRVA + sizeofNewSectionData, sectionAlignmentOfPEImage);
// Write the headers and all sections from the input file into the output file. We copy these data
// from the buffer. And don't forget substract one from lpFileHeader->NumberOfSections
// because we increased the lpFileHeader->NumberOfSections since we will add a new section.
fwrite(peFileBuffer, 1, lpDosHeader->e_lfanew + // This includes the size of IMAGE_DOS_HEADER, Dos stub,
// and takes IMAGE_NT_HEADERS aligment into account
sizeof(IMAGE_NT_HEADERS) +
(lpFileHeader->NumberOfSections - 1) * sizeof(IMAGE_SECTION_HEADER), fileStream);
//fflush(fileStream);
// Fill the required fields of header of our section.
memset(&ourSection, 0, sizeof(IMAGE_SECTION_HEADER));
// These twos are obvious.
memcpy(&ourSection.Name[0], sectionName, strlen(sectionName));
ourSection.Characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ;
ourSection.SizeOfRawData = ourSection.Misc.VirtualSize = sizeofNewSectionData;
// File offset of new data. It is aligned with File Alignment. We use lpLastSection->PointerToRawData
// to calculate the new offset.
ourSection.PointerToRawData = align(lpLastSection->PointerToRawData +
lpLastSection->SizeOfRawData, fileAlignmentOfPEImage);
// Virtual Address of new data in memory. It is aligned with Section Alignment.
// We use lpLastSection->VirtualAddress
// to calculate the new offset.
ourSection.VirtualAddress = ourSectionRVA;
// Write the header of our section into output file.
fwrite(&ourSection, 1, sizeof(IMAGE_SECTION_HEADER), fileStream);
// Now we are going to write the raw section datas both from the input file and our new section.
//
// How many bytes did we write so far?
// We will use this value to keep track of the file offsets of raw section datas from input files.
int fileOffset = ftell(fileStream);
// This will be the file offset of first section data.
fileOffset = align(fileOffset, fileAlignmentOfPEImage);
for (int i = 0; i < lpFileHeader->NumberOfSections - 1; i++)
{
PIMAGE_SECTION_HEADER lpSection = lpImageSections + i;
fseek(fileStream, fileOffset, SEEK_SET);
fwrite(peFileBuffer + lpSection->PointerToRawData, 1, lpSection->SizeOfRawData, fileStream);
// Update the file offset by adding align(lpSection->SizeOfRawData, fileAlignmentOfPEImage)
fileOffset += align(lpSection->SizeOfRawData, fileAlignmentOfPEImage);
}
// And finally, write the raw data of final and our section.
fseek(fileStream, fileOffset, SEEK_SET);
fwrite(sectionData, 1, ourSection.SizeOfRawData, fileStream);
fclose(fileStream);
return 0;
}
int align(int value, int alignment)
{
int remainder = value % alignment;
if (remainder != 0)
return value + alignment - remainder;
else
return value;
}