“从驱动层改建按键:一步步带你实现输入黑科技”
01
提出问题
假定你对汇编语言只了解皮毛,且没有写过任何Windows驱动程序,同时又缺少编译工具的支持,但任务是要在没有事先打算的情况下,实时更改一个运行中的按键驱动程序uart转usb linux 驱动,而且改变鼠标的行为。面对这样的挑战,你会怎么应对呢?
在“CPU眼中python和C”中,我们介绍了WinDbg调试python应用程序的能力,这儿我们就可以用它来调试、并在线更改一下Windows的按键驱动,因而改变鼠标的行为。
02
打算开发环境
首先打算一下开发环境,这儿被调试设备,也就是目标设备,是一台Windows7虚拟机。启动虚拟机后,运行命令:msconfig;在boot标签上面,选择:advancedoptions,并勾选Debug,如图所示:
注意,这儿的调试端口是并口COM1,码率为:115200。最后确认一下,我们就可以暂时把虚拟机关闭了。
随即就是把被调试的虚拟机和用于调试的主机联接上去。这儿我们采用管线化的并口联接方法(也就是进程通讯)。具体操作是:在虚拟机的设置中,找到并口COM1的设置,选择管线,并填写管线名称,这儿我们输入:WinDbg;并拷贝下完整的管线路径,也就是:\.pipeWinDbg,如图所示:
接着就是设置主机了,用administrator模式打开WinDbg;点击KernelDebug,选择并口(COM)联接,并选中管线形式;之后在端口(port),输入刚刚拷贝的管线路径(\.pipeWinDbg)就好,如图所示:
一切顺利的话,点击OK按键后,WinDbg都会开始等待:被调试设备的联接恳求了。
03
代码调试
万事俱备,再度启动虚拟机,不出意外的话,WinDbg的状态,马上都会发生变化,此时WinDbg早已联接上了被调试的虚拟机了,如图所示:
虚拟机顺利步入Windows7的界面后,打开设备管理器,瞧瞧我们即将更改的按键驱动程序,点击driver和details,我们就可以找到这个驱动程序:i8042prt.sys文件,如图所示:
记住这个文件名(i8042prt.sys),我们马上就要用到了。须要注意的是linux内核,虚拟机模拟的是老旧的PS2插口的按键,如图所示:
所以,无论你用的是蓝牙鼠标还是USB鼠标,虚拟机看见的都是PS2按键。
再度打开WinDbg,点击暂停,可以让虚拟机顿时石化,之后在命令行中,输入命令:xi8042prt!*read*
其中i80428042prt就驱动程序的文件名,通过x命令,我们就可以测量出该驱动程序上面,所有包含read字样的函数插口了,如图所示:
很快,我们就发觉了一个可疑的函数:I8xReadPortUchar,依照相关文档,我们晓得这是拿来从PS2中读取鼠标数据的函数插口,假如我们能篡改读到的通配符,就可以间接改变鼠标的行为,这儿我们将尝试,把鼠标的所有输入都改成字符:a
须要说明一下的是:虽然Windows和Linux的驱动框架有很大的差别,而且数据源头都来自于硬件,我们控制了I/O信息,就相当于控制了操作系统的信息来源。
说干就干,通过命令:ui8042prt!I8xReadPortUcharlinux vps,我们就可以看见函数I8xReadPortUchar在显存中的样子,也就是函数的CPU指令。WinDbg还特别贴心的把这种指令翻译成了汇编语言,如图所示:
代码超乎预料的简单,只有3条指令。按照“CPU眼中的参数传递”,可以晓得:第一条指令是把函数的参数从寄存器cx(rcx的低8位)上面,读取到寄存器edx(rdx的低32位)上面。
这个参数(cx)的意义是PS2插口的端标语,按照文档,如图所示:
PS2有两个I/O端口。一个是0x64端口,拿来读取PS2设备的状态;一个是0x60端口,拿来读取PS2设备的数据,也包含PS2按键的通配符。
须要注意的是:不同于ARM的I/O统一编址,x86CPU须要通过专门I/O指令来读取I/O数据。也就是第二条指令:in
它就是从PS2的I/O端口中,把通配符或状态数据读入寄存器al(rax的低8位)上面。其实,如“CPU眼中的函数返回值”所说,寄存器al,也肩负着储存返回值的职责。
最后一条ret指令,作函数返回。所以,这个函数的功能非常简单,就是读取0x64端口或0x60端口上的数值,对应的C语言,大约是这个样子:
int I8xReadPortUchar(int* port)
{
return *port;//in al, dx
}
至于0x64端口上的设备状态信息,我们并不关心,这儿我们只关注从0x60端口,获得的通配符,我们须要把从x60端口读出的通配符,改成字符a的通配符:0x1E。逻辑特别简单,让我们马上动手,在线更改驱动程序吧。
输入这个命令:afffff880`03d0323c
这意味着我们将从这个ret指令所在的显存地址(fffff880`03d0323c)处输入汇编指令,该操作会覆盖ret及旁边的CPU指令,WinDbg则会手动帮我们把汇编指令转成机器码(CPU指令)。如图所示:
这儿,先判定寄存器dx是否是0x60端口,若果是的话,就跳转到旁边的代码,去更改通配符;更改通配符的代码,距离函数首地址的偏斜量是11(0xB)个字节,假如弄错了这个偏斜量也没有关系,我们还可以回过头来更改。
相反,假如不是0x60端口的话,我们就遵照老代码的处理方法:直接ret;此后,就是更改通配符的部份了。
通过mov指令,把拿来储存函数返回值的寄存器al(寄存器rax的低8位)设置为键盘a的通配符,也就是0x1E;最后通过ret指令,使函数正常返回。
这样随着函数的逐层返回,操作系统都会误以为读到的通配符是:a。代码写完,记得再按一下回车键,结束汇编指令的输入。
最后,还可以通过命令:ui8042prt!I8xReadPortUchar,检测一下刚才我们写的代码,如图所示:
其对应的C语言代码,大约是这个样子:
int I8xReadPortUchar(int* port)
{
int value = *port;//in al, dx
if(port == 0x60)
{
value = 0x1e;
}
return value;
}
好了,如今可以验证我们的工作成果了,输入命令go,让虚拟机继续运行。用键盘在虚拟机中打开一个新建的空文本文件,让我们在鼠标上输入:1,2,3
倘若一切顺利的话,Windows果然觉得我们输入的都是:a;因为我们的代码中没有分辨按键的按下,弹起,所以1次键盘,会形成2个a;这时,虽然我们随便按任何键盘,键盘的通配符都被驱动程序改写成了a的通配符。所以,文本文件中只有字符:a。如图所示:
最后,请不要担忧你的按键都会自此失灵了,由于我们只是更改了显存中的按键驱动程序,并没有更改驱动文件:i8042prt.sys,所以,只要我们重新启动虚拟机,Windows再度把原有的驱动程序从文件i8042prt.sys加载到显存中后,你的按键就可以重新恢复正常了。
04
总结
1.驱动程序和应用程序都是程序,都须要使用系统资源,如显存、CPU、存储空间等。许多情况下,驱动程序也是用C/C++语言编撰的,它们的运行原理和实现细节与普通应用程序十分相像,比如都须要函数堆栈的支持。
不仅少数特殊的CPU指令外(比如本文中拿来读取PS2端口的指令:in),许多CPU指令是通用的,既可以用于应用程序,也可以用于驱动程序。
2.驱动程序运行在内核态,拥有对系统和硬件的完整、全面的控制和访问权限。不同于应用程序有操作系统作兜底的错误处理,一个应用程序的错误一般不会影响其他程序或整个系统。但是,驱动程序常常缺少建立的错误处理机制,其错误可能造成系统死机或关机。
3.应用程序和驱动程序的分工不同。应用程序一般为用户提供直接的功能(如文档编辑、浏览网页),而驱动程序一般不与用户直接交互,而是为硬件设备提供必要的支持,便于那些设备才能被应用程序顺利使用。
驱动程序还在硬件与操作系统之间充当中介,帮助操作系统辨识和使用计算机硬件,如主板驱动、打印机驱动和网路适配器驱动等。没有适当的驱动程序,操作系统可能难以正常通讯和使用硬件设备uart转usb linux 驱动,因而造成应用程序没法正常使用这种设备。
总而言之,应用程序主要为用户提供操作和功能,而驱动程序为硬件提供操作系统访问的插口。应用程序一般是面向用户的,而驱动程序是面向系统和硬件的。虽然二者都是计算机软件,但因为功能和运行环境的不同,它们的开发和管理方法也有所差别。
05