Есть программа, которая запускает процесс при помощи CreateProcess, и выполняет инжект dll в этот процесс, используя SetContext. Однако возникает проблема, когда процесс быстро завершается естетственным образом в силу заложенного в него алгоритма. Если проводить инжект на начальных сталиях загрузки процесса (например, в процессе инициализации статических dll), то все работает очень нестабильно. А как поймать момент, когда dll инициализированны и программа перешла на entry point и далее? Можно конечно через IAT определить какие dll будет загружать процесс и в цикле определять модули, загруженные процессом в текущий момент, пока не будут загружены все... но имхо это извращение) Нет ли какого простого и эффективного способа (при этом желательно действовать пассивно, т.е. не влиять напрямую на процесс)? Рассматривается только usermode... ОС: WinXP SP3
Инжект осуществляется следующим образом: 1) Suspend 2) ищется страница доступная для записи (ну или выделяется новая, либо изменяется защита страницы - не суть) 3) туда записывается путь к dll (NtWriteVirtualMemory) и другие необходимые данные 4) запрашивается адрес стека (GetContext) и туда записываются параметры вызова LdrLoadDll 5) через SetContext управление передается LdrLoadDll 6) Resume Думаю, такого рода инжект не прокатит, если его проводить с остановленным сразу после создания процессом, хотя бы потому, что процесс еще не подгрузил ntdll.dll и вызов LdrLoadDll не получится! Да и после загрузки ntdll.dll (но еще во время инициализации других библиотек) код работает очень нестабильно (хотя изредка прокатывает). Чаще всего процесс завершается с ошибкой инициализации и даже без подгрузки требуемой dll. Вообще погуглив я понял, что процесс инициализации статических dll очень чувствительный и малейшая ошибка на этом этапе приводит к немедленному аварийному завершению процесса, поэтому я и хотел начать инжект на более поздней стадии...
ss Загрузчик использует кс LdrpLoaderLock. Если она не освобождена, то какойто поток находится в загрузчике, это просто к сведению. Выполнить бактрейс, последний фрейм будет частью структуры: Код (Text): APC_FRAME struct Frame STACK_FRAME <> ; Ip ~ KiUserApcDispatcher(), !Next pContext PCONTEXT ? ; OPTIONAL NtBase PVOID ? ; ntdll Reserved1 DWORD ? Context CONTEXT <> APC_FRAME ends Контекст содержит необходимые линки на стартап код(Eip/Eax).
Только сейчас вспомнил про семп(по сути задача изменения OEP из DllInitRoutine() таже что и ваша): http://files.virustech.org/indy/Temp/Th/ зы: иногда полезно читать свои посты и кодесы %.
ss ntdll.dll проецируется ядром. Если процесс есть, в нем всегда есть ntdll. А вот другие длл уже проецируются при инициализации. Так что за это не переживай.
Clerk, спасибо! В итоге удалось реализовать предложенный тобой метод с бектрейсом. В цикле с периодом считывается контекст запущенного процесса, бектрейсятся стековые кадры и определяется EP, которая сравнивается со взятой из exe-файла. Если EP совпадают, значит процесс уже инициализировал библиотеки и прошел EP. Конечно, иногда цикл все равно проскакивает нужный момент, но это уже вопрос выбора периода считывания контекста, а так в целом метод работает. Вот только странно... у меня получилось, что EP это не что иное как pContext из структуры APC_FRAME. Разве так должно быть? и еще вопрос про LdrpLoaderLock... Можно ли как-то определить залочена ли секция в запущенном процессе из другого процесса? Да, проверил) Но все равно, если попробовать сменить контекст на этом этапе, то процесс просто тихо умирает без инжекта dll. Хотя возможно я что-то делаю не так, но тот же код прекрасно работает с уже запущенными процессами...
ss Нет. Загрузчик вызывается как APC(LdrInitializeThunk()), после чего выполняется загрузка контекста в процессор и переход на EP, либо стаб(BaseProcessStart() etc.). Пока поток не выполнил возврат из APC последний фрейм это структура APC_FRAME, иначе этой структуры уже нет. Например для BaseProcessStart() в фрейме по [Ebp + 8] лежит линк на EP, это просто параметр этой функции. APC_FRAME может использоваться если поток находится в загрузчике(LdrpLoaderLock не освобождена). Да. Это структура: Код (Text): typedef struct _RTL_CRITICAL_SECTION { PRTL_CRITICAL_SECTION_DEBUG DebugInfo; LONG LockCount; LONG RecursionCount; HANDLE OwningThread; HANDLE LockSemaphore; ULONG_PTR SpinCount; } RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION; Если кс свободна, то LockCount = -1, !OwningThread. Ссылка на эту кс находится в PEB.LoaderLock(+0xA0).
Ага, понятно - видимо это я и нашел... Также попробовал реализовать этот способ... Все работает и даже похоже он значительно попроще) И все же у этих двух способов есть один минус - нет гарантии, что момент после инициализации библиотек будет пойман, если процесс после перехода на EP очень быстро завершится. Поэтому развивая тему, прочитал http://wasm.ru/forum/viewtopic.php?id=38310. И хотелось бы окончательно выяснить с созданием процесса с флагом CREATE_SUSPENDED. Я так понял, что на этом этапе вход в LdrInitializeThunk() еще не произошел? и менять контекст потока, способом описанным в моем втором сообщении, с инжектом dll не получится? Как же тогда гарантированно поймать момент после загрузки статических dll? Может быть ответ в этом?
можно попробовать CreateProcess(DEBUG_ONLY_THIS_PROCESS) + DebugEvent.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT
ss Это ведь асинхронно всё. Получится изменить контекст разумеется, но только для пользовательской APC, а не ядерной(стартап APC ядерная). fsd Отладчик получает уведамление при проецировании секции, тоесть как только проекция будет отображена ядром. Далее возникнет останов в DbgBreakPoint(), до нотификации модулей.
В итоге реализовал следующий способ инжекта: 1) создается процесс с флагом CREATE_SUSPENDED 2) в памяти процесса ищется страница, доступная для записи и туда записывается путь к инжектируемой dll 3) посылается QueueUserAPC с указателем на LoadLibraryA и адресом, записанного на предыдущем шаге пути к dll 4) ResumeThread После загрузки статических dll и до перехода на entry point ГАРАНТИРОВАННО вызывается APC и подгружается инжектируемая библиотека, что собственно и требовалось! В отличие от двух других способов, которые обсуждались в топике, этот прекрасно работает в том числе с процессами, которые в силу заложенного в них алгоритма очень быстро завершаются. Всем спасибо, а особенно Clerk! Благодаря твоим постам узнал много нового) Думаю тему можно закрывать...