Доброго времени суток! В качестве метода проникновения в новосозданный процесс, я использую сплайсинг точки входа (как альтернатива хуку NtResumeThread). Да-да, я уже прекрасно осознаю недостатки сплайсинга и вообще, работы в usermode, но, просто, хочется доделать до конца. Поэтому мне нужен совет. Ситуация такая. Я описал функцию, в которой я в первую очередь выполняю Unhook точки входа (т.к. изначально, на Entry point стоит Relative JMP на мой код), затем делаю PUSH/RET снова на точку входа (схему с bridge я использую как запасной вариант ... не суть). Выполняя свой код, соотв., я добавляю новые данные в стек, работаю с регистрами и пр. Некоторые главные функции программ сконструированы таким образом, что в их конце стоит не вызов, скажем, kernel32!ExitProcess, а что-то вроде RETN. Вот, в этом случае, адрес для перехода на "деструктор" программы (ntdll!RtlCreateUserThread & ntdll!RtlExitUserProcess и пр.) хранится в стеке, который, я, соответственно, "испортил" своим кодом. Возникла мысль - использовать GetThreadContext(GetCurrentThread(), ...) в начале своего кода, чтобы за'backup'ить стек и регистры, а затем, вернуть их с помощью SetThreadContext уже в конце выполнения кода, перед PUSH/RET. Однако, я взглянул на ASM-код своей функции, куда попадаю после Reletive JMP'а и увидел след.: Код (Text): ; 369 : VOID xEntryPoint () { 00000 55 push ebp 00001 8b ec mov ebp, esp 00003 81 ec e0 02 00 00 sub esp, 736 ; 000002e0H 00009 56 push esi 0000a 57 push edi ; ... ; 373 : ; 374 : HANDLE hThread = GetCurrentThread(); 0000e ff 15 00 00 00 00 call DWORD PTR __imp__GetCurrentThread@0 00014 8b f8 mov edi, eax ; 375 : CONTEXT ct; ; 376 : GetThreadContext(hThread, &ct); 00016 8d 85 20 fd ff ff lea eax, DWORD PTR _ct$[ebp] 0001c 50 push eax 0001d 57 push edi 0001e ff 15 00 00 00 00 call DWORD PTR __imp__GetThreadContext@8 Т.е. ещё до того, как начнёт выполняться мой код в моей функции, я не смогу получить "слепок" с пом. GetThreadContext. Хм... теперь на мысль приходит использование мини-дизасма, чтобы восстановить слепок регистров и стэка до момента входа в фукнцию xEntryPoint. Ещё можно вместо первого JMP'а внедрить вызов GetThreadContext, хотя, не хотелось бы переписывать модуль сплайсинга. И вообще, я бы хотел спросить - если мы вызываем GetThreadContext из текущего потока, то мы "во время вызова" функции уже изменяем стек (для передачи аргументов функции) ... хотя, впринципе, сразу же после вызова можно восстановить, ... вроде, нужно будет только ESP поправить? В общем, прошу совета по решению проблемы. Подскажите: "Как грамотно сплайснуть точку входа?"
В общем, похоже, проблема решена. Всего пару строк кода. В моём случае, видимо, будет достаточно восстановить стэк (ESP). Поэтому вот что я сделал: Объявил naked-функцию и использовал её в качестве переходника на основную. Т.е. сначала Relative JMP с просплайсеной функции попадает на naked-функцию, затем, она передаёт управление на основную (на xEntryPoint()). Код (Text): VOID xEntryPoint (); /* Naked function as bridge (just for getting ESP) */ DWORD dwESP; VOID __declspec(naked) xEntryPointNaked () { __asm mov dwESP, esp __asm push xEntryPoint __asm ret } Ну а дальше, переход на unhook'нутую точку входа: Код (Text): VOID xEntryPoint () { /* some evil code here*/ __asm mov esp, dwESP __asm push dwEntryPoint __asm ret } Какие ещё подводные камни могут быть в таком подходе?
..w2k\private\windows\base\client\process.c Код (Text): CreateProcessW: .. // // Create a section object backed by the file // Status = NtCreateSection( &SectionHandle, SECTION_ALL_ACCESS, NULL, NULL, PAGE_EXECUTE, SEC_IMAGE, FileHandle ); .. // // Query the section to determine the stack parameters and // image entrypoint. // Status = NtQuerySection( SectionHandle, SectionImageInformation, &ImageInformation, sizeof( ImageInformation ), NULL ); .. // // Create an initial context for the new thread. // BaseInitializeContext( &ThreadContext, Peb, ImageInformation.TransferAddress, InitialTeb.StackBase, BaseContextTypeProcess ); // // Create the actual thread object // pObja = BaseFormatObjectAttributes(&Obja,lpThreadAttributes,NULL); Status = NtCreateThread( &ThreadHandle, THREAD_ALL_ACCESS, pObja, ProcessHandle, &ClientId, &ThreadContext, &InitialTeb, TRUE ); .. Так сложно хоть раз в сурцы залезть или пройти дебугом чтоб поверхностно осознать как процесс создаётся ? - http://wasm.ru/forum/viewtopic.php?pid=284176#p284176 Там опечатка, BaseProcessStartThunk() принимает только один параметр в Eax. Вызвано тем, что процедура которую вызывает первый поток в процессе, это та из опционального заголовка образа не имеет параметров, а процедура которую вызывают остальные потоки имеет один параметр. Короче в контексте будет: Eip @BaseProcessStartThunk() Eax @OEP - Ну и зачем изменять код, если можно выполнить с контекстом манипуляции ? Зациклился на сплайсе, какбудто ничего другого нет %.. Инфа для размышления по теме. Кошерный способ не должен использовать очевидные фичи. Можно использовать синхроный доступ к обьекту для захвата треда, как частный случай ввод потока в состояние ожидания освобождения критической секции. Тогда мы можем получить контекст ждущего потока, выполнить бактрейс, в нужном стековом фрейме заменить адрес возврата. После чего тред может быть отпущен сигнализацией обьекта, вручную или выходом первого потока из критической секции. Вобщем вот пример(это не пройдёт для захвата потока на этапе инициализации ресурсов) http://paste.org.ru/?j7n4in бу: кнопка для выделения отвалилась