FindWindow函数行为分析

admin 2023年6月5日22:26:24评论14 views字数 6129阅读20分25秒阅读模式

概述:

本篇文章主要探寻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中对应的函数名。

FindWindow函数行为分析

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-&gt;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:该结构体中几个指针变量指向什么位置,是否存在横向的串联关系

不当之处,敬请斧正。


原文始发于微信公众号(0x00实验室):FindWindow函数行为分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月5日22:26:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   FindWindow函数行为分析http://cn-sec.com/archives/1745665.html

发表评论

匿名网友 填写信息