Недетский трюк от Криса

Крис Касперски

Хакер, номер #092, стр. 092-122-1

Сверхбыстрый импорт API-функций

Окруженный компьютерами, опутанный проводами, я сидел в глубине своей хакерской норы и точил зверский план, который впоследствии обогнал Microsoft! Да еще как обогнал! Скорость импорта возросла на порядок, отлично работая как на древней 9x, так и на новом Windows Server 2003, включая все промежуточные системы, причем без грамма ассемблерного кода! Все на 100% Си!

Импорт API-функций «отъедает» существенный процент от общего времени загрузки исполняемых файлов, поэтому и возникает естественное желание его сократить. Системный загрузчик крайне неэффективен и выполняет множество лишних проходов. Разбирая стандартную таблицу импорта, он выполняет для каждой импортируемой функции полный поиск соответствующего имени/ординала в таблице экспорта, не обращая внимания на то, что экспорт KEEL32.DLL и другие системные библиотеки упорядочены по алфавиту, и, если таким же образом упорядочить импорт пользовательских программ, все API-функции можно слинковать за один проход, используя минимум операций сравнения.

В принципе, никто не заставляет нас пользоваться стандартным загрузчиком. Формат таблиц экспорта хорошо описан, и при желании необходимые API-функции можно импортировать и «вручную». В частности, линкер ulink от Юрия Харона именно так и поступает, загружая необходимые ему API-функции по вышеописанному алгоритму (о чем подробно описывают «Записки мыщъх'а», выложенные на ftp://nezumi.org.ru), однако это еще не предел оптимизации.

Коварство и любовь от Microsoft

Рассмотрим устройство стандартной таблицы импорта. На вершине иерархии находится структура Import Directory Table, представляющая собой массив структур IMAGE_IMPORT_DESCRIPTOR, завершаемых нулевым элементом. Каждый IMAGE_IMPORT_DESCRIPTOR содержит ссылки на две подчиненные структуры – lookup-таблицу, содержащую имена и/или ординалы импортируемых функций (Import Name Table), и таблицу импортируемых адресов (Import Address Table), также известную как Thunk Table. В процессе загрузки файла сюда записываются эффективные адреса импортируемых функций.

Обе таблицы представляют собой массив 32-битных элементов, индексы которых взаимно соответствуют друг другу. То есть, если необходимая нам функция some_func находится в i-элементе lookup-таблицы, тогда (после загрузки файла в память) i-индекс таблицы импортируемых адресов будет содержать эффективный виртуальный адрес some_func.

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;

До загрузки файла в память таблица импортируемых адресов дублирует lookup-таблицу, что (теоретически) позволяет загрузчику обходится одной лишь таблицей виртуальных адресов, избавляясь от прыжков по памяти, но практически он ее игнорирует.

Содержание  Вперед на стр. 092-122-2
ttfb: 6.8428516387939 ms