Недетский трюк от Криса Крис Касперски Хакер, номер #092, стр. 092-122-3 13 Index of first forwarder reference 7944639C 2DF WriteFile 79450D1D 174 GetVersion 794569BE 7D ExitProcess После биндинга RVA-адреса имен API-функций сменились эффективными виртуальными адресами самих API-функций. И теперь наша программа будет загружаться не хуже, чем у Microsoft? А вот и нет. Это на твоей системе она будет загружаться «не хуже», а вот у большинства остальных пользователей временная отметка DLL наверняка не совпадет с твоей, и вся оптимизация пойдет насмарку, тем более что Microsoft имеет тенденцию обновлять DLL не только с каждой версией операционной системы, но даже с установкой очередного Service Pack'а! Кажется, что ситуация - ласты, но это не так... Как утереть нос Microsoft Самое простое решение, которое только приходит на ум, — это тащить за собой editbin (благо лицензия этого не запрещает) и делать биндинг непосредственно при установке программы. Нежелающие связываться с Microsoft могут реализовать утилиту для биндинга самостоятельно или воспользоваться линкером ulink от Юрия Харона. Но прежде чем открывать пиво и праздновать победу, задумаемся: что произойдет, если пользователь обновит систему после установки нашей программы? Правильно! Биндинг тут же перестанет работать, скорость загрузки упадет в разы, а это нехорошо. Можно, конечно, порекомендовать пользователю переустановить нашу программу после всякого обновления системы, но это негуманно и вообще жестоко. Гораздо проще поступить так. Пусть при каждом запуске наша программа проверяет TimeDateStamp всех импортируемых DLL и, если он изменился, запускает editbin (или другую утилиту) для ре-биндинга. Поскольку править активный процесс нельзя, то его необходимо завершить, породив перед этим дочерний субпроцесс или запустив bat-файл, который бы ре-биндил нашу программу и тут же перезапускал ее вновь, чтобы эти махинации протекали прозрачно для пользователя и не высаживали его на измену. экстремальная оптимизация Дизассемблировав notepad.exe или наш оптимизированный test.exe, мы увидим, что все API-функции вызываются косвенным образом, что совсем не способствует производительности. .text:0040115F 68 FF 00 00 00 push 0FFh ; uExitCode .text:00401164 FF 15 08 50 40 00 call ds:[ExitProcess] Прямой call addr намного быстрее, чем call [addr] (особенно в циклах), так почему бы не извернуться и не «вживить» в программу эффективные адреса API-функций, определяемые на стадии установки через GetProcAddress (естественно, не забывая о контроле отметки времени)? Ни одна из известных мне утилит этого делать не умеет, поэтому приходится шевелить хвостом и кодить на Си самостоятельно. Разбирая таблицу импорта откомпилированной программы, находим все перекрестные ссылки на API-функции, и, если там будет FFh 15h XXh XXh XXh XXh (косвенный call), записываем поверх него EB YYh YYh YYh YYh 90h (непосредственный CALL + NOP; зачем нам нужен NOP? а затем, что непосредственный вызов на байт короче), где YYh YYh YYh YYh – относительный адрес API-функции, отсчитываемый от конца инструкции CALL. После этого отбрасываем таблицу импорта, оставляя лишь KERNEL32.DLL с единственной импортируемой функцией (неважно какой). Дело в том, что системный загрузчик Windows 2000 содержал ошибку и отказывался загружать программы, не импортирующие ни одной функции из KERNEL32.DLL, а значит, не проецирующих ее на свое адресное пространство. Поскольку сам загрузчик нуждался в KERNEL32.DLL, но забывал проверить, была ли она вообще спроецирована или нет, приложения без таблицы импорта падали с исключением. |