随笔 - 8  文章 - 26  trackbacks - 0
<2009年12月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

常用链接

留言簿(4)

随笔档案

文章分类

文章档案

相册

C++语言

搜索

  •  

最新评论

阅读排行榜

评论排行榜

前两天在学校做的一个小设计的论文,随手发上来,小弟不才,错误之处还请各位大牛多多指点
如果大家还有什么键盘记录的方法和防御的方法一定一定要告诉小弟我啊。

 /*****************************************************************************************/

键盘作为计算机的主要输入设备,是大部分输入信息的主要来源,但是我们每天从键盘输入的信息安全吗?随着互联网的普及,各种网络应用也是层出不穷,电子购物,网上聊天等等等等,每天我们都会在各种程序上输入各式各样得分密码啊用户名啊银行卡号啊,你认为这些秘密安全吗?那位同学要说了:没事啊,不都是有保护措施吗,像什么QQ的号称无懈可击的nprotect技术,网银也有各种安全插件,没事的!” “呵呵,真的像他们说的那摩安全吗,那可不一定,没有什么是绝对的,下面我们就来看看键盘的秘密,看看黑客们是如何记录你的键盘操作的,以及我们该如何抵御这些猥琐的攻击方式。

 

.键盘的硬件模型

其实键盘应该算是计算机中最简单的设备了,在我们使用的普通的计算机系统中,与键盘有关的硬件说白了也就是两个芯片,i8048i8042 i8042也就是intel8042,位于主板上,CPU通过IO端口与i8042通信,i8042负责读取键盘按键的扫描吗或是发送个中键盘命令.i8048,它是位于键盘中的,是将键盘上的按键转换成所谓的扫描码的,然后传送给i8042。呵呵,就是这末简单个东西。上面说的都是比较老的计算机的结构了,现在这些芯片都集成到南桥芯片组里面去了,不过原理还是这样的。

当我们按下一个键与抬起的时候都会触发键盘的中断,在老早的计算机中都是采用两片i8259A芯片级联来管理中断的,键盘挂在主片的IRQ1引脚上,当有按键按下或抬起是会引发硬件中断,然后会调用相应的中断处理程序进行处理.

在实模式与保护模式下对于中断的处理是不同的。在实模式下我就不说了,也记不的了,自己看看微机原理。在保护模式下是采用IDT对中断进行管理的,在IDT中是各种各样的门啊,啥中断门,陷阱门,调用门啊等等等等,对于键盘中断在XP下对应的是0X31号中断服务,但也有的XP对应的事0x93,原因我也不太明白,反正在我的系统上是0x31号。

好了既然都已经都调用中断处理程序了,那么在经过一系列复杂的处理最终我们就可以在应用层上舒舒服服的用WORD打字了。

好了,硬件这块就说到这,下面用到了相关的我们在说。

 

.猥琐的键盘记录器

对于那些盗号的所谓的黑客我是很不齿的,这里我们要探讨一些窃取键盘信息的方法并不是围了写个键盘记录的盗号程序,我们只是站在攻防对立统一的角度来看各式盗号手段,并给出相关的防御措施.

我知道的一些盗号的手段也不多,而且都是一些比较普遍的手法,有些也都是爷爷辈的技术了,不过现在还是很好用,好多盗号的还是用这些土枪土炮打打打打劫

WINDOWS系统是分为应用层与内核层的,从CPU的角度看就是RING3RING0。应用层是受管制的,不可进行端口IO,不可执行特权指令,限制多多。内核就不说了,想干嘛就干吧。

 

我也是按照应用层与内核两个层面进行讨论的。好了废话太多了,进入正题吧.

 

1.爷爷辈的WM_GETTEXT消息获取密码

 

用过MFC的都知道密码框吧,就那个******的框子,他其实是个文本框只不过是加了密码属性而已,本质上还是文本框。对于文本框我们就可以通过对其发送WM_GETTEXT消息来获取密码的。不过还有点问题,在WIN98系列中这样就OK了,但是NT后,你要就是对着密码框大喊WM_GETTEXT是没用的,密码框会说:“你又不是我们家里的,我凭啥把密码告你啊“.其实这是跟操作系统有关的,在WIN98下所用的进程是共享一个4GB的虚拟内存的,那个就没什么你的我的了,所有的都是大家共有的,所以一个进程对另一个进程发送一个WM_GETEXT消息,应为大家都是自己人所以密码就告你了。但是到了NT后各个进程就闹分家了,每个进程独享4GB的虚拟内存,各个进程之间是互相隔离的,所以就没人理你了。

