百度一下 藏锋者 就能快速找到本站! 每日资讯归档 下载藏锋者到桌面一键访问

当前位置:主页 > 网络安全 > QQ登录窗口键盘保护原理编程分析

QQ登录窗口键盘保护原理编程分析

所在栏目:网络安全 时间:04-12 19:52 分享:

腾讯对QQ登录窗口的密码输入框采用键盘保护,防止盗号木马通过键盘输入记录QQ的密码,不记得这事是从哪个版本开始的了,大概是在2005年以后吧。那个时候腾讯使用的是大名鼎鼎的NP技术,从驱动级别上在内核里挂钩了键盘中断,着实阻止了不少盗号程序窃取QQ密码,但同时也给人们在心中留下了一个印象:要想记录QQ的密码很困难。

但是后来有些事情很奇怪。大概是从QQ2008开始的吧,腾讯放弃了NP技术,转而采用另外一种办法来进行键盘保护。究其原因,据说是因为NP组件在稳定性和兼容性上的不足。既然换了保护方案,那么保护强度还是不是像以前的NP那样高呢?估计很多人心里会惯性地认为:这种新的保护方法强度肯定也很高,要从键盘输入记录到QQ的密码难度很大。如果你这样想,那你就错了。在调试了一段时间之后,我得出的结论是:QQ2009目前使用的键盘保护方案保护强度很低。

下面说说我是怎么找到QQ的键盘保护原理的,其中有一些分析和观点可能是错误的,如果有朋友看到的话还请指出。前几个月,我拿着OD对QQ调试了很长一段时间。其间调试的思路是如何的,以及是如何一步一步挖掘到QQ的键盘保护原理的,现在我自己也记不清楚了,所以我就直接说说我调试出来的结果吧。

首先,我发现QQ是通过在登录窗口对自己的密码输入框安装一个钩子,来防止用户输入的密码被窃取。关于这一点,是有图为证的。启动QQ登录窗口,打开大家都很熟悉的工具冰刃,切换到“消息钩子”一栏,仔细观察就会发现其中有一条关于QQ的记录,如图1所示。可以看出QQ自己给自己安装上了一个钩子,而且大家注意这个钩子的类型,是一个我们平时很少见的钩子类型。这个WH_KEYBOADR_LL到底是什么,它是做什么用的呢?其实QQ登录窗口的键盘保护秘密就在这里啦,下面说说我的分析。

QQ登录窗口的键盘保护秘密
图1

WH_KEYBOADR_LL从名称上直译过来,是低级键盘钩子的意思。QQ为什么要给自己的密码输入框安装这个钩子呢?看一看在这个钩子的回调函数里QQ会做些什么就知道了。于是我在OD里面调试QQ,对NtUserSetWindowsHookEx函数调用下条件断点。我们先来看看NtUserSetWindowsHookEx的原型:
HHOOK NtUserSetWindowsHookEx(
IN HANDLE hmod,
IN PUNICODE_STRING pstrLib OPTIONAL,
IN DWORD idThread,
IN int nFilterType,
IN PROC pfnFilterProc,
IN BOOL bAnsi);

我们对nFilterType参数下条件断点,当它等于WH_KEYBOADR_LL的时候就断下。这样,很快就找到了QQ在安装这个钩子时调用NtUserSetWindowsHookEx函数的位置,这时的堆栈信息如图2所示。

堆栈信息
图2

图中用方框标记出来的位置,就是调用NtUserSetWindowsHookEx函数时传入的nFilterType参数,可以看到它的值是0xD,正好等于常数WH_KEYBOADR_LL的值。再找到pfnFilterProc参数,发现它的值是00A6F113,这就是WH_KEYBOADR_LL钩子回调函数的地址了。终于找到回调函数的地址了,转到这个地址上去查看这个钩子的回调函数。这个函数的反汇编代码如下,我在调试这个函数的时候遇到了不少花指令,这里列出的是将花指令去掉以后的代码。

