当前位置: 代码迷 >> 综合 >> 《Windows内核安全与驱动编程》-第八章-键盘的过滤学习-day3
  详细解决方案

《Windows内核安全与驱动编程》-第八章-键盘的过滤学习-day3

热度:31   发布时间:2023-12-04 05:47:44.0

键盘的过滤

8.5 Hook 分发函数

? 前面讲述了进行键盘的过滤。本节开始更加深入地探讨键盘的过滤与反过滤的对抗。无论是过滤还是反过滤,其原理都是进行过滤。取胜的关键在于: 谁将第一个得到消息。

? 前面学到的键盘过滤方法,是通过在设备栈上绑定一个新的设备实现的。这样就有了一个简单的设想来防止黑客对键盘进行过滤: 检查设备栈,看上面是否有不明的设备。

? 但是一般的黑客软件如果想进行过滤的话,不会采用往设备栈中插入设备进行过滤的方法。一个可能的途径是,黑客不会去插入一个能被安全软件轻易监控到的设备,但是依然能通过修改一个已经存在的驱动对象(比如 KdbClass) 的分发函数的指针来实现过滤所有请求的目的。黑客可以将这些函数指针替换成自己的驱动中的函数,这样请求将被黑客的程序首先解惑。然后为了让程序继续进行,黑客可以调用原来被替换过的旧的指针函数指针,让Windwos 的击键过程正常运作下去。

8.5.1 获得类驱动对象

? 当然,首先要获得键盘类的驱动对象,才能去替换下面的分发函数。但是这个操作相对简单,因为这个驱动的名字 “\\Driver\\Kdbclass”, 所以可以直接用函数 ObReferenceObjectByName 来获得。这个函数在前面的例子中已经用到过,但是还没有详细介绍。

// 驱动的名字
#define KBD_DRIVER_NAME L"\\Driver\\kbdclass"
// 当我们求得驱动对象的指针时,将其放到这里
PDRIVER_OBJECT KbdDriverObject;
UNICODE_STRING uniNtNameString;// 初始化驱动的名字字符串
RtlInitUnicodeString(&uniNtNameString,KBD_DRIVER_NAME);
// 根据名字字符串来获得驱动对象
status = ObReferenceObjectByName(&uniNtNameString,OBJ_CASE_INSENSITIVE,NULL,0,IoDriverObjectType,KernelMode,NULL,&KbdDriverObject
);
if(!NT_SUCCESS(status))
{// 如果失败了DbgPrint("MyAttach: Couldn't get the kbd driver Object\n");return STATUS_UNSUCCESSFUL;
}
else
{// 凡是调用了 Reference 系列的函数都要通过调用 ObDereferenceObject// 来解除引用ObDereferenceObject(kbdDriverObject);
}

? 这样就得到了驱动对象,然后只要替换其分发函数就行了。这个操作比想象的更加容易。在前面的几个例子中,我们都给自己的驱动对象设置的分发函数。而这里,要设置分发函数的不再是自己的驱动对象了,而是刚刚打开的键盘类驱动对象。

8.5.2 修改类驱动的分发函数指针

? 虽然驱动对象不同,但是替换方法还是一样的。值得注意的是,必须保存原有的驱动对象的分发函数的指针: 否则,第一,替换之后将无法回复;第二,完成我们自己的处理后无法继续调用原有的分发函数。

? 这里用到一个原子操作: InterlockedExchangePointer。这个操作的好处是,作者设置的新的函数指针的操作是原子的,不会被打断,插入其他可能要执行到调用这些分发函数的代码。

// 这个数组用来保存所有旧的指针
ULONG i;
PDRIVER_DISPATCH OldDispatchFuncitons[IPR_MJ_MAXIMUM_FUNCTION+1];...
// 把所有的分发函数指针都替换成我们自己编写的同一个分发函数
for(i=0;i<=IRP_MJ_MAXIMUM_FUNCTION;++i)
{// 假设 MyFilterDispatch 是笔者已经写好的一个分发函数OldDispatchFunction[i] = KbdDriverObject->MajorFunction[i];//进行原子交换操作InterlockedExchangerPointer(&KbdDriverObject->MajorFunction[i];,MyFilterDispatch);
}

