4.1 技术原理 & 4.2 键盘过滤框架

4.1 预备知识

符号链接:符号链接其实就是一个“别名”。可以用一个不同的名字来代表一个设备对象(实际上),符号链接可以指向任何有名字的对象。

ZwCreateFile是很重要的函数。同名的函数实际上有两个:一个在内核中,一个在应用层。所以在应用程序中直接调用CreateFile,就可以引发对这个函数的调用。
它不但可以打开文件,而且可以打开设备对象(返回得到一个类似文件句柄的句柄)。所以后面会常常看见应用程序为了交互内核而调用这个函数,这个函数最终调用NtCreateFile。

PDO:Phsiycal Device Object的简称(物理设备对象)。PDO是设备栈最下面的那个设备对象(不精确,但是很实在)

nt!IoGetAttachedDevice、nt!ObpCreatHandle等写法是在调试工具WinDbg中常常出现的表示方法。!号之前的内容表示模块名,而之后的内容表示函数名或变量名。比如 nt!IoGetAttachedDevice表示模块nt中的函数IoGetAttachedDevice。

4.1.2 Windows中从击键到内核

在任务管理器的进程列表中可以看到“csrss.exe”进程。这个进程很关键,它有一个线程叫做win32!RawInputThread,这个线程通过一个GUID(GUID_CLASS_KEYBOARD)获得键盘设备中PDO的符号链接名

应用程序是不能直接根据设备名字打开设备的,一般都通过符号链接名来打开。

简单地说,win32k!RawInputThread线程总是nt!ZwReadFile要求读入数据,然后等待键盘的键被按下。当键盘上的键被按下时,win32k!RawInputThread处理nt!ZwReadFile得到的数据,然后nt!ZwReadFile要求读入数据,再等待键盘上的键被按下。

我们一般看到的PS/2键盘的设备栈,如果自己没有另外安装其他键盘过滤程序,那么设备栈的情况是这样的:
** 最顶层的设备对象是驱动 Kdbclass 生成的设备对象。
** 中间层的设备对象是驱动i8042prt生成的设备对象。
** 最底层的设备对象是驱动ACPI生成的设备对象。
这里只需要知道,接下来要找的是驱动Kdbclass的设备对象。

4.1.3 键盘硬件原理

从键盘被敲到计算机屏幕上出现一个字符,中间有很多复杂的变换。这里需要了解一些知识。
** 键并不是用字符代表,而是给每个键规定了一个扫描码。
** 键盘和CPU的交互方式是中断和读取端口,这个操作是串行的。一次中断发出就等于键盘给CPU发送一个通知,这个通知只能通知一个事件:某个键被被按下,某个键弹起。CPU只能接收通知并读取端口的扫描码,从不主动去“查看”任何键。
** 网上资料查到:如果一个键按下的扫描码为 X,则同一个键弹起的扫描码为 X+0x80。
** Windows XP 下端口和中断号都是定死的,即中断号为0x93,端口号为0x60。每次中断发送,CPU都去读取端口0x60中的扫描码,0x60中只能保存一个字节,但是扫描码是可以有两个字节的,此时会发生两次中断,CPU会先后读取扫描码的两个字节

请注意:比如“同时按下两个键”之类的事情在这种机制下是不可能发生的。无论如何按键,信息的传递都是一次一个字节串行发生的。

4.2 键盘过滤的框架
4.2.1 找到所有的键盘设备

打开驱动对象KdbClass,然后绑定它下面所有的设备。

找驱动下的所有对象:
(1)DRIVER_OBJECT下的DeviceObject,驱动下的所有设备对象都在这个设备链中,可以通过链节点中的域NextDevice依次进行遍历。
(2)调用IoEnumerateDeviceObjectList,这个函数可以枚举出一个驱动下的所有设备。

这里的新函数——ObReferenceObjectByName,它用来通过一个名字获得一个对象的指针。

4.2.2 应用设备扩展

在生成一个过滤设备时,我们可以给这个设备指定一个任意长度的“设备扩展”,这个扩展中的内容可以任意填写,作为一个自定义的数据结构。

这样就可以把真实设备的指针保存在设备对象里了,就没有必要做两个数组去对应起来,每次都要找一番。

在键盘过滤中,作者专门定义了一个结构作为设备扩展如下:

typedef struct _C2P_DEV_EXT
{
// 这个结构的大小
ULONG NodeSize;
// 过滤设备对象
PDEVICE_OBJECT pFilterDeviceObject;
// 同时调用时的保护锁
KSPIN_LOCK IoRequestsSpinLock;
// 进程间同步处理
KEVENT IoInProgressEvent;
// 绑定的设备对象
PDEVICE_OBJECT TargetDeviceObject;
// 绑定前底层设备对象(真实设备)
PDEVICE_OBJECT LowerDeviceObject;
} C2P_DEV_EXT, *PC2P_DEV_EXT;

