搞出来很久了,看到看雪上不少同学都很期待,干脆发出来。

本文大部分研究成果归功于mentaldease,欲读此文,请遵守以下约定:
1. 绝不可利用本文所揭示的技术编写或者辅助编写盗号木马,危害中国网民。
2. 也不可将本文所揭示的技术直接或间接传授给写盗号木马的败类。
3. 违背以上两点约定者,天打雷劈,断子绝孙...(以下省略2000字)。

**********************************************************

   不能遵守以上三点约定者,请立即离开!

**********************************************************
在分析QQ之前,尝试直接用低级键盘钩子截获密码。
笔者发现可以获取正确的QQ号,但是截获的QQ密码里面却有很多虚假按键:

事实上,只要将鼠标焦点放在密码输入框里,QQ就自动产生虚假按键。所以,靠低级键盘钩子不能截获正确的QQ密码。

用XueTr查看系统中的消息钩子。笔者发现只要用户双击打开QQ2009登录窗口,QQ就会创建一个WH_KEYBOARD_LL低级键盘钩子,如图2

我们知道WH_KEYBOARD_LL钩子的优先级高于WH_KEYBOARD和WH_GETMESSAGE钩子。显然QQ2009的低级键盘钩子会比其他类型的钩子先执行,QQ获取用户的真实按键后再伪造虚假按键。

笔者在成功截获QQ2009密码之后,挂钩了SetWindowHookEx观察QQ创建WH_KEYBOARD_LL钩子的过程:


由上图可见QQ2009每隔不到0.2秒就创建一个低级键盘钩子,这样确保自己的钩子始终在钩子队列的最顶层。

我们对SetWindowHookEx函数下断,但是断不下来,显然是QQ在这里有反调试。删除刚才的断点,下一个硬件断点,程序断下,执行到返回:

来到042437C5处,tssafeed模块中看到如下指令:
042437BB    56              push    esi
042437BC    50              push    eax
042437BD    FF75 0C         push    dword ptr [ebp+C]
042437C0    E8 AB550000     call    04248D70                         ; jmp 到 MSVCRT.memcmp
042437C5    8B3D 28A02404   mov     edi, dword ptr [424A028]         ; KERNEL32.GetCurrentProcess
042437CB    83C4 0C         add     esp, 0C
042437CE    85C0            test    eax, eax
042437D0    0F84 8F000000   je      04243865

猜测这里很可能就是用memcmp 函数校验SetWindowsHookExA(可能还有其他函数)的代码。在042437BB 处F2,再F9让程序运行,把鼠标焦点移到密码输入框时,断下来了:

单步执行到042437C0,看堆栈信息:

果然是对SetWindowsHookExA初的内存进行校验。memcmp返回0则认为该处内存没有改变。

在77D311D1处下硬件执行断点,成功断下后,堆栈信息如图:


HookType为13,即是WH_KEYBOARD_LL钩子,其HookProc为 04260F1B,转到该地址看看QQ的钩子回调例程:
04260F1B    55              push    ebp
04260F1C    8BEC            mov     ebp, esp
04260F1E    83EC 30         sub     esp, 30
04260F21    56              push    esi
04260F22    57              push    edi
04260F23    C745 FC F00C260>mov     dword ptr [ebp-4], 4260CF0
04260F2A    8D7D D0         lea     edi, dword ptr [ebp-30]
04260F2D    B9 07000000     mov     ecx, 7
04260F32    33C0            xor     eax, eax
04260F34    FC              cld
04260F35    F3:AB           rep     stos dword ptr es:[edi]
04260F37    8B45 10         mov     eax, dword ptr [ebp+10]
04260F3A    8945 F8         mov     dword ptr [ebp-8], eax
04260F3D    C745 F4 0000000>mov     dword ptr [ebp-C], 0
04260F44    E8 20000000     call    04260F69                       ; 跟进
… …

04260F69 处的call:
04260F69    5F              pop     edi
04260F6A    8B4D F4         mov     ecx, dword ptr [ebp-C]
04260F6D    833C8F 00       cmp     dword ptr [edi+ecx*4], 0
04260F71    74 1D           je      short 04260F90
04260F73    8B55 F4         mov     edx, dword ptr [ebp-C]
04260F76    8B0497          mov     eax, dword ptr [edi+edx*4]
04260F79    50              push    eax
04260F7A    8B4D FC         mov     ecx, dword ptr [ebp-4]
04260F7D    FF51 50         call    dword ptr [ecx+50]             ; USER32.GetAsyncKeyState
04260F80    0FBFD0          movsx   edx, ax
04260F83    81E2 00800000   and     edx, 8000
04260F89    75 05           jnz     short 04260F90
04260F8B    FF45 F4         inc     dword ptr [ebp-C]
04260F8E  ^ EB DA           jmp     short 04260F6A
04260F90    8B55 F8         mov     edx, dword ptr [ebp-8]
04260F93    8B02            mov     eax, dword ptr [edx]
04260F95    C745 F0 0000000>mov     dword ptr [ebp-10], 0
04260F9C    E8 3C000000     call    04260FDD          ;跟进
… …

这个函数中有很多花指令,这里就不仔细分析了,对GetForegroundWindow下断,只要将鼠标焦点移到QQ登录窗口,就会断下,再对CallNextHookEx下断观察。它的大概功能猜也猜得出来:QQ自己获取按键之后,发送虚假按键,然后直接返回。现在就是找出QQ2009到底是用什么函数发送虚假按键。对keybd_event下硬件执行断点,没有断下,对sendInput下硬件断点,断下了:

Sendinput函数原型:
UINT SendInput(
  UINT nInputs,     // count of input events
  LPINPUT pInputs,  // array of input events
  int cbSize        // size of structure
);
typedef struct tagINPUT {
  DWORD   type;
  union {
      MOUSEINPUT      mi;
      KEYBDINPUT      ki;
      HARDWAREINPUT   hi;
  };
} INPUT, *PINPUT;
观察堆栈:

数据窗口跟随到0012FA94:

如图所示 01表示的是输入类型INPUT_KEYBOARD,pInouts的结构如下:
typedef struct tagKEYBDINPUT {
  WORD      wVk;
  WORD      wScan;
  DWORD     dwFlags;
  DWORD     time;
  ULONG_PTR dwExtraInfo;
} KEYBDINPUT, *PKEYBDINPUT;
如图所示0x38对应的虚拟按键是数字8,用我们刚才写的那个低级键盘钩子可以看到,所接收到的虚假按键正是8.

现在我们能够写出QQ的低级键盘钩子处理例程:
HWND hwnd = GetForegroundWindow();
If( hWnd == hPassEditWnd)
{
  MapVirtualKey();
  SendInput();
  return  1;
}
else
{
  return  CallNextHookEx(ncode,…);
}
现在2009登录窗口保护的原理已经分析清楚,获取QQ密码的方法也就有了。其关键就是绕过QQ对SetWindowsHookExA等函数的内存校验,然后hook SetWindowsHookExA,判断如果是QQ的WH_KEYBOARD_LL钩子,就替换其回调函数,然后在my_KeyboardllProc中截获密码,下图是截获密码的效果图:

PS:QQ2009的软键盘也被我和mentaldease兄突破,并成功截获密码: