0

I have application without source code that makes direct write to device with use of proxy DLL library that only does one thing, returns memory pointer by calling exported function "GetDataPointer". I would like to make a replacement for this library and see changes between each write to received memory. Memory size is multiple of page size (4096).

I've tried it this way:

  1. in DllMain set exception handler via SetUnhandledExceptionFilter
  2. in GetDataPointer alloc memory via VirtualAlloc, set guard page
  3. in exception handler remove page guard, compare data with previous copy, make page guarded

Everything would be ok, but this application makes use of threads which writes to this memory pointer. Let's say application writes three times to this memory pointer in main thread, in between spawns other thread that makes three writes to this pointer. While without threads this will work, with threads there is a time when it does miss page guarding so it writes directly to memory pointer without raising exception. So exception handler should capture six writes to memory, but because of missing page guard the number is lower. Sadly, I need to do it this way and I need 100% accuracy, because I'm planning to make a wrapper for a obscure API for even more obscure device (graphics card) to make use of this application in modern systems.

I've tried making use of global critical section and setting exception handler for SEH in each attached thread, but still problem remains.

Is there a possibility to workaround this problem? Or is there a better way to implement this? If anybody needs code to explain how this works, I can prepare code which will simulate my working environment (application and library).

Thank you in advance.

Update

Source code (pseudocode) without locking sections looks like this (written in hurry for this post, may be not complete):

Application:

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

Library:

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

I hope this will be more clear.

cafebabe_t
  • 65
  • 1
  • 9
  • 1
    You need an exception for every single write to make it work correctly?? Profile this code before you do anything else. – Hans Passant Aug 24 '16 at 18:22
  • @hans-passant Yes, I need to know every single memory write to make this working correctly. Memory chunk can be interpreted as a structure and I need to know which elements were changed. For example first write would be command which is wrote at one of four static offsets and next catched write would be params which are not static offsets so I need to know what was changed and what value it has. Even more, params for commands can be written at the same offset twice in a row (overwritten). – cafebabe_t Aug 24 '16 at 18:28
  • The way I read the docs for SetUnhandledExceptionFilter is that it only traps un-caught exceptions. So if a thread in the application catches the exception first you won't see it. – Brad Aug 24 '16 at 18:30

1 Answers1

0

can give advice write mini-debugger, which will be debug your main application. while debugger handle exception from thread - all another threads in process is suspended. this is key point. when you handle STATUS_GUARD_PAGE_VIOLATION - you need call SuspendThread for all threads, except current - in debugger no problem maintain list of all threads. or as alternative call ZwSuspendProcess and ZwResumeThread for current thread. then set TF flag (0x100) in current thread and DBG_CONTINUE. then when you got STATUS_SINGLE_STEP exception (or STATUS_WX86_SINGLE_STEP) you restore PAGE_GUARD and ResumeThread for all threads except current. ( or simply call ZwResumeProcess). this of course can be not simply task, but only this give you 100% guarantee that you not miss any tracked memory reference

RbMm
  • 31,280
  • 3
  • 35
  • 56