IT-Блог о программировании и операционных системах

Проверка валидности PE-файла

Автор: Goppit / ARTeam [www.accessroot.com]
Перевод: Rob [www.delphiday.blogspot.com]

В этом туториале мы напишем простое приложение, которое использует штатное диалоговое окно выбора, для открытия файлов и выводит информацию о том, является ли выбранный файл правильным PE или нет. Код приложения основан на втором туториале от Iczelion, но я несколько изменил его, для пущей простоты и наглядности и выдрал SEH.

Обычно, для проверки того, что файл является правильным PE, нам необходимо проверить два условия. Необходимо, чтобы текст в начале DOS заголовка имел значение “MZ” и в конце самого DOS заголовка имелся указатель на заголовок PE, который в свою очередь начинается с “PE”, который завершается двумя нулевыми байтами. Эти сигнатуры объявлены в инклуднике windows.inc и имеют следующий вид:

IMAGE_DOS_SIGNATURE   equ   5A4Dh        ("MZ")
IMAGE_NT_SIGNATURE    equ   00004550h    ("PE" 00 00)



Запустите WinAsm, начните новый проект, добавьте новый диалоговый ресурс и нарисуйте форму похожую на эту:


Кнопка, помеченная тремя точками, предназначена для того, чтобы по клику на ней, выводилось окно, где мы бы могли выбрать нужный нам файл, вместо того, чтобы каждый раз писать новый путь до файла в поле ввода. Теперь наберите следующий код в окне редактирования:

.386
.model flat, stdcall
option casemap :none

include  windows.inc
include  user32.inc
include  kernel32.inc
include  comdlg32.inc
includelib user32.lib
includelib kernel32.lib
includelib comdlg32.lib

DlgProc  PROTO  :DWORD,:DWORD,:DWORD,:DWORD
Validate PROTO  :DWORD

.data
strFilter db  "Executable Files (*.exe, *.dll)",0,
    "*.exe;*.dll",0,"All Files",0,"*.*",0,0 
OpenError db  "Unable to open target file",0
MsgBoxCap db  "Results",0
Invalid  db  "This is not a valid PE file!!",0
Valid  db  "This is a valid PE file!!",0

.data?
ofn  OPENFILENAME <>
hInstance HINSTANCE ?
hTarget  dd  ?
hMapping dd  ?
pMapping dd  ?
TargetName db  512 dup(?)

.const
IDD_MAIN equ  1001
IDC_TARGET equ  1003
IDC_OPEN equ  1004
IDC_GO   equ  1005
IDC_EXIT  equ  1006
ARIcon  equ  2001

.code
start:
 invoke GetModuleHandle, NULL
 mov hInstance, eax
 invoke DialogBoxParam,hInstance,IDD_MAIN,0,addr DlgProc,0
 invoke ExitProcess, eax

DlgProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
 mov eax,uMsg
 .if eax==WM_INITDIALOG
  invoke LoadIcon,hInstance,2001
  invoke SendMessage,hWin,WM_SETICON,1,eax
 .elseif eax==WM_COMMAND
  mov eax,wParam
  .if eax==IDC_OPEN
   mov ofn.lStructSize,SIZEOF ofn 
   mov ofn.lpstrFilter,offset strFilter
   mov ofn.lpstrFile,offset TargetName 
   mov ofn.nMaxFile,512 
   mov ofn.Flags,OFN_FILEMUSTEXIST+OFN_PATHMUSTEXIST+\
      OFN_LONGNAMES+OFN_EXPLORER+OFN_HIDEREADONLY 
   invoke GetOpenFileName,addr ofn
   .if eax==TRUE
    invoke SetDlgItemText,hWin,IDC_TARGET,addr TargetName
    invoke RtlZeroMemory,addr TargetName,512
   .endif
  .elseif eax==IDC_GO
   invoke GetDlgItemText,hWin,IDC_TARGET,addr TargetName,512
   invoke lstrlen,addr TargetName
   .if eax!=0
    invoke Validate,addr TargetName
   .endif
  .elseif eax==IDC_EXIT
   invoke SendMessage,hWin,WM_CLOSE,0,0
  .endif
 .elseif eax==WM_CLOSE
  invoke EndDialog,hWin,0
 .endif
 xor eax,eax
 ret
DlgProc endp

Validate proc FileName:DWORD
 invoke CreateFile,FileName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0 
 .if eax!=INVALID_HANDLE_VALUE
  mov hTarget,eax
  invoke CreateFileMapping,eax,0,PAGE_READ,0,0,0 
  mov hMapping,eax
  invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0 
  mov pMapping,eax 
  .if [eax.IMAGE_DOS_HEADER.e_magic]==IMAGE_DOS_SIGNATURE
   add eax,[eax.IMAGE_DOS_HEADER.e_lfanew]
   .if [eax.IMAGE_NT_HEADERS.Signature]==IMAGE_NT_SIGNATURE
    invoke MessageBox,0,addr Valid,addr MsgBoxCap,MB_ICONASTERISK
   .endif 
  .else 
   invoke MessageBox,0,addr Invalid,addr MsgBoxCap,MB_ICONASTERISK
  .endif 
  invoke UnmapViewOfFile,pMapping
  invoke CloseHandle,hMapping
  invoke CloseHandle,hTarget
 .else
  invoke MessageBox,0,addr OpenError,0,0
 .endif
 xor eax,eax
 Ret
