SetUnhandledExceptionFilter,PAGE_GUARD и Мониторинг адреса
От: Clinch  
Дата: 12.09.08 17:57
Оценка:
Salut !
Пытаюсь сделать что-то вроде обработчика обращений (r/w) по заданному адресу,
этакий "address watch", используя VirtualProtectEx для нужного адреса с
флагом PAGE_GUARD | PAGE_READWRITE, вроде как добился требуемого,
но только с одним большим "Но!"...обработчик срабатывает, после чего,
как и сказанно в MSDN PAGE_GUARD слетает. При повторном обращщении по заданному
адресу, обработчик ессно не срабатывает. Надо снова устанавливать этот флаг, а
это не интересно, т.к. целью является реакция на изменение значения адреса ( положим это
некий счётчик ) в чужой программе.

Один выход нашёлся, но мне он не совсем нравится.
Может есть какие соображения по этому поводу ?

Читайте каменты в приведённом ниже коде.

#include "stdafx.h"
#include <windows.h>

// Переменная, которую хотим мониторить
DWORD  g_dwGlobe = 2008;
// Юзать ли поток реинициализации ?
BOOL   g_bUseReinitThread = TRUE; // Если FALSE, то зациклимся при обращении к g_dwGlobe

// Форвард на установку монитора
template<typename _VALTYPE>
BOOL SetWatchAddress ( LPVOID lpAddress,DWORD dwAccessMode = PAGE_READWRITE );

/*
#define EXCEPTION_EXECUTE_HANDLER       1
#define EXCEPTION_CONTINUE_SEARCH       0
#define EXCEPTION_CONTINUE_EXECUTION    -1
*/

DWORD WINAPI ReinitWatchThread ( VOID *lpAddress )
{
    fprintf(stdout,"RE-INIT: [ %p ]\n",lpAddress);
    if ( SetWatchAddress<DWORD>((LPVOID)lpAddress) )
        fprintf(stdout,"RE-INIT: Ok\n");
    else
        fprintf(stdout,"RE-INIT: Failed...\n");
    return 0;
}