我们要采用些特殊的手段才能成功。也就是要把你的那段发送WM_GETTEXT消息的代码移到目标进程中去执行,方法还是有的,我使用的远程线程技术,也就是将一个功能模块诸如到目标进程中然后执行,这样就OK了。对于如何进行线程注入,方法很多,google一下就可以了。

原理就是这样很简单,问题就在怎样在目标进程中去执行代码,这种方法就说到这把。

 

 

2.屠夫的钩子

 

呵呵,玩过dota吗,对就是屠夫用的钩子,在windows中同样有钩子,而且也是相当的犀利。

使用钩子相当的简单,就一个API函数 SetWindowsHookEx,不过内涵很丰富,在windows下存在各式各样的钩子,消息钩子,鼠标钩子,键盘钩子,日志钩子,等等,具体的看看MSDN,这些钩子各有各的用途,对于黑客们来说主要会用到消息钩子,键盘钩子与日志钩子,这些钩子都可以用来监控键盘,下面分别来说.

 

(1)往日黄花键盘钩子

不要迷恋哥,哥只是个传说。

                                         ------一脑残儿童说

键盘钩子在当年可是相当的辉煌,在那个Rootkit还不是很盛行的年代,各种盗号软件

几乎总是和他联系在一起的,只不过这几年由于所谓的主动防御杀软的出现,这种技术才慢慢的消失.

键盘钩子分为全局钩子与局部钩子。键盘钩子安装之后可以截获所进程的键盘信息。局部钩子只可以截获安装线程的键盘信息。既然要盗号吗,当然是IC卡,IQ卡统统告诉我密码,要大面积撒网,就要安装全局钩子。

键盘钩子也分为两种:普通的键盘钩子与低级键盘钩子。对于这两种钩子的区别我自己在编程中总结的是:低级键盘钩子可以截获一些系统按键,比如Windows健,但是普通的就不行了。我曾经写了个玩Dota时屏蔽Windows健的用的是低级钩子,普通的不行.如果只是拦截个一般的按键两种钩子无所谓了.

具体的编程很简单:调用SetWindwosHookEx函数,参数就填入 WH_KEYBORAD(普通钩子)或者WH_KEYBOARD_LL(低级钩子),然后写个钩子的回调函数,在回调函数里面就可以获取按键的虚拟键码了,在讲虚拟键码经过处理就得到我们想要的了.

再提一点就是关于SHIFT键状态与CapsNum状态的检测,只要调用GetKeyState函数就可以了,具体的不说了,自己看MSDN吧。

 

 

2)竹林蹊径日志钩子

 

日志钩子是用来拦截输入到系统消息队列中的输入消息的钩子,键盘消息既然属于输入消息,那就勾住吧。

用法也是so easy,调用SetWindwosHookEx函数传递WH_JOURNALRECORD参数给他,在他的回调函数里面有个指向EVENTMSG的指针,结构如下:

typedef struct {

    UINT message;

    UINT paramL;

    UINT paramH;

    DWORD time;

    HWND hwnd;

} EVENTMSG, *PEVENTMSG;

我们只拦截message ==WM_KEYDOWN的消息,就是按键按下的消息啦,然后paramL&0x000000FF的值就是虚拟键码,剩下的和键盘钩子就一样了,不说了,下一节吧。

 

3)完美的世界---中英文记录的消息钩子

 

到目前为止我们记录到得都只是些 abc123这些的字母数字,那位小朋友要说了,我要知道他在网上的聊天内容,行吗?消息钩子就站出来了,”no problem,记录中文俺拿手啊

消息钩子,见名知意,肯定是用来过滤消息的。我们先来了解一个概念”IME””.

 IME 是输入法编辑器(Input Method Editor) 的英文缩写(IME),它是一种专门的应用程序,用来输入代表东亚地区书面语言文字的不同字符。

说白了,我们平时输入汉字时其实都是跟这个IME打交道的。IME也是会发出很多的消息的,如

WM_IME_CHAR                    WM_IME_COMPOSITION

WM_IME_COMPOSITIONFULL       WM_IME_CONTROL

WM_IME_ENDCOMPOSITION        WM_IME_KEYDOWN

WM_IME_KEYUP                   WM_IME_NOTIFY

WM_IME_REQUEST                 WM_IME_SELECT

WM_IME_SETCONTEXT             WM_IME_STARTCOMPOSITION

 

我们现在主要关心一个消息WM_IME_COMPOSITION,就是当要拼出一个字的时候会发出这个消息.,并且副参数为GCS_RESULTSTR的时候,就说明输入完了,可以将拼出的句子读出来了,这就得到了汉字了.,下面为参考代码:

 

/* this code from ZWELL 获得输入法处理后的字符串 */

if(pmsg->message==WM_IME_COMPOSITION){

               DWORD dwSize;

               char lpstr[128];

               if(pmsg->lParam & GCS_RESULTSTR){

                      //先获取当前正在输入的窗口的输入法句柄

                      hIMC = ImmGetContext(hWnd);

                      if(!hIMC) return 0;

                      // 先将ImmGetCompositionString的获取长度设为0来获取字符串大小.

                      dwSize = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);

                      // 缓冲区大小要加上字符串的NULL结束符大小,

                      // 考虑到UNICODE

                      dwSize += sizeof(WCHAR);

                      memset(lpstr, 0, sizeof(lpstr));

                      // 再调用一次.ImmGetCompositionString获取字符串

                      ImmGetCompositionString(hIMC, GCS_RESULTSTR, lpstr, dwSize);

                      //现在lpstr里面即是输入的汉字了。你可以处理lpstr,当然也可以保存为文件...

                      //MessageBox(NULL, lpstr, lpstr, MB_OK);

 

 

其实在输入汉字的时候也是会发出WM_CHARWM_KEYDOWN这些消息的,只不过WM_CHAR的参数与输入英文是是不同的。汉字的输入实际上是两个WM_CHAR,用内码就可以判断是否输入的是否是汉字字符。如果是,汉字两个字节的最高位都是1,连续两次判断就可以做到。即每次的CHAR字符的最高位是否是1,如果是,记住这个字符,然后当下CHAR字符来到是,如果最高位还是1,就可以将这两个字符合成汉字。这样就可以记录一个汉字了。

至于WM_KEYDOWN可以用来记录非ASCII的按键,像F1—F12TABENTER等等。

这样就是中英文完美的键盘记录了.

 

 

(4)用泥巴胡个墙吧.---应用层的抗击

 

应用层键盘记录的小伎俩就说这几种吧(呵呵,俺也就会这几种),既然要攻防统一,那我们就来谈谈如何来防御吧。在应用层防御个人感觉很是鸡肋的,实现也很是麻烦,效果也不好,不过还是说说吧,

首先说说全局键盘钩子吧,全局键盘钩子是不能独立存在的,他必须附加一个动态链接库文件,因为全局钩子是要监控所有的进程的,所以这个模块就要注入到其他的进程的地址空间中去,所以要写一个单独的模块。

我们知道在Windows下加载一个模块时使用的APILoadLibrary函数,这个函数内部又会调用LoadLibraryEx函数,windows底层是UINCODE的,所以应该调用的是LoadLibraryExW。如果我们写的正常程序,如果调用了LoadLibrary那摩LoadLibraryExW函数的返回地址应该位于Kernel32.dll中,或者我们就是直接调用了LoadLibraryExW那摩返回地址应该位于我们的程序中。但是如果是被装了钩子后,当你按下一个健后,系统会下按键焦点程序的地址空间中加载黑客写的键盘记录模块,调用的是LoadLibraryExW,那摩这个函数的返回地址就不是以上的两种情况了,经我是实验是位于user32.dll中。哈哈,根据这一点我们就可以判断一个模块是否为非法加载模块了.

原理就是这样啦。具体的实现要用到APIHook技术了。在《Windwos核心编程》中有简绍的。可以HOOKIAT也可以InLineHook,我用的是InLineHokk,在自己写的HOOk函数中首先获取[ESP]的值,这个就是返回值了,具体为什么应该都明白吧,不明白就找本汇编书好好补补吧。那摩就拿这个返回值去比较就可以了。简单吧。。

当然对于HOOKAPI你也可以用微软的那个Hook库,那就更简单了。

 

只能检测是不够的,太被动了,我们应该主动出击。

Windows下的钩子逻辑上是一个链状的,一个系统中可以安装很多的钩子,这些钩子会形成一个钩子链,先装的钩子在最前头,前面的钩子通过调用CallNextHookEx函数将信息传给后面的钩子,如不不调用这个函数那摩链子就断了,后面的钩子永远互惠获取信息。

好了,聪明的你一定想到了。对,我们不往下传递信息,我们自己处理,让下面的钩子瞪着眼着急去吧。

具体做法为:我们在我们的程序中装上局部钩子,在局部钩子的回调函数中我们截获按键消息,我们自己存起来,然后再给密码框发个假消息,比如按下了A健,我们用我们的局部钩子截获了A健消息,我们保存起来,然后我们给密码框发个假消息,,就说我们接受到了个B健,然后让不调用那个CallNextHookEx函数,而是直接返回1,这样下面的钩子就game over .

 

好了,应用层的键盘监控与反监控就说这些吧,由于杀毒软件的发展,特别是所谓的牛X主动防御的出现,这些都已进了历史的垃圾堆了,现在是RootKit的时代,打劫也要讲与时俱进,下面我们就来看看ring0下的键盘记录的手段吧。

 

3.新的战场,新的战斗—rootkit的疯狂

 

人都是属驴的,不打不逼是不会走的.

                                 ----------俺的一位老师说的

ring3下猥琐黑客们的安逸被被杀软打破了,生存还是死亡。当然是要生存下去了,怎没办?”TMD,反了,我们要和杀软对着干。游戏规则被改变了。。黑客软件不在是只做老鼠被杀软这只大猫到处撵这跑,老鼠要吃猫啦。

 

4.中规中矩----键盘过滤驱动

 

既然到了内核的领地,那我们就来看看在内核中是如何处理按键消息的,我们从按下一个健到我们在WORD看到这个字母,究竟发生了什么,下面是网上说的:

 

/*引用自: http://hi.baidu.com/buzztiger/blog/item/a851712b3739c924d52af170.html */

 

写过windows程序的人都知道,win32程序是基于消息驱动的,其中就有键盘消息,这个消息其实是csrss.exe这个进程发送给应用程序的,而在应用程序中我们可以使用setWindowsHook的方法来获得键盘消息,从而实现改键啊,捕捉用户按键内容。那么csrss.exe这个进程的键盘消息是怎么来的呢?原来csrss.exe中有个win32!RawInputThread这个线程,这个线程通过一个GUID,即GUID_CLASS_KEYBOARDDEFINE_GUID(GUID_CLASS_KEYBOARD, 0x884b96c3, 0x56ef, 0x11d1, 0xbc, 0x8c, 0x00, 0xa0, 0xc9, 0x14, 0x05, 0xdd)来获得键盘设备栈中PDO的符号链接名。win32!RawInputThread执行到win32k!openDevice,调用zwCreateFile打开设备,然后调用zwReadFile与键盘驱动通信了。它会创建一个IRP_MJREADIRP发送给键盘驱动,而键盘驱动通常使这个IRP Pending,这样它就会一直被放在那里等待,等来来自键盘的数据,即win32!RawInputThread这个线程也会一直等待,等待这个读操作的完成。当键盘有键按下时这个IRP将会完成,win32!RawInputThread将对得到的数据进行处理,分发给合适的进程(通常是获得焦点的进程)这时win32!RawInputThread又会立即再调用nt!ZwReadFile要求读入数据,又开始了下一个等待,周而复始

 

/*引用结束*/

 

键盘的驱动栈从上到下依次为:kbdclass.sys---ài8042port.sys---àacpi.sys

其中kbdclass.sys为键盘的类驱动,不管是PS/2键盘还是USB键盘都要通过这一层驱动,所以在这一层进行过滤可以有和好的兼容性。

I8042port.sysPS/2键盘的端口驱动,这个只对PS/2键盘好用,USB键盘他管不了的。

对于键盘的过滤驱动,我选择是在kbdclass.sys进行过滤.

具体做法:

1.使用ObReferenceObjectByName获取”\\Driver\\Kbdclass”所对应的驱动对象。

2.枚举这个驱动对象下的所有设备,并创建一个过滤设备附加上去.

3.主要处理IRP_MJ_READ这个IRP。首先设置一个完成函数,然后向下转发此IRP

4.在完成函数中就可以获取此次的案件的扫描码了。

5.对于IRP_MJ_POWER, IRP_MJ_PNP也要进行处理.

 

呵呵,很简单吧,驱动入门级的Hello Wolrd

 

5.乾坤大挪移----HOOKIDT与操纵APIC

 

在前面我们讲到在按下一个键与抬起的时候会触发一个硬件中断,XP下,操作系统回去调用 0x310x93中断处理程序区处理。那么我们可不可以自己写一个ISR去接管键盘中断呢,?当然,别忘了,我们是在内核中现在,我们无所不能

我们知道在保护模式下是采用IDT进行中断管理的,IDT是有许多门组成的。每个闷得结构如下:

typedef struct IDTEntry

{

HB_U16 LowOffset ;//偏移的低16

HB_U16 Selector ;//选择子

HB_U8 Count:5; //参数的双字计数

HB_U8 Reserve:3 ;//保留为0

HB_U8 Type:4 ;//类型

HB_U8 DT0:1; //DT=0,系统段描述符

HB_U8 DPL:2; //DLP

HB_U8 P:1; //P

HB_U16 HightOffset;//偏移的高16

}IDTEntry,*PIDTEntry;

其中LowoffsetHighOffset就构成了实际的中断处理程序的地址.。我们如果自己写一个中断处理程序,然后修改LowoffsetHighOffset,让其指向我们自己的写的那个函数不久可以了吗..

在我们自己的键盘中断处理函数中我们可以直接将数据从i8042的端口读出,存储起来,然后再调用原先的系统默认的函数,这样就神不知鬼不觉的达到的不可告人的目的.

 

还有一种方法使用的手段于此相似,也是替换,不过这次是将键盘的IRQ1中断的处理函数的中断向量更改,不在是指向0x31或是0x93了,而是指向另一个向量.这个向量包含我们自己的处理程序.,这就是APIC机制.。不过我的这个破笔记本比较老了,没有这个机制,

所以只能纸上谈兵了.,还是先简绍一下APIC.

APIC是可以用于多个核心的CPU的新型中断控制器,APIC的作用相当于当一个IRQ发生时,这个硬件决定将IRQ发个呢个CPU核心,以及一何种形式发送等。APIC是可编程的,也可以将PS/2键盘的硬件中断请求发给某个CPU核心,让该核心的IDT中的某个中断号对应的中断服务程序来处理.

WindowsAPIC的系列寄存器映射到了地址0xFEC000000xFEC00010的位置。

也就是说我们可以通过编程来进行中断的重定位,具体操作看着APIC的说明添添数据就可以了,其实和HOOKIDT一样的。就不多说了.

 

 

6.返璞归真轮询i8042

 

有时候其实一个问题的解决方法并不是月复杂就越强悍,有时候简单的土的掉渣的技术反而是最稳固的

轮询,这种古老的技术,虽然由于效率低下已经早已被淘汰,但是我们需要的正是这个。

键盘是可以通过编程关闭中断的,但是当我们按下一个健的时候,键盘的输出缓冲区中仍然会有扫描码填充,只是中断关闭了,操作系统并不知道。

我们的做法是:首先关闭键盘的中断,然后通过轮询的方法读取输出端口的案件扫描码,自己进行一些处理,然后打开键盘中断,再将此按键重放,这样操作系统会获取这个按键,然后在关闭中断,一直这样循环下去。

 

原理就是这样,简单也很可靠,我最终的密码保护就是采用这个方案的。

 

我的程序分了三个层次:

因为在Ring3下无法读写端口的,所以自己写了个驱动,负责读写端口,RING3上载写个DLL通过DeviceIoControl与内核进行通信,传递端口地址与设置的值等信息,并对上面的应用程序提供简单的如READ_PORT(ULONG port),这样的接口。

 

在应用程序中,在要保护的密码框获取焦点时(处理WM_SETFOCUS消息),则关闭键盘中断进行轮询,在市区焦点时则打开键盘中断.在获取按键后你可以在程序中记录下来,再在密码框中填充个假的密码。

由于是不间断的轮询所以保证在第一时间获取扫描码,在上层的如过滤驱动,HOOKIDT

还有应用层得到钩子啥的,统统失效,实践证明还是很可靠的,只不过还不是很完善,目前只支持PS/2键盘,USB的还不行.

 

 

7.我们都OUT----硬件键盘记录器

 

日防夜防,家贼难防啊,要是硬件上做了手脚,那就这能55555555555……

看看这个猥琐的家伙吧高科技哦


 

 

好了,该结尾了,呵呵,现在你还信你的键盘吗?

posted on 2009-12-22 13:56 杨彬彬 阅读(4937) 评论(8)  编辑 收藏 引用

FeedBack:
# re: 安全密码框的设计 2009-12-22 16:04 zwp
土了吧。。。
应用层还有一个强大raw input可以获取键盘、鼠标、HID等设备的输入信息呢。这可是比上面的消息钩子还要底层一点的。  回复  更多评论
  
# re: 安全密码框的设计 2009-12-22 18:59 杨彬彬
谢啦这位大哥,我再看看@zwp
  回复  更多评论
  
# re: 安全密码框的设计 2009-12-22 21:52 tanchuhan
good,楼主写的很好。

挑点小错误:
其实这是跟操作系统有关的,在WIN98下所用的进程是共享一个4GB的虚拟内存的,那个就没什么你的我的了,所有的都是大家共有的,所以一个进程对另一个进程发送一个WM_GETEXT消息,应为大家都是自己人所以密码就告你了。但是到了NT后各个进程就闹分家了,每个进程独享4GB的虚拟内存,各个进程之间是互相隔离的,所以就没人理你了。
---------------
Win95/98和NT都是一样的进程有各自的4G寻址空间(其中前面的1G/2G被系统保留,用来装载系统DLL什么的)
WM_GETTEXT可以跨进程获取文本是微软为了向后(win3x)兼容而作出特别处理的。照常理WM_GETTEXT的lParam非本进程指针,直接写数据会崩溃。而微软检查到这条消息后特别处理,先在本进程new一段缓冲区,等WM_GETTEXT接收者写完后,再复制回发送者进程的那段缓冲区。然后返回结果。
win9x和NT密码框对WM_GETTEXT的不同反应是因为后者考虑了安全性,而特意禁止的。
所以这和虚拟内存是否共享无关。

  回复  更多评论
  
# re: 安全密码框的设计 2009-12-23 09:01 远古毛利人
写得太帅了,很不错的说  回复  更多评论
  
# re: 安全密码框的设计 2009-12-24 16:08 李佳
文章不错 目前主流的都是 过滤驱动 修改IDT 至于后面两种 确实对我来说比较有新意~不错 谢谢博主  回复  更多评论
  
# re: 安全密码框的设计 2009-12-24 16:35 Hero
很犀利  回复  更多评论
  
# re: 安全密码框的设计 2010-11-26 11:11 徐胖子
底层钩子和普通钩子的区别远不只此,他们的层次是完全不一样的。。
WH_KEYBOARD_LL比WH_KEYBOARD先触发,WH_KEYBOARD_LL 是由系统调用回调函数,而WH_KEYBOARD则是钩的进程来调用回调函数。这就意味着,WH_KEYBOARD要插入进程,如果是全局钩子,就会插入每一个进程,WH_KEYBOARD_LL不需要插入进程,所以据说根本不需要放在dll里面,放在exe里面也能运行。

raw input 也是一种常见的截获键盘信息的方法,但并不比低级钩子更底层。我试过,在低级钩子屏蔽后,raw input将无法获得键盘信息。

信息安全学生,从楼主此文中受益匪浅,呵呵 ,希望能进一步请教、讨论。
邮箱xu_pang@163.com希望看到后能联系。  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理