使用结构化异常处理(SEH)监视受保护页面的使用情况

时间:2016-08-24 18:05:23

标签: winapi memory guard seh

我有没有源代码的应用程序使用代理DLL库直接写入设备只执行一件事,通过调用导出函数返回内存指针" GetDataPointer"。我想替换这个库,看看每次写入到接收内存之间的变化。内存大小是页面大小的倍数(4096)。

我已经这样试过了:

    DllMain中的
  1. 通过SetUnhandledExceptionFilter
  2. 设置异常处理程序
  3. 在GetDataPointer中通过VirtualAlloc分配内存,设置保护页面
  4. 在异常处理程序中删除页面保护,将数据与以前的副本进行比较,使页面保护
  5. 一切都会好的,但是这个应用程序使用了写入这个内存指针的线程。让我们说应用程序在主线程中向该内存指针写入三次,在它之间生成对该指针进行三次写入的其他线程。虽然没有线程,但这可以工作,有线程的时候它会错过页面保护,所以它直接写入内存指针而不会引发异常。因此异常处理程序应捕获对内存的六次写入,但由于缺少页面保护,该数字较低。遗憾的是,我需要这样做,我需要100%的准确性,因为我计划为一个模糊的API制作一个包装器,以便更加模糊的设备(图形卡)在现代系统中使用这个应用程序。

    我已尝试在每个连接的线程中使用全局关键部分并为SEH设置异常处理程序,但仍然存在问题。

    是否有可能解决此问题?或者有更好的方法来实现它吗?如果有人需要代码来解释它是如何工作的,我可以准备代码来模拟我的工作环境(应用程序和库)。

    提前谢谢。

    更新

    没有锁定部分的源代码(伪代码)看起来像这样(这篇文章匆匆写完,可能不完整):

    应用:

    extern "C" __declspec(dllimport) void* GetDataPointer(int size);
    extern "C" __declspec(dllimport) int GetCallCount();
    
    #define THREAD_COUNT 32
    
    DWORD WINAPI ThreadEntryPoint(void* param) {
     unsigned int* data = (unsigned int*)param;
    
     data[0] = 0xEEFF0011;
    
     return 0;
    }
    
    int main(int argc, char* argv[]) {
     unsigned int* data = (unsigned int*)GetDataPointer();
    
     std::vector<HANDLE> threads;
    
     data[0] = 0xAABBCCDD;
    
     for(int i = 0; i<THREAD_COUNT; i++) {
      DWORD threadID;
      HANDLE hThread = CreateThread(NULL,0,ThreadEntryPoint,(void*)data,0,&threadID);
      threads.push_back(hThread);
     }
    
     for(int i = 0; i<threads.size(); i++)
      WaitForSingleObject(threads.at(i),INFINITE);
    
     printf("Should be %i calls, was %i.\n",(THREAD_COUNT+1),GetCallCount());
     return 0;
    }
    

    库:

    #define DATA_SIZE   4096
    
    void* data;
    int callCount;
    
    LONG WINAPI ExceptionHandler(LPEXCEPTION_POINTERS ExceptionInfo) {
     LONG ret = EXCEPTION_CONTINUE_SEARCH;
    
     switch(ExceptionInfo->ExceptionRecord->ExceptionCode) {
      case STATUS_GUARD_PAGE_VIOLATION: {
       ExceptionInfo->ContextRecord->EFlags |= 0x100;
       ret = EXCEPTION_CONTINUE_EXECUTION;
       break;
      }
    
      case EXCEPTION_SINGLE_STEP: {
       DWORD old;
    
       callCount++;
    
       VirtualProtect(data,DATA_SIZE,PAGE_READWRITE,&old);
       // Find what was changed in data...
       VirtualProtect(data,DATA_SIZE,PAGE_GUARD | PAGE_READWRITE,&old);
    
       ret = EXCEPTION_CONTINUE_EXECUTION;
       break;
      }
     }
    
     return ret;
    }
    
    extern "C" void* __declspec(dllexport) GetDataPointer() {
     data = (void*)VirtualAlloc((PVOID)data,DATA_SIZE,MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE);
     memset(data,0,DATA_SIZE);
    
     DWORD old;
     VirtualProtect(data,DATA_SIZE,PAGE_GUARD | PAGE_READWRITE,&old);
    
     return data;
    }
    
    extern "C" int __declspec(dllexport) GetCallCount() {
     return callCount;
    }
    
    extern "C" BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
     switch(fdwReason) {
      case DLL_PROCESS_ATTACH: {
       data = NULL;
       callCount = 0;
    
       SetUnhandledExceptionFilter(ExceptionHandler);
    
       break;
      }
    
      case DLL_PROCESS_DETACH: {
       if(data)
        VirtualFree(data,data_size,MEM_RELEASE | MEM_DECOMMIT);
    
       break;
      }
    
      case DLL_THREAD_ATTACH: {
       SetUnhandledExceptionFilter(ExceptionHandler);
    
       break;
      }
    
      case DLL_THREAD_DETACH: {
       break;
      }
     }
    
     return TRUE;
    }
    

    我希望这会更清楚。

1 个答案:

答案 0 :(得分:0)

可以给出建议写迷你调试器,它将调试你的主应用程序。而调试器处理来自线程的异常 - 进程中的所有其他线程都被挂起。这是关键点。当你处理STATUS_GUARD_PAGE_VIOLATION时 - 你需要为所有线程调用SuspendThread,除了当前 - 在调试器中没有问题维护所有线程的列表。或者作为当前线程的替代调用ZwSuspendProcess和ZwResumeThread。然后在当前线程和DBG_CONTINUE中设置TF标志(0x100)。然后当你得到STATUS_SINGLE_STEP异常(或STATUS_WX86_SINGLE_STEP)时,你为除当前之外的所有线程恢复PAGE_GUARD和ResumeThread。 (或简单地调用ZwResumeProcess)。这当然不仅仅是任务,但只有这样才能100%保证您不会错过任何跟踪的内存参考