如何使用写入地址捕获内存写入和调用函数 [英] How to catch a memory write and call function with address of write

查看:21
本文介绍了如何使用写入地址捕获内存写入和调用函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想捕获对特定内存范围的内存写入,并使用正在写入的内存位置的地址调用函数.最好是在写入内存之后.

I would like to catch memory writes to specific memory ranges and call a function with the address of the memory location being written to. Preferably, after the write to memory has already happened.

我知道这可以由操作系统通过处理页表条目来完成.但是,如何在想要执行此操作的应用程序中进行类似的操作?

I know this can be done by the operating system by twiddling with the page table entries. However, how can this be similar accomplished from within an application that wants to do this?

推荐答案

好吧,你可以这样做:

// compile with Open Watcom 1.9: wcl386 wrtrap.c

#include <windows.h>
#include <stdio.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif


UINT_PTR RangeStart = 0;
SIZE_T RangeSize = 0;

UINT_PTR AlignedRangeStart = 0;
SIZE_T AlignedRangeSize = 0;


void MonitorRange(void* Start, size_t Size)
{
  DWORD dummy;

  if (Start &&
      Size &&
      (AlignedRangeStart == 0) &&
      (AlignedRangeSize == 0))
  {
    RangeStart = (UINT_PTR)Start;
    RangeSize = Size;

    // Page-align the range address and size

    AlignedRangeStart = RangeStart & ~(UINT_PTR)(PAGE_SIZE - 1);

    AlignedRangeSize = ((RangeStart + RangeSize - 1 + PAGE_SIZE) &
                        ~(UINT_PTR)(PAGE_SIZE - 1)) -
                       AlignedRangeStart;

    // Make the page range read-only
    VirtualProtect((LPVOID)AlignedRangeStart, 
                   AlignedRangeSize,
                   PAGE_READONLY,
                   &dummy);
  }
  else if (((Start == NULL) || (Size == 0)) &&
           AlignedRangeStart &&
           AlignedRangeSize)
  {
    // Restore the original setting
    // Make the page range read-write
    VirtualProtect((LPVOID)AlignedRangeStart,
                   AlignedRangeSize,
                   PAGE_READWRITE,
                   &dummy);

    RangeStart = 0;
    RangeSize = 0;

    AlignedRangeStart = 0;
    AlignedRangeSize = 0;
  }
}

// This is where the magic happens...
int ExceptionFilter(LPEXCEPTION_POINTERS pEp,
                    void (*pMonitorFxn)(LPEXCEPTION_POINTERS, void*))
{
  CONTEXT* ctx = pEp->ContextRecord;
  ULONG_PTR* info = pEp->ExceptionRecord->ExceptionInformation;
  UINT_PTR addr = info[1];
  DWORD dummy;

  switch (pEp->ExceptionRecord->ExceptionCode)
  {
  case STATUS_ACCESS_VIOLATION:
    // If it's a write to read-only memory,
    // to the pages that we made read-only...
    if ((info[0] == 1) &&
        (addr >= AlignedRangeStart) &&
        (addr < AlignedRangeStart + AlignedRangeSize))
    {
      // Restore the original setting
      // Make the page range read-write
      VirtualProtect((LPVOID)AlignedRangeStart,
                     AlignedRangeSize,
                     PAGE_READWRITE,
                     &dummy);

      // If the write is exactly within the requested range,
      // call our monitoring callback function
      if ((addr >= RangeStart) && (addr < RangeStart + RangeSize))
      {
        pMonitorFxn(pEp, (void*)addr);
      }

      // Set FLAGS.TF to trigger a single-step trap after the
      // next instruction, which is the instruction that has caused
      // this page fault (AKA access violation)
      ctx->EFlags |= (1 << 8);

      // Execute the faulted instruction again
      return EXCEPTION_CONTINUE_EXECUTION;
    }

    // Don't handle other AVs
    goto ContinueSearch;

  case STATUS_SINGLE_STEP:
    // The instruction that caused the page fault
    // has now succeeded writing to memory.
    // Make the page range read-only again
    VirtualProtect((LPVOID)AlignedRangeStart,
                   AlignedRangeSize,
                   PAGE_READONLY,
                   &dummy);

    // Continue executing as usual until the next page fault
    return EXCEPTION_CONTINUE_EXECUTION;

  default:
  ContinueSearch:
    // Don't handle other exceptions
    return EXCEPTION_CONTINUE_SEARCH;
  }
}


