Matthew
Matthew

Reputation: 3946

How to determine the size of an PE executable file from headers and or footers

Assuming you have a stream of data or a block of bytes you want to carve, how can you determine the size of the executables?

There are numerous headers inside the PE executable format, but what header sections do I use to determine (if possible) the total length of the executable?

Here is a picture of the file format.

PE File Format

Upvotes: 10

Views: 16247

Answers (2)

frakod
frakod

Reputation: 1871

I believe the accepted answer is incomplete as there may be symbol table, string table and padding beyond end of last section in an unstripped exe file.

Following is a more complete C++ code that I used in my custom installer to find offset of payload (same as size of exe) in an exe+payload buffer.

I believe it can be adapted to your use case with minimal changes (by removing the ERR_NO_PAYLOAD check from end).

#include <span>
#include <cstdint>
#include <cstddef>
#include <string_view>
#include <stdexcept>

#include <minwindef.h>
#include <winnt.h>

size_t find_payload_offset(std::span<const uint8_t> buffer)
{
    const auto ERR_BAD_FORMAT = std::runtime_error{"find_payload_offset: BAD FORMAT"};
    const auto ERR_NO_PAYLOAD = std::runtime_error{"find_payload_offset: NO PAYLOAD"};
    
    if (buffer.size() < sizeof(IMAGE_DOS_HEADER))
        throw ERR_BAD_FORMAT;
    
    auto dosheader = (IMAGE_DOS_HEADER*)buffer.data();
    
    if (dosheader->e_magic != IMAGE_DOS_SIGNATURE)
        throw ERR_BAD_FORMAT;
    
    if (buffer.size() < size_t(dosheader->e_lfanew) + sizeof(IMAGE_NT_HEADERS))
        throw ERR_BAD_FORMAT;
    
    auto ntheader = (IMAGE_NT_HEADERS*)(buffer.data() + dosheader->e_lfanew);
    
    if (ntheader->Signature != IMAGE_NT_SIGNATURE)
        throw ERR_BAD_FORMAT;
    
    if (buffer.size() < size_t(dosheader->e_lfanew) + sizeof(IMAGE_NT_HEADERS)
                    + sizeof(IMAGE_SECTION_HEADER) * ntheader->FileHeader.NumberOfSections)
        throw ERR_BAD_FORMAT;
    
    auto sectiontable = (IMAGE_SECTION_HEADER*)((uint8_t*)ntheader + sizeof(IMAGE_NT_HEADERS));
    size_t offset;
    
    if (ntheader->FileHeader.PointerToSymbolTable)
    {
        if (std::string_view{(char*)sectiontable->Name, 3} == "UPX") // UPX produces exe with invalid PointerToSymbolTable
            throw std::runtime_error{"find_payload_offset: Unstripped UPX Compressed Executables are NOT SUPPORTED"};
        
        auto symboltable = (IMAGE_SYMBOL*)(buffer.data() + ntheader->FileHeader.PointerToSymbolTable);
        auto stringtable = symboltable + ntheader->FileHeader.NumberOfSymbols;
        auto stringtable_len =  *(int32_t*)stringtable;
        
        auto end = (uint8_t*)stringtable + stringtable_len;
        offset = end - buffer.data();
    }
    else // stripped
    {
        auto last_section_header = sectiontable + ntheader->FileHeader.NumberOfSections - 1;
        offset = last_section_header->PointerToRawData + last_section_header->SizeOfRawData;
    }
    
    auto align = ntheader->OptionalHeader.FileAlignment;
    if (offset % align) offset = (offset/align + 1)*align;
    
    if (offset > buffer.size())
        throw ERR_BAD_FORMAT;
    
    if (offset == buffer.size())
        throw ERR_NO_PAYLOAD;
    
    return offset;
}

Usage Example:

auto offset  = find_payload_offset(buffer);
auto executable = std::span{buffer.begin(), offset};
auto payload = std::span{buffer.begin()+offset, buffer.end()};

NOTE: this code has only been tested with executables produced by msys2 clang compiler.

Upvotes: 0

Neitsa
Neitsa

Reputation: 8166

If the PE file is well formed, the calculation can be simplified as (pseudo-code):

size = IMAGE_NT_HEADERS.OptionalHeader.SizeOfHeaders

foreach section_header in section_headers:
    size += section_header.SizeOfRawData

Where:

SizeOfHeaders field gives the length of all the headers (note: including the 16-bit stub).

  • Each section header is an IMAGE_SECTION_HEADER structure
  • SizeOfRawData field gives the length of each section on disk.

Example with notepad (Windows 10):

  • SizeOfHeaders : 0x400

enter image description here

  • SizeOfRawDataof each sections :
    • .text: 0x15400
    • .data: 0x800
    • .idata: 0x1A00
    • .rsrc: 0x19C00
    • .reloc: 0x1600

(note: SizeOfRawData is called Raw Size in the below picture):

enter image description here

Sum everything:

>>> size_of_headers = 0x400
>>> sec_sizes = [0x15400, 0x800, 0x1a00, 0x19c00, 0x1600]
>>> size_of_headers + sum(sec_sizes)
207872
>>> 

Total size: 207872 bytes.

Verification:

enter image description here

Note: the above calculation doesn't take into account if the PE is badly formed or if there is an overlay.

Upvotes: 16

Related Questions