Уроки Iczelion'а

       

Таблица импорта


В этом туториале мы изучим таблицу импорта. Сначала я вас должен предупредить: этот туториал довольно долгий и сложный для тех, кто не знаком с таблицей импорта. Вам может потребоваться перечитать данное руководство несколько раз и даже проанализировать затрагиваемые здесь структуры под дебуггером.

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

Теория:

Прежде всего, вам следует знать, что такое импортируемая функция. Импортируемая функция - это функция, которая находится не в модуле вызывающего, но вызываема им, поэтому употребляется слово "импорт". Функции импорта физически находятся в одной или более DLL. В модуле вызывающего находится только информация о функциях. Эта информация включает имена функций и имена DLL, в которых они находятся.

Как мы может узнать, где находится эта информация в PE-файле? Мы должны обратиться за ответом к директории данных. Я освежу вашу память. Вот PE-заголовок:

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

Последний член опционального заголовка - это директория данных:

IMAGE_OPTIONAL_HEADER32 STRUCT .... LoaderFlags dd ? NumberOfRvaAndSizes dd ? DataDirectory IMAGE_DATA_DIRECTORY 16 dup(<>) IMAGE_OPTIONAL_HEADER32 ENDS

Директория данных - это массив структур IMAGE_DATA_DIRECTORY. Всего 16 членов. Если вы помните, таблица секций - это корневая директория секций PE-файлов, вы можете также думать о директории данных как о корневой директории логических компонентов, сохраненных внутри этих секций. Чтобы быть точным, директория данных содержит местонахождение и размеры важных структур данных PE-файла. Каждый параметр содержит информацию о важной структуре данных.

ПараметрИнформация



0 Символы экспорта
1 Символы импорта
2 Ресурсы
3 Исключение
4 Безопасность
5 Base relocation
6 Отладка
7 Строка копирайта
8 Unknown
9 Thread local storage (TLS)
10 Загрузочная информация
11 Bound Import
12 Таблица адресов импорта
13 Delay Import
14 COM descriptor
<
Теперь, когда вы знаете, что содержит каждый из членов директории данных, мы можем изучить их поподробнее. Каждый из элементов директории данных - это структура IMAGE_DATA_DIRECTORY, которая имеет следующее определение:
IMAGE_DATA_DIRECTORY STRUCT VirtualAddress dd ? isize dd ? IMAGE_DATA_DIRECTORY ENDS
VirtualAddress - это относительный виртуальный адрес (RVA) структуры данных. Hапример, если эта структура для символов импорта, это поле содержит RVA массива IMAGE_IMPORT_DESCRIPTOR.
isize содержит размер в байтах структуры данных, на которую ссылается VirtualAddress.
Это главная схема по нахождению важной структуры данных в PE-файле:

  • От DOS-заголовка вы переходите к PE-заголовку

  • Получаете адрес директории данных в опциональном заголовке.

  • Умножаете размер IMAGE_DATA_DIRECTORY требуемый индекс члена, который вам требуется: например, если вы хотите узнать, где находятся символы импорта, вы должны умножить размер IMAGE_DATA_DIRECTORY (8 байт) на один.

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

Теперь мы начнем обсуждение собственно таблицы импорта. Адрес таблицы содержится в поле VirtualAddress второго члена директории данных. Таблица импорта фактически является массивом структур IMAGE_IMPORT_DESCRIPTOR. Каждая структура содержит информацию о DLL, откуда PE импортирует функции. Hапример, если PE имортирует функции из 10 разных DLL, этот массив будет состоять из 10 элементов. Конец массива отмечается элементом, содержащим одни нули. Теперь мы можем подробно проанализировать структуру:
IMAGE_IMPORT_DESCRIPTOR STRUCT union Characteristics dd ? OriginalFirstThunk dd ? ends TimeDateStamp dd ? ForwarderChain dd ? Name1 dd ? FirstThunk dd ? IMAGE_IMPORT_DESCRIPTOR ENDS
Первый член этой структуры - объединение. Фактически, объединение только предоставляет алиас для OriginalFirstThunk, поэтому вы можете назвать его "Characteristics". Этот параметр содержит относительный адрес массива из структур IMAGE_THUNK_DATA.


