Уроки Iczelion'а

       

Правильность PE файла


В этом туториале мы научимся, как проверить, является ли файл PE-файлом.

Скачайте пример.

Пpимеp:

Как вы можете проверить, является ли данный файл PE-файлом? Hа этот вопрос трудно сразу ответить. Это зависит от того, с какой степенью надежности вы хотите это сделать. Вы можете проверить каждый параметр файла в PE-формата, а можете ограничиться проверкой самых важных из них. Как правило, проверять все параметры бессмысленно. Если критические структуры верны, мы можем допустить, что файл PE-формата. И мы сделаем это допущение.

Основная структура, которую мы будем проверять - это PE-заголовок. Поэтому нам нужно больше узнать о нем. Фактически PE-заголовок - это структура под названием IMAGE_NT_HEADERS. Она определена следующим образом:

IMAGE_NT_HEADERS STRUCT Signature dd ? FileHeader IMAGE_FILE_HEADER <> OptionalHeader IMAGE_OPTIONAL_HEADER32 <> IMAGE_NT_HEADERS ENDS

Signature - это слово, которое содержит значение 50h, 45h, 00h, 00h. Переводя на человеческий язык, она содержит текст "PE", за которым следуют два нуля. Этот член является сигнатурой PE, поэтому мы будем использовать его для того, чтобы определить, является ли данный файл PE-формата.

FileHeader - это структура, которая содержит информацию о физической структуре PE-файла, такой как количество секций, устройство, на которое ориентирован данный файл и так далее.

OрtionalHeader - это структура, которая содержит информацию о логической структуре PE-файла. Hесмотря на "Oрtional" в ее имени, этот член всегда присутствует.

Hаша цель ясна. Если значение сигнатуры в IMAGE_NT_HEADERS pавно "PE", за которым следуют два нуля, тогда файла является PE. Фактически, специально для подобных сравнений Microsoft определила константу под название IMAGE_NT_SIGNATURE, которую мы можем использовать.

IMAGE_DOS_SIGNATURE equ 5A4Dh IMAGE_OS2_SIGNATURE equ 454Eh IMAGE_OS2_SIGNATURE_LE equ 454Ch IMAGE_VXD_SIGNATURE equ 454Ch IMAGE_NT_SIGNATURE equ 4550h

Следующий вопрос: как мы можем узнать, где начинается PE-заголовок? Ответ прост: DOS MZ-заголовок содержит файловое смещение PE-заголовка. DOS MZ-заголовок определен как структура IMAGE_DOS_HEADER. Параметр e_lfanew этой структуры содержит файловое смещение PE-заголовка.


Теперь мы выполним следующие шаги:



  • Проверяем, верный ли у данного файла DOS MZ-заголовок, сравнивая первое слово этого файла со значением IMAGE_DOS_SIGNATURE.

  • Если у файла верный DOS-заголовок, используем значение параметра e_lfanew, чтобы найти PE-заголовок.

  • Сравниваем первое слово PE-заголовка со значением IMAGE_NT_HEADER. Если оба значения совпадают, тогда мы можем предположить, что этот файл является Portable Executable.

Example:
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\comdlg32.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\comdlg32.lib
SEH struct PrevLink dd ? ; the address of the previous seh structure CurrentHandler dd ? ; the address of the exception handler SafeOffset dd ? ; The offset where it's safe to continue execution PrevEsp dd ? ; the old value in esp PrevEbp dd ? ; The old value in ebp SEH ends
.data AppName db "PE tutorial no.2",0 ofn OPENFILENAME <> FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0 db "All Files",0,"*.*",0,0 FileOpenError db "Cannot open the file for reading",0 FileOpenMappingError db "Cannot open the file for memory mapping",0 FileMappingError db "Cannot map the file into memory",0 FileValidPE db "This file is a valid PE",0 FileInValidPE db "This file is not a valid PE",0
.data? buffer db 512 dup(?) hFile dd ? hMapping dd ? pMapping dd ? ValidPE dd ?
.code start proc LOCAL seh:SEH mov ofn.lStructSize,SIZEOF ofn mov ofn.lpstrFilter, OFFSET FilterString mov ofn.lpstrFile, OFFSET buffer mov ofn.nMaxFile,512 mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY invoke GetOpenFileName, ADDR ofn .if eax==TRUE invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL .if eax!=INVALID_HANDLE_VALUE mov hFile, eax invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0 .if eax!=NULL mov hMapping, eax invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0 .if eax!=NULL mov pMapping,eax assume fs:nothing push fs:[0] pop seh.PrevLink mov seh.CurrentHandler,offset SEHHandler mov seh.SafeOffset,offset FinalExit lea eax,seh mov fs:[0], eax mov seh.PrevEsp,esp mov seh.PrevEbp,ebp mov edi, pMapping assume edi:ptr IMAGE_DOS_HEADER .if [edi].e_magic==IMAGE_DOS_SIGNATURE add edi, [edi].e_lfanew assume edi:ptr IMAGE_NT_HEADERS .if [edi].Signature==IMAGE_NT_SIGNATURE mov ValidPE, TRUE .else mov ValidPE, FALSE .endif .else mov ValidPE,FALSE .endif FinalExit: .if ValidPE==TRUE invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION .else invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION .endif push seh.PrevLink pop fs:[0] invoke UnmapViewOfFile, pMapping .else invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR .endif invoke CloseHandle,hMapping .else invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR .endif invoke CloseHandle, hFile .else invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR .endif .endif invoke ExitProcess, 0 start endp


SEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD mov edx,pFrame assume edx:ptr SEH mov eax,pContext assume eax:ptr CONTEXT push [edx].SafeOffset pop [eax].regEip push [edx].PrevEsp pop [eax].regEsp push [edx].PrevEbp pop [eax]. regEbp mov ValidPE, FALSE mov eax,ExceptionContinueExecution ret SEHHandler endp end start
Анализ:
Программа открывает файл и проверяет, является ли DOS-заголовок верным, если это так, она проверяет, является ли PE-заголовок верным. Если и это так, она pешает, что данный файл - PE. В этом примере я использовал structured exceрtion handling (SEH), поэтому мы не должны проверять любую возможную ошибку, если ошибка происходит, мы предполагаем, что она произошла из-за того, что файл не являлся верным PE. Windows сама по себе очень интенсивно использует SEH в своих процедурах обработки параметров. Если вы заинтересовались SEH'ом, читайте соответствующую статью Jeremy Gordon'а.
Программа отображает окно открытия файла, и когда пользователь выбирает исполняемый файл, она открывает файл и загружает его в память. Перед тем, как проводить проверку файла, она устанавливает SEH.
assume fs:nothing push fs:[0] pop seh.PrevLink mov seh.CurrentHandler,offset SEHHandler mov seh.SafeOffset,offset FinalExit lea eax,seh mov fs:[0], eax mov seh.PrevEsp,esp mov seh.PrevEbp,ebp
Мы начинаем с того, что устанавливаем pежим использования регистра fs "nothing". Потом мы сохраняем адрес предыдущего SEH-обработчика в нашей структуре для использования Windows. Мы сохраняем адрес нашего SEH-обработчика, адрес где стартует обработка исключения, если происходит ошибка, текущие значения esр и ebр, так что наш SEH-обработчик может получить состояние нормально состояние стека перед тем, как продолжать программу.
mov edi, pMapping assume edi:ptr IMAGE_DOS_HEADER .if [edi].e_magic==IMAGE_DOS_SIGNATURE
После установления SEH'а, мы продолжаем проверку. Мы устанавливаем адрес первого байта файла в edi, который является первым байтом DOS-заголовка. Для простоты сравнения, мы говорим ассемблеру, что он может допустить, что edi указывает на структуру IMAGE_DOS_HEADER (что является правдой). Затем мы сравниваем первое слово DOS-заголовка со строкой "MZ", которая определена в windows.inc под названием IMAGE_DOS_SIGNATURE. Если сравнение положительно, мы переходим к PE-заголовку. Если нет, то мы устанавливаем значение ValidPE в FALSE, то есть что файл не является Portable Executable.


add edi, [edi].e_lfanew assume edi:ptr IMAGE_NT_HEADERS .if [edi].Signature==IMAGE_NT_SIGNATURE mov ValidPE, TRUE .else mov ValidPE, FALSE .endif
Чтобы добраться до PE-заголовка, нам нужно значение, находящееся в e_lfanew DOS-заголовка. Это поле содержит смещение в файле PE-заголовка, относительно начала файла. Поэтому мы добавляем это значение к edi и получаем первый байт PE-заголовка. Это то место, где может произойти ошибка. Если файл на самом деле не PE-файл, значение в e_lfanew будет неверным и использование его будет подобно использованию случайного указателя. Если мы не используем SEH, мы должны сравнить e_lfanew с размером файла, что некрасиво. Если все идет хорошо, мы сравниваем первое двойное слово PE-заголовка со строкой "PE". Снова мы можем использовать уже определенную константу под названием IMAGE_NT_SIGNATURE. Если результат сравнения верен, мы предполагаем, что файл является правильным PE.
Если значение в e_lfanew неверно, может произойти ошибка и наш SEH-обработчик получит управление. Он просто восстанавливает указатель на стек, bsae-указатель и продолжает выполнение программы с метки FinalExit.
FinalExit: .if ValidPE==TRUE invoke MessageBox, 0, addr FileValidPE, addr AppName, MB_OK+MB_ICONINFORMATION .else invoke MessageBox, 0, addr FileInValidPE, addr AppName, MB_OK+MB_ICONINFORMATION .endif
Вышеприведенный код сам по себе очень прост. Он проверяет значение в ValidPE и отображает соответствующее сообщение.
push seh.PrevLink pop fs:[0]
Когда SEH больше не используется, мы убираем его из SEH-цепи.
[C] Iczelion, пер. Aquila.

Содержание раздела