概述:
本篇文章主要探寻FindWindow函数的行为,该函数在哪个链表上进行窗口对象的检索操作。怎么查询对应的窗口对象链表。
HWND FindWindowW(
[in, optional] LPCSTR lpClassName, // 类名可以是向 RegisterClass 或 RegisterClassEx 注册的任何名称
[in, optional] LPCSTR lpWindowName //窗口名称 (窗口的标题) 。 如果此参数为 NULL,则所有窗口名称都匹配。
);
该函数在某条链表上进行类名/窗口名对比,将窗口句柄返回。
使用IDA分析 FindWindow 函数。
HWND __stdcall FindWindowW(LPCWSTR lpClassName, LPCWSTR lpWindowName)
{
return (HWND)InternalFindWindowExW(0, 0, lpClassName, lpWindowName, 0);
}
int __stdcall InternalFindWindowExW(
IN HWND hwndParent, //父类句柄
IN HWND hwndChild, //子句柄
WCHAR *pstrClassName, //待查找窗口类名
WCHAR *pstrWindowName, //待查找窗口名
UINT dwType //
)
{
struct _UNICODE_STRING usWindowName; // 宽字节类型窗口名
struct _UNICODE_STRING *pusWindowName; // 对应指针
int v8; // 效果未知
struct _UNICODE_STRING usClassName; // 宽字节类名
struct _UNICODE_STRING *pusClassName; // 对应指针
int v11; // 效果未知
v8 = 0;
v11 = 0;
pusClassName = &usClassName;
if ( ((unsigned int)pstrClassName & 0xFFFF0000) != 0 )
{
RtlInitUnicodeString(&usClassName, pstrClassName);
}
else
{
usClassName.MaximumLength = 0;
usClassName.Length = 0;
usClassName.Buffer = pstrClassName;
}
v8 = 0;
pusWindowName = &usWindowName;
RtlInitUnicodeString(&usWindowName, pstrWindowName);
//进行窗口查询
return NtUserFindWindowEx(hwndParent, hwndChild, (int)pusClassName, (int)pusWindowName, dwType);
}
.text:77D1DA9B _NtUserFindWindowEx@20 proc near ; CODE XREF: InternalFindWindowExA(x,x,x,x,x)+58↑p
.text:77D1DA9B ; InternalFindWindowExW(x,x,x,x,x)+57↓p
.text:77D1DA9B B8 8C 11 00 00 mov eax, 118Ch ;系统调用号
.text:77D1DAA0 BA 00 03 FE 7F mov edx, 7FFE0300h ;KUSER_SHARED_DATA.SystemCall
.text:77D1DAA5 FF 12 call dword ptr [edx];进入内核
.text:77D1DAA5
.text:77D1DAA7 C2 14 00 retn 14h
.text:77D1DAA7
.text:77D1DAA7 _NtUserFindWindowEx@20 endp
系统调用号0x118C。使用windbg查看SSDTS中对应的函数名。
0: kd> dd nt!KeServiceDescriptorTableShadow
83f81a00 83e886f0 00000000 00000191 83e88d38 <==KeServiceDescriptorTable
83f81a10 95ba5000 00000000 00000339 95ba602c <==KeServiceDescriptorTableShadow
附加到一个图形界面上。
PROCESS 88eada48 SessionId: 1 Cid: 0b24 Peb: 7ffd3000 ParentCid: 0a2c
DirBase: 7e8eb3a0 ObjectTable: 98cf8a90 HandleCount: 54.
Image: KmdManager.exe
1: kd> .process /p 88eada48
查看对应的函数名称。
1: kd> dds 95ba5000 L339
95ba5000 95b2eb34 win32k!NtGdiAbortDoc
95ba5004 95b4752e win32k!NtGdiAbortPath
//....
95ba5630 95a28da2 win32k!NtUserFindWindowEx <===索引0x18C所对应的内核函数
//....
可以发现该函数的实现是在win32k.sys中。win32k.sys主要是user32.dll、GDI32.dll的内核实现。接下来分析内核函数实现。
HWND NtUserFindWindowEx(
IN HWND hwndParent,
IN HWND hwndChild,
IN PUNICODE_STRING pstrClassName,
IN PUNICODE_STRING pstrWindowName,
DWORD dwType)
{
UNICODE_STRING strClassName;
UNICODE_STRING strWindowName;
PWND pwndParent, pwndChild;
BEGINATOMICRECV(HWND, NULL);
//win7x32的判断和此处略有不同,判断更复杂
//if ( hwndParent == 0xFFFFFFFD )
if (hwndParent != HWND_MESSAGE) {
ValidateHWNDOPT(pwndParent, hwndParent);
} else {
pwndParent = _GetMessageWindow();
}
ValidateHWNDOPT(pwndChild, hwndChild);
try {
strClassName = ProbeAndReadUnicodeString(pstrClassName);
strWindowName = ProbeAndReadUnicodeString(pstrWindowName);
ProbeForReadUnicodeStringBufferOrId(strClassName);
ProbeForReadUnicodeStringBuffer(strWindowName);
} except (StubExceptionHandler(TRUE)) {
MSGERRORCLEANUP(0);
}
//_FindWindowEx查询窗口句柄
retval = (HWND)_FindWindowEx(
pwndParent,
pwndChild,
strClassName.Buffer,
strWindowName.Buffer,
dwType);
retval = PtoH((PVOID)retval);
CLEANUPRECV();
TRACE("NtUserFindWindowEx");
ENDATOMICRECV();
}
_FindWindowEx函数关键点:
建立窗口列表
BuildHwndList
循环遍历列表,查找对应的窗口类
PBWL类型链表,记录了窗口对象的信息
释放窗口列表
FreeHwndList
PWND _FindWindowEx(
PWND pwndParent,
PWND pwndChild,
LPCWSTR ccxlpszClass,
LPCWSTR ccxlpszName,
DWORD dwType)
{
/*
* 注意,Class和Name指针是客户端地址。
*/
PBWL pbwl;
HWND *phwnd;
PWND pwnd;
WORD atomClass = 0;
LPCWSTR lpName;
BOOL fTryMessage = FALSE; if (ccxlpszClass != NULL) {
//注意,我们在这里做了一个无版本检查,然后立即调用FindClassAtom。
//如果类名存在,就寻找当前类名对应的word宽度数值标记,不存在则返回MAXUSHORT的&值
atomClass = FindClassAtom(ccxlpszClass);
if (atomClass == 0) {
return NULL;
}
}
/*
* 设置父窗口
*/
if (!pwndParent) {
pwndParent = _GetDesktopWindow();
//如果我们从根目录开始,并且没有指定子窗口,那么也检查消息窗口树,以防我们在桌面树中找不到它。
if (!pwndChild)
fTryMessage = TRUE;
}
TryAgain:
//设置第一个子节点
if (!pwndChild) {
pwndChild = pwndParent->spwndChild;
} else {
if (pwndChild->spwndParent != pwndParent) {
RIPMSG0(
RIP_WARNING,
"FindWindowEx: Child window doesn't have proper parent"
);
return NULL;
}
pwndChild = pwndChild->spwndNext;
}
//生成一个顶级窗口列表。
if ((pbwl = BuildHwndList(pwndChild, BWL_ENUMLIST, NULL)) == NULL) {
return NULL;
}
//如果窗口列表为空,则将pwnd设置为NULL。
pwnd = NULL;
try {
for (phwnd = pbwl->rghwnd; *phwnd != (HWND)1; phwnd++) {
//验证这个hwnd,因为我们早些时候离开了临界状态(在下面的循环中,我们发送了一条消息!)
//使用窗口句柄获取窗口信息结构体
if ((pwnd = RevalidateHwnd(*phwnd)) == NULL)
continue;
//确保这个窗口是正确的类型
if (dwType != FW_BOTH) {
if (((dwType == FW_16BIT) && !(GETPTI(pwnd)->TIF_flags & TIF_16BIT)) ||
((dwType == FW_32BIT) && (GETPTI(pwnd)->TIF_flags & TIF_16BIT)))
continue;
}
/*
* 如果指定的类不匹配,跳过这个窗口,注意我们在这里做了一个无版本检查,使用pcls->atomNVClassName
* 类名为空或者类名的哈希能够匹配,进入if
* 如果类名空但是窗口名对比成功,也会跳出循环
*/
if (!atomClass || (atomClass == pwnd->pcls->atomNVClassName)) {
if (!ccxlpszName)
break;
if (pwnd->strName.Length) {
lpName = pwnd->strName.Buffer;
} else {
lpName = szNull;
}
//文本是一样的吗?如果是这样,带着这个窗口返回!
if (_wcsicmp(ccxlpszName, lpName) == 0)
break;
}
//窗户不匹配。
pwnd = NULL;
}
} except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
pwnd = NULL;
}
FreeHwndList(pbwl);
if (!pwnd && fTryMessage) {
fTryMessage = FALSE;
pwndParent = _GetMessageWindow();
pwndChild = NULL;
goto TryAgain;
}
//三目运算符判断是否是柔性数组最后一个成员
//不是最后一个,返回正确的句柄值
return ((*phwnd == (HWND)1) ? NULL : pwnd);
}
在窗口匹配操作时,用到了PBWL类型。其定义如下:
//窗口列表结构
typedef struct tagBWL {
struct tagBWL *pbwlNext; //效果未知
HWND *phwndNext; //效果未知
HWND *phwndMax; //效果未知
PTHREADINFO ptiOwner; //线程信息
HWND rghwnd[1]; //以值 1 结尾的柔性数组
} BWL, *PBWL;
在每次窗口信息对比前,都需要使用RevalidateHwnd(*phwnd)函数,获取窗口句柄对应的窗口信息结构体指针,定义如下:
//窗口信息结构体
typedef struct tagWND : public WW
{
tagWND* spwndNext;
tagWND* spwndPrev;
tagWND* spwndParent;
tagWND* spwndChild;
tagWND* spwndOwner; RECT rcWindow;
RECT rcClient;
WNDPROC lpfnWndProc;
void* pcls; //该指针指向了另一个变量、结构体,其中存储了当前类名计算的Hash值
HRGN hrgnUpdate;
void* ppropList;
void* pSBInfo;
HMENU spmenuSys;
HMENU spmenu;
HRGN hrgnClip;
LARGE_UNICODE_STRING strName; //窗口名称
int cbWndExtra;
void* spwndLastActive;
void* hImc;
void* dwUserData;
void* pActCtx;
} WND, *PWND;
函数追到此处基本上就已经窥视到FindWIndow函数总体操作流程,而产生的新问题就是:
BuildHwndList:列表构建流程
RevalidateHwnd:怎么找到的窗口句柄与窗口信息的对应关系
PBWL:该结构体中几个指针变量指向什么位置,是否存在横向的串联关系
不当之处,敬请斧正。