Яндекс.Метрика

    Песочница

    Разработка функций RvaToRaw и RawToRva

    Цель статьи


    Целью этой статьи является желание автора показать некоторые нюансы по разработке функций RvaToRaw/RawToRva, которые являются важными для системных утилит работающих с исполнимыми файлами формата PE.

    Article-limitation


    • Читатель знаком с форматом файлов «Portable Executable»
    • Читатель >= 1 раза писал парсер этого файла
    • Читатель отлично знает что такое «RvaToRaw»

    Терминология



    RVA — Это аббревиатура англ. слов Relative Virtual Address и означает смещение в байтах от начала реального или предполагаемого адреса загрузки модуля.
    RAW — Файловое смещение от начала файла. Другое удачное название «File offset».

    Разработка


    При поиске в гугле по ключевым словам «rva to raw» можно наткнуться на следующий код:
    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 может быть меньше чем SizeOfHeaders, т.е. значение может указывать внутрь заголовка
    • Значения VirtualAddress, VirtualSize, PointerToRawData никак не выравниваются
    • Используется макрос IMAGE_FIRST_SECTION , который не учитывает 64-битных файлов, об этом честно сказано в winnt.h


    Более корректный код с точки зрения автора:

    uint32_t PeUtils::RvaToRaw( const PeImage& peImage, uint32_t rva )
    {
        if( rva < peImage.NtHeaders().SizeOfHeaders() )
        {
            return rva;
        }
    
        const NtSectionsStorage& sections = peImage.NtSections();
        uint32_t sectAlignment = peImage.NtHeaders().SectionAlignment();
    
        for( int i = 0; i < peImage.NtHeaders().NumberOfSections(); i++ )
        {
            uint32_t VAddr   = AlignDown( sections[i].VirtualAddress(), sectAlignment );
            uint32_t imgSize = AlignUp( peImage.NtHeaders().SizeOfImage(), sectAlignment );
            uint32_t VSize   = AlignUp( sections[i].VirtualSize(), sectAlignment );
            VSize = min( VSize, imgSize - VAddr ); // Am I paranoik? ;)
    
            if( rva >= VAddr && rva < VAddr + VSize )
            {
                uint32_t fileAlignment = peImage.NtHeaders().FileAlignment();
                return AlignDown( sections[i].PointerToRawData(), fileAlignment );
            }
        }
        throw CorruptedError( "Converting RVA to RAW failed" );
    }
    


    Что улучшено:
    • Проверка попадания принадлежности rva в область заголовка
    • Значения VirtualAddress, PointerToRawData выравнены в меньшую сторону на FileAlignment\SectionAlignment
    • Также выравниваются виртуальный\файловый размеры секций


    Тестирование


    Создать тестовый набор исполнимых файлов и периодически проверять свои RvaToRaw\RawToRva по этому набору, которые могли быть изменены после рефакторинга или фиксания багов.

    Пути получения тестовых файлов:
    1. Применить упаковщик исполнимых файлов, который может создать не совсем стандартные значения в заголовках. Примером такого упаковщика является Upack.exe, но существует и множество других
    2. Периодически пополнять свою коллекцию новых образцов зловредных файлов, к примеру с MDL(см. доп. источники)


    Note bene:
    Прошу простить за напоминание, но любой сомнительный файл лучше всего запускать в гостевой виртуальной машине, к примеру на базе VMWare или VirtualBox.

    Также корректность своего кода можно проверить с помощью Hiew, IDA Pro или отладчика.

    Дополнительная источники:


    1. #include <winnt.h>. Хидер входит в MSVC и не только. Этот хидер должен быть настольным справочником для любого кто пишет парсер этого файла!
    2. Мэтт Питрек. «Форматы РЕ и COFF объектных файлов». rsdn.ru/article/baseserv/pe_coff.xml
    3. Максим М. Гумеров. «Загрузчик PE-файлов». 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. Ресурс на котором можно скачать образцы сомнительных файлов


    Post scriptum:
    • Подчеркну что автор не ставил перед собой цель кого-либо удивить или кого-нибудь высмеять. Автор имеет огромное желание повысить качество системного кода. Очень надеюсь что в будущем «великий гугл» будет выдавать ссылки на достаточно корректный код RvaToRaw\RawToRva
    • Автор также обрадуется любым вопросам, любой критике и любым пожеланиям