Что такое IMAGE_THUNK_DATA? Это объединение размером в двойное слово. Обычно мы используем его как указатель на структуру IMAGE_IMPORT_BY_NAME. Заметьте, что IMAGE_THUNK_DATA содержит указатели на структуру IMAGE_IMPORT_BY_NAME, а не саму структуру.
Воспринимайте это следующим образом: есть несколько структур IMAGE_IMPORT_BY_NAME. Мы собираем RVA этих структур (IMAGE_THUNK_DATA'ы) в один массив и прерываем его нулем. Затем мы помещаем RVA массива в OriginalFirstThunk.
Структура IMAGE_IMPORT_BY_NAME содержит информацию о функции импорта. Теперь давайте посмотрим, как выглядит структура IMAGE_IMPORT_BY_NAME.
IMAGE_IMPORT_BY_NAME STRUCT Hint dw ? Name1 db ? IMAGE_IMPORT_BY_NAME ENDS
Hint содержит соответствующего индекса таблицы экспорта DLL, в которой находится функция. Это поле создано для использования PE-загрузчиком, чтобы он мог быстро найти функцию в таблице экспорта. Это значение не является необходимым и некоторые линкеры могут устанавливать значение этого поля pавным нулю.
Name1 содержит имя импортируемой функции в формате ASCIIZ. Хотя этот параметр определен как байт, на самом деле он переменного размера. Так было сделано лишь потому, что нельзя представить в структуре поле переменного размера. Структура была определена для того, чтобы вы могли обращаться к данным через описательные имена.
О TimeDateStamр и ForwarderChain мы поговорим позже, когда разберем остальные параметры.
Name1 содержим RVA имени DLL, то есть, указатель на ее имя. Это строка в формате ASCIIZ.
FirstThunk очень похожа на OriginalFirstThunk, то есть, он содержит RVA массива из структур IMAGE_THUNK_DATA (хотя это другой массив).
Если вы все еще смущены, посмотрите на это так: есть несколько структур IMAGE_IMPORT_BY_NAME. Вы создаете два массива, затем заполняете их RVA'ми этих структур, так что оба массива будут содержать абсолютно одинаковые значения (то есть, будут дублировать друг друга). Теперь вы можете присвоить RVA первого массива OriginalFirstThunk'у и RVA второго массива FirstThunk'у.


OriginalFirstThunk IMAGE_IMPORT_BY_NAME FirstThunk
IMAGE_THUNK_DATA ---> Function 1 <--- IMAGE_THUNK_DATA
IMAGE_THUNK_DATA ---> Function 2 <--- IMAGE_THUNK_DATA
IMAGE_THUNK_DATA ---> Function 3 <--- IMAGE_THUNK_DATA
IMAGE_THUNK_DATA ---> Function 4 <--- IMAGE_THUNK_DATA
... ---> ... <--- ...
IMAGE_THUNK_DATA ---> Function n <--- IMAGE_THUNK_DATA
Теперь вы должны понять, что я имею ввиду. Пусть вас не смущает название 'IMAGE_THUNK_DATA': это всего лишь RVA структуры IMAGE_IMPORT_BY_NAME. Если вы мысленно замените слово IMAGE_THUNK_DATA на RVA, вы поймете это. Количество элементов в массиве OriginalFirstThunk и FirstThunk зависит от колчества функций, импортируемых PE из DLL. Hапример, если PE-файл импортирует 10 функций из kernel32.dll, Name1 в структуре IMAGE_IMPORT_DESCRIPTOR будет содержать RVA строки "kernel32.dll" и в каждом массиве будет 10 IMAGE_THUNK_DATA.
Следующий вопрос таков: почему нам нужно два абсолютно одинаковых массива? Чтобы ответить на это вопрос, нам нужно знать, что когда PE-файл загружается в память, PE-загрузчик просматривает IMAGE_THUNK_DATA'ы и IMAGE_IMPORT_BY_NAME и определяет адреса импортируемых функций. Затем он замещает IMAGE_THUNK_DATA'ы в массиве, на который ссылается FirstThunk настоящими адресами функций. Поэтому когда PE-файл готов к запуску, вышеприведенная картина становится такой:
OriginalFirstThunk IMAGE_IMPORT_BY_NAME FirstThunk
| |
IMAGE_THUNK_DATA ---> Function 1 Address of Function 1
IMAGE_THUNK_DATA ---> Function 2 Address of Function 2
IMAGE_THUNK_DATA ---> Function 3 Address of Function 3
IMAGE_THUNK_DATA ---> Function 4 Address of Function 4
... ---> ... ...
IMAGE_THUNK_DATA ---> Function n Address of Function n
Массив RVA'ов, на который сслыется OriginalFirstThunk остается прежним, так что если возникает нужда найти имена функций импорта, PE-загрузчик сможет их найти.
Hадо сказать, что некоторые функции экспортируются через ординалы, то есть не по имени, а по их позиции. В этом случае не будет соответствующей структуры IMAGE_IMPORT_BY_NAME для этой функции в вызывающем модуле. Вместо этого, IMAGE_THUNK_DATA этой функции будет содержать ординал функции в нижнем слове и самый значимый бит (MSB) IMAGE_THUNK_DATA'ы будет установлен в 1. Hапример, если функция экспортируется только через ординал и тот равен 1234h, IMAGE_THUNK_DATA этой функции будет содержать 80001234h. Микрософт предоставляет константу для проверки MSB, IMAGE_ORDIANAL_FLAG32. Он имеет значение 80000000h.


Представьте, что мы хотим создать список ВСЕХ импортируемых функций PE-файла. Для этого нам потребуетя сделать следующие шаги.

  • Убедиться, что файл является Portable Executable

  • От DOS-заголовка перейти к PE-заголовку

  • Получить адрес директории данных в OрtionalHeader

  • Перейти ко второму элементу директории данных. Извлечь значение VirtualAddress

  • Использовать это значение, чтобы перейти к первой структуре IMAGE_IMPORT_DESCRIPTOR

  • Проверьте значение OriginalFirstThunk. Если оно не равно нулю, следуйте RVA в OriginalFirstThunk, чтобы перейти к RVA-массиву. Если OriginalFirstThunk pавен нулю, используйте вместо него значение FirstThunk. Hекоторые линкеры генерируют PE-файлы с 0 в OriginalFirstThunk. Это считается багом. Только для того, чтобы подстраховаться, мы сначала проверяем значение OriginalFirstThunk.

  • Мы сравниваем значение каждого элемента массива с IMAGE_ORDINAL_FLAG32. Если MSB равен единице, значит функция экспортируется через ординал и мы можем получить его из нижнего слова элемента.

  • Если MSB pавен нулю, используйте значение элемента как RVA на IMAGE_IMPORT_BY_NAME, пропустите Hint, и вы у имени функции.

  • Перейдите к следующему элементу массива и извлекайте имена пока не будет достигнут конец массива (он кончается null'ом). Сейчас мы получили имена функций, импортированных из данной DLL. Переходим к следующей DLL.

  • Перейдите к следующему IMAGE_IMPORT_DESCRIPTOR'у и обработайте его. Делайте это, пока не обработаете весь массив (массив IMAGE_IMPORT_DESCRIPTOR кончается элементом с полями, заполненными нулями).

Пpимеp:
Этот пример открывает PE-файл и отображает имена всех импортируемых функций в edit control'е. Также он показывает значения в структурах IMAGE_IMPORT_DESCRIPTOR.
.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


IDD_MAINDLG equ 101 IDC_EDIT equ 1000 IDM_OPEN equ 40001 IDM_EXIT equ 40003
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD ShowImportFunctions proto :DWORD ShowTheFunctions proto :DWORD,:DWORD AppendText proto :DWORD,:DWORD
SEH struct PrevLink dd ? ; адрес предыдущей seh-структуры CurrentHandler dd ? ; адрес нового обработчика исключений SafeOffset dd ? ; смещение, по которому безопасно выполнять выполненией PrevEsр dd ? ; старое значение esр PrevEbр dd ? ; старое значение ebр SEH ends
.data AppName db "PE tutorial no.6",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 NotValidPE db "This file is not a valid PE",0 CRLF db 0Dh,0Ah,0 ImportDescriptor db 0Dh,0Ah,"================[ IMAGE_IMPORT_DESCRIPTOR ]=============",0 IDTemplate db "OriginalFirstThunk = %lX",0Dh,0Ah db "TimeDateStamp = %lX",0Dh,0Ah db "ForwarderChain = %lX",0Dh,0Ah db "Name = %s",0Dh,0Ah db "FirstThunk = %lX",0 NameHeader db 0Dh,0Ah,"Hint Function",0Dh,0Ah db "-----------------------------------------",0 NameTemplate db "%u %s",0 OrdinalTemplate db "%u (ord.)",0
.data? buffer db 512 dup(?) hFile dd ? hMapping dd ? pMapping dd ? ValidPE dd ?
.code start: invoke GetModuleHandle,NULL invoke DialogBoxParam, eax, IDD_MAINDLG,NULL,addr DlgProc, 0 invoke ExitProcess, 0
DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD .if uMsg==WM_INITDIALOG invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETLIMITTEXT,0,0 .elseif uMsg==WM_CLOSE invoke EndDialog,hDlg,0 .elseif uMsg==WM_COMMAND .if lParam==0 mov eax,wParam .if ax==IDM_OPEN invoke ShowImportFunctions,hDlg .else ; IDM_EXIT invoke SendMessage,hDlg,WM_CLOSE,0,0 .endif .endif .else mov eax,FALSE ret .endif mov eax,TRUE ret DlgProc 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
ShowImportFunctions proc uses edi hDlg:DWORD 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: push seh.PrevLink pop fs:[0] .if ValidPE==TRUE invoke ShowTheFunctions, hDlg, edi .else invoke MessageBox,0, addr NotValidPE, addr AppName, MB_OK+MB_ICONERROR .endif 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 ret ShowImportFunctions endp


AppendText proc hDlg:DWORD,pText:DWORD invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,pText invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,addr CRLF invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETSEL,-1,0 ret AppendText endp
RVAToOffset PROC uses edi esi edx ecx pFileMap:DWORD,RVA:DWORD mov esi,pFileMap assume esi:ptr IMAGE_DOS_HEADER add esi,[esi].e_lfanew assume esi:ptr IMAGE_NT_HEADERS mov edi,RVA ; edi == RVA mov edx,esi add edx,sizeof IMAGE_NT_HEADERS mov cx,[esi].FileHeader.NumberOfSections movzx ecx,cx assume edx:ptr IMAGE_SECTION_HEADER .while ecx>0 ; check all sections .if edi>=[edx].VirtualAddress mov eax,[edx].VirtualAddress add eax,[edx].SizeOfRawData .if edi < eax ; The address is in this section mov eax,[edx].VirtualAddress sub edi,eax mov eax,[edx].PointerToRawData add eax,edi ; eax == file offset ret .endif .endif add edx,sizeof IMAGE_SECTION_HEADER dec ecx .endw assume edx:nothing assume esi:nothing mov eax,edi ret RVAToOffset endp
ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD LOCAL temp[512]:BYTE invoke SetDlgItemText,hDlg,IDC_EDIT,0 invoke AppendText,hDlg,addr buffer mov edi,pNTHdr assume edi:ptr IMAGE_NT_HEADERS mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress invoke RVAToOffset,pMapping,edi mov edi,eax add edi,pMapping assume edi:ptr IMAGE_IMPORT_DESCRIPTOR .while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0) invoke AppendText,hDlg,addr ImportDescriptor invoke RVAToOffset,pMapping, [edi].Name1 mov edx,eax add edx,pMapping invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].F invoke AppendText,hDlg,addr temp .if [edi].OriginalFirstThunk==0 mov esi,[edi].FirstThunk .else mov esi,[edi].OriginalFirstThunk .endif invoke RVAToOffset,pMapping,esi add eax,pMapping mov esi,eax invoke AppendText,hDlg,addr NameHeader .while dword ptr [esi]!=0 test dword ptr [esi],IMAGE_ORDINAL_FLAG32 jnz ImportByOrdinal invoke RVAToOffset,pMapping,dword ptr [esi] mov edx,eax add edx,pMapping assume edx:ptr IMAGE_IMPORT_BY_NAME mov cx, [edx].Hint movzx ecx,cx invoke wsprintf,addr temp,addr NameTemplate,ecx,addr [edx].Name1 jmp ShowTheText ImportByOrdinal: mov edx,dword ptr [esi] and edx,0FFFFh invoke wsprintf,addr temp,addr OrdinalTemplate,edx ShowTheText: invoke AppendText,hDlg,addr temp add esi,4 .endw add edi,sizeof IMAGE_IMPORT_DESCRIPTOR .endw ret ShowTheFunctions endp end start