? 上述代码存在安全问题: 比如一部分分发函数已经被替换,另一部分分发函数还没有被替换,此时又刚好有连续的几个 IRP 要进行处理,中间具有相关性。这种情况下有破坏它们之间的关联。但是只要替换完毕,也就安全了。这种安全问题出现的概率非常小。

8.5.3 类驱动之下的端口驱动

? 前面的过滤方法是替换分发函数指针。但是该方法仍然比较明显,因为分发函数的指针本来是已知的,如果安全监控软件有针对性地对这个指针进行检查保护,就容易发现这个指针已经被替换掉的情况。但是从分发函数出发,下面的各个调用层出不穷,任何一个地方都可能被替换,安全程序又如何去一一保护呢?

? 下面是一个比较邪道的例子。介绍该方法是为了让我们了解盗窃键盘信息的黑客所可能使用的手段,而绝不是推荐在商业软件中使用的方法。它是直接寻找一个用于端口驱动中读取输入缓冲区的函数(但是这个函数实际上是由类驱动提供的),这个函数也可以被Hook来实现过滤。

? KbdClass 被称为键盘类驱动,在 Windows 中,类驱动通常是指管理同一类设备的驱动程序。不管是 USB 键盘,还是 PS/2 键盘均经过它,所以在这层做拦截,能获得很好的通用性。在类驱动之下和实际硬件交互的驱动被称为“端口驱动”。具体到键盘,USB 键盘Kbdhid, 而i8042prtPS/2 键盘的端口驱动。

? 当键盘驱动从端口读出按键的扫描码,最终顺利的将它交给在键盘设备栈最顶端等待的那个主功能号为 IRP_MJ_READIRP。为了完成这个任务,键盘驱动使用了两个循环使用的缓冲区。

? 以比较古老的 PS/2 键盘作为例子进行介绍。因此下面的端口驱动都是 i8042prt

? i8042prtkbdClass 各有自己的一个可以循环使用的缓冲区。缓冲区的每一个单元是一个 KEYBOARD_INPUT_DATA 结构。用来存放一个扫描码及其他相关信息。在键盘驱动中,把这个循环使用的缓冲区叫做输入数据队列。i8042prt 的那个缓冲区被叫做端口键盘输入数据队列。KbdClass 的缓冲区被叫做类输入数据队列。

? 回忆一下设备拓展。我们曾经使用过的设备拓展, i8042prt 这个驱动生成的设备也有自定义的设备拓展。在它的自定义的设备拓展中,保存着一些指针和计数值,用来使用它的输入数据队列。包括

PKEYBOARD_INPUT_DATA 类型的 InputData DataIn DataOut DataEnd
ULONG 类型的 InputCount
InputData 指针,指向输入数据队列的开头。
DataEnd 指针,指向输入数据队列的结尾。
DataIn 指针,指向要进入队列的新数据被放在队列中的位置。
DataOut 指针,指向要出队列的数据在队列中开始的位置。
InputCount 值,为输入数据队列中数据的个数。

? 同时,在 KbdClass 的自定义设备拓展中,也保存着一些指针和计数值,名字和类型和上面的数据是完全一样的。

? 这一段我个人觉得比较难懂。尝试作图去解释一下。

? 缓冲区是这样一个一个的单元组成。并且这样的缓冲区叫做输入数据队列。

[KEYBOARD_INPUT_DATA]->[KEYBOARD_INPUT_DATA]->[KEYBOARD_INPUT_DATA]->[KEYBOARD_INPUT_DATA]->[KEYBOARD_INPUT_DATA]->[KEYBOARD_INPUT_DATA]...

? 其中 InputData 指向该队列的开头,DataEnd 指向该队列的结尾。DataIn 指向即将要进入队列中的新 [KEYBOARD_INPUT_DATA] 应该在的位置……这样去想,感觉好理解一些。

明日计划

今天有点杂事处理,学的不多。明天争取完结该章节,键盘的过滤和反过滤以及用端口操作键盘。

  相关解决方案