Development of RvaToRaw and RawToRva functions

Purpose of the article


The purpose of this article is the author’s desire to show some nuances in the development of RvaToRaw / RawToRva functions that are important for system utilities working with executable files in PE format.

Who is the article aimed at?


  • Reader Familiar with Portable Executable File Format
  • Reader> = 1 time wrote the parser of this file
  • The reader knows what RvaToRaw is.


Terminology



RVA - This is an abbreviation for English. words Relative Virtual Address and means the offset in bytes from the beginning of the real or estimated module load address.
RAW - File offset from the beginning of the file. Another good name is “File offset”.

Development


When searching in Google for the keywords “rva to raw” or “rva to offset”, you can stumble upon the following code:
DWORD RVAToOffset(IMAGE_NT_HEADERS32* pNtHdr, DWORD dwRVA)
 {
    int i;
    WORD wSections;
    PIMAGE_SECTION_HEADER pSectionHdr;
    /* Map first section */
    pSectionHdr = IMAGE_FIRST_SECTION(pNtHdr);
    wSections = pNtHdr->FileHeader.NumberOfSections;
    for (i = 0; i < wSections; i++)
    {
        if (pSectionHdr->VirtualAddress <= dwRVA)
            if ((pSectionHdr->VirtualAddress + pSectionHdr->Misc.VirtualSize) > dwRVA)
            {
                dwRVA -= pSectionHdr->VirtualAddress;
                dwRVA += pSectionHdr->PointerToRawData;
                return (dwRVA);
            }
        pSectionHdr++;
    }
    return (-1);
 }


The code has several gross errors and is very popular.

It does not take into account:
  • The rva value may be less than OptionalHeader.SizeOfHeaders, i.e. value can indicate inside the header
  • VirtualAddress, VirtualSize, PointerToRawData values ​​do not align
  • The macro IMAGE_FIRST_SECTION is used , which does not take into account 64-bit files, honestly said in winnt.h


More correct code, from the point of view of the author:

Offset offset class and address code
inline uint32_t alignDown(uint32_t value_, uint32_t factor)
{
    return value_ & ~(factor-1);
}
inline uint32_t alignUp(uint32_t value_, uint32_t factor)
{
    return alignDown(value_ - 1, factor) + factor;
}
class Aligner
{
public:
    const uint32_t FORCED_FILE_ALIGNMENT = 0x200;
    const uint32_t MIN_SECTION_ALIGNMENT = 0x1000;
public:
    Aligner(uint32_t fileAlignment_, uint32_t sectionAlignement_)
        : fileAlignment(fileAlignment_)
        , sectionAlignement(sectionAlignement_)
    {
    }
    uint32_t getVirtualSize(uint32_t size)
    {
        return needAlign(sectionAlignement) ?
            alignUp(size, sectionAlignement) :
            size;
    }
    uint32_t getVirtualAddress(uint32_t address)
    {
        return needAlign(sectionAlignement) ?
            alignDown(address, sectionAlignement) :
            address;
    }
    uint32_t getFileOffset(uint32_t offset)
    {
        return needAlign(sectionAlignement) ?
            alignDown(offset, FORCED_FILE_ALIGNMENT) :
            offset;
    }
    uint32_t getSectionSize(const ImgSectionHeader& header)
    {
        uint32_t fileSize = header.SizeOfRawData;
        uint32_t virtualSize = header.Misc.VirtualSize;
        if (needAlign(sectionAlignement)) {
            fileSize = alignUp(fileSize, fileAlignment);
            virtualSize = alignUp(virtualSize, sectionAlignement);
        }
        return std::min(fileSize, virtualSize);
    }
private:
    uint32_t fileAlignment;
    uint32_t sectionAlignement;
    bool needAlign(uint32_t sectionAlignement)
    {
        return sectionAlignement >= MIN_SECTION_ALIGNMENT;
    }
};



Method code for converting Rva to Raw
const uint32_t numInvalidRaw = (uint32_t)( -1 );
uint32_t PeUtils::RvaToRaw( const PeImage& peImage, uint32_t rva )
{
    uint32_t result = INVALID_RAW;
    const auto& optionalHeader = peImage.NtHeaders.OptionalHeader;
    if (rva < optionalHeader.SizeOfHeaders)
        return rva;
    Aligner aligner(optionalHeader.FileAlignment, optionalHeader.SectionAlignment);
    if (peImage.NtHeaders.FileHeader.NumberOfSections > 0) {
        for (const auto& section : peImage.Sections) {
            if (section.PointerToRawData == 0)
                continue;
            auto sectionStart = aligner.getVirtualAddress(section.VirtualAddress);
            auto sectionSize = aligner.getSectionSize(section);
            auto sectionEnd = sectionStart + sectionSize;
            if (sectionStart <= rva && rva < sectionEnd) {
                auto sectionOffset = aligner.getFileOffset(section.PointerToRawData);
                sectionOffset += (rva - sectionStart);
                if (sectionOffset < peImage.SizeOfFileImage)
                    result =  sectionOffset;
            }
        } // for
    } 
    else if (rva < aligner.getVirtualSize(optionalHeader.SizeOfImage)) {
        result = rva;
    }
    return result;
}



What is improved:
  • It takes into account the fact that it is not always necessary to align (see the needAlign method)
  • The fact that the number of sections can be zero is taken into account. And such a file can be downloaded!
  • When PointerToRawData and VirtualAddress are aligned down, if required
  • More correct section size calculation


Nota bene:
In fact, even this is not the final version, but it is already closer to how the system loader understands such files.

Testing


Create a test set of executable files and periodically check your RvaToRaw \ RawToRva for this set, which could be changed after refactoring or fixing bugs.

Ways to get test files:
  1. Apply an executable file packer that can create substandard headers. An example of such a packer is Upack.exe, but there are many others.
  2. Periodically replenish your collection of new samples of malicious files, for example with MDL (see additional sources)


Nota bene:
Please forgive me for the reminder, but any dubious file is best run in a guest virtual machine, for example based on VMWare or VirtualBox.

You can also verify the correctness of your code using Hiew, IDA Pro, or the debugger.

Additional sources:


  1. #include . The leader is included in MSVC and not only. This header should be a desktop reference for anyone writing a parser for this file!
  2. Matt Pitrek "Formats PE and COFF object files." rsdn.ru/article/baseserv/pe_coff.xml
  3. Maxim M. Gumerov. "PE file loader." rsdn.ru/article/baseserv/peloader.xml
  4. Forums >> IDA Pro >> # new PE loader bug and new crack-me. www.openrce.org/forums/posts/969
  5. MDL www.malwaredomainlist.com/mdl.php . Resource where you can download samples of dubious files


Post scriptum:
  • I emphasize that the author did not set himself the goal of surprising anyone or making fun of anyone. The author has a great desire to improve the quality of system code. I really hope that in the future, "great Google" will provide links to a fairly correct code RvaToRaw \ RawToRva
  • The author will also be delighted with any questions, any criticism and any wishes.

Also popular now: