Цель статьи
Целью этой статьи является желание автора показать некоторые нюансы по разработке функций RvaToRaw/RawToRva, которые являются важными для системных утилит работающих с исполнимыми файлами формата PE.
На кого нацелена статья?
- Читатель знаком с форматом файлов «Portable Executable»
- Читатель >= 1 раза писал парсер этого файла
- Читатель отлично знает что такое «RvaToRaw»
Терминология
RVA — Это аббревиатура англ. слов Relative Virtual Address и означает смещение в байтах от начала реального или предполагаемого адреса загрузки модуля.
RAW — Файловое смещение от начала файла. Другое удачное название «File offset».
Разработка
При поиске в гугле по ключевым словам «rva to raw» или «rva to offset» можно наткнуться на следующий код:
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); }
Код имеет имеет несколько грубых ошибок и при этом весьма популярен.
В нем не учитывается:
- Значение rva может быть меньше чем OptionalHeader.SizeOfHeaders, т.е. значение может указывать внутрь заголовка
- Значения VirtualAddress, VirtualSize, PointerToRawData никак не выравниваются
- Используется макрос IMAGE_FIRST_SECTION , который не учитывает 64-битных файлов, об этом честно сказано в winnt.h
Более корректный код, с точки зрения автора:
Код класса выравнивающего оффсеты и адреса
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; } };
Код метода для конвертации Rva в 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; }
Что улучшено:
- Учитывается факт того, что не всегда нужно выравнивать(см. метод needAlign)
- Учитывается факт того, что количество секций может быть нулевым. И такой файл может быть загружен!
- Когда требуется выравниваются значения PointerToRawData и VirtualAddress в меньшую сторону
- Более корректное вычисление размера секции
Nota bene:
На самом деле даже это не окончательный вариант, но он уже ближе к тому, как системный загрузчик понимает такие файлы.
Тестирование
Создать тестовый набор исполнимых файлов и периодически проверять свои RvaToRaw\RawToRva по этому набору, которые могли быть изменены после рефакторинга или фиксания багов.
Пути получения тестовых файлов:
- Применить упаковщик исполнимых файлов, который может создать не совсем стандартные значения в заголовках. Примером такого упаковщика является Upack.exe, но существует и множество других
- Периодически пополнять свою коллекцию новых образцов зловредных файлов, к примеру с MDL(см. доп. источники)
Nota bene:
Прошу простить за напоминание, но любой сомнительный файл лучше всего запускать в гостевой виртуальной машине, к примеру на базе VMWare или VirtualBox.
Также корректность своего кода можно проверить с помощью Hiew, IDA Pro или отладчика.
Дополнительная источники:
- #include <winnt.h>. Хидер входит в MSVC и не только. Этот хидер должен быть настольным справочником для любого кто пишет парсер этого файла!
- Мэтт Питрек. «Форматы РЕ и COFF объектных файлов». rsdn.ru/article/baseserv/pe_coff.xml
- Максим М. Гумеров. «Загрузчик PE-файлов». rsdn.ru/article/baseserv/peloader.xml
- Forums >> IDA Pro >> # new PE loader bug and new crack-me. www.openrce.org/forums/posts/969
- MDL. www.malwaredomainlist.com/mdl.php. Ресурс на котором можно скачать образцы сомнительных файлов
Post scriptum:
- Подчеркну что автор не ставил перед собой цель кого-либо удивить или кого-нибудь высмеять. Автор имеет огромное желание повысить качество системного кода. Очень надеюсь что в будущем «великий гугл» будет выдавать ссылки на достаточно корректный код RvaToRaw\RawToRva
- Автор также обрадуется любым вопросам, любой критике и любым пожеланиям
