PDA

Просмотр полной версии : Событие завершения процесса


gevara
10.07.2008, 09:33
Есть произвольный код в адресном пространстве некоторого процесса. Этот код должен отлавливать событие завершения этого процесса. Что-то типа сообщения DLL_PROCESS_DETACH, но это сообщение приходит динамическеим библиотекам, а у меня произвольный код. Можно, конечно, перехватывать функцию ExitProcess, ExitThread, но не хочу гемора с перехватом...

0verbreaK
10.07.2008, 09:52
а вопрос?

gevara
10.07.2008, 10:40
Код сисдит в апликухе и должен сохранить логи по завершении процесса.

slesh
10.07.2008, 12:27
Теоретически можно попробовать следующее.
При старте процесса в стеке сохраняется адрес возврата именно по этому и можно завершать программу по команте ret (если в стек ничего не добавлялось)
При нормально анализе можно в стеке найти этот адрес и подменить на адрес своего обработчика. Геморно но всёже как один из вариантов
ну и + желательно ExitProcess перехватить и тоже пустить на свой обработчик

Ryu
10.07.2008, 12:30
если код действительно в том же процессе, то только сообщение обрабатывать и хукать ничего не надо. (wm_terminate или что то в этом роде), но это только если приложение win а не дос.

slesh
10.07.2008, 12:50
2 Ryu млин, а если приложения WIN но без оконное? ТОгда регистрировать обработчик сообщений нужно. А если консольное, то вообще тогда нужно через SetConsoleCtrlhandler()
так что отлавливать сообщения - многое нужно учесть. При том, что как выше сказано код в произвольном процессе и следовательно он туда както внедряется и должен быть по крайней мере независимым от своего расположения в памяти.

P.S. Ну если код ведет логи, то пусть сразу их и пишет и делает чтото типа flush чтобы сразу скинуть на винт, а там при заврешении процесса - автоматом все дискрипторы загроются

Jes
10.07.2008, 13:20
Можно, конечно, перехватывать функцию ExitProcess, ExitThread, но не хочу гемора с перехватом...
а какой гемор? поменяй в таблице импорта адреса и всё
(если там конечно не динамич импорт)

SlyBit
10.07.2008, 13:22
gevara

Тут только перехватывать ZwExitProcess и ZwExitThread.

P.S. Фишка со стеком, как и с оконными сообщениями не прокатит, если приложение само вызывает ExitProcess.

slesh
10.07.2008, 13:32
2 SlyBit Блин чем дальше в лес, тем больше дров, которые человек наломает.
Такими темпами можно будет уже говорить о методах сплайсенга функций а там уже и до Native Api дойдем со стороны ядра.

P.S. 2 gevara cразу скажи в каком приложении ты это собираешься делать? или в любых. А то мы можем так далеко зайти в обсуждении, а на самом деле всё будет просто.

gevara
10.07.2008, 16:16
приложение оконное.
- адрес возврата подменить не получится - что если приложение многопоточное? перехватывать 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 и прочие...

gevara
10.07.2008, 16:24
Тут только перехватывать ZwExitProcess и ZwExitThread.
таких функций не сущесмтвует

Hellsp@wn
10.07.2008, 17:23
вот такие функции есть:

ntdll.ZwTerminateProcess
ntdll.ZwTerminateThread

SlyBit
10.07.2008, 17:43
таких функций не сущесмтвует
сорри, ошибся, имел ввиду:

ntdll.ZwTerminateProcess
ntdll.ZwTerminateThread

- есть вот какая тема - добавить свою запись в двусвязный список LDR_MODULE (или как его там...), указав адрес обработчика, которому и будут приходить сообщения DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH и прочие...
Вообщем ты себе все круто усложняешь. Лучше все же вернись к перехвату функций, можно подменой адреса в таблице ипорта (как советовал 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 (http://slil.ru/25970231)

gevara
10.07.2008, 18:13
Что толку от перехвата таблицы импорта, если не знаешь какой именнго модуль завершит последний поток? вообще прога может завершить своё выполнение по рету и ExitProcess будет вызван из kernel32 - тут только сплайсинг. а вариант с изменением адреса входа действительно реальный, хотя, возможно, и не самый лучший. ведь адрес точки входа будет лежать за границами модуля, что может вызвать подозрения...

SlyBit
10.07.2008, 18:26
да, я про таблицу экспорта kernel32 и ntdll конечно...какая нафиг импорта ;) мне кажется вариант со сплайзингом такой же палевный как и подмена адреса точки входа либы.

вообще прога может завершить своё выполнение по рету и ExitProcess будет вызван из kernel32
Подменяй тогда адрес возврата.

zl0y
10.07.2008, 18:50
KiSystemFastRet+ZwTerminateProcess.

gevara
10.07.2008, 20:16
да, я про таблицу экспорта kernel32 и ntdll конечно...какая нафиг импорта ;) мне кажется вариант со сплайзингом такой же палевный как и подмена адреса точки входа либы.


Подменяй тогда адрес возврата.
про адрес возврата я уже сказал - не катит на случай многопоточности

SlyBit
10.07.2008, 20:19
А вот и пример подмены точки входа библиотеки подоспел (описание несколькими постами выше):

#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);
}

