驱动开发之 键盘过滤驱动--利用IOAPIC重定位中断处理函数

下图是键盘输入流程的主要过程:

驱动开发之 键盘过滤驱动--利用IOAPIC重定位中断处理函数_第1张图片

现在这种键盘过滤方法是操作在图片红色圈起来的部分。这种方法是修改IOAPIC重定位表中的IRQ对应的中断号,并且在IDT中增加一个新的中断处理。

寒江独钓中是这样介绍这种方式的:

什么是IOAPIC
IOAPIC是可以用于多个核心CPU的新型中断控制器,所以或与应该理解为一种新的可编程硬件。
IOAPIC的作用在于当一个IRQ发生时,这个硬件将负责决定将IRQ发送给哪个CPU核心,以及以何种形式发送等。IOAPIC是可以编程的,因此可以通过编程,也可以将PS/2键盘的硬件中断请求(IRQ1)发送给某个CPU核心,让核心的IDT中的某个中断号对应中断处理服务来处理。

配置IOAPIC的方法是修改IOAPIC中的寄存器,这些寄存器是通过内存映射的方式来访问的。也就是说,直接读/写内存中的某些地址就可以修改这些寄存器的内容。

IOAPIC通过读写相应的寄存器来进行编程。IOAPIC的寄存器是不能像通用寄存器一样直接访问的,而是通过映射成内存的物理地址进行访问。windows把访问IOAPIC系列寄存器所需要的两个寄存器已经映射到物理地址 0xfec00000 和 0xfec00010 的位置,在windows下这是固定的。


要操作IOAPIC的寄存器,必须通过以下两个寄存器:

IOREGSEL ----- I/O寄存器选择寄存器。可通过内存物理地址0xfec00000 直接访问,可读可写。这个寄存器的作用是:选择一个要操作的IOAPIC寄存器。宽度为32为,但只有低8位有效!

IOWIN ---------  I/O窗口寄存器。可通过内存物理地址0xfec00010 直接访问,可读可写。这个寄存器的作用的作用是:读写由 IOREGSEL指定的内部寄存器 的内容,你可以读取或修改IOWIN这个映射寄存器的值,然后映射机制会修改真正的IOAPIC内部寄存器的值。


IOAPIC重定位表的偏移从0x10开始,到0x3f结束,一共48个。IRQ1对应表项所在的寄存器偏移为0x12,0x13,中断号保存在0x12寄存器的低8位(1个字节),修改这个就可修改为其他字节,windows下面默认为0x93.


下面是 Hook键盘中断反过滤与利用IOAPIC重定位修改处理函数的实例代码


#include 

// 这一句存在,则本程序编译为替换INT0x93的做法。如果
// 不存在,则为IOAPIC重定位做法。
 #define BUILD_FOR_IDT_HOOK

// 由于这里我们必须明确一个域是多少位,所以我们预先定义几个明
// 确知道多少位长度的变量,以避免不同环境下编译的麻烦.
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))

#define P2C_LOW16_OF_32(data) \
((P2C_U16)(((P2C_U32)data) & 0xffff))

#define P2C_HIGH16_OF_32(data) \
((P2C_U16)(((P2C_U32)data) >> 16))

// 从sidt指令获得一个如下的结构。从这里可以得到IDT的开始地址
#pragma pack(push,1)
typedef struct P2C_IDTR_ {
	P2C_U16 limit;		// 范围
	P2C_U32 base;		// 基地址(就是开始地址)
} P2C_IDTR, *PP2C_IDTR;
#pragma pack(pop)

