Как проверить соответствие файла PE-формату без запуска

Dragokas

Angry & Scary Developer
Команда форума
Супер-Модератор
Разработчик
Клуб переводчиков
Сообщения
7,813
Реакции
6,592
Автор: ManHunter

a2851de117150a4ed8c4d815eca3a232.png
Как проверить соответствие файла PE-формату без запуска

На форуме EXEL@B встретилась интересная тема по проверке является ли файл корректным PE-файлом, но без его запуска. Статическая проверка по типовым полям PE-заголовка не дает гарантии корректности, так как можно просто приписать к валидному заголовку кусок мусора или испортить его содержимое. Придется как минимум проверить соответствие размера файла суммарному размеру секций, а более тщательные проверки потребуют самостоятельного разбора секций импорта и экспорта, TLS, релоков и еще много чего. Проще всего доверить подобные проверки самой системе.

Самый простой способ - использовать функцию LoadLibraryEx с двумя флагами в параметрах: LOAD_LIBRARY_AS_DATAFILE и DONT_RESOLVE_DLL_REFERENCES. Первый флаг будет подавлять системное сообщение об ошибке загрузки, если проверяемый файл вообще никак не относится к исполняемым, второй не даст выполниться DllMain при загрузке библиотеки и не будет подгружать дополнительные модули из ее таблицы импорта. Это будет гарантировать, что никакой код не получит несанкционированного управления.

Код:
        invoke  LoadLibraryEx,fname,NULL,\
                LOAD_LIBRARY_AS_DATAFILE+DONT_RESOLVE_DLL_REFERENCES
        or      eax,eax
        ; EAX=0 - файл загрузить не удалось
        ; EAX!=0 - заголовок файла соответствует PE-формату

Но, как показала практика, использовать для проверки только эту функцию недостаточно. Поэтому на форуме EXEL@B был предложен второй способ - с использованием функции WinAPICreateFileMapping с флагом SEC_IMAGE. Такая комбинация сообщает системе, что проецируемый файл должен являться исполняемым, и, соответственно, память проекции надо подготовить соответствующим образом, с учетом заголовка и секций. Если файл не является корректным, например, повреждена структура секций или не соответствует заголовок, то CreateFileMapping вернет ошибку. На этом и основана следующая функция проверки:

Код:
;----------------------------------------------------------
; Функция проверки корректности PE-файла
;----------------------------------------------------------
; Параметры:
;    lpszFileName - указатель на путь файла
; На выходе:
;    EAX = 1 - файл корректный
;    EAX = 0 - файл не является PE-файлом
;----------------------------------------------------------
proc isPE lpszFileName:DWORD
        locals
        result  dd ?
        endl

        SEC_IMAGE = 0x01000000

        pusha
        mov     [result],0
        invoke  CreateFile,[lpszFileName],GENERIC_READ,FILE_SHARE_READ,\
                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
        cmp     eax,-1
        je      .loc_ret
        mov     ebx,eax
        invoke  CreateFileMapping,ebx,NULL,PAGE_READONLY+SEC_IMAGE,0,0,NULL
        push    eax
        invoke  CloseHandle,ebx
        pop     ebx
        or      ebx,ebx
        jz      .loc_ret
        invoke  MapViewOfFile,ebx,FILE_MAP_READ,0,0,0
        or      eax,eax
        jz      @f
        invoke  UnmapViewOfFile,eax

        ; Файл соответствует PE-формату
        mov     [result],1
@@:
        invoke  CloseHandle,ebx
.loc_ret:
        popa

        mov     eax,[result]
        ret
endp

Функция принимает единственный параметр lpszFileName - указатель на имя проверяемого файла. На выходе EAX=1 - файл соответствует PE-формату, EAX=0 - файл не является корректным PE-файлом. Осталось протестировать обе функции. Тестирование проводилось на Windows 7 x86, Windows XP и Windows 8.1 x64, приложение скомпилировано как 32-битное. Для наглядности я собрал в табличку результаты тестирования на различных файлах.
Тестовый файл| LoadLibraryEx |Функция isPE
32-битный EXE-файл| Correct| Correct
32-битный DLL-файл| Correct| Correct
64-битный EXE-файл на 32-битной системе| Correct| Invalid
64-битный DLL-файл на 32-битной системе| Correct| Invalid
64-битный EXE-файл на 64-битной системе| Correct| Correct
64-битный DLL-файл на 64-битной системе| Correct| Correct
Поврежденный 32-битный EXE-файл| Correct| Invalid
Поврежденный 32-битный DLL-файл| Correct| Invalid
Поврежденный 64-битный EXE-файл| Correct| Invalid
Поврежденный 64-битный DLL-файл| Correct| Invalid
Текстовый файл| Invalid| Invalid
Поврежденными я назвал изначально корректные исполняемые файлы, от которых я просто отрезал приличный кусок. То есть заголовок остался прежним, а вот внутренняя структура секций была нарушена. При попытке запустить поврежденный файл система выдаст ошибку. Тем не менее, функция LoadLibraryEx бодренько загружает такие файлы в память и возвращает корректный хэндл. Конечно, дальнейшая работа с этим файлом очень сомнительна, например, при попытке загрузить из него какие-нибудь ресурсы, вы почти со стопроцентной вероятностью получите ошибку. Результат "Invalid" LoadLibraryEx дает только на файлах, которые к исполняемым не имеют вообще никакого отношения.

В то же время, если попытаться проверить корректность 64-битного исполняемого файла на 32-битной системе при помощи функции isPE, она вернет результат "Invalid". Своя правда в этом тоже есть, тем самым функция сообщает, что проверяемый файл не может быть запущен в имеющемся окружении, хотя имеет абсолютно корректную внутреннюю структуру. Поэтому результаты функции isPE можно расценивать как ответ на вопрос "может ли проверяемый файл вообще запуститься на этом компьютере?".

В приложении пример программы с исходным текстом, которая проверяет соответствие файла "test.exe" PE-формату с помощью функций LoadLibraryEx и isPE.

// Модератор: файл не прикладывал, т.к. он содержит много ложных срабатываний от антивирусов.
 
Последнее редактирование:
Назад
Сверху Снизу