版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/WUDAIJUN/article/details/8478942
功能:探测指定窗口的窗口信息和所在进程的必要信息,如果该窗口包含密码域,则获取该密码。
分析分为界面,窗口操作,DLL三个部分
界面:
正常窗口透明窗口
- 控件颜色:
对话框的背景在相应WM_ERASEBKGND 的 OnEraseBkgnd中用指定背景位图通过PatBlt重绘。
而对应控件的背景色要做到和对话框背景色一致,则需要响应WM_CTLCOLOR消息。
WM_CTLCOLOR是窗口控件需要重绘时发送给父窗口的消息
HBRUSH CWndSpyDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
在该函数里面,我们可以通过 pDC->SetBkColor pDC->SetBkMode pDC->SetTextColor 以及返回的画刷句柄来进行比较简单的界面调整
SetBkColor用于改变控件文字显示区域(一个矩形),的背景色,而不是整个控件的背景色。
SetBkMode用于调整BkColor的映射模式,默认为OPAQUE 该模式在每次控件界面上文字绘制时,都会先将整个文字背景用当前文字背景色(由SetBkColor设置,如果未用SetBkColor设置, 默认为整个控件的背景色,这个背景色是创建子窗口时指定的,而不是OnCtlColor返回的画刷)刷一遍,然后再绘制文字。还有一种映射模式是TRANSPARENT,该映射模式使得文字在绘制之前不用文字背景重刷,而是使用非文字区域的背景色。这样使得文字周围没有边框。实现文字背景色透明的效果。
SetTextColor指定文字颜色
OnCtlColor返回的画刷值用于系统绘制控件的非文字区域
在上面的界面中,OnCtlColor代码如下:
HBRUSH CWndSpyDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
switch (nCtlColor)
{
case CTLCOLOR_EDIT:
//如果是密码显示框 突出文字为红色
if(pWnd->GetDlgCtrlID() == IDC_EDIT_PASSWORD)
pDC->SetTextColor(RGB(255,0,0));
else
pDC->SetTextColor(TOOL_TIP_COLOR);
pDC->SetBkMode(TRANSPARENT);
return (HBRUSH)m_grayBrush.GetSafeHandle();
case CTLCOLOR_STATIC:
//设置文字背景色透明
pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(TOOL_TIP_COLOR);
//返回透明画刷 使得静态控件非文字区域始终使用其父窗口指定的背景颜色
//由于也设定了TRANSPARENT文字背景属性,因此文字背景色也是父窗口背景色
return (HBRUSH)m_nullBrush.GetSafeHandle();
case CTLCOLOR_LISTBOX:
//如果ListBox的父窗口为ComboBox的父窗口
if (pWnd->GetParent()->GetDlgCtrlID() == m_comboTransparent.GetParent()->GetDlgCtrlID())
{
CRect rectCombo;
CRect rectListBox;
m_comboTransparent.GetWindowRect(&rectCombo);
pWnd->GetWindowRect(&rectListBox);
//并且该ListBox为指定的m_comboTransparent组合框弹出的
if(rectCombo.left == rectListBox.left && rectCombo.bottom == rectListBox.top)
{
//设定文字背景色和非文字区域背景色均用m_grayBrush
//并使用TOOL_TIP_COLOR 颜色字体
pDC->SetBkMode(TRANSPARENT);
pDC->SetTextColor(TOOL_TIP_COLOR);
return (HBRUSH)m_grayBrush.GetSafeHandle();
}
}
break;
default:
break;
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
- 透明窗口:
透明窗口的形成主要依赖于一种窗口样式: WS_EX_LAYERED(分层窗口).
分层窗口可以通过SetLayeredWindowAttributes设置窗口的半透明和局部透明。全透明的区域将不能接收鼠标消息,如果该窗口同时指定了WS_EX_TRANSPARENT 那么该窗口所有区域都收不到鼠标消息。
SetLayeredWindowAttributes函数使用方法:
BOOL SetLayeredWindowAttributes(
HWND hwnd, // 需要设置的窗口句柄 需要WS_EX_LAYERED窗口样式
COLORREF crKey, // 颜色键值 当dwFlags为LWA_COLORKEY时 有效 此时所有颜色值为crKey的地方将透明
BYTE bAlpha, //透明度(0-255),当dwFlags为LWA_ALPHA是有效
DWORD dwFlags // 执行操作,可以是LWA_COLORKEY,LWA_ALPHA 也可以是两者的混合,此时所有crKey颜色变透明并且其他 区域透明度为bAlpha
);
窗口操作:
钩子和动态连接库:
SetLayeredWindowAttributes存在于新版本的SDK中,如果需要在老版本VC6.0下使用。需要在user32.dll中找到该函数,再通过函数指针的方式引用它。方法如下:
OnIntialDialog中的代码:
//设置窗口样式为分层窗口
ModifyStyleEx(0, 0x80000);
//初始化透明窗口组合框
CString strTrans;
for (int i=1; i<=TRANSPARENCY_LEVEL; i++)
{
strTrans.Format("%d%%", i*100/TRANSPARENCY_LEVEL);
m_comboTransparent.InsertString(i-1, strTrans);
}
//初始化选择为100% 不透明
m_comboTransparent.SetCurSel(TRANSPARENCY_LEVEL-1);
//调用消息函数 更新界面 使初始化有效
OnSelchangeComboTransparent();
选择透明度改变时 回调函数
void CWndSpyDlg::OnSelchangeComboTransparent()
{
// TODO: Add your control notification handler code here
int index = m_comboTransparent.GetCurSel();
if (index == CB_ERR)
return;
//调用SetLayeredWindowAttributes为分层窗口设置透明度
//加载User32.lib
HMODULE hDll = ::LoadLibrary(_T("user32"));
typedef BOOL (WINAPI *PFUN)(HWND, COLORREF, BYTE, DWORD);
//得到所需函数地址
PFUN pFun = (PFUN)::GetProcAddress(hDll, "SetLayeredWindowAttributes");
//使用函数
if (!pFun)
{
MessageBox("SetLayeredWindowAttributes Failed!");
::FreeLibrary(hDll);
return;
}
else if(!pFun(this->GetSafeHwnd(), 0, (BYTE)(index+1)*255/TRANSPARENCY_LEVEL, 2))
MessageBox("SetLayeredWindowAttributes Failed!");
::FreeLibrary(hDll);
return;
}
- 提示窗口
这是通过CToolTipCtrl控件来完成的,使用步骤:
CToolTipCtrl m_tooltipctrl
void CWndSpyDlg::SetToolTips()
{
//创建
m_tooltipctrl.Create(this, TTS_ALWAYSTIP);
//设置属性
m_tooltipctrl.SetTipBkColor(TOOL_TIP_COLOR);
m_tooltipctrl.SetTipTextColor(TOOL_TIP_TEXT_COLOR);
m_tooltipctrl.SetDelayTime(1, 2000);
m_tooltipctrl.SetMaxTipWidth(200);
//添加提示窗口
m_tooltipctrl.AddTool(GetDlgItem(IDC_COMBO_TRANSPARENT), _T("The transparency of dialog"));
m_tooltipctrl.AddTool(GetDlgItem(IDC_EDIT_CONTROL), _T("The ID of control which you detecting. In decimal"));
m_tooltipctrl.AddTool(GetDlgItem(IDC_CHECK_MORE), _T("Whether show more information of the detected window"));
//......
//给静态控件添加提示
CRect rectLook;
((CStatic*)GetDlgItem(IDC_LOOK))->GetWindowRect(&rectLook);
ScreenToClient(&rectLook);
m_tooltipctrl.AddTool(this,_T("Detect icon, Drag the icon to window you wana detect."), &rectLook, IDC_LOOK);
CRect rectStayTop;
((CStatic*)GetDlgItem(IDC_STAYTOP))->GetWindowRect(&rectStayTop);
ScreenToClient(&rectStayTop);
m_tooltipctrl.AddTool(this, _T("Let window top most or not"), &rectStayTop, IDC_STAYTOP);
}
窗口操作:
当拖动放大镜到目标窗口时,需要得到当前鼠标位置所在的窗口句柄。Win32 API 有一个函数
HWND WindowFromPoint(POINT point) 通过它可以得到当前鼠标所在位置的窗口。但是有时候鼠标所在位置上可能有多个窗口,这时候WindowFromPoint返回Z-Order最前面的窗口,这可能不是我们想要的,我们希望能够得到当前鼠标位置的最小窗口,否则对于如下窗口将不能获取到密码框窗口,而是总是获取到上面的组框。
因此我们需要重写一个SmallestWindowFromPoint。它枚举WindowFromPoint返回的窗口的所有可见兄弟窗口,找出其中面积最小的一个,并返回其句柄:
HWND SmallestWindowFromPoint(CPoint point)
{
HWND hWnd, hWndParent, hWndSerach;
hWnd = ::WindowFromPoint(point);
RECT rect;
int areaSamller;
int area;
if (hWnd != NULL)
{
//得到当前返回的窗口的面积
::GetWindowRect(hWnd, &rect);
areaSamller = (rect.right-rect.left)*(rect.bottom-rect.top);
hWndParent = ::GetParent(hWnd);
if (hWndParent != NULL)
{
hWndSerach = hWnd;
do
{ //遍历窗口
hWndSerach = ::GetWindow(hWndSerach, GW_HWNDNEXT);
//判断是否是有效的兄弟窗口
if (hWndSerach && ::GetParent(hWndSerach)==hWndParent && ::IsWindowVisible(hWndSerach))
{
::GetWindowRect(hWndSerach, &rect);
area = (rect.right-rect.left)*(rect.bottom-rect.top);
//如果新得到的窗口更小 那么更新hWnd 记录当前最小面积
if (::PtInRect(&rect, point) && area < areaSamller)
{
areaSamller = area;
hWnd = hWndSerach;
}
}
} while (hWndSerach != NULL);
}
}
return hWnd;
}
在得到该窗口句柄后,通过GetClassName GetWindowLong等函数获取窗口信息,高亮该窗口并更新界面显示:
void HignLightWindow(HWND hWnd)
{
#define LINEWIDTH 3
if (hWnd == NULL || !IsWindow(hWnd))
return;
CRect rect;
::GetWindowRect(hWnd, &rect);
if(IsRectEmpty(&rect))
return;
//将屏幕坐标转换为窗口坐标
OffsetRect(&rect, -rect.left, -rect.top);
//绘制矩形
HDC hdc = ::GetWindowDC(hWnd);
PatBlt(hdc, rect.left, rect.top, rect.right-rect.top, LINEWIDTH, DSTINVERT);
PatBlt(hdc, rect.left, rect.bottom-LINEWIDTH, rect.right-rect.top, LINEWIDTH, DSTINVERT);
PatBlt(hdc, rect.left, rect.top, LINEWIDTH, rect.bottom-rect.top, DSTINVERT);
PatBlt(hdc, rect.right-LINEWIDTH, rect.top,LINEWIDTH, rect.bottom-rect.top, DSTINVERT);
::ReleaseDC(hWnd, hdc);
}
如果获得窗口所在模块信息:
1.通过GetWindowThreadProcessId得到创建窗口所在进程的ID
2.通过该ID调用CreateTollhelp32Snapshot创建一个进程快照
3.在该快照中通过Process32First Process32Next枚举进程,得到一个个PROCESSENTRY32的结构体。
4.找到th32ProcessID成员值等于前面所得进程ID的PROCESSENTRY32结构体
5.该结构体的szExeFile成员即为执行模块名称
得到模块路径方式和前面相似:
1.通过进程ID创建一个模块快照
2.通过Module32First Module32Next枚举模块 得到MODULEENTRY32结构体
3.找到结构体成员szExePath(模块全路径)的可执行文件名和前面得到的模块名称相同的结构体。
4.将该结构体szExePath去掉模块名 即得到模块路径
//当前进程ID
DWORD dwProcessID;
GetWindowThreadProcessId(hWnd, &dwProcessID);
CString strProcessID;
strProcessID.Format(_T("%d"), dwProcessID);
m_editProcessID.SetWindowText(strProcessID);
//得到模块名称
CString strExeName;
HANDLE hSnapProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, dwProcessID);
PROCESSENTRY32 pe;
BOOL bOk;
if (hSnapProcess != INVALID_HANDLE_VALUE)
{
for(bOk = Process32First(hSnapProcess, &pe); bOk; bOk = Process32Next(hSnapProcess, &pe))
{
//找到窗口所属的进程条目
if (pe.th32ProcessID == dwProcessID)
{
strExeName = pe.szExeFile;
break;
}
}
}
CloseHandle(hSnapProcess);
m_editModuleName.SetWindowText(strExeName);
//得到模块路径
CString strExePath;
CString str;
HANDLE hSnapModule = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessID);
MODULEENTRY32 me;
if (hSnapModule != INVALID_HANDLE_VALUE)
{
for (bOk = Module32First(hSnapModule, &me); bOk; bOk=Module32Next(hSnapModule, &me))
{
//找到模块名和前面得到的模块名一致的结构体
str = me.szExePath;
int namePos = str.ReverseFind('\\');
str = str.Mid(namePos+1);
if (str.CompareNoCase(strExeName) == 0)
{
str = me.szExePath;
strExePath = str.Left(namePos);
break;
}
}
}
CloseHandle(hSnapModule);
m_editModulePath.SetWindowText(strExePath);
钩子和动态连接库:
这是本程序的核心部分,当知道目标窗口是密码框时,我们如何得到它的密码。
如果探测窗口和目标窗口在同一个进程内或者在Win16平台下,那么就可以通过一条简单的WM_GETTEXT消息获取到。但在Win32及之后的版本中,进程间的消息是独立的,因此不能通过该方式完成。此时我们通过设置钩子的方式将自己的钩子函数代码注入到目标窗口所在的线程。这样就可以在钩子函数中发送WM_GETTEXT消息,这样就获取到了密码。
也就是通过钩子来强制注入钩子函数所在的DLL,使得该DLL成为目标进程的一部分,从而获取密码。
现在还有两个问题:
1.以何种方式设置钩子,即用什么样的形式的钩子函数。
2.得到的密码怎么传回探测窗口。
第一个问题,可以通过设置一个窗口过程钩子,该钩子函数检查探测窗口发送的特定消息,这个特定消息专门用于密码探测,窗口过程函数在发现窗口线程收到这个消息后,便开始像目标窗口发送WM_GETTEXT消息获取密码
注:这里指的特定消息不是简单通过#define MSG WM_USER+1定义的消息,这类消息也是进程间相互独立的 通过RegisterWindowMessage注册的消息可以在进程间进行传送,
第二个问题,进程间进行数据传输有很多中方式,WM_COPYDATA是最简单的一种方式,通过填充COPYDATASTRUCT并发送消息到探测窗口,探测窗口只需要在响应函数OnCopyData中获取消息参数中的数据即可
由于这里用到的钩子是远程钩子,因此它必须放入到动态连接库中才能将钩子函数代码注入到目标线程或进程。在SetWindowsHookEx中传入当前DLL的实例句柄,使得目标线程能够加载该DLL并且找到所要执行的钩子函数代码。
探测窗口代码
//设定钩子 查询密码
SetSpyHook(m_hWnd, hCurWnd, m_uSpyMsg);
QueryPasswordEdit();
UnSetSpyHook();
SetSpyHook和QueryPasswordEdit均位于动态连接库中
BOOL SetSpyHook(HWND hWndCaller, HWND hWndTarget, UINT msg)
{
if (!hWndTarget || !hWndCaller)
return FALSE;
if (g_bReEnter)
{
PopMsg(_T("Re Enter Hook"));
}
g_hook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)CallWndProc,
g_hInstDLL, GetWindowThreadProcessId(hWndTarget, NULL));
if(!g_hook)
return FALSE;
g_hWndCaller = hWndCaller;
g_hWndTarget = hWndTarget;
g_bReEnter = TRUE;
g_msg = msg;
return TRUE;
}
BOOL QueryPasswordEdit()
{
if (!g_hook || !g_hWndCaller || !g_hWndTarget || !g_msg)
{
return FALSE;
}
SendMessage(g_hWndTarget, g_msg, 0, 0);
return TRUE;
}
窗口过程钩子函数:
LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
CWPSTRUCT* pCwp = (CWPSTRUCT*)lParam;
if (pCwp->message == g_msg)
{
TCHAR szData[MAX_PATH] = {0};
SendMessage(g_hWndTarget, WM_GETTEXT, (WPARAM)MAX_PATH, (LPARAM)szData);
COPYDATASTRUCT cds;
cds.dwData = (DWORD)g_hWndTarget;
cds.lpData = szData;
cds.cbData = (lstrlen(szData)+1)*sizeof(TCHAR);
SendMessage(g_hWndCaller, WM_COPYDATA, (WPARAM)g_hWndTarget, (LPARAM)&cds);
}
return CallNextHookEx(g_hook, nCode, wParam, lParam);
}
综上所述,完成的探测流程如下:
加载包含窗口过程钩子的动态连接库
注册进程间通信的密码探测消息m_uSpyMsg
鼠标移动 获取鼠标位置窗口句柄
检测到鼠标所在位置窗口是一个密码窗口
给该窗口所在线程安装窗口过程钩子
向目标窗口发送m_uSpyMsg
之后就是等待目标窗口所在线程的窗口过程钩子函数在检测到m_uSpyMsg时开始获取密码 并发送
WM_COPYDATA消息
在OnCopyData中获取窗口过程钩子发送给探测窗口的密码数据
捕获界面:
源代码下载地址: http://download.csdn.net/detail/wudaijun/4971595