// We'll monitor writes to blah[1].
// volatile is to ensure the memory writes aren't
// optimized away by the compiler.
volatile int blah[3] = { 3, 2, 1 };

void WriteToMonitoredMemory(void)
{
  blah[0] = 5;
  blah[0] = 6;
  blah[0] = 7;
  blah[0] = 8;

  blah[1] = 1;
  blah[1] = 2;
  blah[1] = 3;
  blah[1] = 4;

  blah[2] = 10;
  blah[2] = 20;
  blah[2] = 30;
  blah[2] = 40;
}

// This pointer is an attempt to ensure that the function's code isn't
// inlined. We want to see it's this function's code that modifies the
// monitored memory.
void (* volatile pWriteToMonitoredMemory)(void) = &WriteToMonitoredMemory;

void WriteMonitor(LPEXCEPTION_POINTERS pEp, void* Mem)
{
  printf("We're about to write to 0x%X from EIP=0x%X...
",
         Mem,
         pEp->ContextRecord->Eip);
}

int main(void)
{
  printf("&WriteToMonitoredMemory() = 0x%X
", pWriteToMonitoredMemory);
  printf("&blah[1] = 0x%X
", &blah[1]);

  printf("
start

");

  __try
  {
    printf("blah[0] = %d
", blah[0]);
    printf("blah[1] = %d
", blah[1]);
    printf("blah[2] = %d
", blah[2]);

    // Start monitoring memory writes
    MonitorRange((void*)&blah[1], sizeof(blah[1]));

    // Write to monitored memory
    pWriteToMonitoredMemory();

    // Stop monitoring memory writes
    MonitorRange(NULL, 0);

    printf("blah[0] = %d
", blah[0]);
    printf("blah[1] = %d
", blah[1]);
    printf("blah[2] = %d
", blah[2]);
  }
  __except(ExceptionFilter(GetExceptionInformation(),
                           &WriteMonitor)) // write monitor callback function
  {
    // never executed
  }

  printf("
stop
");
  return 0;
}

输出(在 Windows XP 上运行):

Output (run on Windows XP):

&WriteToMonitoredMemory() = 0x401179
&blah[1] = 0x4080DC

start

blah[0] = 3
blah[1] = 2
blah[2] = 1
We're about to write to 0x4080DC from EIP=0x4011AB...
We're about to write to 0x4080DC from EIP=0x4011B5...
We're about to write to 0x4080DC from EIP=0x4011BF...
We're about to write to 0x4080DC from EIP=0x4011C9...
blah[0] = 8
blah[1] = 4
blah[2] = 40

stop

就是这个想法.

您可能需要进行一些更改以使代码在多线程中正常工作,使其与其他 SEH 代码(如果有)一起工作,C++ 例外(如果适用).

You will likely need to change things around to make the code work well in multiple threads, make it work with other SEH code (if any), with C++ exceptions (if applicable).

当然,如果你真的想要它,你可以让它在写完成后调用写监控回调函数.为此,您需要将 STATUS_ACCESS_VIOLATION 案例中的内存地址保存在某处 (TLS?),以便 STATUS_SINGLE_STEP 案例可以获取它稍后传递给函数.

And, of course, if you really want it, you can make it call the writes monitoring callback function after the write's been completed. For that you'll need to save the memory address from the STATUS_ACCESS_VIOLATION case somewhere (TLS?) so that the STATUS_SINGLE_STEP case can pick it up later and pass to the function.

这篇关于如何使用写入地址捕获内存写入和调用函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