[原创]一起来写控制普通键盘上的3盏LED的驱动
发表于:
2011-2-12 19:57
9935
最近在看ROOTKITS方面的书,有一章直接操纵硬件,是关于键盘上的LED指示器的,感觉对我这样的菜鸟很有帮助,就按照着文章的例程,写下了这篇文章。由于我在ROOTKITS这方面还只能算个菜鸟中的菜鸟,所以文章中的所谓还希望各位看官们,不吝赐教嗷。
说实话不知道应该从哪里讲起,我这方面的知识很零碎……硬着头皮上吧,一边写一边百度,额。
其实光明正大的控制一个键盘很简单。一般的键盘控制器在0X60和0X64上是可寻址的,也就是说这两个端口(PORT)是进入键盘芯片的端口。如果像我一样使用WDK,则可以使用:
READ_PORT_UCHAR(PORT);
WRITE_PORT_UCHAR(PORT,COMMAND/DATA);
这两个函数对这两个端口进行读写操作。
通过这两个端口我们可以做很多事情,比如键盘窃听什么的^^当然还有我们今天的主角,控制键盘上LED指示器。
键盘和鼠标是共享0X60端口的,所以在使用之前,系统要先读取0X64端口,判断是鼠标还是键盘。话说回来。笔记本这方面也应该一样的吧。 设置LED的命令是 0XED。而 OXED 使用的数据格式是一个八位的数据是:
■■■■■■■■ (话说一个■表示一位,即0或者1,最后三位控制键盘上 SCROLL LOCK ,NUM LOCK,CAPS LOCK3个LED灯的亮暗)
↑↑↑
↑↑CAPS LOCK
↑↑
↑NUM LOCK
↑
↑
SCROLL LOCK
而我们所要做的就是使用上面提到的2个函数:
WRITE_PORT_UCHAR(0X60,0XED);
WRITE_PORT_UCHAR(0X60,00000111b); 比如这样表示3个灯全亮。
上面两个就是传说中的核心代码……要是文章能这样结束那该多好呀~~~不过硬件可不是那么好对付的。
如果键盘忙于处理击键动作,那么这种方式很可能无法成功。对话硬件一个很重要的地方就是要等到硬件芯片就绪,如果芯片未就绪就试图向它发送命令、数据,比较理想的情况是什么都不会发生。偶尔么就崩溃了。
程序中出现了一个叫做Dpc的东西,看不懂可以先放放,而且我也不怎么懂,等下我们一起学吧。
前置知识介绍完了,开始动手吧。 对了 这个针对8042键盘,也就是一般是普通台式的键盘,话说笔记本上的3个LED灯就不一样了,比如我这台上面最左边的是表示硬盘读写状态的LED。
写完用WDK编译一下就可以用 InstDrv 加载测试了,呵呵,来一次与硬件的对话吧。
===========================================
#include "ntddk.h" //不解释
#include <stdio.h> //同上
PUCHAR KEYBOARD_PORT_60 = (PUCHAR)0x60;
PUCHAR KEYBOARD_PORT_64 = (PUCHAR)0x64; //定义两个端口
#define IBUFFER_FULL 0X02 //寄存器标志位,等下会与READ_PORT_UCHAR()的返回值进行&运算,来判断是否没有输入,至于为什么是00000010b,我不知道
#define OBUFFER_FULL 0X01 //估计是与键盘输的数据有关吧,因为WDK文档上讲READ_PORT_UCHAR()的返回值是从指定端口的读取的字节。
//键盘输出的字符毕竟有限。
PKTIMER gTimer; //计时器
PKDPC gDPCP; //Dpc,后面细说
UCHAR g_key_bits=0; //这个是控制LED灯循环的
BOOLEAN WaitForKeyboard()
{
INT i=100; //循环的次数
UCHAR mychar;
do
{
mychar=READ_PORT_UCHAR(KEYBOARD_PORT_64);
KeStallExecutionProcessor(666); //这个函数据说能使CPU暂停特定微秒数。于是键盘可以完成它先前的工作。至于为什么
//是666毫秒。。因为书上这么写的。
if(!(mychar&IBUFFER_FULL)) break;
}
while(i--);
if(i) return TRUE;
return FALSE;
}
VOID DrainOutputBuffer() //这个函数的用途是如果键盘缓冲区含有击键动作,它会将缓冲区排干,防止干 // 扰LED灯
{
int i=100; //循环次数
UCHAR c;
do
{
c=READ_PORT_UCHAR(KEYBOARD_PORT_64);
KeStallExecutionProcessor(666); //依然666。
if(!(c&OBUFFER_FULL)) break;
}
while(i--);
}
BOOLEAN SendKeyboardCommand( //等下会被下一个函数SetLEDS调用,开始传给0X60端口命令和数据了 ,文章开
IN UCHAR theCommand //始有说过怎么传的 还记得吧。
)
{
if(TRUE == WaitForKeyboard()) //如果键盘就绪 就开始传递。
{
WRITE_PORT_UCHAR(KEYBOARD_PORT_60,theCommand);
return TRUE;
}
return FALSE;
}
VOID SetLEDS( //这个函数调用上面一个函数。
UCHAR theLEDS
)
{
SendKeyboardCommand(0XED);
SendKeyboardCommand(theLEDS);
}
VOID OnUnload( //卸载驱动的函数。取消该取消的,释放该释放的。
IN PDRIVER_OBJECT DriverObject
)
{
KeCancelTimer(gTimer); //取消计时器
ExFreePool(gTimer); //释放内存池
ExFreePool(gDPCP); //同上
} VOID timerDPC( //这个函数是KeInitializeDpc的一个参数,等下看KeInitializeDpc的时候一起 //讲吧。
IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID sys1,
IN PVOID sys2
)
{
SetLEDS(g_key_bits++);
if(g_key_bits>0X07) g_key_bits=0;
}
NTSTATUS DriverEntry(
IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath
)
{
LARGE_INTEGER timeout;
theDriverObject->DriverUnload=OnUnload; //申明驱动卸载例程
gTimer=ExAllocatePool(NonPagedPool,sizeof(KTIMER)); //分配内存,由于只是个测试,就用NonPagedPool参数了。
gDPCP=ExAllocatePool(NonPagedPool,sizeof(KDPC)); //一样的。
timeout.QuadPart=-10; //为什么是负数,后面讲。
KeInitializeTimer(gTimer); //安装计时器。
KeInitializeDpc(gDPCP,timerDPC,NULL); //安装Dpc。Dpc这方面我也不懂。等下在后面我们一起学吧。
KeSetTimerEx(gTimer,timeout,300,gDPCP); //我的理解是配置计时器。
return STATUS_SUCCESS;
}
==============================================================================
程序就是上面这样。
我们倒着看吧。先看看KeSetTimerEx(gTimer,timeout,300,gDPCP)这个函数。
WDK上的帮助文件如是说:
The KeSetTimerEx routine sets the absolute or relative interval at which a timer object is to be set to a signaled state, optionally supplies a CustomTimerDpc routine to be executed when that interval expires, and optionally supplies a recurring interval for the timer.
BOOLEAN
KeSetTimerEx(
IN PKTIMER Timer,
IN LARGE_INTEGER DueTime,
IN LONG Period OPTIONAL,
IN PKDPC Dpc OPTIONAL
); 我的理解是对计时器的配置。 第一个参数就我们之前用KeInitializeTimer安装的计时器。第二个参数是之前提到的类型是LARGE_INTEGER的timeout,表示计时器的循环响应(“响应”这个词我想了好久,额)时间,如果timeout.QuadPart是正数,就表示绝对响应时间,这里不说开去。这里我们用了timeout.QuadPart=-10,表示相对于系统当前时间的响应时间,也就是10微秒后响应。后面两个参数都是可选的。Period申明一个周期多少微秒,第四个Dpc么依然放到后面讲吧。 再上面一个函数就是安装我提到好几次的Dpc了------KeInitializeDpc(gDPCP,timerDPC,NULL) WDK文档里面说了等于没说,额。
首先谈谈DPC(Deffered Procedure Call)延迟过程调用。
当一段内核代码运行于一个被提升的IRQL上时,其他任何拥有相同或较低IRQL值的中断都不能执行的(在相同CUP上)。然而,如果在很高的IRQL上执行的代码太多,系统总体性能就会下降。时间关键的时间处理会被延迟、并导致更加灾难性的结果。
为了避免这些问题发生,就需要对内核模式代码进行设计,使其尽可能在最低级的IRQL上执行尽量多的代码。延迟过程调用就是这样一个重要策略。
DPC结构允许一个任务从一个高级的IRQL上被触发,但是不被执行。当服务硬件在一个驱动程序上中断时,这种延迟执行是必要的,因为在一个给定任务能被延迟的情况下,没有必要中断低级IRQL代码的执行。 然后是KeInitializeDpc(gDPCP,timerDPC,NULL)这边有3个参数,第一个就是个DPC,第二个是一个PKDEFERRED_ROUTINE结构,可以理解成被延迟的程序,这里就是timerDPC。这个PKDEFERRED_ROUTINE有4个参数……不讲开去了,本来好好的控制键盘LED,浅显易懂,往深了讲还是再开一帖的好。第三个参数为NULL 另外在写这篇文章的时候,偶然查到了liudanking的【讨论】简单的键盘sniff,为何无法正常工作? http://bbs.pediy.com/showthread.php?t=113648 恩,应该看的和我是同一本书吧,哈,那是个sniff键盘的程序,的确运行不起来,不过当时没在意~~这个问题值得思考,我也再想想吧
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: