键盘过滤_IDT中断表与_修改IOAPIC重定位表寄存器_hook_单核

#include <ntddk.h>
//#define BUILD_FOR_IDT_HOOK// 这一句存在,则本程序编译为替换INT0x93的做法。如果不存在,则为IOAPIC重定位做法。
typedef unsigned char P2C_U8;// 由于这里我们必须明确一个域是多少位,所以我们预先定义几个明确知道多少位长度的变量,以避免不同环境下编译的麻烦.
typedef unsigned short P2C_U16;
typedef unsigned long P2C_U32;
#define P2C_MAKELONG(low, high) ((P2C_U32)(((P2C_U16)((P2C_U32)(low)& 0xffff)) | ((P2C_U32)((P2C_U16)((P2C_U32)(high)& 0xffff))) << 16))//取低16位 取高16位 与运算
#define P2C_LOW16_OF_32(data) ((P2C_U16)(((P2C_U32)data) & 0xffff))//低16位
#define P2C_HIGH16_OF_32(data) 	((P2C_U16)(((P2C_U32)data) >> 16))//高16位
#pragma pack(push,1)// 从sidt指令获得一个如下的结构。从这里可以得到IDT的开始地址
typedef struct P2C_IDTR_ {
	P2C_U16 limit;		// 范围
	P2C_U32 base;		// 基地址(就是开始地址)
} P2C_IDTR, *PP2C_IDTR;
#pragma pack(pop)
void *p2cGetIdt()// 下面这个函数用sidt指令读出一个P2C_IDTR结构,并返回IDT的地址。
{
	P2C_IDTR idtr;
	_asm sidt idtr		// 一句汇编读取到IDT的位置。
	return (void *)idtr.base;
}
#pragma pack(push,1)
typedef struct P2C_IDT_ENTRY_ {
	P2C_U16 offset_low;//中断门所在地址地位
	P2C_U16 selector;
	P2C_U8 reserved;
	P2C_U8 type : 4; //位域 代表4位   加起来8位占2字节
	P2C_U8 always0 : 1;
	P2C_U8 dpl : 2;
	P2C_U8 present : 1;
	P2C_U16 offset_high;
} P2C_IDTENTRY, *PP2C_IDTENTRY;
#pragma pack(pop)
#define OBUFFER_FULL 0x02 //没有 OBUFFER_FULL 则可读
#define IBUFFER_FULL 0x01
ULONG p2cWaitForKbRead()//等待到键盘有数据可读
{
	int i = 100;
	P2C_U8 mychar;
	do//命令检测
	{
		_asm in al, 0x64
		_asm mov mychar, al//读命令端口
		KeStallExecutionProcessor(50);//处理器时间间隔 拖延执行处理器 50微妙
		if (!(mychar & OBUFFER_FULL)) break;//可读了跳出
	} while (i--);
	if (i) return TRUE;
	return FALSE;
}
ULONG p2cWaitForKbWrite()//等待到键盘可写(入数据) 
{
	int i = 100;
	P2C_U8 mychar;
	do//检测端口命令 是否可写
	{
		_asm in al, 0x64
		_asm mov mychar, al
		KeStallExecutionProcessor(50);//cpu睡眠
		if (!(mychar & IBUFFER_FULL)) break;
	} while (i--);
	if (i) return TRUE;
	return FALSE;
}
// 首先读端口获得按键扫描码打印出来。然后将这个扫描码写回端口,以便别的应用程序能正确接收到按键。
// 如果不想让别的程序截获按键,可以写回一个任意的数据。
void p2cUserFilter()//我的idt过滤函数
{
	static P2C_U8 sch_pre = 0;//端口数据
	P2C_U8	sch;
	p2cWaitForKbRead();//等待到键盘有数据可读 通过命令判断
	_asm in al, 0x60
	_asm mov sch, al//读ps/2键盘60端口的值    0x60 叫数据端口  0x64 叫命令端口
	KdPrint(("p2c: scan code = %2x\r\n", sch));//扫描码读出键盘端口里没数据了 就要写入端口
	if (sch_pre != sch)//  把数据写回端口,以便让别的程序可以正确读取。  如果是这个值 跳过处理
	{
		sch_pre = sch;
		_asm mov al, 0xd2
		_asm out 0x64, al//写 端口命令
		p2cWaitForKbWrite(); //等待到键盘可写(入数据) 
		_asm mov al, sch
		_asm out 0x60, al//写入 端口数据
	}
}
void *g_p2c_old = NULL;
__declspec(naked) p2cInterruptProc()//裸函数 没有局部变量  新的中断门  idt表中的函数地址 替换了93号
{
	__asm
	{
		pushad					// 保存所有的通用寄存器
			pushfd					// 保存标志寄存器
			call p2cUserFilter	// 调一个我们自己的函数。 这个函数将实现  读端口数据
			// 一些我们自己的功能
			popfd					// 恢复标志寄存器
			popad					// 恢复通用寄存器
			jmp	g_p2c_old		// 跳到原来的中断服务程序	idt入口地址
	}
}
void p2cHookInt93(BOOLEAN hook_or_unhook)// 这个函数修改IDT表中的第0x93项,修改为p2cInterruptProc。 在修改之前要保存到g_p2c_old中。
{
	PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt();//读出来的是idt的结构体
	idt_addr += 0x93;
	KdPrint(("p2c: the current address = %x.\r\n",(void *)P2C_MAKELONG(idt_addr->offset_low, idt_addr->offset_high)));//高地址和低地址 计算出真地址 idt
	if (hook_or_unhook)//真
	{
		KdPrint(("p2c: try to hook interrupt.\r\n"));
		// 如果g_p2c_old是NULL,那么进行hook
		g_p2c_old = (void *)P2C_MAKELONG(idt_addr->offset_low, idt_addr->offset_high);//老的idt表地址
		idt_addr->offset_low = P2C_LOW16_OF_32(p2cInterruptProc);//得到低16位填充结构体 走新的函数
		idt_addr->offset_high = P2C_HIGH16_OF_32(p2cInterruptProc);//得到高16位填充结构体
	}
	else
	{
		KdPrint(("p2c: try to recovery interrupt.\r\n"));
		idt_addr->offset_low = P2C_LOW16_OF_32(g_p2c_old);// 如果g_p2c_old不是NULL,那么取消hook 还原
		idt_addr->offset_high = P2C_HIGH16_OF_32(g_p2c_old);
	}
	KdPrint(("p2c: the current address = %x.\r\n",(void *)P2C_MAKELONG(idt_addr->offset_low, idt_addr->offset_high)));//打印还原后的idt地址
}