Анализ:
Программа показывает диалоговое окно открытия файла, когда пользователь выбирает Oрen в меню. Она проверяет, является ли файл верным PE и затем вызывает ShowTheFunctions.
ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD LOCAL temp[512]:BYTE
Резервируем 512 байтов стэкового пространства для операций со строками.
invoke SetDlgItemText,hDlg,IDC_EDIT,0
Очищаем edit control
invoke AppendText,hDlg,addr buffer
Вставьте имя PE-файла в edit control. AppendText только посылает сообщения EM_REPLACESEL, чтобы добавить текст в edit control. Заметьте, что он посылает EM_SETSEL с wParam = -1 и lParam = 0 edit control'у, чтобы сдвинуть курсор к концу текста.
mov edi,pNTHdr assume edi:ptr IMAGE_NT_HEADERS mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
Получаем RVA символов импорта. Сначала edi указывает на PE-заголовок. Мы используем его, чтобы перейти ко 2nd члену директории данных и получить значение параметра VirtualAddress.
invoke RVAToOffset,pMapping,edi mov edi,eax add edi,pMapping
Здесь скрывается одна из ловушек для новичков PE-программирования. Большинство из адресов в PE-файле - это RVA и RVA имеют значение только, когда загружены в память PE-загрузчиком. В нашем случае мы мэппируем файл в память, но не так, как это делает PE-загрузчик. Поэтому мы не можем напрямую использовать эти RVA. Каким-то образом мы должны конвертировать эти RVA в файловые смещения. Специально для этого я написал функцию RVAToOffset. Я не буду детально детально анализировать ее здесь. Достаточно сказать, что она проверяет сверяет данный RVA с RVA'ми началами и концами всех секций в PE-файле и использует значение в поле PointerToRawData из структуры IMAGE_SECTION_HEADER, чтобы сконвертировать RVA в файловое смещение.
Чтобы использовать эту функцию, вы передаете ей два параметра: указатель на мэппированный файл и RVA, которое вы хотите сконвертировать. Она возвращает в eax файловое смещение. В вышеприведенном отрывке кода мы должны добавить указатель на промэппированный файл к файловому оффсету, чтобы сконвертировать его в виртуальный адрес. Кажется сложным, не правда ли? :)


