Есть произвольный код в адресном пространстве некоторого процесса. Этот код должен отлавливать событие завершения этого процесса. Что-то типа сообщения DLL_PROCESS_DETACH, но это сообщение приходит динамическеим библиотекам, а у меня произвольный код. Можно, конечно, перехватывать функцию ExitProcess, ExitThread, но не хочу гемора с перехватом...
Теоретически можно попробовать следующее. При старте процесса в стеке сохраняется адрес возврата именно по этому и можно завершать программу по команте ret (если в стек ничего не добавлялось) При нормально анализе можно в стеке найти этот адрес и подменить на адрес своего обработчика. Геморно но всёже как один из вариантов ну и + желательно ExitProcess перехватить и тоже пустить на свой обработчик
если код действительно в том же процессе, то только сообщение обрабатывать и хукать ничего не надо. (wm_terminate или что то в этом роде), но это только если приложение win а не дос.
2 Ryu млин, а если приложения WIN но без оконное? ТОгда регистрировать обработчик сообщений нужно. А если консольное, то вообще тогда нужно через SetConsoleCtrlhandler() так что отлавливать сообщения - многое нужно учесть. При том, что как выше сказано код в произвольном процессе и следовательно он туда както внедряется и должен быть по крайней мере независимым от своего расположения в памяти. P.S. Ну если код ведет логи, то пусть сразу их и пишет и делает чтото типа flush чтобы сразу скинуть на винт, а там при заврешении процесса - автоматом все дискрипторы загроются
gevara Тут только перехватывать ZwExitProcess и ZwExitThread. P.S. Фишка со стеком, как и с оконными сообщениями не прокатит, если приложение само вызывает ExitProcess.
2 SlyBit Блин чем дальше в лес, тем больше дров, которые человек наломает. Такими темпами можно будет уже говорить о методах сплайсенга функций а там уже и до Native Api дойдем со стороны ядра. P.S. 2 gevara cразу скажи в каком приложении ты это собираешься делать? или в любых. А то мы можем так далеко зайти в обсуждении, а на самом деле всё будет просто.
приложение оконное. - адрес возврата подменить не получится - что если приложение многопоточное? перехватывать CreateThread проще перехватить ExitThread - Вообще сообщение DLL_PROCESS_DETACH приходит либам в том случае, когда последний поток завершается вызовом ExitThread, то есть перехват оконных сообщений скорее всего всё-таки возможен, но я почему-то не вижу сообщения WM_TERMINATE... такого сообщения в SDK нет вообще. - есть WM_CLOSE, WM_DESTROY, WM_QUIT, но стоит ли надеяться на эти сообщения? множество апликух не юзают их вообще. апликуха может иметь несколько окон и при закрытии одного из них апликуха продолжает работать (приходит сообщение WM_CLOSE) - есть вот какая тема - добавить свою запись в двусвязный список LDR_MODULE (или как его там...), указав адрес обработчика, которому и будут приходить сообщения DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH и прочие...
сорри, ошибся, имел ввиду: ntdll.ZwTerminateProcess ntdll.ZwTerminateThread Вообщем ты себе все круто усложняешь. Лучше все же вернись к перехвату функций, можно подменой адреса в таблице ипорта (как советовал Jes), если не хочешь возиться со сплайзингом. Если хуки тебя не в какую не устраивают, то можно вместо добавления новой структуры в двусвязный список загруженных библиотек (довольно палевно, т.к. такие программы как Process Explorer увидят странную библиотеку), изменить базу существующей библиотеку на свой обработчик. При вызове твоего обработчика проверяй 2-ой параметр Reason на равенство коду DLL_PROCESS_DETACH, пиши там свои логи и затем вызывай оригинальную точку входа библиотеки с такими же параметрами. Подменяй точку входа у невыгружаемых библиотеках, таких как user32.dll, kernel32.dll, ntdll.dll. Теперь постараюсь объяснить как выйти на этот список и что нужно менять. Для начала нужно получить адрес структуры PEB процесса. Указатель лежит по адресу fs:[30h], либо можно взять его из структуры PROCESS_BASIC_INFORMATION по смещению +4 байта. Указатель на саму струкуру PROCESS_BASIC_INFORMATION получаешь вызвав функцию NtQueryInformationProcess со вторым параметром равным 0 (ProcessBasicInformation). Указатель на PEB есть. По смещению 0xC в PEB находится указатель на структуру PPEB_LDR_DATA. В этой структуре находятся 3 указателя на точки входа двусвязных списков, содержащих информацию о библиотеках на стадии загрузки, уже загруженных в память и на стадии инициализации. Нас интересует второй указатель. Теперь нам нужно обойти список LDR_DATA_TABLE_ENTRY и сравнить имя BaseDllName с "ntdll.dll" например. Как находим, заменяем EntryPoint на наш обработчик. Это все в теории, если что-то не так - поправьте. NTDLL.h
Что толку от перехвата таблицы импорта, если не знаешь какой именнго модуль завершит последний поток? вообще прога может завершить своё выполнение по рету и ExitProcess будет вызван из kernel32 - тут только сплайсинг. а вариант с изменением адреса входа действительно реальный, хотя, возможно, и не самый лучший. ведь адрес точки входа будет лежать за границами модуля, что может вызвать подозрения...
да, я про таблицу экспорта kernel32 и ntdll конечно...какая нафиг импорта мне кажется вариант со сплайзингом такой же палевный как и подмена адреса точки входа либы. Подменяй тогда адрес возврата.
А вот и пример подмены точки входа библиотеки подоспел (описание несколькими постами выше): Code: #include <windows.h> #include "ntdll.h" #pragma comment(linker, "/ENTRY:Main") typedef DWORD (WINAPI *PTRUEENTRY)(HMODULE Module, DWORD Reason, LPVOID Reserved); PVOID dwTrueAddr; DWORD WINAPI CatchExit(HMODULE Module, DWORD Reason, LPVOID Reserved) { if(Reason == DLL_PROCESS_DETACH) { // Событие перед завершение приложения } return ((PTRUEENTRY)dwTrueAddr)(Module, Reason, Reserved); } VOID WINAPI HookDllEntry(PWCHAR pDllName, PVOID pDummyEntry) { PPEB pPeb; PPEB_LDR_DATA pPebLdrData; PLDR_DATA_TABLE_ENTRY pLdrDataTableEntry; DWORD dwBlink; __asm { mov eax, fs:[0x30] mov pPeb, eax } pPebLdrData = (PPEB_LDR_DATA)pPeb->Ldr; // Запоминаем адрес последнего элемента в двусвязном списке dwBlink = *(PDWORD)pPebLdrData->InMemoryOrderModuleList.Blink; // Получаем указатель на первую структуру в двусвязном списке pLdrDataTableEntry = (PLDR_DATA_TABLE_ENTRY)pPebLdrData->InLoadOrderModuleList.Flink; // Обходим все структуры и как находим библиотеку с именем pDllName, изменяем её точку входа do { pLdrDataTableEntry = (PLDR_DATA_TABLE_ENTRY)pLdrDataTableEntry->InLoadOrderModuleList.Flink; if(!lstrcmpW(pLdrDataTableEntry->BaseDllName.Buffer, pDllName)) { dwTrueAddr = pLdrDataTableEntry->EntryPoint; pLdrDataTableEntry->EntryPoint = pDummyEntry; } } while(dwBlink != *(PDWORD)pLdrDataTableEntry->InMemoryOrderModuleList.Flink); } VOID WINAPI Main() { HookDllEntry(L"ntdll.dll", CatchExit); ExitProcess(0); }
SlyBit, пасиб, конечно. но это вобщем-то понятно. у меня ситуация несколько другая - вот я и думаю что сделать лучше: перехват PEB или сплайсинг ExitThread или ExitProcess. Вообще-то мой код и так хучит кое-какие функции. В частности имеет доступ к оконным сообщениям. так что идеальный вариант - отслеживать сообщения (есали такие есть) о закрытии всех окон или что-то типа этого...
gevara Я думаю, что оконные сообщения отпадают по как минимум 2м причинам: не отслеживается самостоятельный вызов ExitProcess программой, не всегда они "правильно" обрабатываются программой. Пример Outpost, при посылке его главному окну сообщения WM_CLOSE (щелчек на крестик в правом верхнем углу), оно просто сворачивается, вместо того чтобы закрыться. На сообщения WM_DESTROY и WM_QUIT вообще нет никакой реакции. Калькулятор же спокойно закрывается получив WM_CLOSE. Выбор между модификацией PEB и сплайзингом ExitProcess и ExitThread. В первом случае все просто. Во втором нужно считать количество потоков, приложение закроется как только закроют его последний поток. Для этого нужно будет перехватывать CreateThread и икрементировать счетчик кол-ва потоков и декрементировать при выхове ExitThread, либо подсчитывать количество потоков перед вызовом ExitThread.