// 下面这个函数用sidt指令读出一个P2C_IDTR结构,并返回IDT的地址。
void *p2cGetIdt()
{
	P2C_IDTR idtr;
    // 一句汇编读取到IDT的位置。
	_asm sidt idtr
	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;
		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
#define IBUFFER_FULL 0x01

ULONG p2cWaitForKbRead()
{
	int i = 100;
	P2C_U8 mychar;	
	do
	{
        _asm in al,0x64
        _asm mov mychar,al
	    KeStallExecutionProcessor(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);
		if(!(mychar & IBUFFER_FULL)) break;
	} while (i--);
	if(i) return TRUE;
	return FALSE;
}

// 首先读端口获得按键扫描码打印出来。然后将这个扫
// 描码写回端口,以便别的应用程序能正确接收到按键。
// 如果不想让别的程序截获按键,可以写回一个任意的
// 数据。
void p2cUserFilter()
{
    static P2C_U8 sch_pre = 0;
	P2C_U8	sch;
	p2cWaitForKbRead();
    _asm in al,0x60
    _asm mov sch,al
    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()
{
	__asm
	{
		pushad					// 保存所有的通用寄存器
		pushfd					// 保存标志寄存器
		call p2cUserFilter	// 调一个我们自己的函数。 这个函数将实现
								    // 一些我们自己的功能
		popfd					// 恢复标志寄存器
		popad					// 恢复通用寄存器
		jmp	g_p2c_old		// 跳到原来的中断服务程序
	}
}

// 这个函数修改IDT表中的第0x93项,修改为p2cInterruptProc。
// 在修改之前要保存到g_p2c_old中。
void p2cHookInt93(BOOLEAN hook_or_unhook)
{
    PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt();
    idt_addr += 0x93;
    KdPrint(("p2c: the current address = %x.\r\n",
        (void *)P2C_MAKELONG(idt_addr->offset_low,idt_addr->offset_high)));
    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_addr->offset_low = P2C_LOW16_OF_32(p2cInterruptProc);
        idt_addr->offset_high = P2C_HIGH16_OF_32(p2cInterruptProc);
    }
    else
    {
        KdPrint(("p2c: try to recovery interrupt.\r\n"));
        // 如果g_p2c_old不是NULL,那么取消hook.
        idt_addr->offset_low = P2C_LOW16_OF_32(g_p2c_old);
        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表中找到一个空闲的idtentry的位置。然后返回这个id.这是为
// 了能填入我们新的键盘中断处理入口。如果找不到就返回0。这
// 种情况下无法安装新的中断处理。
P2C_U8 p2cGetIdleIdtVec()
{
    P2C_U8 i;
    PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt();

    // 从vec20搜索到2a即可。
	for(i=0x20;i<0x2a;i++)
	{
        // 如果类型为0说明是空闲位置,返回即可。
        if(idt_addr[i].type == 0)
		{
			return i;
		}
	}
    return 0;
}


P2C_U8 p2cCopyANewIdt93(P2C_U8 id,void *interrupt_proc)
{
    // 我们写入一个新的中断门。这个门完全拷贝原来的0x93
    // 上的idtentry,只是中断处理函数的地址不同。
    PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt();
    idt_addr[id] = idt_addr[0x93];
    idt_addr[id].offset_low = P2C_LOW16_OF_32(interrupt_proc);
    idt_addr[id].offset_high = P2C_HIGH16_OF_32(interrupt_proc);
    return id;
}

// 搜索IOAPIC获得键盘中断,或者设置这个值。
P2C_U8 p2cSeachOrSetIrq1(P2C_U8 new_ch)
{
    // 选择寄存器。选择寄存器虽然是32位的寄存器,但是只使用
    // 低8位,其他的位都被保留。
	P2C_U8 *io_reg_sel;

    // 窗口寄存器,用来读写被选择寄存器选择的值,是32位的。
	P2C_U32 *io_win;
	P2C_U32 ch,ch1;

    // 定义一个物理地址,这个地址为0xfec00000。正是IOAPIC
    // 寄存器组在Windows上的开始地址
	PHYSICAL_ADDRESS	phys ;
	PVOID paddr;
	RtlZeroMemory(&phys,sizeof(PHYSICAL_ADDRESS));
	phys.u.LowPart = 0xfec00000;

    // 物理地址是不能直接读写的。MmMapIoSpace把物理地址映射
    // 为系统空间的虚拟地址。0x14是这片空间的长度。
	paddr = MmMapIoSpace(phys, 0x14, MmNonCached);

    // 如果映射失败了就返回0.
	if (!MmIsAddressValid(paddr))
		return 0;

    // 选择寄存器的偏移为0
	io_reg_sel = (P2C_U8 *)paddr;
    // 窗口寄存器的偏移为0x10.
	io_win = (P2C_U32 *)((P2C_U8 *)(paddr) + 0x10);

    // 选择第0x12,刚好是irq1的项
	*io_reg_sel = 0x12;
	ch = *io_win;

    // 如果new_ch不为0,我们就设置新值。并返回旧值。
    if(new_ch != 0)
    {
        ch1 = *io_win;
        ch1 &= 0xffffff00;
        ch1 |= (P2C_U32)new_ch;
        *io_win = ch1;
        KdPrint(("p2cSeachOrSetIrq1: set %2x to irq1.\r\n",(P2C_U8)new_ch));
    }

    // 窗口寄存器里读出的值是32位的,但是我们只需要
    // 一个字节就可以了。这个字节就是中断向量的值。
    // 一会我们要修改这个值。
    ch &= 0xff;
	MmUnmapIoSpace(paddr, 0x14);
    KdPrint(("p2cSeachOrSetIrq1: the old vec of irq1 is %2x.\r\n",(P2C_U8)ch));
	return (P2C_U8)ch;
}

void p2cResetIoApic(BOOLEAN set_or_recovery)
{
    static P2C_U8 idle_id = 0;
    PP2C_IDTENTRY idt_addr = (PP2C_IDTENTRY)p2cGetIdt();
    P2C_U8 old_id = 0;

    if(set_or_recovery)
    {
        // 如果是设置新的ioapic定位,那么首先在g_p2c_old中保存
        // 原函数的入口。
        idt_addr = (PP2C_IDTENTRY)p2cGetIdt();
        idt_addr += 0x93;
        g_p2c_old = (void *)P2C_MAKELONG(idt_addr->offset_low,idt_addr->offset_high);
 
        // 然后获得一个空闲位,将irq1处理中断门复制一个进去。
        // 里面的跳转函数填写为我们的新的处理函数。
        idle_id = p2cGetIdleIdtVec();
        if(idle_id != 0)
        {
            p2cCopyANewIdt93(idle_id,p2cInterruptProc);
            // 然后重新定位到这个中断。
            old_id = p2cSeachOrSetIrq1(idle_id);
            // 在32位WindowsXP下这个中断默认应该是定位到0x93的。
            ASSERT(old_id == 0x93);
        }
    }
    else
    {
        // 如果是要恢复...
        old_id = p2cSeachOrSetIrq1(0x93);
        ASSERT(old_id == idle_id);
        // 现在那个中断门没用了,设置type = 0使之空闲
        idt_addr[old_id].type = 0;
    }
}

#define  DELAY_ONE_MICROSECOND  (-10)
#define  DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#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")); 
	// 睡眠5秒。等待所有irp处理结束
	interval.QuadPart = (5*1000 * DELAY_ONE_MILLISECOND);		
	KeDelayExecutionThread(KernelMode,FALSE,&interval);
}

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);
#else
    p2cResetIoApic(TRUE);
#endif
    return  STATUS_SUCCESS; 
}


你可能感兴趣的:(Driver,驱动开发,键盘过滤驱动)