Validate EndP

end start

Нам нужно обсудить три новых переменных. Структура OPENFILENAME описана в файле windows.inc следующим образом:


Это просто шаблон. Чтобы нам использовать его в своём приложении, мы должны объявить её копию (структуры) и дать ей имя, так же как и обычным переменным. Мы назвали её ofn, но не инициализировали ни одного поля. Для этого мы и использовали следующую конструкцию:

Ofn OPENFILENAME  <>

Есть только пять полей важных для нас, и они должны быть заполнены, прежде чем мы вызовем функцию GetOpenFileName. Я сделал это в процедуре DlgProc, на приход сообщения WM_COMMAND нашей кнопки.

Оператор SizeOf возвращает размер структурной переменной ofn, которую мы объявили выше. Поле lpstrFilter указывает на переменную strFilter, которая содержит текст, который будет появляться в списке выбора типа файла в нашем диалоге открытия файла. Поле lpstrFile, указывает на переменную буфер, куда запишется имя и путь выбранного файла. Поле nMaxFile содержит максимальную длину буфера.

Функция GetOpenFileName, заполнит буфер TargetName полным путём и именем файла, который мы выберем в диалоге открытия файла, в случаи её успешного завершения. Полное имя файла отобразится в строке редактирования и затем буфер отчистится для следующего раза функцией RtlZeroMemory.

По клику на кнопке Go! Имя файла получается из поля ввода. Делается это для того, чтобы быть уверенным, что если юзер изменит текст в поле ввода, программа пыталась открыть актуальное имя файла (изменённое). Затем, указатель на имя файла передаётся процедуре Validate.

Взгляните ещё раз на код процедуры Validate:

Validate proc FileName:DWORD
 invoke CreateFile,FileName,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0 
 .if eax!=INVALID_HANDLE_VALUE
  mov hTarget,eax
  invoke CreateFileMapping,eax,0,PAGE_READ,0,0,0 
  mov hMapping,eax
  invoke MapViewOfFile,eax,FILE_MAP_READ,0,0,0 
  mov pMapping,eax 
  .if [eax.IMAGE_DOS_HEADER.e_magic]==IMAGE_DOS_SIGNATURE
   add eax,[eax.IMAGE_DOS_HEADER.e_lfanew]
   .if [eax.IMAGE_NT_HEADERS.Signature]==IMAGE_NT_SIGNATURE
    invoke MessageBox,0,addr Valid,addr MsgBoxCap,MB_ICONASTERISK
   .endif 
  .else 
   invoke MessageBox,0,addr Invalid,addr MsgBoxCap,MB_ICONASTERISK
  .endif 
  invoke UnmapViewOfFile,pMapping
  invoke CloseHandle,hMapping
  invoke CloseHandle,hTarget
 .else
  invoke MessageBox,0,addr OpenError,0,0
 .endif
 xor eax,eax
 Ret
Validate EndP

Целевой файл открывается и проецируется в память. Все эти операции над файлом, как открытие и проецирование, выполняются с флагами “только для чтения”, потому, что нам не нужно ничего записывать, нам хватит и простого чтения. Как я писал раньше, функция MapViewOfFile возвращает указатель на первый байт файла в памяти в регистре EAX.

Следующая часть кода, демонстрирует другой способ работы со структурами, для чтения PE заголовков. Структуры заголовков, уже есть в файле windows.inc и в этот раз, нам не нужно обьявлять новую структурную переменную. Мы просто применяем структуру, как шаблон к области памяти, где спроецирован наш файл.

Для начала нам нужен DOS заголовок. В нём нас интересуют всего два поля. Первое, имя которой e-magic и последнее, e_lfanew:


Для начала, обратимся к значению в поле e_magic. Конструкция для обращения будет следующей: IMAGE_DOS_HEADER.e_magic. Теперь, чтобы применить данный шаблон к нашему файлу в памяти и обратиться к значению поля e_magic, используем такую конструкцию: [EAX.IMAGE_DOS_HEADER.e_magic].

Если значение в поле равно значению IMAGE_DOS_SIGNATURE, мы можем продолжить проверять файл и дальше, получив значение поля e_lfanew и прибавив его, к начальному адресу нашего файла в памяти. Так мы получим указатель на PE заголовок:


Теперь, когда мы знаем адрес PE заголовка, мы можем проверить значение первого поля “Signature”. Если оно равно значению IMAGE_NT_SIGNATURE, тогда можно сказать что наш подопытный файл имеет верный формат PE, ну а если нет, то нет.

Код, который приведён здесь использует структуры, чтобы получать значения полей PE файла, но вы можете встретить исходники, где адреса полей получают путём сложения известных смещений в шестнадцатеричном формате. Например, поле e_lfanew, всегда находится на расстаянии 3Ch от начала файла и для того чтобы получить адрес PE заголовка, вы можете встретить инструкции вида add eax,[eax+3Ch], вместо add eax,[eax.IMAGE_DOS_HEADER.e_lfanew].

В следующем уроке, мы создадим приложение, которое может добавлять новую секцию в исполняемый файл, основываясь на уже написанном здесь коде.

Комментариев нет:

Отправка комментария