00A6F113   55  push  ebp
00A6F114   8BEC  mov  ebp, esp
00A6F116   83EC 30  sub  esp, 30
00A6F119   56  push  esi
00A6F11A   57  push  edi
00A6F11B   C745 FC E8EEA60>mov  dword ptr [ebp-4], 0A6EEE8
00A6F122   8D7D D0  lea  edi, dword ptr [ebp-30]
00A6F125   B9 07000000  mov  ecx, 7
00A6F12A   33C0  xor  eax, eax
00A6F12C   FC  cld
00A6F12D   F3:AB   rep  stos dword ptr es:[edi]
00A6F12F   8B45 10  mov  eax, dword ptr [ebp+10]
00A6F132   8945 F8  mov  dword ptr [ebp-8], eax
00A6F135   C745 F4 0000000>mov  dword ptr [ebp-C], 0
00A6F13C   E8 20000000  call  00A6F161

00A6F161   5F  pop  edi
00A6F162   8B4D F4  mov  ecx, dword ptr [ebp-C]
00A6F165   833C8F 00  cmp  dword ptr [edi+ecx*4], 0
00A6F169   74 1D  je  short 00A6F188
00A6F16B   8B55 F4  mov  edx, dword ptr [ebp-C]
00A6F16E   8B0497  mov  eax, dword ptr [edi+edx*4]
00A6F171   50  push  eax
00A6F172   8B4D FC  mov  ecx, dword ptr [ebp-4]
00A6F175   FF51 50  call  dword ptr [ecx+50]
; user32.GetAsyncKeyState
00A6F178   0FBFD0   movsx  edx, ax
00A6F17B   81E2 00800000 and  edx, 8000
00A6F181   75 05  jnz  short 00A6F188
00A6F183   FF45 F4   inc  dword ptr [ebp-C]
00A6F186  ^ EB DA  jmp  short 00A6F162
00A6F188   8B55 F8   mov  edx, dword ptr [ebp-8]
00A6F18B   8B02  mov  eax, dword ptr [edx]
00A6F18D   C745 F0 0000000>mov  dword ptr [ebp-10], 0
00A6F194   E8 3C000000  call  00A6F1D5
00A6F1D5   5E  pop  esi
00A6F1D6   8B4D F0  mov  ecx, dword ptr [ebp-10]
00A6F1D9   833C8E 00  cmp  dword ptr [esi+ecx*4], 0
00A6F1DD   74 0A  je  short 00A6F1E9
00A6F1DF   3B048E  cmp  eax, dword ptr [esi+ecx*4]
00A6F1E2   74 05  je  short 00A6F1E9
00A6F1E4   FF45 F0  inc  dword ptr [ebp-10]
00A6F1E7  ^ EB ED  jmp  short 00A6F1D6
00A6F1E9   837D 08 00  cmp  dword ptr [ebp+8], 0
00A6F1ED   0F8C EF000000  jl  00A6F2E2
00A6F1F3   8B4D FC  mov  ecx, dword ptr [ebp-4]
00A6F1F6   8379 0C 00  cmp  dword ptr [ecx+C], 0
00A6F1FA   0F84 E2000000  je  00A6F2E2
00A6F200   817D 0C 0001000>cmp  dword ptr [ebp+C], 100
00A6F207   74 0D  je  short 00A6F216
00A6F209   817D 0C 0101000>cmp  dword ptr [ebp+C], 101
00A6F210   0F85 CC000000  jnz  00A6F2E2
00A6F216   8B4D F8   mov  ecx, dword ptr [ebp-8]
00A6F219   8379 10 21  cmp  dword ptr [ecx+10], 21
00A6F21D   0F84 BF000000  je  00A6F2E2
00A6F223   8B4D FC   mov  ecx, dword ptr [ebp-4]
00A6F226   8B51 24  mov  edx, dword ptr [ecx+24]
00A6F229   33C0  xor  eax, eax
00A6F22B   8A02  mov  al, byte ptr [edx]
00A6F22D   8B4D F8   mov  ecx, dword ptr [ebp-8]
00A6F230   3901  cmp  dword ptr [ecx], eax
00A6F232   0F82 AA000000  jb  00A6F2E2
00A6F238   8B55 FC   mov  edx, dword ptr [ebp-4]
00A6F23B   8B42 24   mov  eax, dword ptr [edx+24]
00A6F23E   33C9  xor  ecx, ecx
00A6F240   8A48 01   mov  cl, byte ptr [eax+1]
00A6F243   8B55 F8   mov  edx, dword ptr [ebp-8]
00A6F246   390A  cmp  dword ptr [edx], ecx
00A6F248   0F87 94000000  ja  00A6F2E2
00A6F24E   8B45 F4   mov  eax, dword ptr [ebp-C]
00A6F251   833C87 00  cmp  dword ptr [edi+eax*4], 0
00A6F255   0F85 87000000  jnz  00A6F2E2
00A6F25B   8B45 F0   mov  eax, dword ptr [ebp-10]
00A6F25E   833C86 00  cmp  dword ptr [esi+eax*4], 0
00A6F262   75 7E  jnz  short 00A6F2E2
00A6F264   8B55 FC   mov  edx, dword ptr [ebp-4]
00A6F267   FF52 54   call  dword ptr [edx+54]
 ; user32.GetForegroundWindow