LONG WINAPI ExceptionHandler( struct _EXCEPTION_POINTERS *p )
{
    DWORD  dwOldProtect = 0;
    
    if ( p->ExceptionRecord->ExceptionCode != 0x80000001 ) // PAGE_GUARD
        return EXCEPTION_CONTINUE_SEARCH;

    // адрес нашей переменной в ExceptionInformation[1] ( &dwGlobe )
    LPVOID lpAddress = (LPVOID)p->ExceptionRecord->ExceptionInformation[1];
    
    // значение dwGlobe
    DWORD dwValue = *(DWORD*)lpAddress;

#ifdef _DEBUG
    fprintf(stdout,"HANDLER: Access handled to %p [ Initial Value: %d ]\n",lpAddress,dwValue);
    
    fprintf(stdout,"HANDLER: EXCEPTION_RECORD { \n");
    fprintf(stdout,"HANDLER: \tExceptionAddress: %p\n",p->ExceptionRecord->ExceptionAddress);
    fprintf(stdout,"HANDLER: \tExceptionCode: %X\n",p->ExceptionRecord->ExceptionCode);
    fprintf(stdout,"HANDLER: \tExceptionFlags: %X\n",p->ExceptionRecord->ExceptionFlags);
    fprintf(stdout,"HANDLER: \tExceptoinRecord: %p\n",p->ExceptionRecord->ExceptionRecord);
    fprintf(stdout,"HANDLER: \tNumberParameters: %d\n",p->ExceptionRecord->NumberParameters);
    
    for ( int nParam = 0; nParam < p->ExceptionRecord->NumberParameters; nParam++ )
        fprintf(stdout,"HANDLER: \t  ExceptionInformation[%d]: %p\n",nParam,p->ExceptionRecord->ExceptionInformation[nParam]);
    fprintf(stdout,"HANDLER:  };\n");

    fprintf(stdout,"HANDLER: CONTEXT { \n");
    fprintf(stdout,"HANDLER: \tEAX=%08X, EBX=%08X,ECX=%08X,EDX=%08X,EDI=%08X,EBP=%08X,pESP=%08X,ESI=%08X,EIP=%08X\n",
        p->ContextRecord->Eax,p->ContextRecord->Ebx,p->ContextRecord->Ecx,
        p->ContextRecord->Edx,p->ContextRecord->Edi,p->ContextRecord->Ebp,
        p->ContextRecord->Esp,p->ContextRecord->Esi,p->ContextRecord->Eip);
    fprintf(stdout,"HANDLER: };\n");
#endif    
    
    // флаг PAGE_GUARD слетел, нужна его повторная установка,
    // но только в потоке, 
    // т.к. если вызвать SetWatchAddress прямо тут, то зациклимся нахер...
    if ( g_bUseReinitThread )
    {
        DWORD dwTid;
        HANDLE hThread = CreateThread(NULL,1024,ReinitWatchThread,lpAddress,0,&dwTid);
        CloseHandle ( hThread );
    } else
    {
        // косяк конечно с шаблоном, но щас это не главное
        // всё равно зациклимся, эх... просто для наглядности
        SetWatchAddress<DWORD> ( lpAddress ); // READ-WRITE
    }

    fprintf(stdout,"HANDLER: Done\n");

    return EXCEPTION_CONTINUE_EXECUTION;
}



 
// Установка монитора на lpAddress,
// шаблон, имхо, для понта...т.к. в потоке реинициализации, 
// мы явно указываем DWORD...временно ?!
template<typename _VALTYPE>
BOOL SetWatchAddress ( LPVOID lpAddress , DWORD dwAccessMode /* = PAGE_READWRITE */)
{
    HANDLE hProcess = GetCurrentProcess();
    DWORD  dwError = 0;
    DWORD  dwOldProtect = 0;
    DWORD  dwNewProtect = PAGE_GUARD | dwAccessMode;

    fprintf(stdout,"SETUP: Watching lpAddress: %p [ Value: %d ]\n",lpAddress,*(_VALTYPE*)lpAddress);
    VirtualProtectEx(hProcess,lpAddress,sizeof ( _VALTYPE ),dwNewProtect ,&dwOldProtect);
    if (  ( dwError = GetLastError() ) != ERROR_SUCCESS )
        fprintf(stdout,"SETUP: Cannot VirtualProtectEx,Error => %d\n",dwError ); 
    else
    {
        LPTOP_LEVEL_EXCEPTION_FILTER pTopFilter = SetUnhandledExceptionFilter(ExceptionHandler);
        fprintf(stdout,"SETUP: Previous Exception Filter %p\n",pTopFilter);
    }
    return dwError == 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    fprintf(stdout,"\nMAIN: Watching address %p, Initial Value %d\n",&g_dwGlobe,g_dwGlobe);
    
    // Хотим наблюдать за g_dwGlobe...  ( READ-WRITE )
    SetWatchAddress<DWORD> ( &g_dwGlobe );

    
    fprintf(stdout,"MAIN: - WRITE test ( 1 )\n");
    // Сработает обработчик,  !!! сбросит флаг PAGE_GUARD !!!
    // и вернёт нас сюда же
    g_dwGlobe = 2000; 
    fprintf(stdout,"MAIN: - WRITE test ( 1 ) done..., New Value = %d\n",g_dwGlobe);
    
    
    fprintf(stdout,"MAIN: - WRITE test ( 2 )\n");
    // спим 50ms, что бы поток реинициализации смог отработать ( Если юзам поток )
    if ( g_bUseReinitThread )
        SleepEx(50,TRUE); // 50 - вроде хватает для потока 
    // Если тут повторно не вызвать    SetWatchAddress<DWORD> ( &g_dwGlobe );
    // то ....
    g_dwGlobe = 2001; // ... не вылетет в обработчик... ( PAGE_GUARD - сброшен )
    // а Повторно вызывать SetWatchAddress в обработчике нельзя, т.к уйдём в цикл !
    // ...как вариант ( g_bUseReinitThread = TRUE ), в обработчике создаём поток,
    // который и вызвет SetWatchAddress...
    fprintf(stdout,"MAIN: - WRITE test ( 2 ) done..., New Value = %d\n",g_dwGlobe);

#if 0
    // Всё тоже самое касается в случае с чтеним по этому адресу...
    fprintf(stdout,"MAIN: - READ test\n");
    DWORD dwRead = g_dwGlobe;    
    // Тут спим секундочку, чтобы поток успел реинициализировать адресс...ччччиорд !
    SleepEx(1000,1);
    dwRead = g_dwGlobe;
    fprintf(stdout,"MAIN: - READ test done , value %d...\n",dwRead);
#endif
    fprintf(stdout,"MAIN: - Complete...\n");
    return 0;
}


Буду рад, если кому-то это покажется интересным... ))
Удачи!
winapi win32 msvs2008 exceptions watch
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.