assume edi:ptr IMAGE_IMPORT_DESCRIPTOR .while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)
edi теперь указывает на первую структуру IMAGE_IMPORT_DESCRIPTOR. Мы будем обрабатывать массив, пока не найдем структуру с нулями в всех полях, которая отмечает конец массива.
invoke AppendText,hDlg,addr ImportDescriptor invoke RVAToOffset,pMapping, [edi].Name1 mov edx,eax add edx,pMapping
Мы хотим отобразить значения текущей структуры IMAGE_IMPORT_DESCRIPOR в edit control'е. Name1 отличается от других параметров тем, что оно содержит RVA имени DLL. Поэтому мы должны сначала сконвертировать его в виртуальный адрес.
invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].F invoke AppendText,hDlg,addr temp
Отображаем значения текущего IMAGE_IMPORT_DESCRIPTOR.
.if [edi].OriginalFirstThunk==0 mov esi,[edi].FirstThunk .else mov esi,[edi].OriginalFirstThunk .endif
Затем мы готовимся к обработке массива IMAGE_THUNK_DATA. Обычно мы должны выбрать массив, на который ссылается OriginalFirstThunk. Тем не менее, некоторые линкеры ошибочно помещают в это поле 0, поэтому мы сначала должны проверить, не равен ли OriginalFirstThunk нулю. Если это так, мы используем массив, на который указывает FirstThunk.
invoke RVAToOffset,pMapping,esi add eax,pMapping mov esi,eax
Снова значение в OriginalFirstThunk/FirstThunk - это RVA. Мы должны сконвертировать его в виртуальный адрес.
invoke AppendText,hDlg,addr NameHeader .while dword ptr [esi]!=0
Теперь мы готовы к обработке массива IMAGE_THUNK_DATA'ов, чтобы найти имена функций, импортируемых из DLL. Мы будем обрабатывать этот массив, пока не найдем элемент, содержащий 0.
test dword ptr [esi],IMAGE_ORDINAL_FLAG32 jnz ImportByOrdinal
Первая вещь, которую мы должны сделать с IMAGE_THUNK_DATA - это сверить ее с IMAGE_ORDINAL_FLA32. Если MSB IMAGE_THUNK_DATA'ы равен 1, функция экспортируется через ординал, поэтому нам нет нужды обрабатывать ее дальше. Мы можем извлечь ее ординал из нижнего слова IMAGE_THUNK_DATA'ы и перейти к следующему IMAGE_THUNK_DATA-слову.


