Hello again folks,
As I told you, I would release a little tool that let you to add a section to the pe binary of your choice, considering that it's compiled for a x86 target because my lib does not support the x64 one, a.k.a PE32+ or PE+.
Supposing you already know what the article deals with, I will even recall you a bit about what a section is: simply a region of code, which granularity is a memory page (4096 bytes). A section may contain whatever you want: code, data, read-only data... Therefore we point a first interest out: changing the access rights from section to section as well.
By adding a section to your binary, you may create a "code cave" and then put customised opcode / data... This is somewhat useful when you wish to patch a binary (cracking purposes? :P)
Now I suggest you to dive a bit into adding a fresh and new section to your binary...
First of all you have to choose a name and characteristics. This is the easy part.
But you have to think also about other useful information such the starting VirtualAddress, the size of raw data, etc. Since we told that the granularity of a section is actually a memory page, the VirtualAddress field's value must be 4096 bytes aligned and also must follow after the last section's VirtualAddress value plus its VirtualSize (very important in order not to overlap the whole).
E.G. suppose we have a binary composed of two sections: ".text" and ".data". Here is the mapping:
.text 0x00001000 : size 0x3000 bytes
.data 0x00004000 : size 0x2000 bytes
Because the .data section is 0x2000 bytes length, its relative memory addresses range will go from 0x00004000 to 0x00005FFF. So
our new section will be located at 0x00006000.
Instead of doing some math (even though you like it, hackers!) you may get this value by actually copying the one of this the SizeOfImage field in OptionalHeader.
Your section must point to a physical space of data (actually this is not mandatory since some sections does not require some initialized data and just ask the loader to allocate a memory page...) that you'll have to create. So you should append some bytes to your binary. The number of bytes must be aligned to the FileAlignment field of OptionalHeader. With this new created bunch of bytes, you may provide further details to your section, such
PointerToRawData,
SizeOfRawData etc.
Secondly you have to make sure you have enough raw space to put the section header. Because in the memory page going from 0x00000000 to 0x00001000, you have both the MZ header and the PE header. And, at the end of the PE headers, you have every section headers; so if you miss space you would not be able to put your new section header... My algorithm unfortunately does not handle this case yet.
After having put it, you definitely have to edit two fields in the existing headers:
- the first one is obviously the SizeOfImage field that we talked about above; because your binary will grow after that;
- incrementing the NumberOfSection field located in the FileHeader since you have a fresh and new section.
And you're done! :-)
You may check the
AddSection method's code that implements such algorithm.
VOID PortableExecutable::AddSection(PortableExecutable::SectionHeader& newSectionHeader) {
DWORD dwRawSectionOffset = (DWORD)GetFileSize();
/* Aligns dwRawSectionOffset to OptionalHeader.FileAlignment */
dwRawSectionOffset += this->m_imageNtHeaders.OptionalHeader.FileAlignment - (dwRawSectionOffset % this->m_imageNtHeaders.OptionalHeader.FileAlignment);
/* Does the whole header's length overflows OptionalHeader.SizeOfHeaders? */
if(
(sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS) + (this->m_imageNtHeaders.FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)))
> this->m_imageNtHeaders.OptionalHeader.SizeOfHeaders
) {
/* If it's the case, add free space */
AddFreeSpaceAfterHeaders( this->m_imageNtHeaders.OptionalHeader.FileAlignment );
/* Adding 'FileAlignment' value to SizeOfHeaders fields then */
this->m_imageNtHeaders.OptionalHeader.SizeOfHeaders += this->m_imageNtHeaders.OptionalHeader.FileAlignment;
}
newSectionHeader.SetPointerToRawData(dwRawSectionOffset);
/* Adding the new section header */
this->m_sectionHeaders.push_back(newSectionHeader);
/* Incrementing the 'NumberOfSections' field */
++this->m_imageNtHeaders.FileHeader.NumberOfSections;
/* Adding to SizeOfImage the SectionAlignment value */
this->m_imageNtHeaders.OptionalHeader.SizeOfImage += this->m_imageNtHeaders.OptionalHeader.SectionAlignment;
/* Rewriting the whole headers */
m_stream.seekg(this->m_imageDosHeader.e_lfanew, std::ios::beg);
m_stream.write((const char*)&this->m_imageNtHeaders, sizeof(IMAGE_NT_HEADERS));
/* Rewriting the section headers then */
std::vector<PortableExecutable::SectionHeader>::iterator it =
this->m_sectionHeaders.begin();
while(it != this->m_sectionHeaders.end()) {
m_stream.write((const char*)&it->ImageSectionHeader(), sizeof(IMAGE_SECTION_HEADER));
++it;
}
/* Finally adding 'FileAlignment' bytes to the end of the file,
which actually corresponds to the section's memory space! */
m_stream.seekg(this->m_sectionHeaders.at( this->m_sectionHeaders.size()-1).GetPointerToRawData(), std::ios::beg);
char* bytes = new char[this->m_imageNtHeaders.OptionalHeader.FileAlignment];
::memset(bytes, '\0', this->m_imageNtHeaders.OptionalHeader.FileAlignment);
m_stream.write(bytes, this->m_imageNtHeaders.OptionalHeader.FileAlignment);
delete[] bytes;
}
The method does not seem bogus on normal PE. But because I sometimes enjoy repeating myself, do not use it on corkami's files. ;-)
With these algorithms comes a basic-and-not-so-friendly toll called (warning!!!!) PeAddSection that asks your for a binary, section name and section characteristics so it will create a section into the binary if possible
#include <iostream>
#include <cstdlib>
#include <Windows.h>
#include "../PortableExecutable/PortableExecutable.h"
using namespace std;
DWORD ParseCharacteristics(LPCTSTR lpCharacteristics);
int main(int argc, char** argv) {
DWORD dwCharacteristics;
if(argc < 4) {
printf("Usage: %s <pe file> <section name> <characteristics>\n", argv[0]);
ExitProcess(-1);
}
try {
dwCharacteristics = ParseCharacteristics(argv[3]);
PortableExecutable pe(argv[1]);
cout << "[*] Listing section headers." << endl;
vector<PortableExecutable::SectionHeader>::iterator it = pe.SectionHeaders().begin();
while(it != pe.SectionHeaders().end()) {
cout << setw(9) << left << setfill(' ') << it->GetName() << " ";
cout << "0x" << setw(8) << hex << setfill('0') << right << it->GetVirtualAddress() << endl;
++it;
}
cout << "[*] Adding " << argv[2] << " section... with 0x" << ParseCharacteristics(argv[3]) << " in characteristics..." << endl;
pe.AddSection(argv[2], dwCharacteristics);
cout << "[+] Normally done, check your pe!" << endl;
} catch(const PortableExecutable::Exception& e) {
cout << e.what() << endl;
return -1;
}
return EXIT_SUCCESS;
}
DWORD ParseCharacteristics(LPCTSTR lpCharacteristics) {
DWORD dwValue = 0;
if(strlen(lpCharacteristics) == 10 && _strnicmp("0x", lpCharacteristics, 2) == 0) {
sscanf_s(lpCharacteristics + 2, "%08X", &dwValue);
}
return dwValue;
}
All the stuff may be downloaded and followed here:
https://github.com/Ge0bidouille/PeTools (I have created a new repository and modified the previous article). Help yourself!
That's all for the moment. Next time we will see an easy way to add free space to an existing section without creating a new one.
See you!
Ge0