Низкоуровневое программирование для Дzenствующих
Вид материала | Документы |
- Низкоуровневое программирование, 108.99kb.
- Курс является базовым как для изучения других математических дисциплин, так и для более, 36.89kb.
- 1 Обобщенное программирование. Обобщенное программирование это еще одна парадигма программирования,, 55.18kb.
- Введение в линейное программирование линейное программирование (ЛП), 139.72kb.
- Учебно-методический комплекс для студентов заочного обучения специальности Прикладная, 63.23kb.
- Аттестационное тестирование в сфере профессионального образования, 72.49kb.
- Лекции по дисциплине «Социальное моделирование и программирование», 44.69kb.
- Программа дисциплины Линейное программирование Семестр, 17.93kb.
- Программа дисциплины "Программирование" для направления, 488.76kb.
- Рабочая программа по дисциплине Программирование на языке высокого уровня для специальности, 182.97kb.
Часть первая - теоретическая
(продолжение)
Сложное – то, что делается мгновенно,
невозможное – то, что требует лишь
немногим большего времени
Рецензент:
Dr.Golova/UINC.RU
Главный корректор:
Edmond/HI-TECH
1. Лирическое отступление
2. Основные концепции
2.1 RVA/VA и иже с ними
2.2 О секциях PE-файла
2.3 Директория импорта
2.4 Директория ресурсов
2.5 Директория перемещаемых элементов
2.6 NTDLL.DLL – Windows Loader
3. 64-bit PE-files
4. Страшное слово XML
5. Благодарности
2.3 Директория импорта
|
Рис. 2. |
Для закрепления увиденного материала, опять таки, рекомендуется немного оторваться от чтения, и что-то поклацать. Все пояснения полей есть и в англо- и в русскоязычной документации.
Однако перед тем как перейти к прикладным аспектам, давайте немного разберемся с путаницей в терминологии. Прежде всего, массив из структур IMAGE_IMPORT_DESCRIPTOR иначе как директорией импорта называть некорректно. Структура эта содержит ряд полей, среди которых есть OriginalFirstThunk и FirstThunk. Очень важно понимать состояние структур, на которые эти поля показывают. Структуры называются IMAGE_THUNK_DATA – их две!
OriginalFirstThunk ? IMAGE_THUNK_DATA №1
/*обратите внимение, этой структуры может не быть,
точнее, адреса будут заполнены нулями – это можно считать багом
борландовского линкера.
MS утилита bind.exe отказывается обрабатывать такой файл,
почему так – см. ниже*/
FirstThunk ? IMAGE_THUNK_DATA №2
До загрузки образа в память OriginalFirstThunk и FirstThunk содержат RVA (которые, естественно, тоже различаются) на эти структуры, являющиеся ни чем иным, как таблицами адресов импорта (IAT – import address table). Но и здесь уже тоже наизобретали велосипедов! IMAGE_THUNK_DATA на которую указывает OriginalFirstThunk ntdll.dll при загрузке в нормальных условиях не обрабатывается (хотя внутренняя функция ntdll.dll LdrpSnapIAT может читать и оттуда, см. ниже), а адреса в структуре, на которую указывает FirstThunk действительно меняются (патчатся лоадером), поэтому OriginalFirstThunk называют еще import lookup table (import name table), а FirstThunk – вот это уже настоящая IAT, для которой есть свой #define:
#define IMAGE_DIRECTORY_ENTRY_IAT 12
Была когда-то и СЕКЦИЯ импорта, звали .idata, но сейчас она, пожалуй, издохла. Во всяком случае, сейчас этот зверь – редкий. Разве что линкер специально попросите...
Забавно, что даже LordPE, написанный действительно талантливым программистом, и тот не избежал этого бага. Поглядите потом, что вытворяет! Кнопочка – Directories, а директорию импорта называет IAT, а offset вообще обалденный выдает! PETools зато отображает корректно и термины корректные.
Теперь рассмотрим саму структуру и манипуляции с полями. Итак:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
// 0 for terminating null import descriptor
DWORD Characteristics;
// RVA to original unbound IAT (PIMAGE_THUNK_DATA)
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain;// -1 if no forwarders,
//положительное число в противном случае
// RVA на имя dll
DWORD Name;
// RVA to IAT (if bound this IAT has actual addresses)
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
Перед тем, как начинать разбирать структуру, очень важно четко представлять, что в последнее время развелось много всяких наворотов на бедную IAT.
NTDLL.dll (функция LdrpWalkImportDescriptor) может читать IAT из:
1) OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] -
из IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk,
если удовлетворяются определенные условия.
2) OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT] –
т.е., из IMAGE_IMPORT_DESCRIPTOR.FirstThunk - это стандарт
3) OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT]
- все файлы Windows имеют такую привязку
Есть и 4-й #define:
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13
// Несмотря на #define в winnt.h определений полей структуры вы там не найдете.
// Структура определена в delayimp.h в директории VC.
// Малоизвестен тот факт, что линкер
// от MS умеет также ассемблировать код!
// Так вот, именно это здесь и используется.
// В исполняемый файл просто-напросто запихиваются
// дополнительные клочки кода.
NTDLL.dll это не патчит, т.к. здесь уже используется совсем другой механизм. Фактически, это аналог LoadLibrary/GetProcAddress в одной упаковке. Более подробно - как всегда, ссылка скрыта.
Здесь тоже понаворочено более, чем достаточно. Внутри Delay Load Directory Table есть стандартная таблица импорта, может выполняться привязка, т.е. возникать Bound Delay Import Table, есть Unload Delay Import Table и т.п. Мало не покажется!
LordPE понимает, что такое директория отложенного импорта. PE Tools пока выдает только VirtualAddress и Size (т.е. стандартные описания директории), HIEW (включая 6.85) вообще не умеет работать с директориями, IDA, хм.., IDA знает все.
Теперь у лоадера есть выбор – из какого же поля, собственно читать? Сначала обрабатывается цепочка директорий. Функция LdrpWalkImportDescriptor обрабатывает ТОЛЬКО директорию привязанного импорта, если VirtualAddress директории IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT не равен нулю, и временные пометки (TimeDateStamp), зашитые внутри модуля при его создании, соответствуют таковым в библиотеках, к которым этот модуль привязан (системные библиотеки Windows, dll приложения и т.п.).
В случае отсутствия IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (или невыполнения некоторых условий, например, загрузки экспортируемого модуля не по адресу ImageBase) читается уже стандартный IMAGE_IMPORT_DESCRIPTOR.
Так же уже не вполне ясна роль поля IMAGE_IMPORT_DESCRIPTOR.TimeDateStamp, которое может иметь три значения:
- 0 – стандартный IMAGE_DIRECTORY_ENTRY_IMPORT.
- ххххххххh – из IMAGE_IMPORT_DESCRIPTOR.FirstThunk, но с некоторыми оговорками, см. ниже.
- -1 – лоадер читает из IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT. Как уже говорилось, в этом случае лоадеру совершенно безразлично (при соблюдении определенных условий), есть ли IMAGE_DIRECTORY_ENTRY_IMPORT вообще или нет! Если попытка чтения BOUND_IMPORT проваливается в силу, например, несовпадения TimeDateStamp у exe файла и dll, к которым он привязан, то тогда читается стандартная IMAGE_DIRECTORY_ENTRY_IMPORT, но как при этом обрабатывается –1 – неясно. Во всяком случае, в коде LdrpWalkImportDescriptor НЕТ сравнения TimeDateStamp с -1, поэтому можно предположить, что достаточно существования IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT.
Теперь продолжим исследование. Начнем с поля Name. Это RVA на ASCIIZ-строку нужной библиотеки. Совершенно неоправданно на нее опираются во многих алгоритмах, где массив из структур IMAGE_IMPORT_DESCRIPTOR обрабатывается в цикле. С одной стороны да, если имя библиотеки не указано, то откуда изволите функцию импортировать, а с другой стороны – есть поле TimeDateStamp, которое может быть равно –1, точнее, есть директория BIAT (есть и такое название у директории привязанного импорта), и, если она присутствует, о старой директории импорта можно забыть! И можно вытворять, что душе угодно с массивом из IMAGE_IMPORT_DESCRIPTOR, лоадера это нисколько не волнует! Т.е. утилита, обрабатывающая такой файл, это дело должна учитывать, т.е. частично эмулировать работу лоадера. PE Tools это умеет. LordPE - тоже.
При TimeDateStamp == 0 мы имеем дело со старой доброй стандартной IAT. До загрузки OriginalFirstThunk и FirstThunk имеют одинаковую структуру (IMAGE_THUNK_DATA), которая физически продублирована в разных местах РЕ-файла. После загрузки FirstThunk содержит реальные адреса функций, OriginalFirstThunk остается такой же. Лоадер патчит адреса в FirstThunk, меняя атрибуты страницы на время выполнения этой операции.
При TimeDateStamp равном ххххххххh имеем интересный случай со «старой привязкой». Важно понимать, что в этом случае лоадер не делает ничего! Пара проверок на валидность и образ можно запускать! Добиться эффекта можно, запустив утилиту bind с ключом –o.
bind –o –u filename
// без ключа -u изменения в образ записаны не будут,
// если интересно, что
// происходит - используйте ключ -v. В этом случае утилита просто
// озвучит свои действия, но файл не изменит
Поле OriginalFirstThunk не затрагивается, а поле FirstThunk содержит настоящие адреса API-функций. Т.е. IMAGE_IMPORT_BY_NAME смысла более не имеет, т.к. DWORD в IMAGE_THUNK_DATA теперь, по сути, представляет адрес API-функции в памяти, а не ссылку на IMAGE_IMPORT_BY_NAME.
Импорт может просто привязываться, а может и происходить так называемый “API forwarding”, что можно перевести как "перенаправление API". Это очень изящная штука, которая помогает MS скрывать разницу между OS. Классический пример с HeapAlloc. Определена в kernel32.dll, но в действительности КОДА ее там и близко нет! Вместо этого вызов сразу перенаправляется на ntdll.dll, где упирается в функцию RtlAllocateHeap! Т.е. где-то так:
calc.exe -> | kernel32.dll -> | ntdll.dll |
(call HeapAlloc) | (немедленный форвард) | (RtlAllocateHeap) |
| (NTDLL.RtlAllocateHeap) | |
Этого эффекта можно добиться, употребляя следующее выражение при компиляции dll:
#pragma comment(linker, "/export:HeapAlloc=NTDLL.RtlAllocateHeap")
Аналогичного эффекта можно добиться и с помощью def-файла:
EXPORTS
HeapAlloc=NTDLL.RtlAllocateHeap
Exe-файл, использующий dll c таким редиректом, должен загружать ее динамически – через LoadLibrary/GetProcAddress(“HeapAlloc”) - если импорт по имени, либо по ординалу с помощью макроса MAKEINTRESOURCE. Если хотите линковать статически - тоже нет проблем. Код dll:
#include
BOOL APIENTRY DllMain(
HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
return TRUE;
}
void F1(void)
{
MessageBeep(0xFFFFFFFF);
}
def-файл для dll (можно обойтись и без него, если использовать директиву pragma):
LIBRARY Forward
EXPORTS F1 @1
EXPORTS Hi = User32.MessageBoxA @2 NONAME
И, наконец, код exe, который будет использовать такую хитрую dll:
#include
extern "C" int __stdcall Hi(int, char*, char*, int);
int main(void)
{
Hi(0, "Lya-lya-lya", "Test", 0);
return 0;
}
Обратите внимание, в самой dll нет и следа кода форварда, это не является необходимым. Директива Hi = User32.MessageBoxA @2 NONAME предписывает компилятору перенаправить вызов Hi, присвоить этой функции ординал номер два, и сделать так, чтобы в результирующей dll имени функции НЕ было (заметьте, имя функции сохранится в lib, но не будет существовать в dll). В exe файле две директивы extern "C" и __stdcall помогут нам избежать проблем с манглингом функций.
Файл с API-forwarding будет иметь таблицу экспорта с функцией, имеющей нестандартный RVA, значение которого не будет укладываться в лимит размера директории. Алгоритм обработки таких форвардов заложен в функцию LdrpSnapIAT и, как нам кажется, Russell в своей статье здесь допустил ошибку. Смотрите:
NTSTATUS
LdrpSnapIAT
...
{
…
// there are forwarders
if (-1 != pImageImportDescriptor->ForwarderChain)
{
IATFirstThunkEntry =
pLoadingItem->ImageBase +
pImageImportDescriptor->FirstThunk +
(pImageImportDescriptor->ForwarderChain / 4);
…
}
Реальный кусочек кода из лоадера (Windows 2000, SP4):
.text:77F99897 mov eax, [ecx+IMAGE_IMPORT_DESCRIPTOR.ForwarderChain]
.text:77F9989A cmp eax, 0FFFFFFFFh ; -1
.text:77F9989D jz no_forwarder
.text:77F998A3 mov ebx, [edi]
.text:77F998A5 lea edx, [ebx+eax*4]
.text:77F998A8 mov eax, [ecx]
.text:77F998AA add eax, edx
.text:77F998AC mov esi, [ecx+IMAGE_IMPORT_DESCRIPTOR.FirstThunk]
.text:77F998AF add esi, edx
Т.е. не деление, а умножение! Для каждого IMAGE_IMPORT_DESCRIPTOR такой код вызывается единожды. ForwarderChain здесь является индексом первого форварда.
Продолжаем. Если файл привязан к dll, которая загружена по адресу, отличному от ImageBase, привязка перестает быть валидной. В этом случае лоадер будет читать IAT, если не валидна и она – образ загружен не будет.
Время загрузки файла немного уменьшается – это происходит за счет того, что NTDLL.dll уже не патчит адреса внутри модуля, все уже сделано.
При TimeDateStamp равном –1 (т.е. 0xFFFFFFFF) имеем новый стиль привязки.
Лоадер сначала проверяет IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, а только уж потом обращается к IMAGE_IMPORT_DESCRIPTOR (если обращается!). Проведите простой эксперимент: привяжите файл – утилита bind –u filename, потом удалите ВСЕ содержимое IMAGE_IMPORT_DESCRIPTOR, или наполните его мусором. Запустите файл. Ну как? Еще раз повторим. Если не выполняется строгое равенство TimeDateStamp, если файл загружен не на ImageBase, если API адрес не найден и т.п, то мы опять читаем IAT. Поэтому bind отказывается производить привязку образов, не имеющих OriginalFirstThunk – если что-то пойдет не так, мы ведь не сможем восстановиться!
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT имеет свою структуру:
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
DWORD TimeDateStamp; //очень разумная мера!
/*Откуда, например, лоадер может знать,
а не изменилась ли версия dll, к которой привязаны адреса файла?
Только сравнивая «временные штампы» библиотек.
Если данный TimeStamp совпадает с таковым у библиотеки – все ОК,
нет – тогда плохо - выставляется флажок ошибки,
и лоадер начинает работу по чтению IID*/
WORD OffsetModuleName;
//offset (а НЕ RVA!!!) на имя библиотеки от начала директории
WORD NumberOfModuleForwarderRefs;
/*
Заметьте, это поле – счетчик,
указатель количества структур типа IMAGE_BOUND_FORWARDER_REF,
которые следуют ПОСЛЕ данной структуры.
Строение их абсолютно идентично таковому у IMAGE_BOUND_IMPORT_DESCRIPTOR
за исключением поля NumberOfModuleForwarderRefs, которое зарезервировано
*/
// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
Обратите внимание, что структуры IMAGE_BOUND_IMPORT_DESCRIPTOR и IMAGE_BOUND_FORWARDER_REF в файле перемешаны. Форвард означает, что API как бы «перенаправляется» в другую dll. В ту самую, где, собственно и определен. В этом случае первая dll служит как бы переходником, не более того.
«Живой» пример на нашем неизменном calc.exe выглядит так:
; 1-dec-1999 7:37:27
HEADER:01000238 bound_import_directory dd 3844D037h
HEADER:0100023C dw offset на "SHELL32.dll"
;поле содержит 0х38, что прямиком отсылает на к 01000270 (0х38+0х38 == 0х70)
;в IDA жмем G, потом +38 и усе ?
HEADER:0100023E dw 0
HEADER:01000240 dd 37F2C227h ; 30-sep-1999 1:51:35
HEADER:01000244 dw offset на "MSVCRT.dll"
HEADER:01000246 dw 0
...
HEADER:01000266 dw 0
HEADER:01000268 bound_import_directory_terminator dd 0
HEADER:0100026C dw 0
HEADER:0100026E dw 0
HEADER:01000270 bound_SHELL32_dll db 'SHELL32.dll',0
HEADER:0100027C bound_MSVCRT_dll db 'MSVCRT.dll',0
HEADER:01000287 bound_ADVAPI32_dll db 'ADVAPI32.dll',0
HEADER:01000294 bound_KERNEL32_dll db 'KERNEL32.dll',0
HEADER:010002A1 bound_GDI32_dll db 'GDI32.dll',0
HEADER:010002AB bound_USER32_dll db 'USER32.dll',0
Также обязательно необходимо учитывать, что перебазирование dll, к которой привязан данный исполняемый файл (приложение можно "привязать" и к своим библиотекам, bind это позволяет), также повлечет за собой чтение из "старой" директории импорта.
Теперь остановимся на одной грустной-грустной для нас с вами вещи. Положим, мы сдампили программу каким-нибудь дампером, например, LordPE. Положим, также, что некоторые опции утилиты были отключены. Что в этом случае у нас будет с директорией импорта? Предположим, имен и ординалов у нас нет!
Очевидно, файл будет рабочим только при соблюдении одного-единственного непреложного условия – все API-адреса в IAT (FirstThunk) должны быть строго валидными. Шаг в сторону – и такой файл можно просто выбросить. Или нет? Здесь мы подходим к понятию "восстановление таблицы импорта".
Под восстановлением мы понимаем воссоздание массива из структур IMAGE_IMPORT_DESCRIPTOR, обязательно с именами и ординалами. Один из возможных алгоритмов поиска выглядит так. Предполагается, что файл загружен в память и старая директория импорта не является исковерканной.
/************************
* Функция ищет старую таблицу импорта, и возвращает указатель
* на первый IMAGE_IMPORT_DESCRIPTOR
* Первый параметр это - указатель на память (Memory Mapped Files)
* Второй параметр это - размер памяти
*************************/
// Процесс этот ресурсоемкий
LPVOID FindOldImportTable(LPVOID pMem, DWORD dwSize)
{
CHAR *szStr; //Тут хранится имя модуля
HMODULE hModule;
PIMAGE_IMPORT_DESCRIPTOR pIID = {0};
for(DWORD dwRVA = 0; dwRVA < dwSize; dwRVA++)
{
// Устанавливаем SEH, так как указатель pIID->Name,
// иногда ведёт за пределы файла
__try
{
// Получаем структуру IMAGE_IMPORT_DESCRIPTOR по адресу dwRVA
pIID = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pMem + dwRVA);
// Если pIID->Name равно хоть чему нибудь, идём дальше
if(pIID->Name)
{
// Получаем имя модуля по этому адресу
szStr = (CHAR*)ImageRvaToVa(
ImageNtHeader(pMem),
pMem, pIID->Name,
NULL);
if(!IsBadStringPtr(szStr, 40))
{
// Если строка больше 4-х символов, идём дальше.
// Почему 4-м? Потому, что ".dll"
if(lstrlen(szStr) > 4)
{
// Загружаем Dll, если загрузилась идём дальше
hModule = LoadLibrary(szStr);
if(hModule != NULL)
{
FreeLibrary(hModule); // Выгружаем Dll
// Небольшая проверка на валидность структуры
if(pIID->TimeDateStamp == 0xFFFFFFFF ||
pIID->TimeDateStamp == 0x00000000)
// Возвращаем указатель
// на первый IMAGE_IMPORT_DESCRIPTOR
return pIID;
}
}
}
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
// "Access violation" однако. Все равно, продолжаем...
continue;
}
}
return NULL;
}
/************************
* Функция делает простой rebuild, и возвращает размер новой таблицы импорта
* Первый параметр это – указатель на память (Memory Mapped Files)
* Второй параметр это - адрес возвращённый функцией FindOldImportTable
*************************/
DWORD RebuildIT(LPVOID pMem, LPVOID pStart)
{
PIMAGE_DOS_HEADER pDos = {0};
PIMAGE_NT_HEADERS pNT = {0};
PIMAGE_IMPORT_DESCRIPTOR pIID = {0};
DWORD dwITSize = sizeof(IMAGE_IMPORT_DESCRIPTOR);
pDos = (PIMAGE_DOS_HEADER)pMem;
pNT = (PIMAGE_NT_HEADERS)((DWORD)pMem + pDos->e_lfanew);
pIID = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pStart);
if(!pIID)
return 0;
while (pIID->Name)
{
dwITSize += sizeof(IMAGE_IMPORT_DESCRIPTOR);
pIID->TimeDateStamp = 0x00000000;
pIID->ForwarderChain = 0x00000000;
++pIID;
}
return dwITSize;
}
//Пример использования:
DWORD dwMapSize; // Размер памяти
LPVOID m_pMap; // Указатель на память (Memory Mapped Files)
PIMAGE_DOS_HEADER pDos = {0};
PIMAGE_NT_HEADERS pNT = {0};
...
// Ищем старую таблицу импорта
IMAGE_IMPORT_DESCRIPTOR* pIID=
(PIMAGE_IMPORT_DESCRIPTOR)FindOldImportTable(m_pMap, dwMapSize);
if(pIID)
{
// Если найдена, делаем rebuild
DWORD dwSize = RebuildIT(m_pMap, pIID);
if(dwSize > 0)
{
pDos = (PIMAGE_DOS_HEADER)m_pMap;
pNT = (PIMAGE_NT_HEADERS)((DWORD) m_pMap + pDos->e_lfanew);
// Устанавливаем RVA таблицы
pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
= (DWORD)pIID - (DWORD)m_pMap;
// Устанавливаем размер таблицы
pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size
= dwSize;
}
}
...
Заметим, что данный алгоритм будет работать только на самых простых пакерах (например PECompact), с более продвинутыми, работать уже НЕ будет. Пошла сейчас мода коверкать директорию импорта. И здесь пакеры потихоньку выигрывают, т.к. искалечить всегда легче, чем восстановить. Никаких новых алгоритмов и идей здесь более обсуждаться не будет, т.к. это знание пойдет только во вред ибо может быть использовано авторами пакеров. К нашему глубокому сожалению в сети существуют некоторые проекты по восстановлению директории импорта с открытым исходным кодом, однако это большой недостаток. Уж лучше иметь работающий ImpRec, который худо-бедно но обновляется, чем открытый исходный код с устаревшей, бесполезной идеей.
Теперь рассмотрим, как работать со структурами PE-файла в IDA.
Простите за банальность – IDA – это очень мощный дизассемблер. Практически все операции идут под «капотом». Дизассемблировав PE-файл, вы даже не увидите его заголовков, IDA автоматически прогуляется по всей таблице импорта, все сделано за вас. Это и хорошо, и плохо. Хорошо это тогда, когда нет ни времени, ни желания ковыряться в секциях, просто быстро поломать. Плохо это тогда, когда попадается хитро подправленный файл, который IDA в автоматическом режиме взять не может.
Для примера давайте быстро вручную пробежимся по структурам секции импорта calc.exe. Для начала нам надо найти начало секции таблицы импорта. Итак, быренько запускаем какой-нибудь PE-редактор, например, PE Tools. В PE Editor давим кнопочку Directories. Как вы помните, это массив из #define, где секция импорта идет второй
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1
Ее RVA - 12A40. ImageBase файла – OptionalHeader.ImageBase – 01000000, следовательно, в IDA давим G, потом вводим 1012A40 (01000000 + 12A40). Вот прямо с этого места у нас и начинается массив из структур типа IMAGE_IMPORT_DESCRIPTOR.
Выглядит так:
.text:01012A40 db 0C0h ; L
.text:01012A41 db 2Bh ; +
.text:01012A42 db 1 ;
.text:01012A43 db 0 ;
.text:01012A44 db 0FFh ;
.text:01012A45 db 0FFh ;
.text:01012A46 db 0FFh ;
...
Обратите внимание на название директории еще раз. Директория будет располагаться внутри секции. Более того, ничто не мешает слить большинство секций файла в одну. Однако мы отвлеклись.
Для того чтобы указать IDA, что тут у нас структура и надо бы преобразовать поля, лезем в Structures. Там создаем Ins структуру тютелька в тютельку по документации из winnt.h:
0000 IMAGE_IMPORT_DESCRIPTOR struc
; (sizeof=0x14, standard type)
0000 OriginalFirstThunk dd ?
0004 TimeDateStamp dd ?
0008 ForwarderChain dd ?
000C Name dd ?
0010 FirstThunk dd ?
0014 IMAGE_IMPORT_DESCRIPTOR ends
Далее остается применить эту структуру к смещению 1012A40. Примерно так:
.text:01012A40 dd 12BC0h ; OriginalFirstThunk
.text:01012A40 dd 0FFFFFFFFh ; TimeDateStamp
.text:01012A40 dd 0FFFFFFFFh ; ForwarderChain
.text:01012A40 dd 12CE6h ; Name
.text:01012A40 dd 10F4h ; FirstThunk
И так до тех пор пока не попадется IMAGE_IMPORT_DESCRIPTOR, ВСЕ поля которого содержат нули. Аналогичную песню с определением новых структур придется спеть и для IMAGE_THUNK_DATA (а, в случае TimeDateStamp == 0 и для IMAGE_IMPORT_BY_NAME) и т.д., и т.п. А может и не придется! Ведь IDA хранит список стандартных структур! Поэтому, Add struct type > Add standard structure > Search. Но неплохо бы это все дело автоматизировать еще больше! Что ж, не мы с вами такие ленивые. Топаем на ссылка скрыта и сливаем оттуда PE utilities от Atli Mar Gudmundsson. Необходимо поместить их в %IDADIR%\idc, а потом F2 и файл pe_sections.idc. Если до этого вы не просто читали, а и пытались что-то делать ручками, то будете приятно удивлены.