📜 ⬆️ ⬇️

Development of RvaToRaw and RawToRva features

Purpose of the article


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

Who is the article aimed at?




Terminology



RVA - This is an abbreviation of 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 successful name is “File offset”.
')

Development


When searching Google for the keywords “rva to raw” or “rva to offset” you may 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 a few blunders and is very popular.

It does not include:


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

Code class leveling offsets and addresses
 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:


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 suite 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 wrapper that can create off-standard values ​​in the headers. An example of such a packer is Upack.exe, but there are many other
  2. Periodically replenish their collection of new malicious files, for example with MDL (see additional sources)


Nota bene:
I apologize for the reminder, but any doubtful file is best run in a guest virtual machine, for example, based on VMWare or VirtualBox.

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

Additional sources:


  1. #include <winnt.h>. Hider is included in MSVC and not only. This leader should be a desktop reference for anyone who writes the parser of this file!
  2. Matt Pitrek. "PE and COFF object file formats". rsdn.ru/article/baseserv/pe_coff.xml
  3. Maxim M. Gumerov. "PE File Downloader". 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 questionable files


Post scriptum:

Source: https://habr.com/ru/post/129241/


All Articles