直接访问键盘控制芯片获取键盘记录
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
直接访问键盘控制芯片获取键盘记录
键盘是用户和机器之间主要的硬件接口,看看键盘上的那些键就可知道它有多么的复杂了。键盘是我们隐私的源泉,我们各种信息的传递都要通过它。作为用户表现信息的媒介,很多怀有恶意目的的人都想截取我们的键盘输入。目前有很多方法可以做到,比如全局钩子、GetKeyboardStatus()、驱动过滤钩子等,但是如果要截取像QQ这种Hook键盘中断处理程序的保护技术,这些方法都显得无能为力。为此,我将为大家介绍一种直接访问键盘控制芯片的方法,来实现对键盘输入数据的最底层的读取。
如果我们知道了硬件的输入输出地址,就可以直接对它们进行访问了。键盘上有一个称为键盘编码器的处理器(Intel 8048或兼容芯片)专门用来扫描收集所有按键按下和松开的状态数据(即扫描码)。当一个键被按下时,键盘发送的扫描码称为接通扫描码(Make code),或简称为接通码;当一个被按下的键放开时发送的扫描码被称为断开扫描码(Break code),或简称为断开码。
主机键盘控制器专门用来对接收到的键盘扫描码进行解码。因为每个按键的接通和断开码都是不同的,所以键盘控制器根据扫描码就可以确定用户在操作哪个键了。整个键盘上所有按键的接通和断开码就组成了键盘的一个扫描码集(Scan Code Set)。根据电脑的发展,目前已有三套扫描码集可供使用,它们分别是第一套扫描码集,原始XT键盘扫描码集,目前的键盘已经很少发送这类扫描码;第二套扫描码集,现代键盘预设使用的扫描码集,通常称为AT键盘扫描码集;第三套扫描码集,PS/2键盘扫描码集,原IBM推出PS/2微处理机时使用的扫描码集,已很少使用。
AT键盘预设发送的是第二套扫描码集。虽然如此,主机键盘控制器为了与PC/XT机的软体兼容起见,仍然会把所有接收到的第二套键盘扫描码转换成第一套扫描码,因此,我们从键盘控制器获取的通常为第一套扫描码集。
当一个键被按下时,我们可以从键盘控制器接收到一个XT键盘接通码。这个扫描码仅表示键盘上某个位置处的键被按下,但还没有对应到某个字符代码上,接通码通常都是一个位组宽度。例如,按下键“A”的接通码是30(0x1E)。当一个按下的键被松开时,从键盘控制器端口收到的就是一个断开码。对于XT键盘(即键盘控制器程序设计端口收到的扫描码),断开码是其接通码加上0x80。例如,上述“A”键的断开码就是“0x80 + 0x1E = 0x9E”。
表1列出了Scan Code Set 1中的所有扫描码,我们写程序时需要用到!
表1
剩下的问题就是我们如何获取键盘的扫描码,并且将它们翻译出来了。比如我们如何获取扫描码1E,并且能将它翻译为A键被按下。键盘控制芯片给我们使用的PC机提供了两个可访问的I/O端口,一个是0x60,一个是0x64,我们能够读取键盘缓冲区中的数据,也能发送控制命令。打开设备管理器,如图1所示,我们可以清晰地看到它们。0x60为数据端口,0x64 为命令端口。
图1
通常情况下,我们从0x60读取的数据就是键盘的扫描码,而从0x64读取的数据为键盘的状态字。状态字的各位含义如下:
Bit7: 从键盘获得的数据奇偶校验错误;
Bit6: 接收超时,置1;
Bit5: 发送超时,置1;
Bit4: 为1,键盘没有被禁止。为0,键盘被禁止;
Bit3: 为1,输入缓冲器中的内容为命令,为0,输入缓冲器中的内容为数据;
Bit2: 系统标志,加电启动置0,自检通过后置1;
Bit1: 输入缓冲器满置1,i8042 取走后置0;
Bit0: 输出缓冲器满置1,CPU读取后置0。
接下来我们再说一下端口的操作方法。Windows NT系统是不允许直接操作端口的,只有通过驱动程序才能实现,可供选择的驱动程序有免费开源的winio、windriver等。由于我的毕业设计涉及到视频采集卡驱动程序的设计,所以就以windirver为例来说明端口操作方法了。安装好windriver之后,在“\samples \basic_io\”目录下有一个basic_io.c,我们可以直接使用它提供的端口操作函数读写端口。注意修改IO_init()函数添加如下语句:(责任编辑:admin)
WD_LICENSE lic;
strcpy(lic.cLicense,你的licenseString);
WD_License(hWD, &lic);
这样,我们使用的windrier驱动就去掉了30天试用期限的限制了。它可以提供的函数如下,有了这些函数,我们就可以直接读写端口了。
BYTE IO_inp(DWORD dwIOAddr)
//从dwIOAddr读取一个字节
WORD IO_inpw(DWORD dwIOAddr)
//从dwIOAddr读取两个字节
DWORD IO_inpd(DWORD dwIOAddr)
//从dwIOAddr读取四个字节
void IO_outp(DWORD dwIOAddr, BYTE bData)
//将一个字节的bData写入dwIOAddr
void IO_outpw(DWORD dwIOAddr, WORD wData)
//将两个字节的bData写入dwIOAddr
void IO_outpd(DWORD dwIOAddr, DWORD dwData)
//将四个字节的bData写入dwIOAddr
为了方便大家测试,我给大家提供一个有趣的例子。通过读写I/O端口控制键盘上的Num Lock、Caps Lock、Scroll Lock这三个LED指示灯,向0x60端口发送控制命令就可以控制它们的亮灭了。控制LED的命令是0xED,我们可以在LED没亮之前(实验之前请先使LED 指示灯关掉)发送0xED命令到0x60端口,然后立即发送另一个BYTE指示设置哪个LED,第2个BYTE用最低的3个字节设置这三个指示灯。问题是这种直接的方法没有考虑键盘控制器是否准备好接收命令。通常情况下,我们必须等待芯片准备接收命令时再发送命令控制字。如果芯片没有准备好,将不会产生任何效果。另外,即使我们读取0x64端口的状态字,确定键盘处于准备接收命令状态,但是我们也不能保证随后发送控制命令的操作不会受到键盘本身驱动程序读写端口引起的干扰。因此,我们可以采用如下的测试程序。
void light()
{
BYTE status;
while(1)
{
status=IO_inp( 0x00000064);
printf("%x",status);
IO_outp( 0x00000060,0xed);
IO_outp( 0x00000060,0x07);
Sleep(100);
}
}
这样,键盘正确接收到我们控制命令的概率就比较大了。程序运行几十秒,就会发现键盘上的三个指示灯突然全部亮了起来。
罗嗦了这么多,大家对端口的操作应该有了基本了解了,下面我们开始分析计算机是如何处理我们的按键操作的。当我们按下或者释放某一按键时,键盘控制器将会在IRQ1号线上送出中断信号,8259A中断控制器将此中断信号与其它外部设备通过其余的IRQ线送来的中断信号进行判优、排队,最后将此信息送给CPU。CPU在一条指令运行结束后,会查询一下是否有中断信号送来,如果此时发现有中断信号送来,就会通过此中断信号的中断向量在中断描述符表中查询应当使用哪一个中断处理程序。当找到中断处理程序后,CPU将调用