00A6F26A   8B55 FC   mov  edx, dword ptr [ebp-4]
00A6F26D   3B42 1C   cmp  eax, dword ptr [edx+1C]
00A6F270   75 70  jnz  short 00A6F2E2
00A6F272   C745 D0 0100000>mov  dword ptr [ebp-30], 1
00A6F279   8B4D FC   mov  ecx, dword ptr [ebp-4]
00A6F27C   8B51 24   mov  edx, dword ptr [ecx+24]
00A6F27F   8BCA  mov  ecx, edx
00A6F281   33C0  xor  eax, eax
00A6F283   8A01  mov  al, byte ptr [ecx]
00A6F285   8B4D F8   mov  ecx, dword ptr [ebp-8]
00A6F288   8B09  mov  ecx, dword ptr [ecx]
00A6F28A   2BC8  sub  ecx, eax
00A6F28C   81E1 FF000000  and  ecx, 0FF
00A6F292   66:0FB65411 02  movzx  dx, byte ptr [ecx+edx+2]
00A6F298   66:8955 D4   mov  word ptr [ebp-2C], dx
00A6F29C   6A 00  push  0
00A6F29E   0FB745 D4  movzx  eax, word ptr [ebp-2C]
00A6F2A2   50  push  eax
00A6F2A3   8B55 FC  mov  edx, dword ptr [ebp-4]
00A6F2A6   FF52 48   call  dword ptr [edx+48]
; user32.MapVirtualKeyA
00A6F2A9   66:8945 D6  mov  word ptr [ebp-2A], ax
00A6F2AD   8B45 F8   mov  eax, dword ptr [ebp-8]
00A6F2B0   8B48 0C   mov  ecx, dword ptr [eax+C]
00A6F2B3   894D DC  mov  dword ptr [ebp-24], ecx
00A6F2B6   C745 E0 2100000>mov  dword ptr [ebp-20], 21
00A6F2BD   817D 0C 0101000>cmp  dword ptr [ebp+C], 101
00A6F2C4   75 07  jnz  short 00A6F2CD
00A6F2C6   C745 D8 0200000>mov  dword ptr [ebp-28], 2
00A6F2CD   6A 1C   push  1C
00A6F2CF   8D55 D0  lea  edx, dword ptr [ebp-30]
00A6F2D2   52  push  edx
00A6F2D3   6A 01  push  1
00A6F2D5   8B45 FC  mov  eax, dword ptr [ebp-4]
00A6F2D8   FF50 4C   call  dword ptr [eax+4C]
; user32.SendInput
00A6F2DB   B8 01000000  mov  eax, 1
00A6F2E0   EB 69  jmp  short 00A6F34B
00A6F2E2   8B55 F8  mov  edx, dword ptr [ebp-8]
00A6F2E5   8B02  mov  eax, dword ptr [edx]
00A6F2E7   C745 EC 0000000>mov  dword ptr [ebp-14], 0
00A6F2EE   E8 0C000000  call  00A6F2FF
00A6F2FF   5E  pop  esi
00A6F300   8B4D EC   mov  ecx, dword ptr [ebp-14]
00A6F303   833C8E 00  cmp  dword ptr [esi+ecx*4], 0
00A6F307   74 0A  je  short 00A6F313
00A6F309   3B048E   cmp  eax, dword ptr [esi+ecx*4]
00A6F30C   74 05  je  short 00A6F313
00A6F30E   FF45 EC  inc  dword ptr [ebp-14]
00A6F311  ^ EB ED  jmp  short 00A6F300
00A6F313   837D 08 00  cmp  dword ptr [ebp+8], 0
00A6F317   7C 19  jl  short 00A6F332
00A6F319   8B4D FC  mov  ecx, dword ptr [ebp-4]
00A6F31C   8379 0C 00  cmp  dword ptr [ecx+C], 0
00A6F320   74 10  je  short 00A6F332
00A6F322   8B55 EC  mov  edx, dword ptr [ebp-14]
00A6F325   833C96 00  cmp  dword ptr [esi+edx*4], 0
00A6F329   74 07  je  short 00A6F332
00A6F32B   B8 01000000  mov  eax, 1
00A6F330   EB 19  jmp  short 00A6F34B
00A6F332   8B4D 10   mov  ecx, dword ptr [ebp+10]
00A6F335   51  push  ecx
00A6F336   8B55 0C   mov  edx, dword ptr [ebp+C]
00A6F339   52  push  edx
00A6F33A   8B45 08   mov  eax, dword ptr [ebp+8]
00A6F33D   50  push  eax
00A6F33E   8B4D FC  mov  ecx, dword ptr [ebp-4]
00A6F341   8B51 14   mov  edx, dword ptr [ecx+14]
00A6F344   52  push  edx
00A6F345   8B45 FC   mov  eax, dword ptr [ebp-4]
00A6F348   FF50 38   call  dword ptr [eax+38]
; user32.CallNextHookEx
00A6F34B   5F  pop  edi
00A6F34C   5E  pop  esi
00A6F34D   8BE5  mov  esp, ebp
00A6F34F   5D  pop  ebp
00A6F350   C2 0C00   retn  0C

