_________________ Глава 18. Прорыв за пределы процесса 625

Теперь мне оставалось вызвать ResumeThread, и удаленный поток начал бы выполнять
мои машинные команды, т. е. загрузил бы DLL, выгрузил бы ее и завершил поток.

Кое-что в этом способе надо обсудить подробнее. Во-первых, мне пришлось самому
вызывать ExitThread, так как я изменил указатель команд. Вспомните: для потока он иници-
ализируется адресом функции StartOfThread (см. главу 4). Побочный эффект от изменения
указателя команд состоит в том, что StartOfThread не выполняется. Обычно StartOfThread
вызывает функцию потока, но так как я изменил указатель команд, StartOfThread мою фун-
кцию потока не вызывает. Следовательно, в конец кода нельзя поместить инструкцию RET
(для Intel x86) — процессор не знал бы, куда передать управление. И, чтобы корректно
завершить удаленный поток, приходится явно вызывать ExitThread.

Во-вторых, если библиотеку не удалось инициализировать должным образом, Load-
Library A возвращает NULL. В этом месте мой код помещает NULL в стек и вызывает free-
Library. Наверное, правильнее было бы сравнивать значение регистра ЕАХ с 0 и вызывать
FreeLibrary, только если ЕАХ не равен 0. Я не включил в код такую проверку, потому что мне
совсем не хотелось самому кодировать на машинном языке еще и эти инструкции. Мне
казалось, FreeLibrary «поймет», что я передаю неверное значение hinstDll, и просто вернет
FALSE, сообщая о неудачном вызове.

В-третьих, пропуск функции StartOfThread означает, что SEH-фрейм, создаваемый
для потока по умолчанию, установлен некорректно. Это проблема, если код потока вызовет
исключение. Допустим, мы отмахнулись от нее. В конце концов, что может случиться при
вызове LoadLibraryA, FreeLibrary и ExitThreacP. Но вспомним главу 12: этот поток отвечает и
за выполнение DllMain (из загружаемой нами библиотеки) со значениями fdwReason, равны-
ми DLL_PROCESS_ATTACH и DLL_PROCESS_DETACH. Если при этом появится необрабо-
танное исключение, система немедленно завершит процесс, ничего не сообщив пользовате-
лю. Конечно, я мог бы написать дополнительный машинный код и сам создать нужный
SEH-фрейм, но это действительно очень трудно. К тому же SEH привязана к архитектуре
конкретного процессора и крайне сложна.

И наконец, как видите, самая большая проблема — кропотливое кодирование на язы-
ке ассемблера. Мне как-то не понравилась идея работать на уровне машинных кодов, и я
отказался от устранения потенциальных проблем. Кроме того, всю эту работу пришлось бы
проделать заново для каждого типа процессора. И представьте, я уже провел ее для MIPS и
взялся было за Alpha, когда один мой приятель предположил, что все-таки должен быть
какой-то способ написать этот код как аппаратно-независимый.

Признаюсь, поначалу мы с ним были настроены весьма скептически, но потом стали
обмениваться всякими бредовыми мыслями и в конце концов кое-что придумали. Я засел
за код (сохранив, конечно, прежний вариант), и через несколько часов (что-то около двух
ночи) мы все-таки получили нечто, что заработало на Intel и MIPS. Так появилась версия 2.

Версия 2: A/JocProcessMemory и Create Thread

Вот к чему мы пришли:

1. Выделяем участок памяти в адресном пространстве удаленного процесса.

2. Копируем код функции из адресного пространства нашего процесса в адресное
пространство удаленного (подробности ниже).

3. Копируем в адресное пространство удаленного процесса структуру данных INJ-
LIBINFO, содержащую полное имя DLL и другую важную информацию.

4. Вызываем CreateRemote Thread, передавая ей адрес скопированной функции в уда-
ленном процессе как параметр IpStartAddr, а адрес структуры INJLIBINFO как па-
раметр IpvThreadParm.

5. Ожидаем завершения удаленного потока.

6. Освобождаем память, выделенную на первом этапе.