gevara
10.07.2008, 20:29
SlyBit, пасиб, конечно. но это вобщем-то понятно. у меня ситуация несколько другая - вот я и думаю что сделать лучше: перехват PEB или сплайсинг ExitThread или ExitProcess. Вообще-то мой код и так хучит кое-какие функции. В частности имеет доступ к оконным сообщениям. так что идеальный вариант - отслеживать сообщения (есали такие есть) о закрытии всех окон или что-то типа этого...

SlyBit
10.07.2008, 21:18
gevara

Я думаю, что оконные сообщения отпадают по как минимум 2м причинам: не отслеживается самостоятельный вызов ExitProcess программой, не всегда они "правильно" обрабатываются программой. Пример Outpost, при посылке его главному окну сообщения WM_CLOSE (щелчек на крестик в правом верхнем углу), оно просто сворачивается, вместо того чтобы закрыться. На сообщения WM_DESTROY и WM_QUIT вообще нет никакой реакции. Калькулятор же спокойно закрывается получив WM_CLOSE.

Выбор между модификацией PEB и сплайзингом ExitProcess и ExitThread. В первом случае все просто. Во втором нужно считать количество потоков, приложение закроется как только закроют его последний поток. Для этого нужно будет перехватывать CreateThread и икрементировать счетчик кол-ва потоков и декрементировать при выхове ExitThread, либо подсчитывать количество потоков перед вызовом ExitThread.

razzzar
10.07.2008, 22:21
когда-то писал программку, в которой надо было выполнить код, при завершении собственно этой программы. помогла кртшная функция _onexit(); она ставит функции на выполнение, при завершении процесса.
http://msdn.microsoft.com/en-us/library/zk17ww08(VS.71).aspx

gevara
10.07.2008, 23:15
gevara

Я думаю, что оконные сообщения отпадают по как минимум 2м причинам: не отслеживается самостоятельный вызов ExitProcess программой, не всегда они "правильно" обрабатываются программой. Пример Outpost, при посылке его главному окну сообщения WM_CLOSE (щелчек на крестик в правом верхнем углу), оно просто сворачивается, вместо того чтобы закрыться. На сообщения WM_DESTROY и WM_QUIT вообще нет никакой реакции. Калькулятор же спокойно закрывается получив WM_CLOSE.

Выбор между модификацией PEB и сплайзингом ExitProcess и ExitThread. В первом случае все просто. Во втором нужно считать количество потоков, приложение закроется как только закроют его последний поток. Для этого нужно будет перехватывать CreateThread и икрементировать счетчик кол-ва потоков и декрементировать при выхове ExitThread, либо подсчитывать количество потоков перед вызовом ExitThread.
Вынужден тебя огорчить.
1) при перехвате ExitProcess считать потоки совершенно не обязательно
2) при перехвате ExitThread их считать тоже не обязательно. посмотри что делает система:

7C80CCA9 > 6A 14 PUSH 14
7C80CCAB 68 10CD807C PUSH kernel32.7C80CD10
7C80CCB0 E8 1658FFFF CALL kernel32.7C8024CB
7C80CCB5 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
7C80CCBB 8BF0 MOV ESI,EAX
7C80CCBD 8975 E0 MOV DWORD PTR SS:[EBP-20],ESI
7C80CCC0 33FF XOR EDI,EDI
7C80CCC2 57 PUSH EDI
7C80CCC3 6A 04 PUSH 4
7C80CCC5 8D45 E4 LEA EAX,DWORD PTR SS:[EBP-1C]
7C80CCC8 50 PUSH EAX
7C80CCC9 6A 0C PUSH 0C
7C80CCCB 6A FE PUSH -2
7C80CCCD FF15 2811807C CALL DWORD PTR DS:[<&ntdll.NtQueryInform>; ntdll.ZwQueryInformationThread
7C80CCD3 3BC7 CMP EAX,EDI
7C80CCD5 75 05 JNZ SHORT kernel32.7C80CCDC
7C80CCD7 397D E4 CMP DWORD PTR SS:[EBP-1C],EDI
7C80CCDA 75 28 JNZ SHORT kernel32.7C80CD04
7C80CCDC FF15 F414807C CALL DWORD PTR DS:[<&ntdll.RtlFreeThread>; ntdll.RtlFreeThreadActivationContextStack
7C80CCE2 E8 3A000000 CALL <JMP.&ntdll.LdrShutdownThread>
7C80CCE7 39BE 940F0000 CMP DWORD PTR DS:[ESI+F94],EDI
7C80CCED 0F85 F6770300 JNZ kernel32.7C8444E9
7C80CCF3 C686 750F0000 01 MOV BYTE PTR DS:[ESI+F75],1
7C80CCFA FF75 08 PUSH DWORD PTR SS:[EBP+8]
7C80CCFD 57 PUSH EDI
7C80CCFE FF15 6C14807C CALL DWORD PTR DS:[<&ntdll.NtTerminateTh>; ntdll.ZwTerminateThread
7C80CD04 FF75 08 PUSH DWORD PTR SS:[EBP+8]
7C80CD07 E8 96FD0000 CALL kernel32.ExitProcess

SlyBit
11.07.2008, 02:03
Я совсем не огорчен, это были только мои предположения ;) Выходит в перехвате ExitThread нет необходимости. Однако приложение может поубивать свои потоки функцией TerminateThread или завершиться с помощью TerminateProcess.