要生成一个带有设备扩展信息的设备对象,关键是在调用IoCreateDevice时,注意第二个参数填入扩展的长度。比如前面的例子中生成过滤设备时,所用的代码是:

status = IoCreateDevice(
IN DriverObject,
IN sizeof(C2P_DEV_EXT), //扩展的长度
IN NULL,
IN pTargetDeviceObject->DeviceType,
IN pTargetDeviceObject->Characteristics,
IN FALSE,
OUT &pFilterDeviceObject
);

扩展域的填写函数如下:

NTSTATUS
c2pDevExtInit(
IN PC2P_DEV_EXT devExt,
IN PDEVICE_OBJECT pFilterDeviceObject,
IN PDEVICE_OBJECT pTargetDeviceObject,
IN PDEVICE_OBJECT pLowerDeviceObject )
{
memset(devExt, , sizeof(C2P_DEV_EXT));
devExt->NodeSize = sizeof(C2P_DEV_EXT);
devExt->pFilterDeviceObject = pFilterDeviceObject;
KeInitializeSpinLock(&(devExt->IoRequestsSpinLock));
KeInitializeEvent(&(devExt->IoInProgressEvent), NotificationEvent, FALSE);
devExt->TargetDeviceObject = pTargetDeviceObject;
devExt->LowerDeviceObject = pLowerDeviceObject;
return( STATUS_SUCCESS );
}

4.2.3 键盘过滤模块的DriverEntry
与串口的差别就是,这里需要关系键盘的移除。

NTSTATUS DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
ULONG i;
NTSTATUS status;
KdPrint (("c2p.SYS: entering DriverEntry\n")); // 填写所有的分发函数的指针
for (i = ; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DriverObject->MajorFunction[i] = c2pDispatchGeneral;
} // 单独的填写一个Read分发函数。因为要的过滤就是读取来的按键信息
// 其他的都不重要。这个分发函数单独写。
DriverObject->MajorFunction[IRP_MJ_READ] = c2pDispatchRead; // 单独的填写一个IRP_MJ_POWER函数。这是因为这类请求中间要调用
// 一个PoCallDriver和一个PoStartNextPowerIrp,比较特殊。
DriverObject->MajorFunction [IRP_MJ_POWER] = c2pPower; // 我们想知道什么时候一个我们绑定过的设备被卸载了(比如从机器上
// 被拔掉了?)所以专门写一个PNP(即插即用)分发函数
DriverObject->MajorFunction [IRP_MJ_PNP] = c2pPnP; // 卸载函数。
DriverObject->DriverUnload = c2pUnload;
gDriverObject = DriverObject;
// 绑定所有键盘设备
status =c2pAttachDevices(DriverObject, RegistryPath); return status;
}

4.2.4 键盘过滤模块的动态卸载

与串口过滤稍有不同的是,键盘总是处于“有一个读请求没有完成”的状态。
键盘上有键被按下——》触发中断——》键盘驱动从端口读取扫描码——》从键盘得到的数据交给IRP-——》结束这个IRP——》导致win32k!RawInputThread线程对这个读操作的等待结束——》处理数据,处理完成后——》调用nt!ZwReadFile,开始新的等待。

防止未决请求没有完成的方法就是使用gC2pKeyCount。gC2pKeyCount在这里是一个全局变量,每次有一个读请求到来的时候,gC2pKeyCount被加1;每次完成时,则减1。于是只有所有请求都完成后,才结束等待;否则无休止的等待下去。

实际上,只有一个键被按下时,这个卸载过程才结束。gC2pKeyCount的运算在下面的4.3节“键盘过滤的请求处理”中会看到。

#define  DELAY_ONE_MICROSECOND  (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000) VOID
c2pUnload(IN PDRIVER_OBJECT DriverObject)
{
PDEVICE_OBJECT DeviceObject;
PDEVICE_OBJECT OldDeviceObject;
PC2P_DEV_EXT devExt; LARGE_INTEGER lDelay;
PRKTHREAD CurrentThread;
//delay some time
lDelay = RtlConvertLongToLargeInteger( * DELAY_ONE_MILLISECOND);
CurrentThread = KeGetCurrentThread();
// 把当前线程设置为低实时模式,以便让它的运行尽量少影响其他程序。
KeSetPriorityThread(CurrentThread, LOW_REALTIME_PRIORITY); UNREFERENCED_PARAMETER(DriverObject);
KdPrint(("DriverEntry unLoading...\n")); // 遍历所有设备并一律解除绑定
DeviceObject = DriverObject->DeviceObject;
while (DeviceObject)
{
// 解除绑定并删除所有的设备
c2pDetach(DeviceObject);
DeviceObject = DeviceObject->NextDevice;
}
ASSERT(NULL == DriverObject->DeviceObject); while (gC2pKeyCount)
{
KeDelayExecutionThread(KernelMode, FALSE, &lDelay);
}
KdPrint(("DriverEntry unLoad OK!\n"));
return;
}

