原理:
在 IDT 表中的e 号 处理 是 页面异常处理; 如果 我们 hook 掉 这个回调函数;那么就能获得全部的页面异常;再通过 cr3 对比 捕获指定的 cr3 (进程)的信息;最后再共享的区域将数据输出;然后测试程序获取该自己的页面异常信息;
实验中 容易出现的错误:
在 c 的时候注意将使用的寄存器(这里是eax)先保存起来;注意 通过栈保存 eax的话注意 相关进入中断后和栈相关的数据,就会和esp 的相对偏移产生变化。
测试程序代码.c:
此程序 主要是 中断进入 内核;然后读取数据;看时候有新的和自己有关的异常页面信息;这样
// 9_页面异常.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <stdio.h>#include<stdlib.h>#include<Windows.h>// 定义一些内核地址宏;用来保存当前程序相关数据(cr3)#define K_BOOL_READY 0x8003f3f0 // * 当前是否已经提交 cr3,变为准备接收异常状态#define K_ESP 0x8003f3f4 // esp * 异常代码 #defineK_ESP_NEG4 0x8003f3f8 // esp - 4 * 异常地址 eip(产生异常的代码地址)#define K_TARGET_CR30x8003f3ec // cr3 * 异常程序cr3#define K_CR2 0x8003f3e8 // cr2 * 异常地址(产生页面异常页中的虚拟地址)#define K_BOOL_NEW_EXPT 0x8003f3e4 // * 是否有新的异常DWORD g_bReady = FALSE;DWORD g_bNewExpt = FALSE;DWORD g_pEIP = 0;DWORD g_pExceptPvn = 0;DWORD g_iCr3 = 0;DWORD g_iErrCode = -1;// 0x401040void __declspec(naked) IdtEntry(){__asm mov eax, ds:[K_BOOL_READY];__asm mov g_bReady, eax;__asm{ mov eax, cr3; mov cr3, eax;}if (g_bReady != 1){ // 第一次运行还没有提交CR3数据: __asm { mov eax, cr3; mov ds : [K_TARGET_CR3], eax; // 提交完成的标识。 mov eax, 0x1; mov ds : [K_BOOL_READY], eax; // 重置新异常信号; mov eax, 0x0; mov ds : [K_BOOL_NEW_EXPT], eax; iretd; }}else{ // 已经提交 cr3 开始接收异常数据: // ----* 检测是否有新的异常需要接收; __asm mov eax, dword ptr ds : [K_BOOL_NEW_EXPT]; __asm mov g_bNewExpt, eax; if (g_bNewExpt == 0x1) { // 如果 有新的异常 __asm { // 获取异常信息 --》 到本地全局,以便输出 mov eax, dword ptr ds : [K_ESP_NEG4]; mov g_pEIP, eax; mov eax, dword ptr ds : [K_CR2]; mov g_pExceptPvn, eax; mov eax, dword ptr ds : [K_TARGET_CR3]; mov g_iCr3, eax; mov eax, dword ptr ds : [K_ESP]; mov g_iErrCode, eax; // 接收之后 重置新异常信号; mov eax, 0x0; mov ds : [K_BOOL_NEW_EXPT], eax; } } __asm { mov eax, cr3; mov cr3, eax; iretd; }}}void _declspec(naked) go(){__asm{ int 0x20; ret;}}int main(){if ((DWORD)IdtEntry != 0x401040){ printf("Func addres is wrong !!"); Sleep(5000); system("pause"); exit(-1);}while (1){ go(); //printf("%d\n", g_bNewExpt); if (g_bNewExpt == 0x1) { printf("new error ***cr3: %p -- eip: %p -- errorcode:%p -- pfvn:%p\n", g_iCr3, g_pEIP, g_iErrCode, g_pExceptPvn); g_bNewExpt = 0; }} }
Hook IDT_E 回调函数 且 通过识别cr3 将相关信息过滤到 共识的共享数据区域 .c :
// 9_页面异常_过滤获取目标异常信息.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include<stdio.h>#include<stdlib.h>#include<Windows.h>// 定义一些内核地址宏;用来保存当前程序相关数据(cr3)#define K_BOOL_READY 0x8003f3f0 // * 当前是否已经提交 cr3,变为准备接收异常状态#define K_ESP 0x8003f3f4 // esp * 异常代码 #defineK_ESP_NEG4 0x8003f3f8 // esp - 4 * 异常地址 eip(产生异常的代码地址)#define K_TARGET_CR30x8003f3ec // cr3 * 异常程序cr3#define K_CR2 0x8003f3e8 // cr2 * 异常地址(产生页面异常页中的虚拟地址)#define K_BOOL_NEW_EXPT 0x8003f3e4 // * 是否有新的异常#define K_HOOK_IDT_E_CODE 0x8003F120 // * Hook 代码所在#define K_HOOK_IDT_E_SRCCODE 0x80541450 // * Hook目标 所在DWORD g_bReady = FALSE;DWORD g_bNewExpt = FALSE;DWORD g_pEIP = 0;DWORD g_pExceptPvn = 0;DWORD g_iCr3 = 0;DWORD g_iErrCode = -1;DWORD g_iCurCr3 = 0;int g_i = 0;DWORD g_p = 0;void checkAndCap();// -- int0x20 -- 0x401040void __declspec(naked) HookIdt_0xe(){// hook __asm{ // 修改写保护 WP //cli;//将处理器标志寄存器的中断标志位清0 ,不允许中断 mov eax, cr0 and eax, not 0x10000 mov cr0, eax mov eax, 0x0; mov ds : [K_BOOL_READY], eax; // 原来的第一句 有7个字节;而我们push ret 只有6个字节 ;扩充到 7个字节 68 20 f1 03 80 C3 90; // push 0x8003f120; // ret // nop mov al, 0x68; mov byte ptr ds : [K_HOOK_IDT_E_SRCCODE], al; mov eax, 0x8003f120; mov dword ptr ds : [K_HOOK_IDT_E_SRCCODE + 1], eax; mov ax, 0x90c3; mov word ptr ds : [K_HOOK_IDT_E_SRCCODE + 5], ax; mov eax, cr0 or eax, 0x10000 mov cr0, eax //sti;//将中断恢复}g_i = 0;g_p = K_HOOK_IDT_E_CODE;// 拷贝hook 代码带内核区for (; g_i < 128; g_i++){ *(BYTE*)(g_p + g_i) = *(byte*)((DWORD)checkAndCap + g_i);}__asm{ iretd;}}// 0x401040 -- void _declspec(naked) checkAndCap(){__asm{ push eax; // 判断程序是否已经就位 mov eax, ds:[K_BOOL_READY]; cmp eax, 0x1; jnz END; // 如果 目标程序 已经提交cr3 那么 就可以开始捕获异常了; mov eax,cr3; // cr3不能用于比较的 参数 cmp eax, ds:[K_TARGET_CR3]; jnz END; // 来到这一步,表明是目标的异常 mov eax, [esp + 8]; // 前面push 了一个 eax 我去 浪费时间··· esp + 0x4 --> esp+ 0x8; mov ds : [K_ESP_NEG4], eax; // eip 异常地址 mov eax, cr2; mov ds : [K_CR2], eax; // 异常读/写/访 的目标分页地址 mov eax, [esp+4];// esp-->esp +4 因为前面压入了 eax mov ds : [K_ESP], eax; // 异常代码 // 给新的异常信号 mov eax, 0x1; mov ds : [K_BOOL_NEW_EXPT], eax;END: pop eax; mov word ptr[esp + 2], 0 // 恢复 原来的执行 push 0x80541457; // 返回之前hook 的下一句 ret}}void _declspec(naked) go(){__asm{ int 0x20; ret;}}int main(){if ((DWORD)HookIdt_0xe != 0x401040){ printf("HOOK出错了:%p", HookIdt_0xe); Sleep(10000); exit(-1);}if ((DWORD)checkAndCap != 0x4010c0){ printf("CHEC出错了:%p", checkAndCap); Sleep(10000); exit(-1);}go();system("pause");}