P2C_U8 p2cGetIdleIdtVec()// 在idt表中找到一个空闲的idtentry的位置。然后返回这个id. 这是为了能填入我们新的键盘中断处理入口 如果找不到就返回0	这种情况下无法安装新的中断处理。
{
	P2C_U8 i;
	PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt();//得到idt表地址
	for (i = 0x20; i < 0x2a; i++)// 从vec20搜索到2a即可。
	{
		if (idt_addr[i].type == 0)// 如果类型为0说明是空闲位置,返回即可。
		{
			return i;
		}
	}
	return 0;
}
P2C_U8 p2cCopyANewIdt93(P2C_U8 id, void *interrupt_proc)//中断门重定位 改下空闲的项为键盘中断
{
	PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt();
	idt_addr[id] = idt_addr[0x93];// 我们写入一个新的中断门。这个门完全拷贝原来的0x93上的idtentry	
	idt_addr[id].offset_low = P2C_LOW16_OF_32(interrupt_proc); //中断处理函数的地址不同	  我们的过滤函数 为 p2cInterruptProc
	idt_addr[id].offset_high = P2C_HIGH16_OF_32(interrupt_proc);
	return id;
}
P2C_U8 p2cSeachOrSetIrq1(P2C_U8 new_ch)// 搜索IOAPIC获得键盘中断,或者设置这个值  空闲位
{
	P2C_U8 *io_reg_sel;// 选择寄存器。选择寄存器虽然是32位的寄存器,但是只使用 低8位,其他的位都被保留。
	P2C_U32 *io_win;//窗口寄存器,用来读写被选择寄存器选择的值,是32位的。
	P2C_U32 ch, ch1;
	// 定义一个物理地址,这个地址为0xfec00000。正是IOAPIC
	// 寄存器组在Windows上的开始地址
	PHYSICAL_ADDRESS	phys;//物理地址
	PVOID paddr;
	RtlZeroMemory(&phys, sizeof(PHYSICAL_ADDRESS));
	phys.u.LowPart = 0xfec00000;//物理地址
	paddr = MmMapIoSpace(phys, 0x14, MmNonCached);	// 物理地址是不能直接读写的。MmMapIoSpace把物理地址映射 为系统空间的虚拟地址。0x14是这片空间的长度    内存不应该被缓存的处理器
	if (!MmIsAddressValid(paddr))// 如果映射失败了就返回0.
		return 0;
	io_reg_sel = (P2C_U8 *)paddr;// 选择寄存器的偏移为0		虚拟机地址赋值寄存器
	io_win = (P2C_U32 *)((P2C_U8 *)(paddr)+0x10);// 窗口寄存器的偏移为0x10赋值为选择寄存器					 0x3f为止 中断表 表的寄存器 48个
	*io_reg_sel = 0x12;//选择第0x12,刚好是irq1的项	 键盘中断   默认的是12号
	ch = *io_win;//窗口寄存器
	if (new_ch != 0)//空闲位 如果new_ch不为0,我们就设置新值。并返回旧值。  如果搜索到空闲项 改成空闲号
	{
		ch1 = *io_win;//窗口寄存器
		ch1 &= 0xffffff00;//取高48位
		ch1 |= (P2C_U32)new_ch;//计算    0x93
		*io_win = ch1;//设置窗口寄存器的值
		KdPrint(("p2cSeachOrSetIrq1: set %2x to irq1.\r\n", (P2C_U8)new_ch));//打印空闲号
	}
	ch &= 0xff;// 窗口寄存器里读出的值是32位的,但是我们只需要一个字节就可以了 这个字节就是中断向量的值 一会我们要修改这个值。
	MmUnmapIoSpace(paddr, 0x14);//解除映射
	KdPrint(("p2cSeachOrSetIrq1: the old vec of irq1 is %2x.\r\n", (P2C_U8)ch));//返回中断号  0x93  
	return (P2C_U8)ch;
}
void p2cResetIoApic(BOOLEAN set_or_recovery)//hook 重定位表
{
	static P2C_U8 idle_id = 0;
	PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt();//得到idt表地址
	P2C_U8 old_id = 0;
	if (set_or_recovery)//真
	{
		idt_addr = (PP2C_IDTENTRY)p2cGetIdt();// 如果是设置新的ioapic定位,那么首先在g_p2c_old中	保存原函数的入口。
		idt_addr += 0x93;
		g_p2c_old = (void *)P2C_MAKELONG(idt_addr->offset_low, idt_addr->offset_high);//老的硬盘中断
		idle_id = p2cGetIdleIdtVec();// 然后获得一个空闲位,将irq1处理中断门复制一个进去	 里面的跳转函数填写为我们的新的处理函数。
		if (idle_id != 0)//找到空闲的了
		{
			p2cCopyANewIdt93(idle_id, p2cInterruptProc);//中断门重定位 改下 空闲的项为键盘中断	 然后重新定位到这个中断。	
			old_id = p2cSeachOrSetIrq1(idle_id);// 在32位WindowsXP下这个中断默认应该是定位到0x93的。	
			ASSERT(old_id == 0x93);//当前中断门 不等于93就是打印错误
		}
	}
	else
	{
		// 如果是要恢复...
		old_id = p2cSeachOrSetIrq1(0x93);//返回 当前的中断门
		ASSERT(old_id == idle_id);//如果返回0就 错误
		idt_addr[old_id].type = 0;// 现在那个中断门没用了,设置type = 0使之空闲
	}
}
#define  DELAY_ONE_MICROSECOND  (-10)
#define  DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)//-10000
#define  DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
void p2cUnload(PDRIVER_OBJECT drv)
{
	LARGE_INTEGER interval;
#ifdef BUILD_FOR_IDT_HOOK
	p2cHookInt93(FALSE);
#else
	p2cResetIoApic(FALSE);
#endif
	KdPrint(("p2c: unloading\n"));
	interval.QuadPart = (5 * 1000 * DELAY_ONE_MILLISECOND);
	KeDelayExecutionThread(KernelMode, FALSE, &interval);	// 睡眠5秒。等待所有irp处理结束  延迟执行线程
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath	)
{
	ULONG i;
	KdPrint(("p2c: entering DriverEntry\n"));
	DriverObject->DriverUnload = p2cUnload;// 卸载函数。
#ifdef BUILD_FOR_IDT_HOOK
	p2cHookInt93(TRUE);//hook idt
#else
	p2cResetIoApic(TRUE);//hook 重定位表
#endif
	return  STATUS_SUCCESS;
}

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值