- Сообщения
- 7,564
- Реакции
- 6,419
Автор: ManHunter
Как проверить соответствие файла PE-формату без запуска
На форуме EXEL@B встретилась интересная тема по проверке является ли файл корректным PE-файлом, но без его запуска. Статическая проверка по типовым полям PE-заголовка не дает гарантии корректности, так как можно просто приписать к валидному заголовку кусок мусора или испортить его содержимое. Придется как минимум проверить соответствие размера файла суммарному размеру секций, а более тщательные проверки потребуют самостоятельного разбора секций импорта и экспорта, TLS, релоков и еще много чего. Проще всего доверить подобные проверки самой системе.
Самый простой способ - использовать функцию LoadLibraryEx с двумя флагами в параметрах: LOAD_LIBRARY_AS_DATAFILE и DONT_RESOLVE_DLL_REFERENCES. Первый флаг будет подавлять системное сообщение об ошибке загрузки, если проверяемый файл вообще никак не относится к исполняемым, второй не даст выполниться DllMain при загрузке библиотеки и не будет подгружать дополнительные модули из ее таблицы импорта. Это будет гарантировать, что никакой код не получит несанкционированного управления.
Но, как показала практика, использовать для проверки только эту функцию недостаточно. Поэтому на форуме EXEL@B был предложен второй способ - с использованием функции WinAPICreateFileMapping с флагом SEC_IMAGE. Такая комбинация сообщает системе, что проецируемый файл должен являться исполняемым, и, соответственно, память проекции надо подготовить соответствующим образом, с учетом заголовка и секций. Если файл не является корректным, например, повреждена структура секций или не соответствует заголовок, то CreateFileMapping вернет ошибку. На этом и основана следующая функция проверки:
Функция принимает единственный параметр 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.
// Модератор: файл не прикладывал, т.к. он содержит много ложных срабатываний от антивирусов.

Как проверить соответствие файла 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-битное. Для наглядности я собрал в табличку результаты тестирования на различных файлах.
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
В то же время, если попытаться проверить корректность 64-битного исполняемого файла на 32-битной системе при помощи функции isPE, она вернет результат "Invalid". Своя правда в этом тоже есть, тем самым функция сообщает, что проверяемый файл не может быть запущен в имеющемся окружении, хотя имеет абсолютно корректную внутреннюю структуру. Поэтому результаты функции isPE можно расценивать как ответ на вопрос "может ли проверяемый файл вообще запуститься на этом компьютере?".
В приложении пример программы с исходным текстом, которая проверяет соответствие файла "test.exe" PE-формату с помощью функций LoadLibraryEx и isPE.
// Модератор: файл не прикладывал, т.к. он содержит много ложных срабатываний от антивирусов.
Последнее редактирование: