Недетский трюк от Криса Крис Касперски Хакер, номер #092, стр. 092-122-1 Сверхбыстрый импорт API-функций Окруженный компьютерами, опутанный проводами, я сидел в глубине своей хакерской норы и точил зверский план, который впоследствии обогнал Microsoft! Да еще как обогнал! Скорость импорта возросла на порядок, отлично работая как на древней 9x, так и на новом Windows Server 2003, включая все промежуточные системы, причем без грамма ассемблерного кода! Все на 100% Си! Импорт API-функций «отъедает» существенный процент от общего времени загрузки исполняемых файлов, поэтому и возникает естественное желание его сократить. Системный загрузчик крайне неэффективен и выполняет множество лишних проходов. Разбирая стандартную таблицу импорта, он выполняет для каждой импортируемой функции полный поиск соответствующего имени/ординала в таблице экспорта, не обращая внимания на то, что экспорт KERNEL32.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 date\time 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-таблицу, что (теоретически) позволяет загрузчику обходится одной лишь таблицей виртуальных адресов, избавляясь от прыжков по памяти, но практически он ее игнорирует. |