这样列出来可能看得不是很清楚,为了表达得更清晰,将以上的汇编代码表示成伪代码,大致上就是如下这个样子。

HWND hwnd = GetForegroundWindow();
if( hwnd == 密码输入框的句柄 )
{
MapVirtualKeyA();
SendInput();
return 1;
}
else
{
return CallNextHookEx(nCode, wParam, lParam);
}

可以看出,QQ首先通过GetForegroundWindow函数得到前景窗口的句柄,然后判断前景窗口是不是QQ的密码输入框,如果是密码输入框的话,QQ没有调用CallNextHookEx函数,而是直接“return 1”,这样其他钩子和密码框的窗口过程就收不到消息了,于是防止了盗号程序通过键盘钩子来记录QQ的密码;只有在不是密码框的时候,QQ才调用CallNextHookEx函数,把消息往下面传递下去,交给其他钩子和密码框的窗口过程继续处理。这就是QQ登录窗口目前使用的键盘保护原理了。

还有一个问题,QQ为什么要给自己的密码输入框安装这种类型的钩子,而不是别的类型呢?一开始我也没想明白,后来和ConTrail讨论了一番,终于想明白了。这种类型的钩子有个特点,在于它的优先级别比WH_KEYBOARD类型的普通键盘钩子要高。所以在按键消息产生时,首先得到处理权的是由QQ安装的WH_KEYBOARD_LL钩子,这样,QQ就能决定要不要把消息交给后面的钩子和密码框的窗口过程去处理了。

以上是我通过调试对QQ键盘保护的原理的一些认识,有实践的地方,也有自己的猜测,所以可能并不完全正确,不对的地方希望有兴趣的朋友给指出来。在深入挖掘QQ的键盘保护原理之前,我也阅读了很多文章,包括黑防以前刊登的一篇介绍NP保护技术的文章和一些网络上的分析文章。这些文章给了我很多很好的思路和借鉴,感谢这些作者们。

对于如何破解QQ的这种键盘保护,本来我想谈一谈自己的看法,不过担心被人利用这些方法来写盗号程序,散播到互联网上造成危害,所以略去不介绍了。最后希望给腾讯提一个建议,软件的安全防范不能做得这么简单,还是像以前一样加大强度比较好。

QQ登录窗口键盘保护原理编程分析 免费邮件订阅: 邮件订阅

图片推荐

热点排行榜

CopyRight? 2013 www.cangfengzhe.com All rights reserved