因此,解决方案(代码片段)如下:
首先,我有一个变量:
__attribute__ ((aligned (4096))) int g_test;
其次,在我的主要功能中,我执行以下操作:
AddVectoredExceptionHandler(1, VectoredHandler);
DWORD old;
VirtualProtect(&g_test, 4096, PAGE_READWRITE | PAGE_GUARD, &old);
处理程序如下所示:
LONG WINAPI VectoredHandler(struct _EXCEPTION_POINTERS *ExceptionInfo)
{
static DWORD last_addr;
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_GUARD_PAGE_VIOLATION) {
last_addr = ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
ExceptionInfo->ContextRecord->EFlags |= 0x100; /* Single step to trigger the next one */
return EXCEPTION_CONTINUE_EXECUTION;
}
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP) {
DWORD old;
VirtualProtect((PVOID)(last_addr & ~PAGE_MASK), 4096, PAGE_READWRITE | PAGE_GUARD, &old);
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
这只是功能的基本框架。基本上我保护变量所在的页面,我有一些链接列表,其中我保存指向函数的指针和相关地址的值。我检查故障生成地址是否在我的列表中,然后触发回调。
在第一次防护命中时,系统将禁用页面保护,但我可以调用我的 PRE_WRITE 回调,我可以在其中保存变量状态。因为单步是通过 EFlags 发出的,所以它之后会立即出现单步异常(这意味着变量已被写入),并且我可以触发 WRITE 回调。操作所需的所有数据都包含在 ExceptionInformation 数组中。
当有人尝试写入该变量时:
*(int *)&g_test = 1;
一个 PRE_WRITE 后跟一个 WRITE 将被触发,
当我做:
int x = *(int *)&g_test;
将发出 READ。
通过这种方式,我可以以不需要修改原始源代码的方式操纵数据流。注意:这旨在用作测试框架的一部分,任何惩罚命中都被认为是可以接受的。
例如,W1C(Write 1 to clear)操作可以完成:
void MYREG_hook(reg_cbk_t type)
{
/** We need to save the pre-write state
* This is safe since we are assured to be called with
* both PRE_WRITE and WRITE in the correct order
*/
static int pre;
switch (type) {
case REG_READ: /* Called pre-read */
break;
case REG_PRE_WRITE: /* Called pre-write */
pre = g_test;
break;
case REG_WRITE: /* Called after write */
g_test = pre & ~g_test; /* W1C */
break;
default:
break;
}
}
这也可能与非法地址上的段错误有关,但我必须为每个 R/W 发出一个,并跟踪“虚拟寄存器文件”,因此受到更大的惩罚。通过这种方式,我只能保护特定的内存区域或不保护,具体取决于注册的监视器。