Событие завершения процесса

Discussion in 'С/С++, C#, Rust, Swift, Go, Java, Perl, Ruby' started by gevara, 10 Jul 2008.

  1. gevara

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    Есть произвольный код в адресном пространстве некоторого процесса. Этот код должен отлавливать событие завершения этого процесса. Что-то типа сообщения DLL_PROCESS_DETACH, но это сообщение приходит динамическеим библиотекам, а у меня произвольный код. Можно, конечно, перехватывать функцию ExitProcess, ExitThread, но не хочу гемора с перехватом...
     
  2. 0verbreaK

    0verbreaK Elder - Старейшина

    Joined:
    30 Apr 2008
    Messages:
    318
    Likes Received:
    42
    Reputations:
    -3
    а вопрос?
     
  3. gevara

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    Код сисдит в апликухе и должен сохранить логи по завершении процесса.
     
  4. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    Теоретически можно попробовать следующее.
    При старте процесса в стеке сохраняется адрес возврата именно по этому и можно завершать программу по команте ret (если в стек ничего не добавлялось)
    При нормально анализе можно в стеке найти этот адрес и подменить на адрес своего обработчика. Геморно но всёже как один из вариантов
    ну и + желательно ExitProcess перехватить и тоже пустить на свой обработчик
     
  5. Ryu

    Ryu New Member

    Joined:
    13 Jun 2008
    Messages:
    19
    Likes Received:
    3
    Reputations:
    2
    если код действительно в том же процессе, то только сообщение обрабатывать и хукать ничего не надо. (wm_terminate или что то в этом роде), но это только если приложение win а не дос.
     
    #5 Ryu, 10 Jul 2008
    Last edited: 10 Jul 2008
  6. slesh

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    2 Ryu млин, а если приложения WIN но без оконное? ТОгда регистрировать обработчик сообщений нужно. А если консольное, то вообще тогда нужно через SetConsoleCtrlhandler()
    так что отлавливать сообщения - многое нужно учесть. При том, что как выше сказано код в произвольном процессе и следовательно он туда както внедряется и должен быть по крайней мере независимым от своего расположения в памяти.

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

    Jes Elder - Старейшина

    Joined:
    16 Apr 2007
    Messages:
    370
    Likes Received:
    391
    Reputations:
    34
    а какой гемор? поменяй в таблице импорта адреса и всё
    (если там конечно не динамич импорт)
     
    1 person likes this.
  8. SlyBit

    SlyBit Elder - Старейшина

    Joined:
    4 Jul 2008
    Messages:
    49
    Likes Received:
    8
    Reputations:
    0
    gevara

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

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

    slesh Elder - Старейшина

    Joined:
    5 Mar 2007
    Messages:
    2,702
    Likes Received:
    1,224
    Reputations:
    455
    2 SlyBit Блин чем дальше в лес, тем больше дров, которые человек наломает.
    Такими темпами можно будет уже говорить о методах сплайсенга функций а там уже и до Native Api дойдем со стороны ядра.

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

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    приложение оконное.
    - адрес возврата подменить не получится - что если приложение многопоточное? перехватывать 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 и прочие...
     
    #10 gevara, 10 Jul 2008
    Last edited: 10 Jul 2008
  11. gevara

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    таких функций не сущесмтвует
     
  12. Hellsp@wn

    Hellsp@wn Elder - Старейшина

    Joined:
    29 Apr 2007
    Messages:
    401
    Likes Received:
    153
    Reputations:
    48
    вот такие функции есть:

    ntdll.ZwTerminateProcess
    ntdll.ZwTerminateThread
     
  13. SlyBit

    SlyBit Elder - Старейшина

    Joined:
    4 Jul 2008
    Messages:
    49
    Likes Received:
    8
    Reputations:
    0
    сорри, ошибся, имел ввиду:

    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
     
    #13 SlyBit, 10 Jul 2008
    Last edited: 10 Jul 2008
  14. gevara

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    Что толку от перехвата таблицы импорта, если не знаешь какой именнго модуль завершит последний поток? вообще прога может завершить своё выполнение по рету и ExitProcess будет вызван из kernel32 - тут только сплайсинг. а вариант с изменением адреса входа действительно реальный, хотя, возможно, и не самый лучший. ведь адрес точки входа будет лежать за границами модуля, что может вызвать подозрения...
     
    1 person likes this.
  15. SlyBit

    SlyBit Elder - Старейшина

    Joined:
    4 Jul 2008
    Messages:
    49
    Likes Received:
    8
    Reputations:
    0
    да, я про таблицу экспорта kernel32 и ntdll конечно...какая нафиг импорта ;) мне кажется вариант со сплайзингом такой же палевный как и подмена адреса точки входа либы.

    Подменяй тогда адрес возврата.
     
    #15 SlyBit, 10 Jul 2008
    Last edited: 10 Jul 2008
  16. zl0y

    zl0y Banned

    Joined:
    13 Sep 2006
    Messages:
    371
    Likes Received:
    270
    Reputations:
    109
    KiSystemFastRet+ZwTerminateProcess.
     
  17. gevara

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    про адрес возврата я уже сказал - не катит на случай многопоточности
     
  18. SlyBit

    SlyBit Elder - Старейшина

    Joined:
    4 Jul 2008
    Messages:
    49
    Likes Received:
    8
    Reputations:
    0
    А вот и пример подмены точки входа библиотеки подоспел (описание несколькими постами выше):

    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);
    }
     
    #18 SlyBit, 10 Jul 2008
    Last edited: 10 Jul 2008
  19. gevara

    gevara Elder - Старейшина

    Joined:
    29 Nov 2006
    Messages:
    47
    Likes Received:
    7
    Reputations:
    5
    SlyBit, пасиб, конечно. но это вобщем-то понятно. у меня ситуация несколько другая - вот я и думаю что сделать лучше: перехват PEB или сплайсинг ExitThread или ExitProcess. Вообще-то мой код и так хучит кое-какие функции. В частности имеет доступ к оконным сообщениям. так что идеальный вариант - отслеживать сообщения (есали такие есть) о закрытии всех окон или что-то типа этого...
     
  20. SlyBit

    SlyBit Elder - Старейшина

    Joined:
    4 Jul 2008
    Messages:
    49
    Likes Received:
    8
    Reputations:
    0
    gevara

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

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