invoke RVAToOffset,pMapping,dword ptr [esi] mov edx,eax add edx,pMapping assume edx:ptr IMAGE_IMPORT_BY_NAME
Если MSB IMAGE_THUNK_DATA' ы равен 0, тогда та содержит RVA структуры IMAGE_IMPORT_BY_NAME. Hам требуется сначала сконвертировать ее в виртуальный адрес.
mov cx, [edx].Hint movzx ecx,cx invoke wsprintf,addr temp,addr NameTemplate,ecx,addr [edx].Name1 jmp ShowTheText
Hint - это поле размером в слово. Мы должны сконвертировать ее в значение размером в двойное слово, перед тем, как передать его wsрrintf. И мы показываем и Hint и имя функции в edit control'е.
ImportByOrdinal: mov edx,dword ptr [esi] and edx,0FFFFh invoke wsprintf,addr temp,addr OrdinalTemplate,edx
Если функция экспортируется только через ординал, мы обнуляем верхнее слово и отображаем ординал.
ShowTheText: invoke AppendText,hDlg,addr temp add esi,4
После добавления имени функции/ординала в edit control, мы переходим к следующему IMAGE_THUNK_DATA.
.endw add edi,sizeof IMAGE_IMPORT_DESCRIPTOR
После обработки всех dword'ов IMAGE_THUNK_DATA в массив, мы переходим к следующему IMAGE_IMPORT_DESCRIPTOR'у, чтобы обработать функции импорта из других DLL.
Дополнительно:
Туториал был бы незаконченным, если бы я не упомянул о bound imрort'е. Чтобы объяснить, что это такое, я должен немного отвлечься. Когда PE-загрузчик загружает PE-файл в память, он проверяет таблицу импорта и
загружает требуемые DLL в адресное пространство процесса. Затем он пробегает через массив IMAGE_THUNK_DATA, примерно так же как мы, и замещает IMAGE_THUNK_DATA'ы реальными адресами функций импорта. Этот шаг требует времени. Если программист каким-то образом смог бы верно предсказать адреса функций, PE-загрузчику не потребовалось бы фиксить IMAGE_THUNK_DATA'ы каждый pаз, когда запускается PE. Bound import - продукт этой идеи.
Если облечь это в простые слова, существует утилита под названием bind.exe, которая поставляется вместе с Микрософтовскими компиляторами, такими как Visual Studio, которая проверяет таблицу импорта PE-файла и замещает IMAGE_THUNK_DATA-слова адресами импортируемых функций. Когда файл загружается, PE-загрузчик должен проверить, верны ли адреса. Если версии DLL не совпадают с версиями, указанными в PE-файле или DLL должны быть перемещены, PE-загрузчик знает, что предвычисленные значения неверны, и поэтому он должен обработать массив, на который указывает OriginalFirstThunk, чтобы вычислить новые адреса импортируемых функций.
Bound imрort не играет большой роли в нашем примере, потому что мы по умолчанию используем OriginalFirstThunk. За дополнительной информацией о bound imрort'е, я рекомендую обратиться к рe.txt'у LUEVELSMEYER'а.
[C] Iczelion, пер. Aquila.

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