【问题标题】:Using Structured Exception Handling (SEH) for monitoring usage of guarded pages使用结构化异常处理 (SEH) 监控受保护页面的使用情况
【发布时间】:2016-08-24 18:05:22
【问题描述】:

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

我试过这样:

  1. 在 DllMain 中通过 SetUnhandledExceptionFilter 设置异常处理程序
  2. 在GetDataPointer中通过VirtualAlloc分配内存,设置保护页
  3. 在异常处理程序中删除页面保护,将数据与以前的副本进行比较,使页面受到保护

一切都会好的,但是这个应用程序使用了写入这个内存指针的线程。假设应用程序在主线程中向这个内存指针写入了 3 次,在这之间产生了其他线程,对这个指针进行了 3 次写入。虽然没有线程这将起作用,但使用线程有时它确实会错过页面保护,因此它直接写入内存指针而不会引发异常。所以异常处理程序应该捕获对内存的六次写入,但由于缺少页面保护,这个数字较低。可悲的是,我需要这样做并且我需要 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;
}

我希望这会更清楚。

【问题讨论】:

  • 每次写入都需要一个例外以使其正常工作??在您执行任何其他操作之前分析此代码。
  • @hans-passant 是的,我需要知道每一次内存写入才能使其正常工作。内存块可以解释为一个结构,我需要知道哪些元素被改变了。例如,第一次写入将是在四个静态偏移量之一处写入的命令,而下一次捕获的写入将是不是静态偏移量的参数,因此我需要知道更改了什么以及它具有什么值。更重要的是,命令的参数可以连续两次以相同的偏移量写入(覆盖)。
  • 我阅读 SetUnhandledExceptionFilter 文档的方式是它只捕获未捕获的异常。因此,如果应用程序中的线程首先捕获异常,您将看不到它。

标签: winapi memory guard seh


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-14
    • 1970-01-01
    • 2012-04-22
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    相关资源
    最近更新 更多