Saturday, August 4, 2012

Adding a section to your PE: the easy way

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

7 comments:

  1. Your "AddSection" function has probable bug. I have PE32 (32-bit) file with File Offset of Bound Directory after Last Section Header. This filename is 'ksuser.dll'. Can your code add new section in this file ?

    ReplyDelete
  2. Please, see:

    Name RVA Size
    Export 000010E4 0000009C
    Import 000014F8 00000050
    Resource 00002000 000003E8
    Exception 00000000 00000000
    Security 00000000 00000000
    Fixups 00003000 00000020
    Debug 000015F0 00000038
    Description 00000000 00000000
    MIPS GP 00000000 00000000
    TLS 00000000 00000000
    Load config 00000000 00000000
    Bound Import 00000248 0000004C
    Import Table 00001000 00000024
    Delay Import 00000000 00000000
    COM Runtime 00000000 00000000
    (reserved) 00000000 00000000

    and Last Section header:

    00000220: 2E 72 65 6C-6F 63 00 00-20 00 00 00-00 30 00 00 .reloc 0
    00000230: 00 02 00 00-00 10 00 00-00 00 00 00-00 00 00 00 ☻ ►
    00000240: 00 00 00 00-40 00 00 42-6F DA 5B 4A-28 00 00 00 @ Bo┌[J(
    00000250: DB DA 5B 4A-33 00 00 00-AD DA 5B 4A-3D 00 01 00 █┌[J3 н┌[J= ☺
    00000260: DB DA 5B 4A-33 00 00 00-00 00 00 00-00 00 00 00 █┌[J3
    00000270: 6D 73 76 63-72 74 2E 64-6C 6C 00 6E-74 64 6C 6C msvcrt.dll ntdll

    ReplyDelete
    Replies
    1. Hey!

      It seems like you miss space to add additional section header. As I wrote: "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."

      I think this is the case for you.

      If you have any physical space appendable then this is ok, but Do not add some too much or you could overflow above the page's size (the one where your PE header is located).

      What you could do is to move your PE header to the end of your file so you would be able to append any section header you want... But be careful, this might me tangled everywhere then.

      I simply suggest you to add any code cave on the last available section's space in your file if you really want to patch a binary.

      In advance I apologize if my words are not quite understandable since I am French. Feel free to get in touch with mail if you're unhappy with my answer.

      Thx

      Ge0

      Delete
    2. I'm Russian and From Moscow. Sorry for my English level... This is very difficult language :)
      I'm writting my C++ Framework for PE-image working. If you want you can join to my project.

      Delete
    3. No problem dude,

      Your project seems very interesting, I was thinking about doing such stuff so if I can help feel free to involve me in!

      Delete
  3. Using bind.exe to bind certain imports seems to always place the bound imports structure just after the section table. I have twitted about that before.

    Since the bound imports are no longer effective with the introduction of ASLR (Windows Vista and 7), you can just set its RVA and size fields to zero as part of your "AddSection" function.

    ReplyDelete
    Replies
    1. Hello Walied,

      What is bind.exe binary and what do you mean by bound imports? I'm afraid I don't understand the whole stuff in your comment.

      Delete