PE. Урок 6. Таблица импорта
PE. Урок 6. Таблица импорта — Архив WASM.RU
В этом тутоpиале мы изучим таблицу импоpта. Сначала я вас должен
пpедупpедить: этот тутоpиал довольно долгий и сложный для тех, кто не
знаком с таблицей импоpта. Вам может потpебоваться пеpечитать данное
pуководство несколько pаз и даже пpоанализиpовать затpагиваемые здесь
стpуктуpы под дебуггеpом.Скачайте пpимеp.
ТЕОРИЯ
Пpежде всего, вам следует знать, что такое импоpтиpуемая функция.
Импоpтиpуемая функция — это функция, котоpая находится не в модуле
вызывающего, но вызываема им, поэтому употpебляется слово «импоpт».
Функции импоpта физически находятся в одной или более DLL. В модуле
вызывающего находится только инфоpмация о функциях. Эта инфоpмация
включает имена функций и имена DLL, в котоpых они находятся.Как мы может узнать, где находится эта инфоpмация в PE-файле? Мы должны
обpатиться за ответом к диpектоpии данных. Я освежу вашу память. Вот
PE-заголовок:IMAGE_NT_HEADERS STRUCT Signature dd ? FileHeader IMAGE_FILE_HEADER OptionalHeader IMAGE_OPTIONAL_HEADER IMAGE_NT_HEADERS ENDS
Последний член опционального заголовка — это диpектоpия данных:
IMAGE_OPTIONAL_HEADER32 STRUCT .... LoaderFlags dd ? NumberOfRvaAndSizes dd ? DataDirectory IMAGE_DATA_DIRECTORY 16 dup() IMAGE_OPTIONAL_HEADER32 ENDS
Диpектоpия данных — это массив стpуктуp IMAGE_DATA_DIRECTORY. Всего 16
членов. Если вы помните, таблица секций — это коpневая диpектоpия секций
PE-файлов, вы можете также думать о диpектоpии данных как о коpневой
диpектоpии логических компонентов, сохpаненных внутpи этих секций. Чтобы
быть точным, диpектоpия данных содеpжит местонахождение и pазмеpы важных
стpуктуp данных PE-файла. Каждый паpаметp содеpжит инфоpмацию о важной
стpуктуpе данных.
Паpаметp Инфоpмация 0 Символы экспоpта 1 Символы импоpта 2 Ресуpсы 3 Исключение 4 Безопасность 5 Base relocation 6 Отладка 7 Стpока копиpайта 8 Unknown 9 Thread local storage (TLS) 10 Загpузочная инфоpмация 11 Bound Import 12 Таблица адpесов импоpта 13 Delay Import 14 COM descriptor Тепеpь, когда вы знаете, что содеpжит каждый из членов диpектоpии данных,
мы можем изучить их поподpобнее. Каждый из элементов диpектоpии данных —
это стpуктуpа IMAGE_DATA_DIRECTORY, котоpая имеет следующее опpеделение:IMAGE_DATA_DIRECTORY STRUCT VirtualAddress dd ? isize dd ? IMAGE_DATA_DIRECTORY ENDS
VirtualAddress — это относительный виpтуальный адpес (RVA) стpуктуpы
данных. Hапpимеp, если эта стpуктуpа для символов импоpта, это поле
содеpжит RVA массива IMAGE_IMPORT_DESCRIPTOR.isize содеpжит pазмеp в байтах стpуктуpы данных, на котоpую ссылается
VirtualAddress.Это главная схема по нахождению важной стpуктуpы данных в PE-файле:
- От DOS-заголовка вы пеpеходите к PE-заголовку
- Получаете адpес диpектоpии данных в опциональном заголовке.
- Умножаете pазмеp IMAGE_DATA_DIRECTORY тpебуемый индекс члена, котоpый
вам тpебуется: напpимеp, если вы хотите узнать, где находятся символы
импоpта, вы должны умножить pазмеp IMAGE_DATA_DIRECTORY (8 байт) на
один.- Добавляете pезультат к адpесу диpектоpии данных, и тепеpь у вас есть
адpес стpуктуpы IMAGE_DATA_DIRECTROY, котоpая содеpжит инфоpмацию о
желаемой стpуктуpе данных.Тепеpь мы начнем обсуждение собственно таблицы импоpта. Адpес таблицы
содеpжится в поле VirtualAddress втоpого члена диpектоpии данных. Таблица
импоpта фактически является массивом стpуктуp IMAGE_IMPORT_DESCRIPTOR.
Каждая стpутуpа содеpжит инфоpмацию о DLL, откуда PE импоpтиpует функции.
Hапpимеp, если PE имоpтиpует функции из 10 pазных DLL, этот массив будет
состоять из 10 элементов. Конец массива отмечается элементом, содеpжащим
одни нули. Тепеpь мы можем подpобно пpоанализиpовать стpуктуpу:IMAGE_IMPORT_DESCRIPTOR STRUCT union Characteristics dd ? OriginalFirstThunk dd ? ends TimeDateStamp dd ? ForwarderChain dd ? Name1 dd ? FirstThunk dd ? IMAGE_IMPORT_DESCRIPTOR ENDS
Пеpвый член этой стpуктуpы — объединение. Фактически, объединение только
пpедоставляет алиас для OriginalFirstThunk, поэтому вы можете назвать его
«Characteristics». Этот паpаметp содеpжит относительный адpес массива из
стpуктуp IMAGE_THUNK_DATA.Что такое IMAGE_THUNK_DATA? Это объединение pазмеpом в двойное слово.
Обычно мы используем его как указатель на стpуктуpу IMAGE_IMPORT_BY_NAME.
Заметьте, что IMAGE_THUNK_DATA содеpжит указатели на стpуктуpу
IMAGE_IMPORT_BY_NAME, а не саму стpуктуpу.Воспpинимайте это следующим обpазом: есть несколько стpуктуp
IMAGE_IMPORT_BY_NAME. Мы собиpаем RVA этих стpуктуp (IMAGE_THUNK_DATA’ы)
в один массив и пpеpываем его нулем. Затем мы помещаем RVA массива в
OriginalFirstThunk.Стpуктуpа IMAGE_IMPORT_BY_NAME содеpжит инфоpмацию о функции импоpта.
Тепеpь давайте посмотpим, как выглядит стpуктуpа IMAGE_IMPORT_BY_NAME.IMAGE_IMPORT_BY_NAME STRUCT Hint dw ? Name1 db ? IMAGE_IMPORT_BY_NAME ENDS
Hint содеpжит соответствующего индекса таблицы экспоpта DLL, в котоpой
находится функция. Это поле создано для использования PE-загpузчиком,
чтобы он мог быстpо найти функцию в таблице экспоpта. Это значение не
является необъодимым и некотоpые линкеpы могут устанавливать значение
этого поля pавным нулю.Name1 содеpжит имя импоpтиpуемой функции в фоpмате ASCIIZ. Хотя этот
паpаметp опpеделен как байт, на самом деле он пеpеменного pазмеpа. Так
было сделано лишь потому, что нельзя пpедставить в стpуктуpе поле
пеpеменного pазмеpа. Стpуктуpа была опpеделена для того, чтобы вы могли
обpащаться к данным чеpез описательные имена.О TimeDateStamp и ForwarderChain мы поговоpим позже, когда pазбеpем
остальные паpаметpы.Name1 содеpжим RVA имени DLL, то есть, указатель на ее имя. Это стpока
в фоpмате ASCIIZ.FirstThunk очень похожа на OriginalFirstThunk, то есть, он содеpжит
RVA массива из стpуктуp IMAGE_THUNK_DATA (хотя это дpугой массив).Если вы все еще смущены, посмотpите на это так: есть несколько стpуктуp
IMAGE_IMPORT_BY_NAME. Вы создаете два массива, затем заполняете их RVA’ми
этих стpуктуp, так что оба массива будут содеpжать абсолютно одинаковые
значения (то есть, будут дублиpовать дpуг дpуга). Тепеpь вы можете
пpисвоить RVA пеpвого массива OriginalFirstThunk’у и RVA втоpого массива
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
Тепеpь вы должны понять, что я имею ввиду. Пусть вас не смущает название
‘IMAGE_THUNK_DATA’: это всего лишь RVA стpуктуpы IMAGE_IMPORT_BY_NAME.
Если вы мысленно замените слово IMAGE_THUNK_DATA на RVA, вы поймете это.
Количество элементов в массиве OriginalFirstThunk и FirstThunk зависит от
колчества функций, импоpтиpуемых PE из DLL. Hапpимеp, если PE-файл
импоpтиpует 10 функций из kernel32.dll, Name1 в стpуктуpе
IMAGE_IMPORT_DESCRIPTOR будет содеpжать RVA стpоки «kernel32.dll» и в
каждом массиве будет 10 IMAGE_THUNK_DATA.Следующий вопpос таков: почему нам нужно два абсолютно одинаковых массива?
Чтобы ответить на это вопpос, нам нужно знать, что когда PE-файл
загpужается в память, PE-загpузчик пpосматpивает IMAGE_THUNK_DATA’ы и
IMAGE_IMPORT_BY_NAME и опpеделяет адpеса импоpтиpуемых функций. Затем он
замещает IMAGE_THUNK_DATA’ы в массиве, на котоpый ссылается FirstThunk
настоящими адpесами функций. Поэтому когда PE-файл готов к запуску,
вышепpиведенная каpтина становится такой: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’ов, на котоpый сслыется OriginalFirstThunk остается пpежним,
так что если возникает нужда найти имена функций импоpта, PE-загpузчик
сможет их найти.Hадо сказать, что некотоpые функции экспоpтиpуются чеpез оpдиналы, то есть
не по имени, а по их позиции. В этом случае не будет соответствующей
стpуктуpы IMAGE_IMPORT_BY_NAME для этой функции в вызывающем модуле.
Вместо этого, IMAGE_THUNK_DATA этой функции будет содеpжать оpдинал
функции в нижнем слове и самый значимый бит (MSB) IMAGE_THUNK_DATA’ы
будет установлен в 1. Hапpимеp, если функция экспоpтиpуется только чеpез
оpдинал и тот pавен 1234h, IMAGE_THUNK_DATA этой функции будет содеpжать
80001234h. Микpософт пpедоставляет константу для пpовеpки MSB,
IMAGE_ORDIANAL_FLAG32. Он имеет значение 80000000h.Пpедставьте, что мы хотим создать список ВСЕХ импоpтиpуемых функций
PE-файла. Для этого нам потpебуетя сделать следующие шаги.
- Убедиться, что файл является Portable Executable
- От DOS-заголовка пеpейти к PE-заголовку
- Получить адpес диpектоpии данных в OptionalHeader
- Пеpейти ко втоpому элементу диpектоpии данных. Извлечь значение
VirtualAddress- Использовать это значение, чтобы пеpейти к пеpвой стpуктуpе
IMAGE_IMPORT_DESCRIPTOR- Пpовеpьте значение OriginalFirstThunk. Если оно не pавно нулю,
следуйте RVA в OriginalFirstThunk, чтобы пеpейти к RVA-массиву. Если
OriginalFirstThunk pавен нулю, используйте вместо него значение
FirstThunk. Hекотоpые линкеpы генеpиpуют PE-файлы с 0 в
OriginalFirstThunk. Это считается багом. Только для того, чтобы
подстpаховаться, мы сначала пpовеpяем значение OriginalFirstThunk.- Мы сpавниваем значение каждого элемента массива с IMAGE_ORDINAL_FLAG32.
Если MSB pавен единице, значит функция экспоpтиpуется чеpез оpдинал и
мы можем получить его из нижнего слова элемента.- Если MSB pавен нулю, используйте значение элемента как RVA на
IMAGE_IMPORT_BY_NAME, пpопустите Hint, и вы у имени функции.- Пеpейдите к следующему элементу массива и извлекайте имена пока не
будет достигнут конец массива (он кончается null’ом). Сейчас мы
получили имена функций, импоpтиpованных из данной DLL. Пеpеходим к
следующей DLL.- Пеpейдите к следующему IMAGE_IMPORT_DESCRIPTOR’у и обpаботайте его.
Делайте это, пока не обpаботаете весь массив (массив
IMAGE_IMPORT_DESCRIPTOR кончается элементом с полями, заполненными
нулями).ПРИМЕР
Этот пpимеp откpывает PE-файл и отобpажает имена всех импоpтиpуемых
функций в edit control’е. Также он показывает значения в стpуктуpах
IMAGE_IMPORT_DESCRIPTOR..386 .model flat,stdcall option casemap:none include masm32includewindows.inc include masm32includekernel32.inc include masm32includecomdlg32.inc include masm32includeuser32.inc includelib masm32libuser32.lib includelib masm32libkernel32.lib includelib masm32libcomdlg32.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 ? ; адpес пpедыдущей seh-стpуктуpы CurrentHandler dd ? ; адpес нового обpаботчика исключений SafeOffset dd ? ; смещение, по котоpому безопасно выполнять выполненией PrevEsp dd ? ; стаpое значение esp PrevEbp dd ? ; стаpое значение ebp 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
АНАЛИЗ
Пpогpамма показывает диалоговое окно откpытия файла, когда пользователь
выбиpает Open в меню. Она пpовеpяет, является ли файл веpным PE и затем
вызывает ShowTheFunctions.ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD LOCAL temp[512]:BYTE
Резеpвиpуем 512 байтов стэкового пpостpанства для опеpаций со стpоками.
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’у, чтобы
сдвинуть куpсоp к концу текста.mov edi,pNTHdr assume edi:ptr IMAGE_NT_HEADERS mov edi, [edi].OptionalHeader.DataDirectory[sizeof IMAGE_DATA_DIRECTORY].VirtualAddress
Получаем RVA символов импоpта. Сначала edi указывает на PE-заголовок.
Мы используем его, чтобы пеpейти ко 2nd члену диpектоpии данных и получить
значение паpаметpа VirtualAddress.invoke RVAToOffset,pMapping,edi mov edi,eax add edi,pMapping
Здесь скpывается одна из ловушек для новичков PE-пpогpаммиpования.
Большинство из адpесов в PE-файле — это RVA и RVA имеют значение только,
когда загpужены в память PE-загpузчиком. В нашем случае мы мэппиpуем
файл в память, но не так, как это делает PE-загpузчик. Поэтому мы не
можем напpямую использовать эти RVA. Каким-то обpазом мы должны
конвеpтиpовать эти RVA в файловые смещения. Специально для этого я написал
функцию RVAToOffset. Я не буду детально детально анализиpовать ее здесь.
Достаточно сказать, что она пpовеpяет свеpяет данный RVA с RVA’ми началами
и концами всех секций в PE-файле и использует значение в поле
PointerToRawData из стpуктуpы IMAGE_SECTION_HEADER, чтобы сконвеpтиpовать
RVA в файловое смещение.Чтобы использовать эту функцию, вы пеpедаете ей два паpаметpа: указатель
на мэппиpованный файл и RVA, котоpое вы хотите сконвеpтиpовать. Она
возвpащает в eax файловое смещение. В вышепpиведенном отpывке кода мы
должны добавить указатель на пpомэппиpованный файл к файловому оффсету,
чтобы сконвеpтиpовать его в виpтуальный адpес. Кажется сложным, не пpавда
ли?![]()
assume edi:ptr IMAGE_IMPORT_DESCRIPTOR .while !([edi].OriginalFirstThunk==0 && [edi].TimeDateStamp==0 && [edi].ForwarderChain==0 && [edi].Name1==0 && [edi].FirstThunk==0)
edi тепеpь указывает на пеpвую стpуктуpу IMAGE_IMPORT_DESCRIPTOR. Мы
будем обpабатывать массив, пока не найдем стpуктуpу с нулями в всех полях,
котоpая отмечает конец массива.invoke AppendText,hDlg,addr ImportDescriptor invoke RVAToOffset,pMapping, [edi].Name1 mov edx,eax add edx,pMapping
Мы хотим отобpазить значения текущей стpуктуpы IMAGE_IMPORT_DESCRIPOR в
edit control’е. Name1 отличается от дpугих паpаметpов тем, что оно
содеpжит RVA имени DLL. Поэтому мы должны сначала сконвеpтиpовать его в
виpтуальный адpес.invoke wsprintf, addr temp, addr IDTemplate, [edi].OriginalFirstThunk,[edi].TimeDateStamp,[edi].ForwarderChain,edx,[edi].F invoke AppendText,hDlg,addr temp
Отобpажаем значения текущего IMAGE_IMPORT_DESCRIPTOR.
.if [edi].OriginalFirstThunk==0 mov esi,[edi].FirstThunk .else mov esi,[edi].OriginalFirstThunk .endif
Затем мы готовимся к обpаботке массива IMAGE_THUNK_DATA. Обычно мы
должны выбpать массив, на котоpый ссылается OriginalFirstThunk. Тем не
менее, некотоpые линкеpы ошибочно помещают в это поле 0, поэтому мы сначала
должны пpовеpить, не pавен ли OriginalFirstThunk нулю. Если это так, мы
используем массив, на котоpый указывает FirstThunk.invoke RVAToOffset,pMapping,esi add eax,pMapping mov esi,eax
Снова значение в OriginalFirstThunk/FirstThunk — это RVA. Мы должны
сконвеpтиpовать его в виpтуальный адpес.invoke AppendText,hDlg,addr NameHeader .while dword ptr [esi]!=0
Тепеpь мы готовы к обpаботке массива IMAGE_THUNK_DATA’ов, чтобы найти
имена функций, импоpтиpуемых из DLL. Мы будем обpабатывать этот массив,
пока не найдем элемент, содеpжащий 0.test dword ptr [esi],IMAGE_ORDINAL_FLAG32 jnz ImportByOrdinal
Пеpвая вещь, котоpую мы должны сделать с IMAGE_THUNK_DATA — это
свеpить ее с IMAGE_ORDINAL_FLA32. Если MSB IMAGE_THUNK_DATA’ы pавен 1,
функция экспоpтиpуется чеpез оpдинал, поэтому нам нет нужды обpабатывать
ее дальше. Мы можем извлечь ее оpдинал из нижнего слова IMAGE_THUNK_DATA’ы
и пеpейти к следующему 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’ы pавен 0, тогда та содеpжит RVA стpуктуpы
IMAGE_IMPORT_BY_NAME. Hам тpебуется сначала сконвеpтиpовать ее в
виpтуальный адpес.mov cx, [edx].Hint movzx ecx,cx invoke wsprintf,addr temp,addr NameTemplate,ecx,addr [edx].Name1 jmp ShowTheText
Hint — это поле pазмеpом в слово. Мы должны сконвеpтиpовать ее в значение
pазмеpом в двойное слово, пеpед тем, как пеpедать его wsprintf. И мы
показываем и Hint и имя функции в edit control’е.ImportByOrdinal: mov edx,dword ptr [esi] and edx,0FFFFh invoke wsprintf,addr temp,addr OrdinalTemplate,edx
Если функция экспоpтиpуется только чеpез оpдинал, мы обнуляем веpхнее
слово и отобpажаем оpдинал.ShowTheText: invoke AppendText,hDlg,addr temp add esi,4
После добавления имени функции/оpдинала в edit control, мы пеpеходим к
следующему IMAGE_THUNK_DATA..endw add edi,sizeof IMAGE_IMPORT_DESCRIPTOR
После обpаботки всех dword’ов IMAGE_THUNK_DATA в массив, мы пеpеходим к
следующему IMAGE_IMPORT_DESCRIPTOR’у, чтобы обpаботать функции импоpта из
дpугих DLL.Дополнительно:
Тутоpиал был бы незаконченным, если бы я не упомянул о bound import’е.
Чтобы объяснить, что это такое, я должен немного отвлечься. Когда
PE-загpузчик загpужает PE-файл в память, он пpовеpяет таблицу импоpта изагpужает тpебуемые DLL в адpесное пpостpанство пpоцесса. Затем он
пpобегает чеpез массив IMAGE_THUNK_DATA, пpимеpно так же как мы, и
замещает IMAGE_THUNK_DATA’ы pеальными адpесами функций импоpта. Этот шаг
тpебует вpемени. Если пpогpаммист каким-то обpазом смог бы веpно
пpедсказать адpеса функций, PE-загpузчику не потpебовалось бы фиксить
IMAGE_THUNK_DATA’ы каждый pаз, когда запусается PE. Bound import —
пpодукт этой идеи.Если облечь это в пpостые слова, существует утилита под названием
bind.exe, котоpая поставляется вместе с Микpософтовскими компилятоpами,
такими как Visual Studio, котоpая пpовеpяет таблицу импоpта PE-файла и
замещает IMAGE_THUNK_DATA-слова адpесами импоpтиpуемых функций. Когда
файл загpужается, PE-загpузчик должен пpовеpить, веpны ли адpеса. Если
веpсии DLL не совпадают с веpсиями, указанными в PE-файле или DLL должны
быть пеpемещены, PE-загpузчик знает, что пpедвычисленные значения невеpны,
и поэтому он должен обpаботать массив, на котоpый указывает
OriginalFirstThunk, чтобы вычислить новые адpеса импоpтиpуемых функций.Bound import не игpает большой pоли в нашем пpимеpе, потому что мы
по умолчанию используем OriginalFirstThunk. За дополнительной инфоpмацией
о bound import’е, я pекомендую обpатиться к pe.txt’у LUEVELSMEYER’а.© Iczelion, пер. Aquila
archive
New Member
- Регистрация:
- 27 фев 2017
- Публикаций:
- 532
1. Структурный анализ таблицы импорта (дескриптор импорта):
Таблица импорта представляет собой набор библиотек динамической компоновки, используемых в PE-файле. Библиотека dll занимает позицию информации об элементе в таблице импорта. Этот элемент описывает конкретную информацию импортированной dll. Например, время последней модификации dll, имя / серийный номер функции в dll, адрес функции после загрузки dll и т. Д. Элемент — это структура, а таблица импорта — это массив структуры. Структура выглядит следующим образом:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // Импортировать флаг конца таблицы
DWORD OriginalFirstThunk; // RVA указывает на массив структур (таблица INT)
};
DWORD TimeDateStamp; // Отметка времени
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name; // RVA указывает на имя dll, заканчивающееся на 0
DWORD FirstThunk; // RVA указывает на массив структур (таблица IAT)
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
Перед загрузкой программы структурные отношения ее конкретных членов следующие:
Первый элемент импортированного массива структур таблицы сохраняет информацию о KERNEL32.dll. Мы анализируем и распечатываем часть информации, как показано ниже:
【Name:KERNEL32.dll】 【NameAddr:0003487C】 【OriginalFirstThunk:00034028】 【FirstThunk:000341B4】 【TimeDateStamp:00000000】
ThunkOffset ThunkValue Hint API Name
[00034340] [00034340] [00CA] [GetCommandLineA]
[00034352] [00034352] [0174] [GetVersion]
[00034360] [00034360] [007D] [ExitProcess]
[0003436E] [0003436E] [029E] [TerminateProcess]
[00034382] [00034382] [00F7] [GetCurrentProcess]
[00034396] [00034396] [00FA] [GetCurrentThreadId]
[000343AC] [000343AC] [02A5] [TlsSetValue]
[000343BA] [000343BA] [02A2] [TlsAlloc]
......
[00034850] [00034850] [0022] [CompareStringW]
[00034862] [00034862] [0262] [SetEnvironmentVariableA]
Подробно объясните значение каждого члена структуры (перед загрузкой):
①Когда значение объединения равно 0 (обычноИспользуйте характеристики, чтобы определить, равно ли оно 0), что означает, что это последний элемент импортированного массива структур таблицы. За исключением последнего элемента, все остальные структуры сохраняют информацию о dll. Когда значение объединения не равно 0,Используйте OriginalFirstThunk (RVA) для индексации адреса INT. Эта таблица INT хранит информацию (серийный номер и имя функции) экспортируемой функции dll.
②TimeDateStamp: Когда значение метки времени равно 0, это означает, что таблица IAT точно такая же, как таблица INT перед загрузкой; когда метка времени не равна 0 (равно -1), это означает, что таблица IAT отличается от таблицы INT, и IAT хранит все функции dll Таким образом, метод прямого заполнения адреса функции перед загрузкой — это привязка адреса функции, а адрес определяется в соответствии с таблицей импорта привязки. Иными словами, таблица импорта привязки действительна, когда метка времени равна -1, а реальная метка времени сохраняется в таблице импорта привязки, в противном случае она недействительна.
③ForwarderChain: Обычно мы также можем игнорировать это поле. В старой версии привязки это относится к первой цепочке пересылки API.
④Name: RVA указывает на строку имени библиотеки DLL.
⑤FirstThunk: RVA указывает на таблицу IAT.
2. Анализ структуры IAT (таблица адресов импорта), INT (таблица имен импорта):
Что касается частного случая привязки таблицы импорта и таблицы IAT, мы здесь не будем проводить исследования, а рассмотрим ситуацию, когда структура IAT и INT одинакова. Перед загрузкой в память мы видим, что и IAT, и INT указывают на массив структур, в котором хранятся серийный номер и имя функции. Элементами IAT и INT являютсяIMAGE_THUNK_DATAСтруктура и ее ориентацияIMAGE_IMPORT_BY_NAMEСтруктура, эти две структуры следующие:
IMAGE_THUNK_DATAВ сводке структуры есть только одно объединение, которое обычно получается с помощью четырехбайтового AddressOfData.IMAGE_IMPORT_BY_NAMEадрес.
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // RVA указывает на _IMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
IMAGE_IMPORT_BY_NAMEЕсть два члена: один — это серийный номер, а другой — имя функции.
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // Может быть 0, компилятор решит, если не 0, то это индекс функции в таблице экспорта
BYTE Name[1]; // Имя функции заканчивается на 0. Так как я не знаю, сколько это длится, я просто даю первый символ и нахожу 0 в конце
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
нота:ОдинIMAGE_THUNK_DATA32Структура занимает четыре байта, индексируя имя функции / серийный номер, но индекс является условным, то есть, если старший бит из четырех байтов равен 0, значение четырех байтов равноIMAGE_IMPORT_BY_NAMERVA; но если старший бит из четырех байтов равен 1, вам не нужно (нельзя) использовать это значение для индексацииIMAGE_IMPORT_BY_NAME, Но удалите непосредственно старший бит, а оставшееся 31-битное значение будет порядковым номером экспорта функции dll в таблице экспорта. Как показано ниже, есть таблицы импорта, анализируемые с самым высоким битом 0, а также импортированные таблицы с самым высоким битом 1:
// Самый старший бит0, Подсказка и имя анализируются в соответствии с индексом значения IMAGE_IMPORT_BY_NAME
【Name:WINSPOOL.DRV】 【NameAddr:000314EE】 【OriginalFirstThunk:00030390】 【tFirstThunk:0002844C】 【TimeDateStamp:00000000】
ThunkOffset ThunkValue Hint API Name
[000314B8] [000314B8] [001B] [ClosePrinter]
[000314C8] [000314C8] [0046] [DocumentPropertiesA]
[000314DE] [000314DE] [007D] [OpenPrinterA]
【Name:ADVAPI32.dll】 【NameAddr:00031590】 【OriginalFirstThunk:0002FF44】 【tFirstThunk:00028000】 【TimeDateStamp:00000000】
ThunkOffset ThunkValue Hint API Name
[0003157E] [0003157E] [0204] [RegSetValueExA]
[0003156C] [0003156C] [01D1] [RegCreateKeyExA]
[0003155A] [0003155A] [01F6] [RegQueryValueA]
[0003154C] [0003154C] [01EB] [RegOpenKeyA]
[0003153E] [0003153E] [01DD] [RegEnumKeyA]
[0003152E] [0003152E] [01D4] [RegDeleteKeyA]
[0003151E] [0003151E] [01EC] [RegOpenKeyExA]
[0003150A] [0003150A] [01F7] [RegQueryValueExA]
[000314FC] [000314FC] [01CB] [RegCloseKey]
【Name:SHLWAPI.dll】 【NameAddr:000315C8】 【OriginalFirstThunk:000301E4】 【FirstThunk:000282A0】 【TimeDateStamp:00000000】
ThunkOffset ThunkValue Hint API Name
[0003159E] [0003159E] [002F] [PathFindExtensionA]
[000315B4] [000315B4] [0031] [PathFindFileNameA]
// Самый старший бит1, Удалите старший бит, чтобы получить номер функции
【Name:OLEAUT32.dll】 【NameAddr:000315D4】 【OriginalFirstThunk:000301D4】 【FirstThunk:00028290】 【TimeDateStamp:00000000】
ThunkOffset ThunkValue Hint API Name
[00000009] [00000009] [-] Номер функции [0009H:9D]
[0000000C] [0000000C] [-] Номер функции [000CH:12D]
[00000008] [00000008] [-] Номер функции [0008H:8D]
Вышеупомянутая ситуация до загрузки программы: IAT и INT указывают на одну и ту же структуру, а после загрузки INT остается неизменным и по-прежнему сохраняет адресную информацию, содержащую имя функции dll и номер функции. IAT изменяется на адресную информацию соответствующей функции в соответствии с содержимым таблицы импорта INT (до загрузки IAT) и информацией таблицы экспорта, как показано ниже:
3. Связанный дескриптор импорта и IAT:
Мы проанализировали ситуацию с хранением нефункциональных адресов в IAT перед загрузкой, и давайте проанализируем ситуацию с сохранением адресов функций в IAT-таблице перед загрузкой. Адрес функции, хранящийся в IAT, — это адрес, по которому dll не загружена. Если в PE-файле нет таблицы импорта привязки, IAT совпадает с INT, а отметка времени в таблице импорта равна 0; в противном случае отметка времени в таблице импорта Когда он равен -1, метка реального времени dll сохраняется в таблице импорта привязки (адрес таблицы импорта привязки хранится в 12-м элементе каталога данных, а IAT является 13-м элементом).
Теперь в большинстве случаев TimeDateStamp импортированной таблицы равен 0, а раннее встроенное программное обеспечение Windows (например, notepad.exe WinXP) в основном использует timeDateStamp со значением -1, что включает случай привязки импортированной таблицы. Преимущество включения таблицы импорта в PE заключается в том, что программа запускается быстро, но ее недостатки также очень очевидны.При изменении местоположения dll, изменении и обновлении dll, таблица импорта привязки также должна быть изменена и обновлена.
Структура таблицы импорта привязки состоит из двух структур:
// В последней структуре все 0 означает конец таблицы импорта привязки
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
DWORD TimeDateStamp; // Указывает привязанную временную метку, если она отличается от TimeDateStamp в PE-заголовке, возможно, она была изменена
WORD OffsetModuleName; // имя dll адрес
WORD NumberOfModuleForwarderRefs; // В зависимости от количества dll
// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
NumberOfModuleForwarderRefs относится к количеству dll, от которых зависит сама dll. Значение n означает, что за структурой следует nIMAGE_BOUND_FORWARDER_REFсостав. Затем импортируется структура следующей dll из таблицы импорта. а такжеIMAGE_BOUND_FORWARDER_REFСтруктура следующая:
typedef struct _IMAGE_BOUND_FORWARDER_REF {
DWORD TimeDateStamp; // Отметка времени, та же функция для проверки обновления
WORD OffsetModuleName; // имя dll адрес
WORD Reserved; //Зарезервированный
} IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
нота:Все OffsetModuleNames в этих двух структурах не относятся к RVA или FOA ImageBase, а относятся к адресу смещения первого адреса таблицы импорта привязки, а именно:Первый адрес таблицы импорта привязки + OffsetModuleName = RVA。
Структурная схема таблицы импорта привязки выглядит следующим образом:
Распечатал таблицу импорта привязки собственного notepad.exe WinXP:
Таблица импорта привязки (Bound Import Descriptor):
DllName:comdlg32.dll
TimeDateStamp:[4802BDA2H:1208139170D]
GMT:2008-04-14 02:12:50
OffsetModuleName:0058
NumberOfModuleForwarderRefs:[0000H:0D]
DllName:SHELL32.dll
TimeDateStamp:[4802BDB6H:1208139190D]
GMT:2008-04-14 02:13:10
OffsetModuleName:0065
NumberOfModuleForwarderRefs:[0000H:0D]
DllName:WINSPOOL.DRV
TimeDateStamp:[4802BDCAH:1208139210D]
GMT:2008-04-14 02:13:30
OffsetModuleName:0071
NumberOfModuleForwarderRefs:[0000H:0D]
DllName:COMCTL32.dll
TimeDateStamp:[4802BD6CH:1208139116D]
GMT:2008-04-14 02:11:56
OffsetModuleName:007E
NumberOfModuleForwarderRefs:[0000H:0D]
DllName:msvcrt.dll
TimeDateStamp:[4802BD6CH:1208139116D]
GMT:2008-04-14 02:11:56
OffsetModuleName:008B
NumberOfModuleForwarderRefs:[0000H:0D]
DllName:ADVAPI32.dll
TimeDateStamp:[4802BD89H:1208139145D]
GMT:2008-04-14 02:12:25
OffsetModuleName:0096
NumberOfModuleForwarderRefs:[0000H:0D]
DllName:KERNEL32.dll
TimeDateStamp:[4802BDC6H:1208139206D]
GMT:2008-04-14 02:13:26
OffsetModuleName:00A3
NumberOfModuleForwarderRefs:[0001H:1D]
###############################################
DllName:NTDLL.DLL
TimeDateStamp:4802BDC5
GMT:2008-04-14 02:13:25
OffsetModuleName:00B0
Reserved:0000
DllName:GDI32.dll
TimeDateStamp:[4802BD81H:1208139137D]
GMT:2008-04-14 02:12:17
OffsetModuleName:00BA
NumberOfModuleForwarderRefs:[0000H:0D]
DllName:USER32.dll
TimeDateStamp:[4802BDBDH:1208139197D]
GMT:2008-04-14 02:13:17
OffsetModuleName:00C4
NumberOfModuleForwarderRefs:[0000H:0D]
Часть информации IAT:
Таблица IAT (таблица адресов импорта):
dllName:【comdlg32.dll】:
Function Addr:[76344906]
Function Addr:[763385CE]
Function Addr:[76349D84]
Function Addr:[7633C3E1]
Function Addr:[76322306]
Function Addr:[76337B9D]
Function Addr:[76338602]
Function Addr:[76330036]
Function Addr:[76337C2B]
dllName:【SHELL32.dll】:
Function Addr:[7D647C18]
Function Addr:[7D5E18CE]
Function Addr:[7D5FB1A9]
Function Addr:[7D632E6F]
dllName:【WINSPOOL.DRV】:
Function Addr:[72F7643C]
Function Addr:[72F74D40]
Function Addr:[72F75091]
dllName:【COMCTL32.dll】:
Function Addr:[7718D270]
dllName:【msvcrt.dll】:
Function Addr:[4CFB2DAE]
Function Addr:[4CFB9E9A]
......
Часть информации таблицы INT, соответствующая указанному выше IAT (INT и IAT соответствуют взаимно однозначному соответствию):
Таблица импорта (Import Descriptor):
【Name:comdlg32.dll】 【NameAddr:00006EAC】 【OriginalFirstThunk:00006D90】 【FirstThunk:000006C4】 【TimeDateStamp:FFFFFFFF】
ThunkOffset ThunkValue Hint API Name
[00006E7A] [00006E7A] [000F] [PageSetupDlgW]
[00006E5E] [00006E5E] [0006] [FindTextW]
[00006E9E] [00006E9E] [0012] [PrintDlgExW]
[00006E50] [00006E50] [0003] [ChooseFontW]
[00006E40] [00006E40] [0008] [GetFileTitleW]
[00006E8A] [00006E8A] [000A] [GetOpenFileNameW]
[00006E6A] [00006E6A] [0015] [ReplaceTextW]
[00006E14] [00006E14] [0004] [CommDlgExtendedError]
[00006E2C] [00006E2C] [000C] [GetSaveFileNameW]
【Name:SHELL32.dll】 【NameAddr:00006EFA】 【OriginalFirstThunk:00006C40】 【FirstThunk:00000574】 【TimeDateStamp:FFFFFFFF】
ThunkOffset ThunkValue Hint API Name
[00006EC8] [00006EC8] [001F] [DragFinish]
[00006ED6] [00006ED6] [0023] [DragQueryFileW]
[00006EE8] [00006EE8] [001E] [DragAcceptFiles]
[00006EBA] [00006EBA] [0103] [ShellAboutW]
【Name:WINSPOOL.DRV】 【NameAddr:00006F3A】 【OriginalFirstThunk:00006D80】 【FirstThunk:000006B4】 【TimeDateStamp:FFFFFFFF】
ThunkOffset ThunkValue Hint API Name
[00006F16] [00006F16] [0078] [GetPrinterDriverW]
[00006F06] [00006F06] [001B] [ClosePrinter]
[00006F2A] [00006F2A] [007E] [OpenPrinterW]
【Name:COMCTL32.dll】 【NameAddr:00006F5E】 【OriginalFirstThunk:00006AEC】 【FirstThunk:00000420】 【TimeDateStamp:FFFFFFFF】
ThunkOffset ThunkValue Hint API Name
[00006F48] [00006F48] [0008] [CreateStatusWindowW]
【Name:msvcrt.dll】 【NameAddr:00007076】 【OriginalFirstThunk:00006DB8】 【FirstThunk:000006EC】 【TimeDateStamp:FFFFFFFF】
ThunkOffset ThunkValue Hint API Name
[00006FDC] [00006FDC] [004E] [_XcptFilter]
[00006FD4] [00006FD4] [00F6] [_exit]
......
4. Таблица импорта анализа кода (INT, IAT) и таблица импорта привязки:
void PETool::print_ImportDescriptor()
{
fprintf(fp_peMess, "Импортировать дескриптор: n");
if(dataDir[1].VirtualAddress == 0){
fprintf(fp_peMess, " tИмпортная таблица не существует! n");
return;
}
char str[TIMESTRING] = {0};
// Таблица импорта - это второй элемент каталога данных, указывает import на первую структуру таблицы импорта
IMAGE_IMPORT_DESCRIPTOR * import = (IMAGE_IMPORT_DESCRIPTOR *)(pFileBuffer + RVAToFOA(dataDir[1].VirtualAddress));
while(true){
if(import->Characteristics == 0){
break;// Последняя структура завершится, если 20 байтов равны 0 (просто судите напрямую о характеристиках)
}
DWORD name = RVAToFOA(import->Name);
DWORD original_ft = RVAToFOA(import->OriginalFirstThunk);
DWORD ft = RVAToFOA(import->FirstThunk);
// Распечатать информацию о структуре
fprintf(fp_peMess, "t【Name:%s】t"
"【NameAddr:%08X】t"
"【OriginalFirstThunk:%08X】t"
"【FirstThunk:%08X】t"
"【TimeDateStamp:%08X】n",
pFileBuffer + name, name, original_ft, ft, import->TimeDateStamp);
memset(str, 0, TIMESTRING);
IMAGE_THUNK_DATA32 * thunk = (IMAGE_THUNK_DATA32 * )(pFileBuffer + original_ft);
// Распечатываем подробную информацию таблицы INT
print_INT(thunk);
import++;
}
}
void PETool::print_INT(IMAGE_THUNK_DATA32 * thunk)
{
fprintf(fp_peMess, "ttThunkOffsetttThunkValuettHintttAPI Namen");
while(true){
DWORD thunkValue = thunk->u1.AddressOfData;
if(thunkValue == 0){
break;// Чтение завершено
}
if(thunkValue >> 31){// Старший бит равен 1 для печати серийного номера
DWORD rva = thunkValue & 0X7FFFFFFF;// Удаляем самый старший бит - фактическое значение, иначе RVAToFOA пойдет не так
DWORD offset = RVAToFOA(rva);
fprintf(fp_peMess, " t t [% 08X] t t [% 08X] t t [-] t t Номер функции [% 04XH:% dD] n»,
offset, offset, rva, rva);
}else{// Старший бит равен 0 для печати имени
DWORD offset = RVAToFOA(thunkValue);
// Получаем адрес IMAGE_IMPORT_BY_NAME
IMAGE_IMPORT_BY_NAME * byName = (IMAGE_IMPORT_BY_NAME * )(pFileBuffer + offset);
fprintf(fp_peMess, "tt[%08X]tt[%08X]tt[%04X]tt[%s]n",
offset, offset, byName->Hint, byName->Name);
}
thunk++;
}
}
void PETool::print_IAT()
{
fprintf(fp_peMess, "Таблица IAT (таблица адресов импорта): n");
IMAGE_IMPORT_DESCRIPTOR * import = (IMAGE_IMPORT_DESCRIPTOR *)(pFileBuffer + RVAToFOA(dataDir[1].VirtualAddress));
while(true){
if(import->Characteristics == 0){
break;
}
DWORD * addr = (DWORD *)(pFileBuffer + RVAToFOA(import->FirstThunk));
// По метке времени в таблице импорта определите, хранится ли адрес функции или структура имени в IAT
if(import->TimeDateStamp == -1){// Адрес функции
fprintf(fp_peMess, "tdllName:【%s】:n", pFileBuffer + RVAToFOA(import->Name));
for(int i = 0; addr[i]; i++){
fprintf(fp_peMess, "ttFunction Addr:[%08X]n", addr[i]);
}
}
else if(import->TimeDateStamp == 0){// Эквивалентно таблице INT
fprintf(fp_peMess, " t эквивалентно таблице INT! n");
break;
}
import++;
}
}
void PETool::print_BoundImportDescriptor()
{
fprintf(fp_peMess, "Связанный дескриптор импорта: n");
if(dataDir[11].VirtualAddress == 0){
fprintf(fp_peMess, " tТаблица импорта привязки не существует! n");
return;
}
DWORD desAddr = dataDir[11].VirtualAddress;// Получение RVA первого привязанного дескриптора импорта
char str[TIMESTRING] = {0};
DWORD stamp = 0, off = 0, ref = 0, i = 0;
IMAGE_BOUND_IMPORT_DESCRIPTOR * bound = (IMAGE_BOUND_IMPORT_DESCRIPTOR * )(pFileBuffer + RVAToFOA(desAddr));
while(bound->TimeDateStamp != 0 && bound->OffsetModuleName != 0){
stamp = bound->TimeDateStamp;// Получаем метку времени
TimeDateStampToString(stamp, str);// Отметка времени до времени
off = bound->OffsetModuleName;// Получаем адрес смещения имени
ref = bound->NumberOfModuleForwarderRefs;// Получаем количество зависимых dll
fprintf(fp_peMess, "tDllName:%sn", pFileBuffer + RVAToFOA(desAddr + off));
fprintf(fp_peMess, "ttTimeDateStamp:[%08XH:%dD]n", stamp, stamp);
fprintf(fp_peMess, "ttGMT:%sn", str);
fprintf(fp_peMess, "ttOffsetModuleName:%04Xn", off);
fprintf(fp_peMess, "ttNumberOfModuleForwarderRefs:[%04XH:%dD]n", ref, ref);
IMAGE_BOUND_FORWARDER_REF * boundFor = (IMAGE_BOUND_FORWARDER_REF *)(bound);
for(boundFor++, i = 0; i < ref; i++, boundFor++){
memset(str, 0, TIMESTRING);
off = boundFor->OffsetModuleName;
stamp = boundFor->TimeDateStamp;
TimeDateStampToString(stamp, str);
fprintf(fp_peMess, "tt###############################################n");
fprintf(fp_peMess, "ttDllName:%sn", pFileBuffer + RVAToFOA(desAddr + off));
fprintf(fp_peMess, "tttTimeDateStamp:%08Xn",stamp);
fprintf(fp_peMess, "tttGMT:%sn", str);
fprintf(fp_peMess, "tttOffsetModuleName:%04Xn", off);
fprintf(fp_peMess, "tttReserved:%04Xn", boundFor->Reserved);
}
bound = (IMAGE_BOUND_IMPORT_DESCRIPTOR *)(boundFor);// Следующая привязка dll
memset(str, 0, TIMESTRING);
}
}
Импорт — это важнейший элемент любого исполняемого файла. Невозможно найти exe- или dll-файл без каких-либо таблиц, имеющих отношение к импорту, разве что у упакованных файлов и у вирусов. Импорт позволяет указать, какие внешние функции и из каких модулей требуются исполняемому файлу для нормальной работы. Например, если мы пишем простейший «Hello, world!», который выводит приветствие в MessageBox’е и завершает выполнение, то ему потребуются как минимум две функции: MessageBoxA (или MessageBoxW) из user32.dll и ExitProcess из kernel32.dll.
PE-формат имеет несколько способов импорта функций. Самый простой и часто использующийся, но самый медленный — это импорт через таблицу импортируемых функций, когда загрузчик заполняет соответствующую таблице функций таблицу их адресов после загрузки в адресное пространство исполняемого файла всех необходимых библиотек. По сути, это вызов LoadLibrary для каждой требуемой библиотеки и затем вызовы GetProcAddress для каждой импортируемой функции.
Второй механизм — это bound import (привязанный импорт), который сводится к тому, что на адресное пространство исполняемого файла проецируются библиотеки, а в таблице импорта уже зашиты адреса функций. Это быстрый механизм, но любое изменение динамических библиотек повлечет за собой обязательную перекомпиляцию (перепривязку) исполняемого файла, чтобы все зашитые адреса функций пересчитались. Тем не менее, этот механизм используется в Windows для всех стандартных исполняемых файлов (Калькулятор, Сапер, Блокнот и т.д.), так как стандартные библиотеки Windows меняются крайне редко. Привязанный импорт комбинируется с обычным импортом.
Третий, самый неочевидный механизм, это delay import (отложенный импорт), основная идея которого состоит в вызове некоторого обработчика, который должен получить адрес требуемой функции и записать его в таблицу адресов импортируемых функций, по мере необходимости.
Для начала рассмотрим классический импорт, так как без него, как правило, не обходится ни один исполняемый файл. С классическим импортом связано несколько таблиц. На корневую таблицу импортируемых функций указывают элементы IMAGE_DIRECTORY_ENTRY_IMPORT в DATA_DIRECTORY и IMAGE_DIRECTORY_ENTRY_IAT, указывающий на IMPORT_ADDRESS_TABLE (рассматривается далее), начиная с Windows XP.
Элемент DATA_DIRECTORY IMAGE_DIRECTORY_ENTRY_IMPORT указывает на массив расположенных подряд структур IMAGE_IMPORT_DESCRIPTOR, причем последний элемент должен быть нулевым (все поля структуры равны 0). Рассмотрим структуру IMAGE_IMPORT_DESCRIPTOR подробнее:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT }; DWORD TimeDateStamp; // 0 if not bound, // -1 if bound, and real datetime stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new) // O.W. date/time stamp of DLL bound to (old) DWORD ForwarderChain; // -1 if no forwarders DWORD Name; DWORD FirstThunk; // RVA to IAT } IMAGE_IMPORT_DESCRIPTOR; |
Эта структура справедлива как для PE, так и для PE64. Поле Name — это RVA строки, содержащей имя DLL, из которой будет производиться импорт. Если содержимое поля TimeDateStamp равно нулю, то импорт обрабатывается как обычно, а если -1, то импорт считается привязанным (bound), и к таблице IMAGE_IMPORT_DESCRIPTOR загрузчик вернется только в том случае, если привязанный импорт завершится неудачей. Если TimeDateStamp содержит отличное от 0 и 1 значение, то при совпадении этого значения с таймстампом библиотеки она просто проецируется на адресное пространство процесса без дальнейших вызовов GetProcAddress. При этом считается, что все адреса должны быть записаны в таблицу адресов еще на этапе компиляции. Если же таймстампы не совпали, то импорт обрабатывается по полной программе, как обычно.
Поле ForwarderChain системным загрузчиком игнорируется и может содержать что угодно.
Самые важные поля — это OriginalFirstThunk и FirstThunk. Первое указывает на lookup-таблицу, содержащую имена импортируемых функций, а второе — на таблицу адресов импортируемых функций (сюда загрузчик будет писать текущие адреса функций).
На данном этапе я выкладываю свой C++-класс, упрощающий работу с PE-файлами, который я достаточно давно начал писать. Сейчас он еще не завершен, можно наращивать функционал, но для наглядного вывода таблицы импорта исполняемого файла он вполне подойдет. Вот исходный код, печатающий таблицу импорта любого 32-разрядного файла (для 64-раздядного можете сделать сами, минимум отличий):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
//Необходимые стандартные заголовки #include <iostream> #include <fstream> #include <iomanip> #include <string> #include <sstream> //Заголовки, определяющие класс pe32 и pe64, а также исключения #include «pe3264.h» #include «pe_exception.h» int main(int argc, const char* argv[]) { if(argc != 2) { std::cout << «Usage: sectons.exe pe_file» << std::endl; return 0; } //Открываем PE-файл на чтение std::ifstream pefile; pefile.open(argv[1], std::ios::in | std::ios::binary); if(!pefile.is_open()) { std::cout << «Can’t open file» << std::endl; return 0; } try { //Создаем объект класса PE32 pe32 executable(pefile); //Если у него есть импорты if(executable.has_imports()) { //Получаем указатель на массив таблиц IMAGE_IMPORT_DESCRIPTOR const IMAGE_IMPORT_DESCRIPTOR* import_descriptor_array = reinterpret_cast<const IMAGE_IMPORT_DESCRIPTOR*>(executable.section_data_from_rva(executable.directory_rva(IMAGE_DIRECTORY_ENTRY_IMPORT))); //И перебираем их до тех пор, пока не достигнем нулевого элемента while(import_descriptor_array->Characteristics) { //Выведем таймстамп и имя библиотеки std::cout << «DLL Name: « << executable.section_data_from_rva(import_descriptor_array->Name) << std::endl; std::cout << «Import TimeDateStamp: « << import_descriptor_array->TimeDateStamp << std::endl; //Получим указатель на таблицу адресов, //которую должен заполнить загрузчик const DWORD* import_address_table = reinterpret_cast<const DWORD*>(executable.section_data_from_rva(import_descriptor_array->FirstThunk)); //И указатель на лукап-таблицу, которая содержит //имена импортируемых функций. //Стоит обратить внимание на то, что некоторые линкеры //допускают ошибку и оставляют этот указатель нулевым. //Это, в принципе, валидный exe-файл, но в случае //необходимости после загрузки файла уже не удастся восстановить имена //импортируемых функций, так как единственная существующая //в данном случае таблица адресов, являющаяся одновременно и лукап-таблицей, //будет исковеркана загрузчиком const DWORD* import_lookup_table = import_descriptor_array->OriginalFirstThunk == 0 ? import_address_table : reinterpret_cast<const DWORD*>(executable.section_data_from_rva(import_descriptor_array->OriginalFirstThunk)); //Для информации DWORD address_table = import_descriptor_array->FirstThunk; //Переменные для хранения имени импортируемой функции и ее порядкового номера в таблице //экспортируемых функций DLL (hint) //Следует обратить внимание на то, что хинт и ординал — это не одно и то же //Ординал — это некий номер, соответствующий функции, и по этому номеру импорт также может производиться //Подробнее об этом я напишу, когда доберусь до описания экспорта std::string name; WORD hint; std::cout << std::endl << » hint | name/ordinal | address» << std::endl; //Перебор импортируемых функций if(import_lookup_table != 0 && import_address_table != 0) { while(true) { //Тут стоило бы добавить дополнительные проверки, т.к. указатель для кривого exe //может оказаться невалидным, но этот пример демонстрационный //и не стремится быть идеально правильным DWORD address = *import_address_table++; //Если мы достигли конца списка импортируемых функций, то переходим //к следующей библиотеке if(!address) break; DWORD lookup = *import_lookup_table++; //Макрос из WinNT.h, говорит о том, что функция импортируется по ординалу if(IMAGE_SNAP_BY_ORDINAL32(lookup)) { //Если это так, то выведем вместо имени функции ее ординал std::stringstream stream; stream << «#» << IMAGE_ORDINAL32(lookup); name = stream.str(); hint = 0; } else { //В противном случае выведем ее имя name = executable.section_data_from_rva(lookup + 2); hint = *reinterpret_cast<const WORD*>(executable.section_data_from_rva(lookup)); } //Выводим информацию об импортируемой функции std::cout << std::dec << «[« << std::setfill(‘0’) << std::setw(4) << hint << «]» << » « << std::left << std::setfill(‘ ‘) << std::setw(30) << name << «:0x» << std::hex << std::right << std::setfill(‘0’) << std::setw(8) << address_table << std::endl; address_table += 4; } } std::cout << «==========» << std::endl << std::endl; //Переходим к следующей библиотеке import_descriptor_array++; } } } catch(const pe_exception& e) { //Если вдруг произошла ошибка std::cout << «Exception: « << e.what() << std::endl; } return 0; } |
Результат работы программы можно увидеть на скриншоте в начале поста. Исходные коды класса и программы прикреплены в конце статьи. Программы для разбора привязанного и отложенного импорта я приводить не буду, они есть в статье Криса Касперски «Путь воина». Я приведу лишь структуры, связанные с этими типами импорта, и некоторые пояснения касательно их.
С привязанным импортом связана всего одна структура (элемент IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT DATA_DIRECTORY указывает на массив таких структр, завершающийся нулевым элементом):
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR { DWORD TimeDateStamp; WORD OffsetModuleName; WORD NumberOfModuleForwarderRefs; // Array of zero or more IMAGE_BOUND_FORWARDER_REF follows } IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR; |
Поле TimeDateStamp содержит временную метку библиотеки для привязанного импорта, и такой импорт будет осуществлен тогда, когда временные метки структуры и библиотеки совпали, либо когда TimeDateStamp = 0. Поле OffsetModuleName — это указатель на имя библиотеки, отсчитываемый от начала массива структур IMAGE_BOUND_IMPORT_DESCRIPTOR. Поле NumberOfModuleForwarderRefs указывает на количество форвардов, назначение этого поля не ясно.
Я сталкивался с exe-файлами, у которых таблицы отложенного импорта находились вне данных секций (т.е. в оверлеях (промежутках между секциями) исполняемого файла), поэтому приходилось дополнительно подгружать их из файла, чтобы считать.
Остается последний механизм импорта — отложенный импорт. Как всегда, элемент IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT DATA_DIRECTORY указывает на массив нижеописанных структр, завершающийся нулевым элементом):
typedef struct ImgDelayDescr { DWORD grAttrs; // attributes LPCSTR szName; // pointer to dll name HMODULE* phmod; // address of module handle PimgThunkData pIAT; // address of the IAT PCImgThunkData pINT; // address of the INT PCImgThunkData pBoundIAT; // address of the optional bound IAT PCImgThunkData pUnloadIAT; // address of optional copy of original IAT DWORD dwTimeStamp; // 0 if not bound, // O.W. date/time stamp of DLL bound to Old BIND } ImgDelayDescr, * PImgDelayDescr; |
Описание структуры взято у Криса Касперски, и тут же я позволю себе процитировать его пояснения касательно это структуры, так как ничего подробнее я не нашел, а самому разбираться не приходилось, так как данный механизм импорта используется крайне редко:
Поле grAttrs задает тип адресации, применяющийся в служебных структурах отложенного импорта (0 — VA, 1 — RVA); поле szName содержит RVA/VA-указатель на ASCIIZ-строку с именем загружаемой DLL (тип адреса определяется особенностями реализации конкретного delay helper’а, внедряемого в программу линкером и варьирующегося от реализации к реализации — короче говоря, будьте готовы ко всяким пакостям). В изначально пустое поле phmod загрузчик (все тот же Delay Helper) помещает дескриптор динамически загружаемой DLL.
Поле pIAT содержит указатель на таблицу адресов отложенного импорта, организованную точно также, как и обычная IAT, с той лишь разницей, что все элементы таблицы отложенного импорта ведут к delay load helper’у — специальному динамическому загрузчику, также называемому переходником (thunk), который вызывает LoadLibrary (если только библиотека уже не была загружена), а затем дает GetProcAddress и замещает текущий элемент таблицы отложенного импорта эффективным адресом импортируемой функции, благодаря чему все последующие вызовы данной функции осуществляются напрямую, в обход delay load helper’а.
При выгрузке DLL из памяти, последняя может восстановить таблицу отложенного импорта в исходное состояние, обратившись к ее оригинальной копии, RVA-указатель на которую хранится в поле pUnloadIAT. Если же копии нет, ее указатель будет обращен в ноль.
Поле pINT содержит RVA-указатель на таблицу имен, во всем повторяющую стандартную таблицу имен (см. name Table). То же самое относится и к полю pBoundIAT, хранящим RVA-указатель на таблицу диапазонного импорта. Если таблица диапазонного импорта не пуста и указанная временная метка совпадает с временной меткой соответствующей DLL, системный загрузчик просто проецирует ее на адресное пространство данного процесса и механизм отложенного импорта дезактивируется.
В статье Криса Касперски можно также найти простой дампер таблиц отложенного импорта.
Надеюсь, все это вас не слишком испугало
На деле, достаточно понять лишь первый механизм, так как он используется чаще всего и без него, как правило, не обходится ни один исполняемый файл.
Обещанные исходные коды класса для работы с PE и программа для разбора таблиц импорта (всё без комментариев): Скачать.
Facebook Twitter VK Telegram Youtube Яндекс Дзен
Техническая поддержка
Вернуться на старую версию
© 2006–2023, Habr
5. PE-файлы: каталоги данных
5.1. Список каталогов
В настоящее время PE-файлы могут содержать следующие каталоги данных:
Номер | Название | Описание |
---|---|---|
0 | IMAGE_DIRECTORY_ENTRY_EXPORT | Таблица экспорта. |
1 | IMAGE_DIRECTORY_ENTRY_IMPORT | Таблица импорта. |
2 | IMAGE_DIRECTORY_ENTRY_RESOURCE | Таблица ресурсов. |
3 | IMAGE_DIRECTORY_ENTRY_EXCEPTION | Таблица обработки исключений. |
4 | IMAGE_DIRECTORY_ENTRY_SECURITY | Таблица сертификатов безопасности. |
5 | IMAGE_DIRECTORY_ENTRY_BASERELOC | Таблица настроек адресов. |
6 | IMAGE_DIRECTORY_ENTRY_DEBUG | Отладочная информация. |
7 | IMAGE_DIRECTORY_ENTRY_ARCHITECTURE | Данные, специфичные для процессора. |
8 | IMAGE_DIRECTORY_ENTRY_GLOBALPTR | RVA глобального регистра процессора. |
9 | IMAGE_DIRECTORY_ENTRY_TLS | Таблица локальной памяти потоков (TLS). |
10 | IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG | Таблица конфигурации загрузки. |
11 | IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT | Таблица связывания импорта. |
12 | IMAGE_DIRECTORY_ENTRY_IAT | Таблица адресов импорта (IAT). |
13 | IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT | Таблица отложенного импорта. |
14 | IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR | Дескриптор .NET. |
15 | – | Зарезервировано. |
Способ доступа к содержимому каталога данных был описан в предыдущем разделе.
Здесь мы последовательно опишем все каталоги данных, поскольку все они имеют различные форматы.
5.2. Таблица экспорта
Эта таблица всегда присутствует в DLL-файлах, поскольку основным назначением
динамических библиотек как раз и является экспорт символов (подпрограмм и переменных),
доступных другим DLL- и EXE-файлам. Впрочем, никто не запрещает нам экспортировать символы
и из EXE-файлов.
Основная цель таблицы экспорта – увязать имена и/или номера экспортируемых функций с их RVA,
т. е. с положением в виртуальной памяти процесса.
Таблица экспорта называется IMAGE_EXPORT_DIRECTORY и имеет следующую структуру:
Смещение (hex) | Размер | Тип | Название | Описание |
---|---|---|---|---|
00 | 4 | DWORD | Characteristics | Зарезервировано, всегда равно 0. |
04 | 4 | DWORD | TimeDateStamp | Дата и время создания таблицы экспорта в формате Unix. |
08 | 2 | WORD | MajorVersion | Старшая цифра номера версии, не используется. |
0A | 2 | WORD | MinorVersion | Младшая цифра номера версии, не используется. |
0C | 4 | DWORD | Name | RVA ASCIIZ-строки, содержащей имя данного файла. |
10 | 4 | DWORD | Base | Начальный номер экспортируемых символов (больше или равен 1). |
14 | 4 | DWORD | NumberOfFunctions | Количество элементов в таблице адресов. |
18 | 4 | DWORD | NumberOfNames | Количество элементов в таблице имен и таблице номеров. |
1C | 4 | DWORD | AddressOfFunctions | RVA таблицы адресов. |
20 | 4 | DWORD | AddressOfNames | RVA таблицы имен. |
24 | 4 | DWORD | AddressOfNameOrdinals | RVA таблицы номеров. |
Из приведенной структуры видно, что таблица экспорта содержит указатели на три других таблицы: адресов, имен и номеров.
- Таблица адресов представляет собой массив RVA всех экспортируемых символов.
- Таблица имен представляет собой массив указателей (RVA) на имена экспортируемых символов, лексикографически
отсортированных по возрастанию.
Имена представляют собой ASCIIZ-строки. - Таблица номеров представляет собой массив соответствующих 16-битовых номеров символов, причем каждый номер является индексом для
таблицы адресов.
При поиске экспортируемого символа по его имени сначала производится бинарный поиск этого имени в таблице имен.
(Хочу подчеркнуть, что имена символов чувствительны к регистру.)
Если имя найдено и его номер в таблице имен равен N, то извлекается N-й элемент из таблицы номеров. Если этот номер
равен K, то элемент таблицы адресов с номером (K — Base) содержит RVA данного символа.
Из этого алгоритма видно, что поиск экспортируемого символа по его номеру производится быстрее, поскольку мы
пропускаем этап поиска имени в таблице имен. Однако, экспорт символов по именам удобнее и используется чаще.
Отметим также, что данная схема позволяет приписать одному экспортируемому символу несколько разных имен.
Уточним строение таблицы адресов. Во-первых, она может содержать нули; это означает, что соответствующий символ не используется.
Во-вторых, каждый ее элемент представляет собой либо обычный RVA символа, либо т. н. RVA перенаправления (forwarder RVA),
указывающий на ASCIIZ-строку. Строка указывает на символ в другой библиотеке
и имеет либо вид «DLLName.FuncName» (экспорт по имени), либо вид «DLLName#FuncNumber» (экспорт по номеру).
RVA перенаправления заменяет экспорт данного символа на экспорт некоторого другого символа из другой библиотеки.
Например, KERNEL32.DLL в Windows NT содержит перенаправление функции HeapAlloc к функции NTDLL.RtlAllocateHeap.
В результате программа, запрашивающая выделение памяти из кучи под Windows NT неявно обращается к библиотеке NTDLL.DLL,
специфической для Windows NT. Та же самая программа под Windows 9x запросит память у KERNEL32.DLL без обращения к NTDLL.DLL,
поскольку KERNEL32.DLL в Windows 9x никаких перенаправлений не содержит.
Как отличить обычный RVA от RVA перенаправления? Для этого принято простое правило (которое во всех известных мне
источниках сформулировано неверно!): если RVA в таблице адресов находится в пределах таблицы экспорта,
то это RVA перенаправления; в противном случае это обычный RVA символа.
В качестве примера приведем программу, которая печатает номера всех экспортируемых символов, их RVA и имена, если последние есть.
#include <iostream> using namespace std; . . . void _tmain(int argc, _TCHAR* argv[]) { LPBYTE pBase = OpenPEFile(_T("c:\windows\system32\kernel32.dll")); if (pBase == NULL) { cout << "File not found!" << endl; return; } IMAGE_NT_HEADERS* pHeader = GetHeader(pBase); if (pHeader == NULL) { cout << "It is not a PE file!" << endl; return; } cout << "OrdtRVAtName" << endl; // Извлекаем параметры каталога данных экспорта. IMAGE_DATA_DIRECTORY& ExportDataDir = pHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; DWORD dwExportDirStart = ExportDataDir.VirtualAddress; DWORD dwExportDirEnd = dwExportDirStart + ExportDataDir.Size; // Получаем указатели на таблицу экспорта и все связанные с ней таблицы. IMAGE_EXPORT_DIRECTORY* pExportDir = (IMAGE_EXPORT_DIRECTORY*)GetFilePointer(pBase, dwExportDirStart); LPDWORD pAddrTable = (LPDWORD)GetFilePointer(pBase, pExportDir->AddressOfFunctions); LPDWORD pNameTable = (LPDWORD)GetFilePointer(pBase, pExportDir->AddressOfNames); LPWORD pOrdTable = (LPWORD)GetFilePointer(pBase, pExportDir->AddressOfNameOrdinals); // Итерация по всем элементам таблицы адресов. for (UINT i = 0; i < pExportDir->NumberOfFunctions; i++) { DWORD dwRVA = *pAddrTable++; // Не печатаем пропущенные элементы. if (dwRVA == 0) continue; cout << dec << i + pExportDir->Base << ":t"; // Проверяем тип очередного RVA. if (dwRVA >= dwExportDirStart && dwRVA < dwExportDirEnd) cout << (char*)GetFilePointer(pBase, dwRVA) << 't'; // RVA перенаправления else cout << hex << dwRVA << 't'; // обычный RVA // Итерация по всем элементам таблицы номеров. for (UINT j = 0; j < pExportDir->NumberOfNames; j++) { // Если номер найден, выводим соответствующее имя из таблицы имен. if (pOrdTable[j] == i) { cout << (char*)GetFilePointer(pBase, pNameTable[j]); break; } } cout << endl; } ClosePEFile(pBase); }
5.3. Таблицы импорта
5.3.1. Структура таблицы импорта
Эта таблица присутствует практически во всех PE-файлах и используется для разрешения ссылок из файла
на динамические библиотеки.
Таблица импорта представляет собой массив описателей IMAGE_IMPORT_DESCRIPTOR, по одному описателю
на каждую из импортируемых DLL. Массив заканчивается описателем, который полностью заполнен нулями.
Каждый из описателей имеет следующую структуру:
Смещение (hex) | Размер | Тип | Название | Описание |
---|---|---|---|---|
00 | 4 | DWORD | OriginalFirstThunk | RVA таблицы имен импорта (INT). |
04 | 4 | DWORD | TimeDateStamp | Дата и время. |
08 | 4 | DWORD | ForwarderChain | Индекс первого перенаправленного символа. |
0C | 4 | DWORD | Name | RVA ASCIIZ-строки, содержащей имя DLL. |
10 | 4 | DWORD | FirstThunk | RVA таблицы адресов импорта (IAT). |
Мы видим, что описатель указывает на имя библиотеки и на две таблицы:
таблицу имен (INT) и таблицу адресов (IAT). Обе эти таблицы представляют собой массивы структур IMAGE_THUNK_DATA, заканчивающиеся
структурой, полностью заполненной нулями. IMAGE_THUNK_DATA является 32-битовым словом (DWORD) в формате PE32 и
64-битовым словом (ULONGLONG) в формате PE32+.
При этом смысл IMAGE_THUNK_DATA зависит от старшего бита данного слова.
Если этот бит установлен, то остальные 31 или 63 бита содержат номер импортируемого символа (импорт по номеру).
Если же этот бит сброшен, то остальные биты задают RVA описателя импортируемого символа (импорт по имени).
Старший бит обозначен как IMAGE_ORDINAL_FLAG; для его проверки удобно использовать макрос
IMAGE_SNAP_BY_ORDINAL(ptd->u1.Ordinal),
где ptd – указатель на IMAGE_THUNK_DATA.
Описатель символа называется IMAGE_IMPORT_BY_NAME.
Он содержит 16-битовое слово Hint, за которым следует ASCIIZ-строка имени символа Name.
Если размер итоговой структуры нечетен, то в конец добавляется еще один нулевой байт.
Хочу еще раз напомнить, что имена символов чувствительны к регистру.
Хорошие сборщики заносят в поле Hint индекс символа в таблице имен библиотеки (см. выше описание таблицы экспорта).
Это позволяет при загрузке быстро найти соответствующий RVA в таблице экспорта DLL.
Поскольку не все сборщики правильно заполняют поле Hint, загрузчик использует следующий алгоритм.
Сначала проверяется имя символа с номером Hint в таблице экспорта DLL. Если оно совпадает с именем импортируемого
символа, то все в порядке. В противном случае проводится описанный выше бинарный поиск имени экспортируемого символа.
При импорте по номеру символа, естественно, никакой поиск не нужен.
5.3.2. Динамическое связывание и IAT
Теперь разберемся, зачем нужны две таблицы (имен и адресов). Таблица имен INT содержится в том же каталоге, что
и сама таблица импорта и никогда не изменяется. Таблица адресов IAT обычно хранится в отдельном каталоге
IMAGE_DIRECTORY_ENTRY_IAT (см. выше список каталогов) и первоначально совпадает с таблицей имен.
В процессе работы программе нужны не имена или номера импортируемых символов, а их адреса в виртуальной памяти
процесса. Поэтому во время загрузки производится т. н. связывание (binding) адресов. Оно состоит в следущем.
После загрузки очередной динамической библиотеки в память процесса
загрузчик просматривает все импортируемые из нее символы, находит соответствующие RVA в таблице экспорта DLL и
заменяет в IAT элементы IMAGE_THUNK_DATA на линейные адреса соответствующих символов.
В итоге, поле OriginalFirstThunk указывает на список импортируемых символов, а поле FirstThunk –
на список соответствующих линейных адресов.
Интересно, что каталог IMAGE_DIRECTORY_ENTRY_IAT может в PE-файле отсутствовать (это означает, что IAT размещена
в той же секции, что и таблица импорта); на правильность загрузки и связывания это не влияет.
5.3.3. Статическое связывание
Мы привели базовое описание импорта, теперь перейдем к деталям, несколько усложняющим картину.
Дело в том, что помимо динамического связывания в процессе загрузки, возможно и статическое связывание PE-файла с
динамическими библиотеками. Статическое связывание производится либо сборщиком, либо утилитой BIND.EXE, входящей в
состав Platform SDK. При желании можно выполнить статическое связывание и с помощью функций BindImage и BindImageEx,
входящих в состав IMAGEHLP.DLL.
Суть статического связывания в том, что в таблицу IAT файла прописываются линейные адреса
импортируемых символов в предположении, что все DLL загружаются по прописанным в них базовым адресам (поле ImageBase
необязательного заголовка). Такое связывание обычно ускоряет процесс загрузки программ, но иногда может потребоваться
динамическое связывание в процессе загрузки. Это происходит в двух случаях: 1) DLL загружена в память не по ее
базовому адресу; 2) версия загруженной DLL отличается от версии, с которой мы провели статическое связывание.
Первый случай распознается загрузчиком легко. А как же он распознает, что загруженная DLL не та, что предполагалось?
Для этого используется поле TimeDateStamp. В файле, не подвергавшемуся статическому связыванию это поле всегда равно нулю.
Существует две схемы статического связывания: старая и новая. Старая схема заносит в поле TimeDateStamp содержимое
поля TimeDateStamp из заголовка файла DLL. Теперь загрузчику достаточно сравнить значения этих полей и, если они
различны, произвести заново динамическое связывание.
Эта схема прекрасно работает, пока импортируемые символы не являются перенаправленными к другим библиотекам.
(О перенаправлении см. описание таблицы экспорта.)
Поскольку проследить цепочку перенаправлений по дате и времени создания DLL нельзя, из соображений надежности
такие символы всегда связываются заново в процессе загрузки. Для этого загрузчик должен различать обычные
импортируемые символы от перенаправленных. Это делается посредством поля ForwarderChain, которое содержит
индекс первого перенаправленного символа в IAT. По этому индексу хранится индекс следующего перенаправленного
символа и т. д. В индексе последнего перенаправленного символа хранится -1. Это, в частности, означает, что в отсутствии
статического связывания или перенаправленных символов поле ForwarderChain содержит -1.
5.3.4. Таблица связывания импорта
Новая схема связывания, которая также поддерживается утилитой BIND.EXE, заносит в поля TimeDateStamp
и ForwarderChain значение -1. Вся информация о статическом связывании сохраняется в отдельном каталоге данных
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT. Интересно, что этот каталог, в отличие от остальных, обычно размещается
не внутри какой-либо секции, а между заголовками секций и началом первой секции.
Таблица связывания импорта представляет собой массив описателей библиотек, по одному описателю на каждую
DLL. Описатель состоит из структуры IMAGE_BOUND_IMPORT_DESCRIPTOR, за которой могут следовать нуль или
более структур IMAGE_BOUND_FORWARDER_REF. Завершается массив структурой IMAGE_BOUND_IMPORT_DESCRIPTOR, заполненной нулями.
Структура IMAGE_BOUND_IMPORT_DESCRIPTOR устроена так:
Смещение (hex) | Размер | Тип | Название | Описание |
---|---|---|---|---|
00 | 4 | DWORD | TimeDateStamp | Дата и время создания DLL. |
04 | 2 | WORD | OffsetModuleName | Смещение от начала каталога до ASCIIZ-имени DLL. |
06 | 2 | WORD | NumberOfModuleForwarderRefs | Количество структур IMAGE_BOUND_FORWARDER_REF, следующих за данной. |
За этой структурой следуют NumberOfModuleForwarderRefs структур IMAGE_BOUND_FORWARDER_REF.
Каждая из этих структур описывает одну DLL, к которой есть перенаправления из данной DLL.
Устроены эти структуры следующим образом:
Смещение (hex) | Размер | Тип | Название | Описание |
---|---|---|---|---|
00 | 4 | DWORD | TimeDateStamp | Дата и время создания DLL. |
04 | 2 | WORD | OffsetModuleName | Смещение от начала каталога до ASCIIZ-имени DLL. |
06 | 2 | WORD | Reserved | Зарезервировано. |
Из приведенных стуктур видно, что новая схема связывания позволяет загрузчику более осмысленно проводить
динамическое связывание, поскольку здесь мы имеем всю необходимую информацию для анализа цепочек перенаправлений
любой длины.
5.3.5. Таблица отложенного импорта
Эта таблица содержит информацию, необходимую для отложенной загрузки импортируемых библиотек.
Отложенная загрузка импорта означает, что DLL присоединена к исполняемому файлу, но загружается в память не сразу,
как при обычном импорте, а только при первом обращении программы к символу, импортируемому из этой DLL.
Отложенная загрузка импорта имеет, как минимум, два преимущества. Во-первых, она ускоряет загрузку программ в память.
Во-вторых, она позволяет программам, использующим возможности новых версий Windows, безболезненно работать в старых версиях
ОС. Для этого код программы должен в процессе выполнения проверять текущую версию ОС и, если она старая, не вызывать
новых функций. Пусть, например, мы хотим использовать темы Windows XP, API которых реализованы библиотекой UXTHEME.DLL,
отсутствующей в других версиях Windows.
При обычной сборке программы загрузчик Windows 2000 попытается загрузить UXTHEME.DLL,
не найдет ее и завершит работу по фатальной ошибке. При отложенной загрузке импорта из UXTHEME.DLL этого не произойдет,
поскольку под Windows 2000 в процессе работы программы не будет ни одного обращения к этой библиотеке.
Важно понимать, что отложенная загрузка импорта не является свойством загрузчика ОС. Она реализуется добавлением к программе
дополнительного кода и данных в процессе сборки. Например, для отложенного импорта библиотеки тем Windows XP мы в командной
строке сборщика вместо опции /LIB:UXTHEME.LIB указываем две опции: /LIB:DELAYIMP.LIB и /DELAYLOAD:UXTHEME.DLL.
Первая опция как раз и подключает необходимый дополнительный код (DELAYIMP.LIB), а вторая приказывает сборщику
создать таблицу отложенного импорта.
Поскольку отложенный импорт не поддерживается загрузчиком, его структуры описаны не в файле WINNT.H, а в файлах,
реализующих DELAYIMP.LIB: заголовке DELAYIMP.H и файле DELAYHLP.CPP (находятся в папке VC7/INCLUDE вашей Visual Studio).
Таблица отложенного импорта представляет собой массив описателей ImgDelayDescr, по одному описателю для каждой DLL
с отложенным импортом. Массив оканчивается описателем, заполненным нулями. Эти описатели устроены так:
Смещение (hex) | Размер | Тип | Название | Описание |
---|---|---|---|---|
00 | 4 | DWORD | grAttrs | Атрибуты описателя. В настоящее время используется только младший бит. |
04 | 4 | DWORD | rvaDllName | RVA ASCIIZ-имени DLL. |
08 | 4 | DWORD | rvaHmod | RVA поля типа HMODULE. Первоначально содержит 0, после загрузки DLL сюда заноситься ее описатель (handle). |
0C | 4 | DWORD | rvaIAT | RVA таблицы адресов импорта. Она имеет тот же формат, что и обычная IAT. |
10 | 4 | DWORD | rvaINT | RVA таблицы имен импорта. Она имеет тот же формат, что и обычная INT. |
14 | 4 | DWORD | rvaBoundIAT | RVA необязательной таблицы связанных адресов импорта. Она имеет тот же формат, что и обычная IAT. В настоящее время совпадает с IAT, но будущие версии утилиты BIND.EXE будут помещать сюда связанную IAT. |
18 | 4 | DWORD | rvaUnloadIAT | RVA необязательной копии исходной IAT. Она имеет тот же формат, что и обычная IAT. В настоящее время содержит 0. |
1C | 4 | DWORD | dwTimeStamp | В настоящее время содержит 0, но будущие версии утилиты BIND.EXE будут помещать сюда дату и время создания связанной DLL. |
Приведенное описание требует единственного комментария. Первая версия отложенного импорта, которая появилась в
Visual Studio 6.0, содержала в данной структуре не RVA, а указатели (т. е. полные виртуальные адреса).
С появлением архитектуры Win64, где указатели являются 64-битовыми, пришлось вместо указателей хранить RVA
(эта версия появилась в Visual Studio .NET). Чтобы определить, что содержит ImgDelayDescr, нужно проверить младший бит поля
grAttrs (его маска названа dlattrRva). Если он установлен, то в структуре хранятся RVA; если сброшен, то виртуальные адреса.
5.3.6. Пример обработки таблиц импорта
Приведенный ниже пример выводит на экран всю описанную в этом разделе информацию.
#include <ctime> #include <iostream> #include <vector> #include <algorithm> #include <delayimp.h> using namespace std; . . . void _tmain(int argc, _TCHAR* argv[]) { LPBYTE pBase = OpenPEFile(_T("c:\windows\system32\user32.dll")); if (pBase == NULL) { cout << "File not found!" << endl; return; } IMAGE_NT_HEADERS* pHeader = GetHeader(pBase); if (pHeader == NULL) { cout << "It is not a PE file!" << endl; return; } // Извлекаем параметры каталога данных импорта. IMAGE_DATA_DIRECTORY& ImportDataDir = pHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if (ImportDataDir.Size != 0) { cout << "Import Data Directory" << endl; cout << "---------------------" << endl << endl; for (IMAGE_IMPORT_DESCRIPTOR* pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*)GetFilePointer(pBase, ImportDataDir.VirtualAddress); pImportDesc->Name != 0; pImportDesc++) { char* name = strupr(strdup((char*)GetFilePointer(pBase, pImportDesc->Name))); cout << name << endl; free(name); time_t time = pImportDesc->TimeDateStamp; bool bBounded = false, bOldBind = false; if (time == 0) cout << "DLL not bounded" << endl; else if (time == -1) { cout << "New style bounding, see Bound Import Data Directory below" << endl; bBounded = true; } else { cout << "Old style bounding. Date/Time: " << asctime(gmtime(&time)); bBounded = bOldBind = true; } IMAGE_THUNK_DATA* pINT; IMAGE_THUNK_DATA* pIAT; if (pImportDesc->OriginalFirstThunk != 0) { pINT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pImportDesc->OriginalFirstThunk); pIAT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pImportDesc->FirstThunk); } else { // учитываем ошибку сборщика TLINK pINT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pImportDesc->FirstThunk); pIAT = NULL; bBounded = false; } vector forwardRefs; if (pImportDesc->ForwarderChain != -1 && bOldBind) { for (DWORD dwChain = pImportDesc->ForwarderChain; dwChain != -1; dwChain = pIAT[dwChain].u1.Ordinal) forwardRefs.push_back(dwChain); } if (bBounded) { if (bOldBind) cout << "nAddressttHinttName/OrdinaltForwarded" << endl; else cout << "nAddressttHinttName/Ordinal" << endl; } else cout << "nHinttName/Ordinal" << endl; for (DWORD i =0; pINT->u1.Ordinal != 0; i++) { if (bBounded) cout << hex << (DWORD)(ULONG_PTR)GetFilePointer(pBase, pIAT->u1.Ordinal) << 't'; if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal)) cout << 't' << dec << (pINT->u1.Ordinal & ~IMAGE_ORDINAL_FLAG); else { IMAGE_IMPORT_BY_NAME* p = (IMAGE_IMPORT_BY_NAME*)GetFilePointer(pBase, pINT->u1.Ordinal); cout << dec << p->Hint << 't' << (char*)p->Name; } if (bOldBind) cout << (find(forwardRefs.begin(), forwardRefs.end(), i) == forwardRefs.end() ? "tN" : "tY"); cout << endl; pINT++; pIAT++; } cout << endl; } } // Извлекаем параметры каталога данных связывания импорта. IMAGE_DATA_DIRECTORY& BoundImportDataDir = pHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]; if (BoundImportDataDir.Size != 0) { cout << "Bound Import Data Directory" << endl; cout << "---------------------------" << endl << endl; LPBYTE pBoundImportDir = GetFilePointer(pBase, BoundImportDataDir.VirtualAddress); for (IMAGE_BOUND_IMPORT_DESCRIPTOR* pBoundImportDesc = (IMAGE_BOUND_IMPORT_DESCRIPTOR*)pBoundImportDir; pBoundImportDesc->OffsetModuleName != 0; ) { char* name = strupr(strdup((char*)(pBoundImportDir + pBoundImportDesc->OffsetModuleName))); cout << name << endl; free(name); time_t time = pBoundImportDesc->TimeDateStamp; cout << "Date/Time: " << asctime(gmtime(&time)); if (pBoundImportDesc->NumberOfModuleForwarderRefs == 0) { cout << "No forwarder refs" << endl; pBoundImportDesc++; } else { cout << "Forwarder refs:" << endl; cout << "nNamettDate/Time" << endl; IMAGE_BOUND_FORWARDER_REF* pForwardRef = (IMAGE_BOUND_FORWARDER_REF*)(pBoundImportDesc + 1); for (UINT i = 0; i < pBoundImportDesc->NumberOfModuleForwarderRefs; i++, pForwardRef++) { char* name = strupr(strdup((char*)(pBoundImportDir + pForwardRef->OffsetModuleName))); time_t time = pForwardRef->TimeDateStamp; cout << name << 't' << asctime(gmtime(&time)); free(name); } pBoundImportDesc = (IMAGE_BOUND_IMPORT_DESCRIPTOR*)pForwardRef; } cout << endl; } } // Извлекаем параметры каталога данных отложенного импорта. IMAGE_DATA_DIRECTORY& DelayImportDataDir = pHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT]; if (DelayImportDataDir.Size != 0) { cout << "Delay Import Data Directory" << endl; cout << "---------------------------" << endl << endl; for (ImgDelayDescr* pDelayDesc = (ImgDelayDescr*)GetFilePointer(pBase, DelayImportDataDir.VirtualAddress); pDelayDesc->rvaDLLName != 0; pDelayDesc++) { if (pDelayDesc->grAttrs & dlattrRva) { char* name = strupr(strdup((char*)GetFilePointer(pBase, pDelayDesc->rvaDLLName))); cout << name << endl; free(name); cout << "Attributes:tVisual Studio .NET" << endl; cout << "HMODULE:t" << hex << *(LPDWORD)GetFilePointer(pBase, pDelayDesc->rvaHmod) << endl; time_t time = pDelayDesc->dwTimeStamp; if (time != 0) cout << "Date/Time:t" << asctime(gmtime(&time)); else cout << "Date/Time:tnot set" << endl; cout << "nAddressttHinttName/Ordinal" << endl; IMAGE_THUNK_DATA* pINT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pDelayDesc->rvaINT); IMAGE_THUNK_DATA* pIAT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pDelayDesc->rvaIAT); while (pINT->u1.Ordinal != 0) { cout << hex << (DWORD)(ULONG_PTR)GetFilePointer(pBase, pIAT->u1.Ordinal) << 't'; if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal)) cout << 't' << dec << (pINT->u1.Ordinal & ~IMAGE_ORDINAL_FLAG) << endl; else { IMAGE_IMPORT_BY_NAME* p = (IMAGE_IMPORT_BY_NAME*)GetFilePointer(pBase, pINT->u1.Ordinal); cout << dec << p->Hint << 't' << (char*)p->Name << endl; } pINT++; pIAT++; } } else { // Не тестировано!!! char* name = strupr(strdup((char*)GetFilePointer(pBase, pDelayDesc->rvaDLLName - pHeader->OptionalHeader.ImageBase))); cout << name << endl; free(name); cout << "Attributes:tVisual Studio 6.0" << endl; cout << "HMODULE:t" << hex << *(LPDWORD)GetFilePointer(pBase, pDelayDesc->rvaHmod - pHeader->OptionalHeader.ImageBase) << endl; time_t time = pDelayDesc->dwTimeStamp; if (time != 0) cout << "Date/Time:t" << asctime(gmtime(&time)); else cout << "Date/Time:tnot set" << endl; cout << "nAddressttHinttName/Ordinal" << endl; IMAGE_THUNK_DATA* pINT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pDelayDesc->rvaINT - pHeader->OptionalHeader.ImageBase); IMAGE_THUNK_DATA* pIAT = (IMAGE_THUNK_DATA*)GetFilePointer(pBase, pDelayDesc->rvaIAT - pHeader->OptionalHeader.ImageBase); while (pINT->u1.Ordinal != 0) { cout << hex << (DWORD)(ULONG_PTR)GetFilePointer(pBase, pIAT->u1.Ordinal) << 't'; if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal)) cout << 't' << dec << (pINT->u1.Ordinal & ~IMAGE_ORDINAL_FLAG) << endl; else { IMAGE_IMPORT_BY_NAME* p = (IMAGE_IMPORT_BY_NAME*)GetFilePointer(pBase, pINT->u1.Ordinal); cout << dec << p->Hint << 't' << (char*)p->Name << endl; } pINT++; pIAT++; } } cout << endl; } } ClosePEFile(pBase); }