[内核编程] 4.1 技术原理 & 4.2 键盘过滤框架的更多相关文章

  1. 4.1 技术原理 & 4.2 键盘过滤框架

    4.1 技术原理 & 4.2 键盘过滤框架 4.1 预备知识 符号链接:符号链接其实就是一个“别名”.可以用一个不同的名字来代表一个设备对象(实际上),符号链接可以指向任何有名字的对象. Zw ...

  2. Atitit.异步编程技术原理与实践attilax总结

    Atitit.异步编程技术原理与实践attilax总结 1. 俩种实现模式 类库方式,以及语言方式,java futuretask ,c# await1 2. 事件(中断)机制1 3. Await 模 ...

  3. 超级干货:动态防御WAF技术原理及编程实战!

    本文带给大家的内容是动态防御WAF的技术原理及编程实战. 将通过介绍ShareWAF的核心技术点,向大家展示动态防御的优势.实现思路,并以编程实战的方式向大家展示如何在WAF产品开发过程中应用动态防御 ...

  4. Atitit.ide技术原理与实践attilax总结

    Atitit.ide技术原理与实践attilax总结 1.1. 语法着色1 1.2. 智能提示1 1.3. 类成员outline..func list1 1.4. 类型推导(type inferenc ...

  5. Linux设备驱动开发详解-Note(5)---Linux 内核及内核编程(1)

    Linux 内核及内核编程(1) 成于坚持,败于止步 Linux 2.6 内核的特点 Linux 2.6 相对于 Linux 2.4 有相当大的改进,主要体现在如下几个方面. 1.新的调度器 2.6 ...

  6. 《天书夜读:从汇编语言到windows内核编程》六 驱动、设备、与请求

    1)跳入到基础篇的内核编程第7章,驱动入口函数DriverEnter的返回值决定驱动程序是否加载成功,当打算反汇编阅读驱动内核程序时,可寻找该位置. 2)DRIVER_OBJECT下的派遣函数(分发函 ...

  7. Android热修复技术原理详解(最新最全版本)

    本文框架 什么是热修复? 热修复框架分类 技术原理及特点 Tinker框架解析 各框架对比图 总结   通过阅读本文,你会对热修复技术有更深的认知,本文会列出各类框架的优缺点以及技术原理,文章末尾简单 ...

  8. Linux内核编程规范与代码风格

    source: https://www.kernel.org/doc/html/latest/process/coding-style.html translated by trav, travmym ...

  9. [内核编程] 4.5 HOOK分发函数

    4.5 HOOK分发函数 本节开始深入的探讨键盘的过滤与反过滤.有趣的是,无论是过滤还是反过 滤,其原理都是进行过滤.取胜的关键在于:谁将第一个得到信息. 黑客可能会通过修改一个已经存在的驱动对象(比 ...

随机推荐

  1. pdnsd 解析原理

    apt install dnsmasq dnsmasq-fullvim /etc/dnsmasq.conf vim /etc/pdnsd.conf killall pdnsdpdnsd -c /etc ...

  2. 用openssl生成含有中文信息的证书

    openssl 支持 ASCII 和 UTF-8 两种编码,应该可以制作中文证书. 在生成证书签发申请时,当输入中文则 openssl 报错,这是因为当前输入的字符是 ANSI 本地编码格式,超出了 ...

  3. js--07 编解码,eval

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...

  4. HDU 多校联合 6033 6043

    http://acm.hdu.edu.cn/showproblem.php?pid=6033 Add More Zero Time Limit: 2000/1000 MS (Java/Others)  ...

  5. CSS笔记 - SVG Polyline 图片绘制动画边框

    <style> div{ width: 420px; height: 200px; background: url('./img/timg.jpg') no-repeat; } polyl ...

  6. Appium_python3 抓取客户端toast

    在客户端登录或者退出登录的时候会有吐司提示,因此需要抓取来验证用户登录成功或者注销成功: 在获取toast之前需要添加   desired_caps['automationName'] = 'Uiau ...

  7. 【Educational Codeforces Round 35 D】Inversion Counting

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 排列中交换任意两个数字. 排列的逆序对个数的奇偶性会发生变化. 翻转这个过程其实就是len/2对数字发生交换. 交换了偶数次的话,不 ...

  8. 洛谷 P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib

    P1218 [USACO1.5]特殊的质数肋骨 Superprime Rib 题目描述 农民约翰的母牛总是产生最好的肋骨.你能通过农民约翰和美国农业部标记在每根肋骨上的数字认出它们.农民约翰确定他卖给 ...

  9. java位运算应用

    位移动运算符: <<表示左移, 左移一位表示原来的值乘2. 比如:3 <<2(3为int型)  1)把3转换为二进制数字0000 0000 0000 0000 0000 000 ...

  10. 使用Microsoft.Office.Interop.Excel时,64位问题

    前不久,碰到一个问题. 曾经用的好好的Microsoft.Office.Interop.Excel实现的导出Excel,迁移至64位server后,就出现: 检索 COM 类工厂中 CLSID 为 { ...