首页
社区
课程
招聘
[原创]win32子系统分析:用户层句柄分析、EnterSharedCrit、ValidateHwnd、NtUserQueryWindow
发表于: 2013-5-29 15:55 32236

[原创]win32子系统分析:用户层句柄分析、EnterSharedCrit、ValidateHwnd、NtUserQueryWindow

2013-5-29 15:55
32236

名称:win32子系统之一:用户层句柄分析
作者:mengwuji
时间:2013.5.28  0:08
链接:http://mengwuji.net/forum.php?mod=viewthread&tid=73&extra=page%3D1

windows NT内核经历十多年,从开始的封闭慢慢转成现在的半透明态。中间无数高手通过逆向分析等手段对各种windows组件不断探索和挖掘使我们这些后辈有更好的学习资料加以掌握。从微软的wrk公布以来,内核的特性不断的被公布开来,让这个封闭的操作系统给人眼前一亮之情景。但是,win32子系统作为操作系统的一个组件却是鲜为人知,对它分析的文章也是寥寥无几,而我们的gui编程无时无刻的不与之打交道。所以,我决定对win32子系统做一些研究,提供给后者参考,以自己肤浅的认识来揭露win32子系统的一些秘密。让还是小菜的我来激起各种大牛对win32子系统的重视,使这个封闭的系统更加透明。
我打算做成一系列文章来探讨win32子系统,而这是第一篇,所以我们从用户层开始研究再转入内核中去。好了,我们进入正题。

windows窗口在操作时关联了一个句柄,这个句柄是HWND类型,也即是整数型。我们平常操作这个句柄就能操作一个窗口,那么我们为了研究便利,首先找一个窗口关联的api探索就最好不过了。当然这个api最好有个参数是HWND,方便我们在进入内核中查看它是如何操纵这个句柄的。
这里我选定的函数是:
DWORD GetWindowThreadProcessId(
    HWND hWnd,
    LPDWORD lpdwProcessId
);

这个函数的作用是通过一个窗口句柄得到创建此窗口的进程id和线程id。第一个参数是我们要获取进程和线程id关联的窗口句柄,第二个参数是个DWORD类型的指针,是接收返回的进程id,可以为空值。返回值是创建该窗口的线程id。
那么我通过od得到它的汇编代码如下:

//用户层部分
7704EE32        MOV EDI,EDI
7704EE34        PUSH EBP
7704EE35        MOV EBP,ESP
7704EE37        PUSH ESI
7704EE38        PUSH DWORD PTR SS:[EBP+8]         ; hwnd
7704EE3B        CALL USER32.7704EE76         ; 一系列判断句柄是够有效并且返回一个句柄关联的值,call分析下面
7704EE40        MOV ESI,EAX         ; esi接收返回值
7704EE42        TEST ESI,ESI         ; 如果返回零,就表示出错了,那就直接返回
7704EE44        JE SHORT USER32.7704EE6C
7704EE46        CALL USER32.77055187         ;这个call返回的结果是当前线程TEB的win32ThreadInfo,call分析下面
7704EE4B        CMP ESI,EAX
7704EE4D        JE SHORT USER32.7704EEC5         ;如果当前线程的win32ThreadInfo和通过句柄查询到的值相等,就跳转(实际这里很明显传入的窗口句柄是自己的),这个跳转下去代码比较少,我放在下面分析
-------------------------------------------------------->
7704EEC5        MOV EAX,DWORD PTR SS:[EBP+C]         ;上面的跳转到这里;eax=接收PID的指针
7704EEC8        TEST EAX,EAX         ;如果传入的指针为空
7704EECA        JE SHORT USER32.7704EED8         ;跳到下面把本线程id赋值给eax
7704EECC        MOV ECX,DWORD PTR FS:[18]
7704EED3        MOV ECX,DWORD PTR DS:[ECX+20]
7704EED6        MOV DWORD PTR DS:[EAX],ECX         ;本进程id赋值给传入的指针
7704EED8        MOV EAX,DWORD PTR FS:[18]
7704EEDE        MOV EAX,DWORD PTR DS:[EAX+24]         ;eax=本线程id
7704EEE1        JMP SHORT USER32.7704EE6C         ;跳到退出位置
<--------------------------------------------------------

7704EE4F        MOV ESI,DWORD PTR SS:[EBP+C]         //如果hwnd不是本线程所属窗口的就会执行到这里;esi=第二个参数
7704EE52        TEST ESI,ESI
7704EE54        JE SHORT USER32.7704EE62         //第二个参数为零跳转
7704EE56        PUSH 0
7704EE58        PUSH DWORD PTR SS:[EBP+8]
7704EE5B        CALL USER32.77055BAA         //调用NtUserQueryWindow,第一个参数为窗口句柄,第二个参数是个标志,这个标记为0指明返回值是创建此窗口的进程id
7704EE60        MOV DWORD PTR DS:[ESI],EAX
7704EE62        PUSH 2
7704EE64        PUSH DWORD PTR SS:[EBP+8]
7704EE67        CALL USER32.77055BAA         //调用NtUserQueryWindow,第一个参数为窗口句柄,第二个参数是个标志,这个标记为2指明返回值是创建此窗口的线程id
7704EE6C        POP ESI
7704EE6D        POP EBP
7704EE6E        RETN 8

7704EE76        MOV EDI,EDI
7704EE78        PUSH EBP
7704EE79        MOV EBP,ESP
7704EE7B        MOV ECX,DWORD PTR SS:[EBP+8]         ;ecx=hwnd
7704EE7E        MOV EAX,DWORD PTR DS:[770A90F4]         ;eax=[770A90F4]
7704EE83        MOVZX EDX,CX         ;edx=窗口句柄的低16位
7704EE86        CMP EDX,DWORD PTR DS:[EAX+4]         ;窗口句柄低16位和某个值比较,可能是句柄值的上限
7704EE89        JNB USER32.77070425         ;如果比那个上限值大,说明有问题,就跳到错误处理 错误码对应1400,对应的是无效的窗口句柄
7704EE8F        MOV EAX,DWORD PTR DS:[770A9448]         ;eax=[770A9448],这个值可能是一个有关句柄结构的大小
7704EE94        IMUL EAX,EDX         ;句柄值低16位乘以一个关于句柄结构的大小就能定位到具体句柄关联结构偏移
7704EE97        ADD EAX,DWORD PTR DS:[770A9444]         ;eax是句柄关联的结构偏移,那么加上[770A9444]可能是定位到这个结构的绝对地址
7704EE9D        CMP BYTE PTR DS:[EAX+8],1         ;eax定位到了某个关于句柄的结构,这个结构的+8位置是个bool值,由于看了下面的跳转,所以确定这个标记是指明当前的句柄是否还有效了(窗口可能关闭),无效为false,并且写入错误代码1400,也就是窗口句柄无效
7704EEA1        JNZ USER32.77070425         ;窗口句柄无效的话就跳
7704EEA7        TEST BYTE PTR DS:[EAX+9],1         ;eax+9位置也是个标记,这个标记是true的话下面就进行跳转,也是和上面的跳转一样,但是不明白这个标记是代表什么情况
7704EEAB        JNZ USER32.77070425         ;窗口句柄无效跳转
7704EEB1        SHR ECX,10         ;ecx是窗口句柄,右移16位可得到句柄的高16位值
7704EEB4        CMP CX,WORD PTR DS:[EAX+A]         ;eax+ A位置和句柄高十六位对比,不相等跳转;跳下去会比较句柄高十六位是够为零,为零的话又跳回到7704EEBE继续执行;不为零就比较是否是最大值ffff(两字节),是的话就跳回7704EEBE继续执行;否则,跳到错误处理,说明句柄无效。错误代码1400
7704EEB8        JNZ USER32.7706218A         ;错误处理
7704EEBE        MOV EAX,DWORD PTR DS:[EAX+4]         ;到达这里,返回值写入eax+4的值,然后返回
7704EEC1        POP EBP
7704EEC2        RETN 4

77055187        MOV EAX,DWORD PTR FS:[18]         ;fs在用户层是指向TEB,在内核中指向_kpcr。+0x18偏移是指向自己
7705518D        CMP DWORD PTR DS:[EAX+40],0         ;TEB的0x40偏移是win32ThreadInfo,是个和gui线程关联的对象,如果是cui线程,这个地方的值是零。
77055191        JE USER32.770855B5         ;如果这个地方的值是零,那么证明没有初始化,那这个跳转就会填写0xf参数去调用NtUserGetThreadState系统服务,如果返回非零值,就跳到77055197继续执行,否则直接返回(调用NtUserGetThreadState函数会改写win32ThreadInfo的值)。
77055197        MOV EAX,DWORD PTR FS:[18]
7705519D        MOV EAX,DWORD PTR DS:[EAX+40]         ;能到这里,说明无论如何win32ThreadInfo都有值了。这里返回此值
770551A0        RETN

为了更加清楚的表达用户层的行为,我把它整理成c语言方便阅读,如下:
struct        _SharedHwndInfo{
        ULONG        uHwndObjBase;
        ULONG        uHwndObjSize;
}SharedHwndInfo,*pSharedHwndInfo;

struct        _HwndUserObj{
        ULONG        uUnkown;
        ULONG        uWin32ThreadInfo;
        bool        bIsInvalid;
        bool        bUnFlag;
        WORD        wHighHwnd;
}HwndUserObj,*pHwndUserObj;

static ULONG        g_uSystemGloValue1 = 0x770A90F4;
static SharedHwndInfo        *g_pSharedHwndInfo = 0x770A9444;

ULONG IsHwndValid(HWND hWnd)
{
        USHORT        uHwndObjOffset;
        ULONG        uHwndObjMaxOffset;
        HwndUserObj        HUserObj;

        uHwndObjOffset = (USHORT)((ULONG)hWnd>>0x10);

        uHwndObjMaxOffset = *(ULONG*)(*(ULONG*)g_uSystemGloValue1 + 4);
        if (uHwndObjOffset>uHwndObjMaxOffset)
        {
                goto        __ErrorLeep;
        }
        

        HUserObj = ((HwndUserObj *)g_pSharedHwndInfo->uHwndObjBase)[uHwndObjMaxOffset];
        if (!HUserObj.bIsInvalid)
        {
                goto        __ErrorLeep;
        }
        
        if (!HUserObj.bUnFlag)
        {
                goto        __ErrorLeep;
        }

        if (HUserObj.wHighHwnd!=(ULONG)hWnd>>0x10)
        {
                if ((ULONG)hWnd>>0x10!=0||
                        (ULONG)hWnd>>0x10!=0xffff)
                {
                        goto        __ErrorLeep;
                }
        }

        return        HUserObj.uWin32ThreadInfo;

__ErrorLeep:
        return        0;
}

ULONG GetCurrentWin32ThreadInfo()
{
        ULONG        uThreadTEB;
        ULONG        uWin32ThreadInfo;

        __asm{
                MOV        EAX,DWORD PTR FS:[18]
                MOV        uThreadTEB,EAX
        }

        uWin32ThreadInfo = *(ULONG*)(uThreadTEB+0x40);
        if (!uWin32ThreadInfo)
        {
                if(!NtUserGetThreadState(0xf))
                {
                        return 0;
                }
        }

        __asm{
                MOV        EAX,DWORD PTR FS:[18]
                MOV        uThreadTEB,EAX
        }

        return        uThreadTEB;
}

DWORD GetWindowThreadProcessId(
        HWND hWnd,
        LPDWORD lpdwProcessId
)
{
        ULONG        uWin32ThreadInfo;
        ULONG        uCurrentWin32ThreadInfo;
        
        uWin32ThreadInfo = IsHwndValid(hWnd);
        if (!uWin32ThreadInfo)
        {
                return 0;
        }
        
        uCurrentWin32ThreadInfo=GetCurrentWin32ThreadInfo();
        if (uCurrentWin32ThreadInfo==uWin32ThreadInfo)
        {
                if (lpdwProcessId!=0)
                {
                        *lpdwProcessId = GetCurrentProcessId();
                }
                return        GetCurrentThreadId();
        }

        if (lpdwProcessId!=0)
        {
                &lpdwProcessId = NtUserQueryWindow(hWnd,0);
        }

        return        NtUserQueryWindow(hWnd,2);
}

因为在汇编语言中都注释了,为了避免浪费时间,我就没在逆向出的c语言中注释。再说基本能看懂c语言的人都明白大概程序在做什么事情。这里面有个东西要注意,就是在用户层有个全局数组,这个数组里面记录了系统当前所有句柄的一些信息,可以通过这个数值得到当前窗口的数量。这说明win32子系统把一部分信息保存到用户空间中,为什么这么做呢?我想是因为避免ring0到ring3切换带来的效率降低。
那么,用户层所做的事情就比较简单了,只是判断一些信息,然后调用NtUserQueryWindow函数,这个函数会转入到内核中去执行内核的NtUserQueryWindow函数;下一篇我们便在内核中去一探究竟吧!

还有,小弟新建了一个学习交流论坛,希望大家多多光顾!(www.mengwuji.net)

ps:本系列全都针对windows7 sp1 32位系统


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 5
支持
分享
最新回复 (14)
雪    币: 61
活跃值: (56)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2
名称:win32子系统之二:EnterSharedCrit函数分析
作者:mengwuji
时间:2013.5.28 12:35
链接:http://mengwuji.net/forum.php?mod=viewthread&tid=75&extra=page%3D1

上一篇我们说过用户层相关的一些东西,这一篇起我们转入内核分析。下面进入正题:

ULONG        NtUserQueryWindow(HWND        hWnd,ULONG Flag);

win32k!NtUserQueryWindow:
92dbe20a        mov     edi,edi
92dbe20c        push    ebp
92dbe20d        mov     ebp,esp
92dbe20f        push    esi
92dbe210        push    edi
//win32子系统的系统服务在执行时都会在首部调用EnterSharedCrit和在尾部调用UserSessionSwitchLeaveCrit,因为频繁出现,所以我们有必要研究下它们到底是做什么的(在开始就遇到艰巨的任务了!)。函数见下面分析。
92dbe211        call    win32k!EnterSharedCrit (92dd3141)
..................

win32k!EnterSharedCrit:
92dd3141        push    dword ptr [win32k!gpresUser (92f226ac)]                //未知的一个全局变量
//调用92ef4270偏移量为零的值作为函数地址,这个地址是81e9d3f4
92dd3147        call    dword ptr [win32k!_imp__ExEnterPriorityRegionAndAcquireResourceShared (92ef4270)]
92dd314d        ret

nt!ExEnterPriorityRegionAndAcquireResourceShared:
81e9d3f4        mov     edi,edi
81e9d3f6        push    ebp
81e9d3f7        mov     ebp,esp
81e9d3f9        push    esi
81e9d3fa        mov     esi,dword ptr fs:[124h]                //fs在内核中是_kpcr结构,+0x124偏移是当前线程的ETHREAD指针
81e9d401        inc     byte ptr [esi+28Bh]                //+0x28B是PriorityRegionActive,表示线程的活动状态
81e9d407        dec     word ptr [esi+84h]                //+0x84是CombinedApcDisable,关于一个apc相关的计数值
81e9d40e        push    1                                //应该是一个标记
81e9d410        push    dword ptr [ebp+8]                //[gpresUser]
//下面函数接收两个参数,执行完成后返回值设置为当前线程的Win32Thread成员,为了一探究竟,我们继续深入的看看这个函数会做些什么;当然,如果不关心它就认为EnterSharedCrit执行结束后,返回值就表示当前线程的的Win32Thread成员就可以了
81e9d413        call    nt!ExAcquireResourceSharedLite (81e4ad3c)
81e9d418        mov     eax,dword ptr [esi+18Ch]
81e9d41e        pop     esi
81e9d41f        pop     ebp
81e9d420        ret     4

(声明一下,首先分析完这个函数时才发现这个函数在WRK中有了(岂不是白白让我浪费时间了!)~~~~(>_<)~~~~ 不过wrk中的没这个这么复杂)
nt!ExAcquireResourceSharedLite:
81e4ad3c        mov     edi,edi
81e4ad3e        push    ebp
81e4ad3f        mov     ebp,esp
81e4ad41        and     esp,0FFFFFFF8h
81e4ad44        sub     esp,24h
81e4ad47        push    ebx
81e4ad48        push    esi
//第一个参数是[gpresUser]
81e4ad49        mov     esi,dword ptr [ebp+8]
81e4ad4c        push    edi
//fs:[20h]是_KPRCB结构的指针,这个结构是与当前处理器控制块关联的,里面的信息量庞大到让人头痛的地步
81e4ad4d        mov     edi,dword ptr fs:[20h]
//_KPRCB结构的35ACh的偏移位置是ExAcqResSharedAttempts,这个值维和了当前处理器尝试请求资源共享的计数
81e4ad54        inc     dword ptr [edi+35ACh]
//PerfGlobalGroupMask是全局组掩码,用来提供WMI组件相关的支持
81e4ad5a        mov     ebx,dword ptr [nt!PerfGlobalGroupMask+0x4 (81f49684)]
//eax=当前线程的ethread
81e4ad60        mov     eax,dword ptr fs:[00000124h]
//对得到的一个掩码值进行操作
81e4ad66        shr     ebx,11h
//得到第17位的值
81e4ad69        and     bl,1
//因为是提供给KeAcquireInStackQueuedSpinLock函数调用的,所以esi+0x34h是个KSPIN_LOCK类型成员,从这里得到一个重要信息,就是esi对应的这个结构我们知道+34h是个KSPIN_LOCK类型,那么通过解析win32.sys的符号链接,一个_ERESOURCE结构体让我重视起来
81e4ad6c        lea     ecx,[esi+34h]
//edx是表示输出参数,是KLOCK_QUEUE_HANDLE类型
81e4ad6f        lea     edx,[esp+18h]
//以下两句是保存一些信息
81e4ad73        mov     byte ptr [esp+0Eh],bl
//保存线程对象到临时变量
81e4ad77        mov     dword ptr [esp+10h],eax
//VOID KeAcquireInStackQueuedSpinLock(_Inout_  PKSPIN_LOCK SpinLock,_Out_    PKLOCK_QUEUE_HANDLE LockHandle);这是函数的原型,其中ecx是第一个参数,edx是第二个参数,这函数是获取一个自旋锁
81e4ad7b        call    dword ptr [nt!_imp_KeAcquireInStackQueuedSpinLock (81e14144)]
//直接跳到下面
81e4ad81        jmp     nt!ExAcquireResourceSharedLite+0xd2 (81e4ae0e)
//+e是Flag,比较这个标记
81e4ad86        test    byte ptr [esi+0Eh],80h
根据这个标记&80得到的为真值,就不会跳,否则跳
81e4ad8a        je      nt!ExAcquireResourceSharedLite+0x75 (81e4adb1)
//eax得到当前线程对象
81e4ad8c        mov     eax,dword ptr [esp+10h]
//比较当前线程对象和_RESOURCE的OwnerEntry,这个OwnerEntry第一个成员实际也是个线程对象
81e4ad90        cmp     dword ptr [esi+18h],eax
//相等就跳转
81e4ad93        je      nt!ExAcquireResourceSharedLite+0x116 (81e4ae52)
//调用KeAcquireInStackQueuedSpinLock的输出参数
81e4ad99        lea     eax,[esp+18h]
//压入堆栈
81e4ad9d        push    eax
//eax保存_RESOUCE(寄存器传参)
81e4ad9e        mov     eax,esi
//调用ExpFindEmptyEntry寻找一个空闲的进入点
81e4ada0        call    nt!ExpFindEmptyEntry (81e48f96)
81e4ada5        mov     ebx,eax
81e4ada7        test    ebx,ebx
//如果没有寻找到就跳转
81e4ada9        je      nt!ExAcquireResourceSharedLite+0xce (81e4ae0a)
//寻找到的话,eax保存当前线程对象后跳转
81e4adab        mov     eax,dword ptr [esp+10h]
81e4adaf        jmp     nt!ExAcquireResourceSharedLite+0xaf (81e4adeb)

81e4adb1        xor     eax,eax
//+2c是NumberOfExclusiveWaiters,是个计数,但目前的信息不能推测出来具体是什么
81e4adb3        cmp     dword ptr [esi+2Ch],eax
//根据+2c是否为零来决定al的值
81e4adb6        setne   al
//作为ExpFindCurrentThread的一个参数,侥幸的去wrk中尝试获取这个函数的信息;惊奇的发现wrk包括了对于"resource"的很多信息,那么我们肯定好充分的利用好这个便利了!因为我用的win7系统,所以这个函数不是wrk中的三个参数了。在win7下面它是四个参数
81e4adb9        push    eax
//esp+18h来保存这个值(如果是ebp来定位的话就方便多了!)
81e4adba        mov     dword ptr [esp+18h],eax
//这里eax=esp+0x1Ch,经过转换后,实际也就是上面调用KeAcquireInStackQueuedSpinLock后输出的第二个参数
81e4adbe        lea     eax,[esp+1Ch]
//传入这个值
81e4adc2        push    eax
//这里的esp+18通过计算是上面保存线程对象的临时变量
81e4adc3        push    dword ptr [esp+18h]
//esi是_RESOURCE结构,我们也要通过寄存器的方式传入这个参数
81e4adc7        mov     eax,esi
//好吧,这里ExpFindCurrentThread参数只比wrk中的多出最后一个,最后一个是个bool值,这里我们忽略这个函数的分析(避免增加复杂度,我们的初衷不能在这点小问题上耗费时间,对于它,我们只要知道返回什么东西就可以了)。
81e4adc9        call    nt!ExpFindCurrentThread (81eb42ba)
//参考wrk,知道这个函数返回一个POWNER_ENTRY指针,然后给ebx进行保存
81e4adce        mov     ebx,eax
81e4add0        test    ebx,ebx
//如果返回为零(寻找失败?),这里会跳到下面,下面会重新跳到上面来重复此过程
81e4add2        je      nt!ExAcquireResourceSharedLite+0xce (81e4ae0a)
//到这里说明寻找成功了,那么进行一些验证;esp+0x10是我们保存过的当前线程对象。而_OWNER_ENTRY的第一个成员是OwnerThread
81e4add4        mov     eax,dword ptr [esp+10h]
//比较返回的结构是否是本线程相关的
81e4add8        cmp     dword ptr [ebx],eax
//如果是和本线程相关的那么就跳下去执行其他操作
81e4adda        je      nt!ExAcquireResourceSharedLite+0x15c (81e4ae98)
//这里的esp+0x14在上面保存过,是根据_RESOURCE的NumberOfExclusiveWaiters真假来决定的一个bool值
81e4ade0        cmp     dword ptr [esp+14h],0
//这里直接为零直接跳到下面
81e4ade5        je      nt!ExAcquireResourceSharedLite+0x1a5 (81e4aee1)
//第二个参数的值来决定是否跳转,因为传入的是1,所以这里不会跳转
81e4adeb        cmp     byte ptr [ebp+0Ch],0
81e4adef        je      nt!ExAcquireResourceSharedLite+0x1d4 (81e4af10)
//_ERESOURCE+10h是SharedWaiters,这是个_KSEMAPHORE结构指针类型。判断它是否为零,不为零跳
81e4adf5        cmp     dword ptr [esi+10h],0
81e4adf9        jne     nt!ExAcquireResourceSharedLite+0x1eb (81e4af27)
//eax是上面调用KeAcquireInStackQueuedSpinLock后输出的第二个参数
81e4adff        lea     eax,[esp+18h]
//把它压入堆栈
81e4ae03        push    eax
//把_RESOURCE压入堆栈
81e4ae04        push    esi
//调用ExpAllocateSharedWaiterSemaphore函数,这个函数可以简单理解为分配一个资源
81e4ae05        call    nt!ExpAllocateSharedWaiterSemaphore (81e41397)
//esp+E是个全局掩码得到的值
81e4ae0a        mov     bl,byte ptr [esp+0Eh]

//现在假设esi对应的是_ERESOURCE结构,这个结构的+20h是ActiveEntries,可能表示当前获取到资源的一个关联值,如果这个值不为空应该是我们还不能立即获取它,要等到它为空我们才有获取的权利
81e4ae0e        cmp     dword ptr [esi+20h],0
//这里是往回跳
81e4ae12        jne     nt!ExAcquireResourceSharedLite+0x4a (81e4ad86)
//eax=当前线程对象
81e4ae18        mov     eax,dword ptr [esp+10h]
//_RESOURE的OwnerEntry的OwnerThread赋值为当前线程
81e4ae1c        mov     dword ptr [esi+18h],eax
//esi+0x1c多值组合。
81e4ae1f        mov     eax,dword ptr [esi+1Ch]
//这里得到低两位
81e4ae22        and     eax,3
//第三位值一
81e4ae25        or      eax,4
//eax重新赋值给esi+0x1c
81e4ae28        mov     dword ptr [esi+1Ch],eax
81e4ae2b        xor     eax,eax
81e4ae2d        inc     eax
//ecx得到调用KeAcquireInStackQueuedSpinLock的返回值
81e4ae2e        lea     ecx,[esp+18h]
//ActiveEntries赋值为1
81e4ae32        mov     dword ptr [esi+20h],eax
//ActiveCount赋值为1,实际上面这几句都是把新的_ERESOURCE初始化
81e4ae35        mov     word ptr [esi+0Ch],ax
//释放自旋锁
81e4ae39        call    dword ptr [nt!_imp_KeReleaseInStackQueuedSpinLock (81e14140)]
//edi是_kprcb,35b4是ExAcqResSharedAcquiresShared
81e4ae3f        inc     dword ptr [edi+35B4h]
//3584是ExecutiveResourceAcquiresCount
81e4ae45        inc     dword ptr [edi+3584h]
81e4ae4b        test    bl,bl
//跳,这里是等于退出了
81e4ae4d        jmp     nt!ExAcquireResourceSharedLite+0x2b2 (81e4afee)

81e4ae52        mov     eax,dword ptr [esi+1Ch]
81e4ae55        mov     ecx,eax
//实际就是得到OwnerEntry的OwnerCount
81e4ae57        and     ecx,0FFFFFFFCh
81e4ae5a        add     ecx,4
81e4ae5d        and     eax,3
81e4ae60        xor     ecx,eax
//经过上述运算后重新给会TableSize
81e4ae62        mov     dword ptr [esi+1Ch],ecx
81e4ae65        lea     ecx,[esp+18h]
//释放自旋锁
81e4ae69        call    dword ptr [nt!_imp_KeReleaseInStackQueuedSpinLock (81e14140)]
//上面解释过了
81e4ae6f        inc     dword ptr [edi+35B0h]
81e4ae75        inc     dword ptr [edi+3584h]
81e4ae7b        test    bl,bl
//根据全局掩码进行相应跳转
81e4ae7d        je      nt!ExAcquireResourceSharedLite+0x2c4 (81e4b000)
//ContentionCount
81e4ae83        push    dword ptr [esi+24h]
//eax=TableSize
81e4ae86        mov     eax,dword ptr [esi+1Ch]
81e4ae89        shr     eax,2
81e4ae8c        push    eax
81e4ae8d        push    esi
81e4ae8e        push    10031h
81e4ae93        jmp     nt!ExAcquireResourceSharedLite+0x2bf (81e4affb)

//ebx是OwnerEntry,+4是TableSize
81e4ae98        mov     eax,dword ptr [ebx+4]
81e4ae9b        mov     ecx,eax
81e4ae9d        and     ecx,0FFFFFFFCh
81e4aea0        add     ecx,4
81e4aea3        and     eax,3
81e4aea6        xor     ecx,eax
//上面是对OwnerCount进行一些运算,然后把运算后的值重新写入回去
81e4aea8        mov     dword ptr [ebx+4],ecx
//KeAcquireInStackQueuedSpinLock的输出参数
81e4aeab        lea     ecx,[esp+18h]
//释放自旋锁
81e4aeaf        call    dword ptr [nt!_imp_KeReleaseInStackQueuedSpinLock (81e14140)]
//上面讲解过了
81e4aeb5        inc     dword ptr [edi+35B8h]
81e4aebb        inc     dword ptr [edi+3584h]
//比较全局掩码相关
81e4aec1        cmp     byte ptr [esp+0Eh],0
//为零就退出
81e4aec6        je      nt!ExAcquireResourceSharedLite+0x2c4 (81e4b000)
//ContentionCount
81e4aecc        push    dword ptr [esi+24h]
81e4aecf        mov     eax,dword ptr [ebx+4]
81e4aed2        shr     eax,2
81e4aed5        push    eax
81e4aed6        push    esi
81e4aed7        push    10051h
81e4aedc        jmp     nt!ExAcquireResourceSharedLite+0x2bf (81e4affb)

//eax是当前线程对象,写入到OwnerEntry的OwnerThread成员里
81e4aee1        mov     dword ptr [ebx],eax
81e4aee3        mov     eax,dword ptr [ebx+4]
81e4aee6        and     eax,3
81e4aee9        or      eax,4
//得到OwnerEntry的IoPriorityBoosted和OwnerReferenced以及第三位值1,然后把值写入回去
81e4aeec        mov     dword ptr [ebx+4],eax
//eax = ActiveEntries
81e4aeef        mov     eax,dword ptr [esi+20h]
81e4aef2        test    eax,eax
//ActiveEntries不为零跳转
81e4aef4        jne     nt!ExAcquireResourceSharedLite+0x1c1 (81e4aefd)
81e4aef6        inc     eax
//ActiveEntries+1后写入到ActiveCount
81e4aef7        mov     word ptr [esi+0Ch],ax
81e4aefb        jmp     nt!ExAcquireResourceSharedLite+0x1c2 (81e4aefe)

81e4aefd        inc     eax
81e4aefe        lea     ecx,[esp+18h]
//加1写入回ActiveEntries,并且释放自旋锁
81e4af02        mov     dword ptr [esi+20h],eax
81e4af05        call    dword ptr [nt!_imp_KeReleaseInStackQueuedSpinLock (81e14140)]
81e4af0b        jmp     nt!ExAcquireResourceSharedLite+0x2a1 (81e4afdd)

81e4af10        lea     ecx,[esp+18h]
//释放自旋锁
81e4af14        call    dword ptr [nt!_imp_KeReleaseInStackQueuedSpinLock (81e14140)]
//ExAcqResSharedNotAcquires++
81e4af1a        inc     dword ptr [edi+35C0h]
81e4af20        xor     al,al
81e4af22        jmp     nt!ExAcquireResourceSharedLite+0x2c6 (81e4b002)

//当前线程对象写入OwnerThread
81e4af27        mov     dword ptr [ebx],eax
81e4af29        mov     eax,dword ptr [ebx+4]
81e4af2c        and     eax,3
81e4af2f        or      eax,4
81e4af32        mov     dword ptr [ebx+4],eax
//上面在这个函数中见的很多,以后不解析;下面把ebx赋值为KeReleaseInStackQueuedSpinLock,好在后面调用
81e4af35        mov     ebx,dword ptr [nt!_imp_KeReleaseInStackQueuedSpinLock (81e14140)]
//NumberOfSharedWaiters++
81e4af3b        inc     dword ptr [esi+28h]
81e4af3e        lea     ecx,[esp+18h]
//释放自旋锁
81e4af42        call    ebx
81e4af44        inc     dword ptr [edi+35BCh]
81e4af4a        cmp     byte ptr [esp+0Eh],0
81e4af4f        je      nt!ExAcquireResourceSharedLite+0x222 (81e4af5e)
81e4af51        push    0
81e4af53        push    esi
81e4af54        push    10044h
//这个是指向等待资源被释放
81e4af59        call    nt!PerfLogExecutiveResourceWait (81f25518)
//SharedWaiters入栈,SharedWaiters是个分发器对象,指定此对象是可指向同步处理的
81e4af5e        push    dword ptr [esi+10h]
81e4af61        mov     eax,esi
//在SharedWaiters这个对象上等待被有信号
81e4af63        call    nt!ExpWaitForResource (81e4802f)
//Flag&0x4
81e4af68        test    byte ptr [esi+0Eh],4
81e4af6c        mov     byte ptr [esp+0Fh],0
//flag的第三位为零,就跳
81e4af71        je      nt!ExAcquireResourceSharedLite+0x2a1 (81e4afdd)
//eax是当前线程对象
81e4af73        mov     eax,dword ptr [esp+10h]
//eax+280h是线程对象的CrossThreadFlags成员
81e4af77        mov     eax,dword ptr [eax+280h]
//得到线程对象的ThreadIoPriority成员
81e4af7d        and     eax,1C00h
//ThreadIoPriority800h实际就是高于2
81e4af82        cmp     eax,800h
//跳转
81e4af87        jae     nt!ExAcquireResourceSharedLite+0x2a1 (81e4afdd)

//下面调用KeAcquireInStackQueuedSpinLock在上面有讲过
81e4af89        lea     edx,[esp+24h]
81e4af8d        lea     ecx,[esi+34h]
81e4af90        call    dword ptr [nt!_imp_KeAcquireInStackQueuedSpinLock (81e14144)]
81e4af96        push    1
81e4af98        push    0
81e4af9a        push    dword ptr [esp+18h]
81e4af9e        mov     eax,esi
//ExpFindCurrentThread寻找一个资源数组中空闲的节点,返回
81e4afa0        call    nt!ExpFindCurrentThread (81eb42ba)
81e4afa5        test    byte ptr [eax+4],1
81e4afa9        jne     nt!ExAcquireResourceSharedLite+0x289 (81e4afc5)
//ecx是本线程对象
81e4afab        mov     ecx,dword ptr [esp+10h]
81e4afaf        xor     edx,edx
//线程对象的IoBoostCount
81e4afb1        add     ecx,2A4h
81e4afb7        inc     edx
//IoBoostCount++;
81e4afb8        lock xadd dword ptr [ecx],edx
//OwnerEntry的IoPriorityBoosted置1
81e4afbc        or      dword ptr [eax+4],1
81e4afc0        mov     byte ptr [esp+0Fh],1
81e4afc5        lea     ecx,[esp+24h]
// call KeReleaseInStackQueuedSpinLock
81e4afc9        call    ebx
81e4afcb        cmp     byte ptr [esp+0Fh],1
//根据返回的结果决定是否调用IoBoostThreadIoPriority函数
81e4afd0        jne     nt!ExAcquireResourceSharedLite+0x2a1 (81e4afdd)
81e4afd2        push    2
81e4afd4        push    dword ptr [esp+14h]
81e4afd8        call    nt!IoBoostThreadIoPriority (81e3593e)
//上面说过
81e4afdd        inc     dword ptr [edi+35B4h]
81e4afe3        inc     dword ptr [edi+3584h]
81e4afe9        cmp     byte ptr [esp+0Eh],0
//更加一个全局掩码确认是否调用PerfLogExecutiveResourceAcquire
81e4afee        je      nt!ExAcquireResourceSharedLite+0x2c4 (81e4b000)
81e4aff0        push    dword ptr [esi+24h]
81e4aff3        push    1
81e4aff5        push    esi
81e4aff6        push    10041h
81e4affb        call    nt!PerfLogExecutiveResourceAcquire (81f2541f)
//返回结果
81e4b000        mov     al,1
81e4b002        pop     edi
81e4b003        pop     esi
81e4b004        pop     ebx
81e4b005        mov     esp,ebp
81e4b007        pop     ebp
81e4b008        ret     8

ExAcquireResourceSharedLite汇编代码有点多,但是基本都是些逻辑处理,重要的内容没有多少,下面我简单总结下这个函数的执行流程:
首先这个函数的第一个参数是和一个系统全局资源相关的ERESOURCE数据结构,而在函数的开始处,便对传入的资源请求自旋锁。
请求到自旋锁,就会马上进入一个循环里,在这个循环里面不断尝试请求对这个资源的控制权,直到请求被处理为止才退出循环。
循环的内部,首先根据传入的ERESOURCE结构的ActiveEntries判断当前活动的条目是否存在;如果不存在,那么我们更新这个结构内容,把传入的资源关联到本线程,然后释放请求到的锁,并且根据一个全局掩码来决定是够写入系统日志(关于这个是不是真的是执行更新日志相关内容的操作我不是很确定,所以可能这里表达错误)。
如果当前存在活动的条目,便根据传入资源的Flag成员进行判断,根据Flag的值来决定在接下来的行为;Flag&0x80为真值的话,那么就在这些活动的条目中寻找一个节点返回(调用ExpFindCurrentThread函数完成此任务),如果找到的话,再次判断找到的节点是够关联的是本线程(说明之前本线程可能已经请求过该资源了),是的话,就更新传入的ERESOURCE结构,然后释放对这个结构的锁,中间的流程会根据一个全局掩码值来判断是否把相应操作记入系统日志;不是关联本线程的话,那就说明当前活动条目中不存在当前线程,ExpFindCurrentThread函数执行的时候只是给重新分配了一个可用节点而已,并根据传入的第二个参数决定是否等待此资源可用,然后判断资源的SharedWaiters是否为零,不为零的话,释放资源的锁并更新资源,根据掩码值决定是否计入日志;SharedWaiters为零的话,那就重新执行上述循环,直到找到一个符合条件的节点为止。

Flag&0x80不为真的话,那就直接寻找一个空的条目节点(调用ExpFindEmptyEntry完成此过程),如果没有寻找到,那就重复上面的循环;如果寻找到则根据第二个参数来判断是够等待该资源可用,不等待该资源是否可用的话,那就判断此资源的SharedWaiters成员是否为空,不为空就更新该资源信息,然后释放对该资源的锁,返回就行了;SharedWaiters为空的话,那就重复上述循环,直到找到符号条件的节点为止。

上面就是这个函数的大致流程,实际细节方面没有必要了解多少,主要明白这个函数做什么就行了。
这个函数唯一的任务,是获取当前线程对传入资源的控制权。请求得到许可后,那么当前线程以后对各种资源的访问也就有效进行了。这是操作系统管理公共资源的一种手段,因为资源是比较宝贵的东西,所以制定相关策略使资源被妥善和可控制的利用是很有必要的。所以在gui的各种相关系统服务函数入口都要调用此函数完成上述所说功能。结尾完成的功能我没有分析,不过肯定是释放对系统资源的控制权。

那么,ExAcquireResourceSharedLite函数就分析到这里,没什么用处,只要明白这个函数做的任务就行了,细节可以直接忽视。下一篇中,我们重新转到NtQueryWindow函数里面看看它的执行流程。

新建了一个论坛,希望大家多多光顾。(www.mengwuji.net)
2013-5-29 16:00
0
雪    币: 61
活跃值: (56)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
3
名称:win32子系统之三:ValidateHwnd函数分析
作者:mengwuji
时间:2013.5.28 18:32
链接:http://mengwuji.net/forum.php?mod=viewthread&tid=99&extra=page%3D1

win32k!NtUserQueryWindow:
92dbe20a        mov     edi,edi
92dbe20c        push    ebp
92dbe20d        mov     ebp,esp
92dbe20f        push    esi
92dbe210        push    edi
//分析过EnterSharedCrit函数的用处,主要是为了让当前线程获取对系统资源的控制权
92dbe211        call    win32k!EnterSharedCrit (92dd3141)
//ecx = hwnd
92dbe216        mov     ecx,dword ptr [ebp+8]
//这个函数是验证传入窗口句柄的有效性,那么我们可以看看系统如何判断一个窗口句柄的有效性的(好吧,看来想明白NtUserQueryWindow原理还要等一段时间了。中间调用的函数都够我分析老半天了)
92dbe219        call    win32k!ValidateHwnd (92dd90c0)
............

win32k!ValidateHwnd:
92dd90c0        mov     edi,edi
92dd90c2        push    ebp
92dd90c3        mov     ebp,esp
92dd90c5        sub     esp,0Ch
//edx = [gpsi]        gpsi以前分析过,是个成员为tagSERVERINFO结构的数组,这个结构信息量很多,下面遇到的时候再说
92dd90c8        mov     edx,dword ptr [win32k!gpsi (92f2255c)]
92dd90ce        push    ebx
//和用户层一样的,取句柄低2字节,实际是个下标
92dd90cf        movzx   eax,cx
92dd90d2        push    esi
92dd90d3        push    edi
//先保存下句柄,后面还会用到
92dd90d4        mov     dword ptr [ebp-8],ecx
//tagSERVERINFO偏移4的位置是cHandleEntries,这个应该是窗口句柄的最大个数
92dd90d7        cmp     eax,dword ptr [edx+4]
//大于cHandleEntries就出错了,然后跳到错误处理
92dd90da        jae     win32k!ValidateHwnd+0x15a (92dd921a)
//gSharedInfo是个tagSHAREDINFO结构(以前分析定时器的时候研究过),偏移+8的位置是HeEntrySize,这个应该是描述窗口句柄关联的一个对象尺寸
92dd90e0        mov     esi,dword ptr [win32k!gSharedInfo+0x8 (92f22448)]
//定位到当前句柄所在句柄关联的对象数组里面的偏移
92dd90e6        imul    esi,eax
//gSharedInfo+4是aheList,说明aheList是系统中窗口句柄关联的一个对象数组基址。通过它可以定位到具体句柄关联的对象,句柄关联的对象是HANDLEENTRY结构
92dd90e9        add     esi,dword ptr [win32k!gSharedInfo+0x4 (92f22444)]
//下面开始取窗口句柄的高2字节
92dd90ef        mov     eax,ecx
92dd90f1        shr     eax,10h
//现在的esi经过上面的计算已经知道是个HANDLEENTRY结构,+a位置是wUniq。比较窗口句柄的高2字节和这个值
92dd90f4        cmp     ax,word ptr [esi+0Ah]
//相等跳转
92dd90f8        je      win32k!ValidateHwnd+0x4d (92dd910d)
//不相等比较句柄高两字节是否为零
92dd90fa        test    ax,ax
//为零跳转
92dd90fd        je      win32k!ValidateHwnd+0x4d (92dd910d)
//比较句柄高两字节是够为ffff
92dd90ff        mov     ecx,0FFFFh
92dd9104        cmp     ax,cx
//不等于ffff,到这里说明出错了。那么就执行错误处理
92dd9107        jne     win32k!ValidateHwnd+0x15a (92dd921a)
//HANDLEENTRY的bType是句柄的类型,这个类型和1比较
92dd910d        cmp     byte ptr [esi+8],1
//不等于1说明出错了,跳到错误处理
92dd9111        jne     win32k!ValidateHwnd+0x15a (92dd921a)
//接下来调用PsGetCurrentThreadWin32Thread函数,这个函数很简单,三句代码。我直接放到下面来分析
------------------------------------------------>
//PsGetCurrentThreadWin32Thread
81e9d498        mov     eax,dword ptr fs:[00000124h]        //eax得到当前线程对象
81e9d49e        mov     eax,dword ptr [eax+18Ch]        //+18c位置是Win32Thread
81e9d4a4        ret
<------------------------------------------------
//上面的只是得到当前线程对象的Win32Thread成员,这个成员是个_W32THREAD结构,但是它不够全面描述一个线程gui信息。实际还有一个tagTHREADINFO结构,也是描述Win32Thread这个成员的,tagTHREADINFO结构的信息才是全面的,大家可以用windbg看看(这些结构是之前分析定时器的时候发现的,这里不重复分析具体分析过程)
92dd9117        call    dword ptr [win32k!_imp__PsGetCurrentThreadWin32Thread (92ef4034)]
//ebx = phead,是个句柄关联对象的头
92dd911d        mov     ebx,dword ptr [esi]
//保存当前线程的tagTHREADINFO结构
92dd911f        mov     edi,eax
//保存句柄管理的对象头
92dd9121        mov     dword ptr [ebp-4],ebx
92dd9124        test    ebx,ebx
//对象头等于0跳转,说明发生错误,跳到错误处理位置
92dd9126        je      win32k!ValidateHwnd+0x15a (92dd921a)
//比较句柄关联对象的bFlags成员第0位是否为真
92dd912c        test    byte ptr [esi+9],1
//所关联对象头是个_HEAD结构,但是这个结构貌似也表示的不全面,所以+8位置的值现在还不能确定是什么
92dd9130        mov     eax,dword ptr [ebx+8]
//保存+8位置的值
92dd9133        mov     dword ptr [ebp-0Ch],eax
//bFlags&0x1为真的话就跳转,跳到错误处理
92dd9136        jne     win32k!ValidateHwnd+0x15a (92dd921a)
//上面不知道对象头+8位置是什么东西,下面的比较就暴露了是什么了。是这个对象关联的gui线程的tagTHREADINFO结构,这里进行比较是判断我们传入的句柄,是否是我们这条线程本身创建的窗口
92dd913c        cmp     eax,edi
//是的话就跳转
92dd913e        je      win32k!ValidateHwnd+0xac (92dd916c)
//对象头+c,我们看下一句就能明白这个地方是什么了
92dd9140        mov     ecx,dword ptr [ebx+0Ch]
//tagTHREADINFO+0xC8位置是rpdesk成员,它是一个tagDESKTOP对象,是桌面相关的数据结构,那么上面的对象头+c位置就是这个句柄所在的桌面了,下面这个比较就是比较这个句柄是否在当前桌面(因为windows支持多个桌面的!)
92dd9143        cmp     ecx,dword ptr [edi+0C8h]
//是当前桌面的句柄,就跳转
92dd9149        je      win32k!ValidateHwnd+0xac (92dd916c)
//如果不是当前桌面的句柄,就比较看看tagTHREADINFO的TIF_flags第三位是否为真
92dd914b        test    byte ptr [edi+0D8h],4
92dd9152        jne     win32k!ValidateHwnd+0xac (92dd916c)
//tagTHREADINFO的ppi成员,这个成员是一个tagPROCESSINFO对象,这个对象是当前线程所在进程关联的tagPROCESSINFO对象
92dd9154        mov     edx,dword ptr [edi+0B8h]
//push        rpdesk
92dd915a        push    ecx
//push        ppi
92dd915b        push    edx
//call        GetDesktopView,我们在下面看看GetDesktopView的流程,很简单所以直接放到这里看!
-------------------------------------------------------------->
//GetDesktopView
92dcf9aa        mov     edi,edi
92dcf9ac        push    ebp
92dcf9ad        mov     ebp,esp
92dcf9af        mov     eax,dword ptr [ebp+8]                        //ppi
//pdvList,是个tagDESKTOPVIEW结构,这个结构是系统所有桌面组成的一个单链表
92dcf9b2        mov     eax,dword ptr [eax+154h]
92dcf9b8        jmp     win32k!GetDesktopView+0x1a (92dcf9c4)        //这里进行一个简单的循环
92dcf9ba        mov     ecx,dword ptr [eax+4]
92dcf9bd        cmp     ecx,dword ptr [ebp+0Ch]
92dcf9c0        je      win32k!GetDesktopView+0x1e (92dcf9c8)
92dcf9c2        mov     eax,dword ptr [eax]
92dcf9c4        test    eax,eax
92dcf9c6        jne     win32k!GetDesktopView+0x10 (92dcf9ba)
92dcf9c8        pop     ebp
92dcf9c9        ret     8
<--------------------------------------------------------------
//GetDesktopView函数进行一下简单的遍历工作,找到传入句柄所关联的一个tagDESKTOPVIEW对象,实际调用此函数的目的只是为了证明这个句柄是在某个桌面中的,如果一个桌面都没有这个窗口,那么肯定就出错了
92dd915c        call    win32k!GetDesktopView (92dcf9aa)
92dd9161        test    eax,eax
//比较查找的是够为零,是的话,就出错了,然后退出
92dd9163        je      win32k!ValidateHwnd+0x15a (92dd921a)
//epb-c保存的是当前句柄关联线程的tagTHREADINFO结构
92dd9169        mov     eax,dword ptr [ebp-0Ch]
//一个全局标记,目前不知道做什么
92dd916c        cmp     byte ptr [win32k!gbValidateHandleForIL (92f21f38)],0
根据这个值执行不同两个流程
92dd9173        je      win32k!ValidateHwnd+0x10f (92dd91cf)
//句柄关联的tagTHREADINFO为零跳转
92dd9175        test    eax,eax
92dd9177        je      win32k!ValidateHwnd+0x10f (92dd91cf)
//ebx = 传入句柄的ppi
92dd9179        mov     ebx,dword ptr [eax+0B8h]
//eax = 当前线程的ppi
92dd917f        mov     eax,dword ptr [edi+0B8h]
//ecx = 传入句柄的pvwplWndGCList
92dd9185        mov     ecx,dword ptr [ebx+1ACh]
//edx = 当前线程的pvwplWndGCList
92dd918b        mov     edx,dword ptr [eax+1ACh]
92dd9191        push    ecx
92dd9192        push    edx
//这个函数比较简单,我们在下面直接分析
--------------------------------------------------------->
win32k!CheckAccessForIntegrityLevel:
92dd9232        mov     edi,edi
92dd9234        push    ebp
92dd9235        mov     ebp,esp
92dd9237        cmp     dword ptr [win32k!gbEnforceUIPI (92f21f3c)],0        //未知的全局变量
92dd923e        jne     win32k!CheckAccessForIntegrityLevel+0x13 (92dd9245)
//到这里的话就返回1
92dd9240        xor     eax,eax
92dd9242        inc     eax
92dd9243        jmp     win32k!CheckAccessForIntegrityLevel+0x1c (92dd924e)
92dd9245        mov     eax,dword ptr [ebp+8]        //当前线程的pvwplWndGCList
92dd9248        cmp     eax,dword ptr [ebp+0Ch]        //传入句柄的pvwplWndGCList,相等cf为1,不相等cf为0
92dd924b        sbb     eax,eax                        //如果相等,后面的操作执行后返回零,否则返回1
92dd924d        inc     eax
92dd924e        pop     ebp
92dd924f        ret     8
<---------------------------------------------------------
//这个函数比较传入的两个参数是否相等,不相等的话返回零,相等的话返回一
92dd9193        call    win32k!CheckAccessForIntegrityLevel (92dd9232)
92dd9198        test    eax,eax
92dd919a        jne     win32k!ValidateHwnd+0x10c (92dd91cc)
//eax = 传入句柄的进程对象
92dd919c        mov     eax,dword ptr [ebx]
//比较这个进程对象是不是csrss.exe进程的
92dd919e        cmp     eax,dword ptr [win32k!gpepCSRSS (92f225ec)]
//是的话跳转
92dd91a4        je      win32k!ValidateHwnd+0x10c (92dd91cc)
//下面直到92dd91cb都是错误处理,我也懒得看了
92dd91a6        movzx   ecx,byte ptr [esi+8]
92dd91aa        mov     edx,dword ptr [ebp-8]
92dd91ad        mov     eax,dword ptr [edi+0B8h]
92dd91b3        push    ecx
92dd91b4        push    edx
92dd91b5        push    ebx
92dd91b6        push    eax
92dd91b7        call    win32k!EtwTraceUIPIHandleValidationError (92e588d8)
92dd91bc        push    5
92dd91be        call    win32k!UserSetLastError (92d9769d)
92dd91c3        pop     edi
92dd91c4        pop     esi
92dd91c5        xor     eax,eax
92dd91c7        pop     ebx
92dd91c8        mov     esp,ebp
92dd91ca        pop     ebp
92dd91cb        ret
//传入句柄关联的那个对象头给ebx
92dd91cc        mov     ebx,dword ptr [ebp-4]
//+D8是当前线程的TIF_flags
92dd91cf        test dword ptr [edi+0D8h],20000000h
//根据比较后选择跳转
92dd91d9        je      win32k!ValidateHwnd+0x151 (92dd9211)
//ecx = 当前线程的ppi
92dd91db        mov     ecx,dword ptr [edi+0B8h]
//edx = 当前进程的pW32Job
92dd91e1        mov     edx,dword ptr [ecx+170h]
//eax = 当前进程pW32JOB的restrictions
92dd91e7        mov     eax,dword ptr [edx+0Ch]
//看第零位是否存在
92dd91ea        and     eax,1
//存在就返回
92dd91ed        je      win32k!ValidateHwnd+0x151 (92dd9211)
//eax是传入的句柄
92dd91ef        mov     eax,dword ptr [ebp-8]
//esi是句柄关联的对象
92dd91f2        push    esi
92dd91f3        push    eax
//调用IsHandleEntrySecure函数,这个函数是检测句柄的安全性,具体我也不分析了,有兴趣的人可以看看。
92dd91f4        call    win32k!IsHandleEntrySecure (92e38dc2)
92dd91f9        test    eax,eax
92dd91fb        jne     win32k!ValidateHwnd+0x151 (92dd9211)
92dd91fd        push    578h
92dd9202        call    win32k!UserSetLastError (92d9769d)
92dd9207        mov     dword ptr [ebp-4],0
92dd920e        mov     ebx,dword ptr [ebp-4]
92dd9211        pop     edi
92dd9212        pop     esi
92dd9213        mov     eax,ebx
92dd9215        pop     ebx
92dd9216        mov     esp,ebp
92dd9218        pop     ebp
92dd9219        ret
92dd921a        push    578h
92dd921f        call    win32k!UserSetLastError (92d9769d)
92dd9224        pop     edi
92dd9225        pop     esi
92dd9226        xor     eax,eax
92dd9228        pop     ebx
92dd9229        mov     esp,ebp
92dd922b        pop     ebp
92dd922c        ret

//ValidateHwnd这个函数无非检测句柄的合法性,通过各种判断然后确定传入的句柄是否有效。至于怎么判断的,汇编代码里面有详细的注释了。值得注意的是tagTHREADINFO是关于gui线程的一个数据结构,tagPROCESSINFO是关于gui进程的一个数据结构。然后遍历窗口句柄实际可以通过gSharedInfo全局结构来遍历,还能得到关于这个句柄对应的线程进程信息,和句柄所对应的句柄对象都可以。总之,这里面涉及的一些数据结构能掌握或者了解就相当不错了。

希望大家耐心看看,我不想在这里反汇编成c代码是因为提升效率。因为这个函数分析完成只是开始,后面还有更加多的内容在等待着我们。
我只起一个引导作用,接下来就看大家各自发挥吧......

ps:www.mengwuji.net
2013-5-29 16:02
0
雪    币: 61
活跃值: (56)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
4
名称:win32子系统之四:NtUserQueryWindow函数分析
作者:mengwuji
时间:2013.5.29 14:49
链接:http://mengwuji.net/forum.php?mod=viewthread&tid=103&extra=page%3D1

接着上一篇,我们看看这一篇内容

win32k!NtUserQueryWindow:
92dbe20a        mov     edi,edi
92dbe20c        push    ebp
92dbe20d        mov     ebp,esp
92dbe20f        push    esi
92dbe210        push    edi
//分析过EnterSharedCrit函数的用处,主要是为了让当前线程获取对系统资源的控制权
92dbe211        call    win32k!EnterSharedCrit (92dd3141)
//ecx = hwnd
92dbe216        mov     ecx,dword ptr [ebp+8]
//这个函数是验证传入窗口句柄的有效性,已经分析过了, 只要它返回有值,那就证明此句柄是“有效”的,至于什么才是判断一个句柄“有效”的凭据呢,认真看过我对ValidateHwnd分析的人应该已经知道。
92dbe219        call    win32k!ValidateHwnd (92dd90c0)
92dbe21e        mov     edi,eax
92dbe220        test    edi,edi
//句柄无效则直接跳到退出
92dbe222        je      win32k!NtUserQueryWindow+0x102 (92dbe30c)
//这个是查询号
92dbe228        mov     ecx,dword ptr [ebp+0Ch]
//edi是ValidateHwnd的返回结果,ValidateHwnd函数的返回结果是句柄所关联的_HANDLEENTRY对象的phead成员,也就是这个对象头部,数据结构是_HEAD,由于_HEAD所表述的信息本身就不完整,我在别的地方分析的时候发现一个_THRDESKHEAD结构和这个十分相似,至少目前我可以确定_HANDLEENTRY不过是体现了_THRDESKHEAD结构的一部分内容罢了,但是通过对ValidateHwnd的分析可以知道一些它不完整的信息;+8位置是一个句柄关联的tagTHREADINFO结构,所以下面的eax = 本句柄的tagTHREADINFO结构
92dbe22b        mov     eax,dword ptr [edi+8]
//比较查询号
92dbe22e        cmp     ecx,9
//看来查询号不能超过9,说明有10种类型
92dbe231        ja      win32k!NtUserQueryWindow+0x102 (92dbe30c)
//这个是switch语句了,通过查询号的具体指判断应该跳到哪儿去
92dbe237        jmp     dword ptr win32k!NtUserQueryWindow+0x114 (92dbe31b)[ecx*4]

//下面为不同的查询号划分了一下所对应的数据块,让本身有点麻烦的函数顿时变得简单了,那么希望接下来的分析能一帆风顺的就万事大吉了!
//查询号为1到来的case块
//这个就再好分析不过了
//既然知道了edi是_THRDESKHEAD结构,那我们当然希望用完整的结构了。第一个成员便是KTHREAD这个结构体
92dbe23e        push    dword ptr [eax]
//PsGetThreadProcessId这个函数很简单,我们就在这里分析它
------------------------------------------------------->
nt!PsGetThreadProcessId:
81ed4461        mov     edi,edi
81ed4463        push    ebp
81ed4464        mov     ebp,esp
81ed4466        mov     eax,dword ptr [ebp+8]
81ed4469        mov     eax,dword ptr [eax+22Ch]        //KTHREAD的22c偏移处就是进程的id
81ed446f        pop     ebp
81ed4470        ret     4
<-------------------------------------------------------
//说明调用这个函数的返回值是句柄关联的线程id
92dbe240        call    dword ptr [win32k!_imp__PsGetThreadProcessId (92ef40a8)]
92dbe246        mov     esi,eax
//这里跳到退出。说明功能号为1表示查询创建传入句柄的那个进程id(根据前面的用户层分析,这明显是2号功能的作用嘛)
92dbe248        jmp     win32k!NtUserQueryWindow+0x104 (92dbe30e)

//查询号为0到来的case块
//好了,到这里就是头痛的开始了。为什么这么说呢,看edi是什么?edi前面说过是一个_THRDESKHEAD结构,但是不巧的是这个结构根本就没有+AC偏移位置的成员,或者说这个结构没这么大!这可怎么是好呢?那么,有两种结论可以参考;第一种是这个结构就像_HANDLEENTRY或者_W32THREAD结构一样是不完整的。第二种是这个结构包含在某个结构中的,就像我们知道KTHREAD结构,如果不知道RTHREAD结构那么超出KTHREAD的范围我们根本也就不知道了。那么第一种我感觉不太靠谱,因为微软除非有毛病,或者win32子系统真的复杂到要分很多很多层次结构来设计,不然我真想不通一个结构为何要分成几个不同的数据结构表示!那么第二种呢,我感觉这个希望很大,尤其是我想到KTHREAD和ETHREAD的关系时更加认定了这一种(毕竟符合微软的设计风格嘛)。那接下来,在参看了一些其他函数后,我的结论被证实了,我又找到了一个结构,这个结构式tagWND!看到它我开始兴奋了,因为之前研究窗口句柄所对应的内核对象的数据结构时,发现这个内核对象就是tagWND!那么我们看看tagWND这个结构的+AC处是什么。+AC处是一些标志,ExStyle可以认为是窗口的类型。ExStyle&400h可以得到bConsoleWindow,为真说明是个控制台窗口
92dbe24d        test dword ptr [edi+0ACh],400h
//不是控制台就转到1号功能去执行.....
92dbe257        je      win32k!NtUserQueryWindow+0x34 (92dbe23e)
//到这里来我的思绪开始更加混乱了。因为嘛,tagWND就没有+B0成员!难道我分析错了?那这个错误可真能让人受的......可是在分析后发现貌似我没分析错呀,或者我的错误我还没发现!那么在我没发现我分析错误之前,我就姑且认为它是正确的吧,那+B0不就无法解释了吗?是的,我暂时解释不了,只能推测这个tagWND或者分为图形界面的tagWND和控制台窗口的tagWND,当是控制台的时候tagWND貌似后面还有什么其他附加信息,这些附加信息是什么呢?只能通过分析控制台窗口的初始化时大概能搞清楚。那么至少+B0位置通过用户层调用这个功能号返回的结果看,B0位置是句柄所在进程的id了。这里微软不公开控制台的tagWND完整结构,那么我也就不好怎么试验我的推测了,在以后的编程实践中再来验证我说的对错吧。
92dbe259        mov     esi,dword ptr [edi+0B0h]
//查询到了就退出了
92dbe25f        jmp     win32k!NtUserQueryWindow+0x104 (92dbe30e)

//查询号为2到来的case块
//这个在用户层分析中是来返回创建句柄的线程id的,这里又是判断是否控制台窗口
92dbe264        test dword ptr [edi+0ACh],400h
//不是的话跳转
92dbe26e        je      win32k!NtUserQueryWindow+0x71 (92dbe27b)
//是的话那么B4位置记录了控制台窗口的线程id........
92dbe270        mov     esi,dword ptr [edi+0B4h]
//控制台窗口返回线程id,退出
92dbe276        jmp     win32k!NtUserQueryWindow+0x104 (92dbe30e)
//得到KTHREAD
92dbe27b        push    dword ptr [eax]
//这个函数更加简单,我们看看
----------------------------------------------------------->
nt!PsGetThreadId:
81ed4876        mov     edi,edi
81ed4878        push    ebp
81ed4879        mov     ebp,esp
81ed487b        mov     eax,dword ptr [ebp+8]
81ed487e        mov     eax,dword ptr [eax+230h]        //传入KTHREAD关联的线程id
81ed4884        pop     ebp
81ed4885        ret     4
<-----------------------------------------------------------
//得到线程id,退出返回
92dbe27d        call    dword ptr [win32k!_imp__PsGetThreadId (92ef40a4)]
92dbe283        jmp     win32k!NtUserQueryWindow+0x3c (92dbe246)

//查询号为3到来的case块
//好了,我们知道eax是tagTHREADINFO结构,+BC是pq成员,这个成员结构是tagQ
92dbe285        mov     eax,dword ptr [eax+0BCh]
//tagQ这个结构+28是spwndActive,是个tagWND。看名字知道大概是前一个获取窗口对象
92dbe28b        mov     eax,dword ptr [eax+28h]
//上一个获取窗口对象否为零
92dbe28e        test    eax,eax
//为零就直接退出
92dbe290        je      win32k!NtUserQueryWindow+0x102 (92dbe30c)
//第一个成员是_THRDESKHEAD结构的成员,_THRDESKHEAD结构的第一个是h,是个PVOID类型,这里可以理解为hwnd?
92dbe292        mov     esi,dword ptr [eax]
//返回,3号功能应该是查询上个活动的窗口句柄
92dbe294        jmp     win32k!NtUserQueryWindow+0x104 (92dbe30e)

//查询号为4到来的case块
//这个我懒的分析了,看到+bc,+24让我看到了三号功能号的影子,具体做什么的我还没猜出来,不过对我们理解完全不造成任何障碍
92dbe296        mov     eax,dword ptr [eax+0BCh]
92dbe29c        mov     eax,dword ptr [eax+24h]
92dbe29f        jmp     win32k!NtUserQueryWindow+0x84 (92dbe28e)

//查询号为5到来的case块
//好吧,这里的edi即是_HEAD类型,也是_THRDESKHEAD类型,或者是tagWND类型,具体哪种看下面调用的函数怎么使用这个结构了
92dbe2a1        push    edi
//IsGhostWindow函数很简单,我们看看下面分析
---------------------------------------------------->
win32k!IsGhostWindow:
92da3f54        mov     edi,edi
92da3f56        push    ebp
92da3f57        mov     ebp,esp
92da3f59        mov     eax,dword ptr [ebp+8]
92da3f5c        movzx   eax,word ptr [eax+2Ah]        //这里明显传入的参数是tagWND类型,+2A是fnid,干什么的我也不知道
92da3f60        and     eax,0FFFF3FFFh
92da3f65        sub     eax,2AAh
92da3f6a        neg     eax
92da3f6c        sbb     eax,eax
92da3f6e        inc     eax        //返回真或假......
92da3f6f        pop     ebp
92da3f70        ret     4
<----------------------------------------------------
//判断是不是Ghost窗口,什么是Ghost的窗口我小白....不懂,难道是安装系统时候显示的那个Ghost?
92dbe2a2        call    win32k!IsGhostWindow (92da3f54)
92dbe2a7        test    eax,eax
//是的话,跳转
92dbe2a9        je      win32k!NtUserQueryWindow+0xa6 (92dbe2b0)
92dbe2ab        xor     esi,esi
92dbe2ad        inc     esi
//不是跳转退出
92dbe2ae        jmp     win32k!NtUserQueryWindow+0x104 (92dbe30e)
//push tagWND
92dbe2b0        push    edi
//又是调用一个函数判断啥啥啥的,哎,大家可以去分析下,这东西我都分析腻了.....
92dbe2b1        call    win32k!IsHungWindow (92dcc2a4)
92dbe2b6        mov     esi,eax
92dbe2b8        test    esi,esi
//好吧,这个艰难的功能号也是这个函数里面判断最多的功能号有兴趣的可以分析下,我完全没兴趣!(都是体力活,没难点,难点都被我分析的123了)
92dbe2ba        je      win32k!NtUserQueryWindow+0x104 (92dbe30e)
92dbe2bc        push    edi
92dbe2bd        call    win32k!ShouldProcessHungWindow (92e0a38b)
92dbe2c2        test    eax,eax
92dbe2c4        je      win32k!NtUserQueryWindow+0x104 (92dbe30e)
92dbe2c6        mov     edi,dword ptr [eax]
92dbe2c8        call    win32k!LeaveSharedEnterCrit (92e02695)
92dbe2cd        mov     dl,1
92dbe2cf        mov     ecx,edi
92dbe2d1        call    win32k!HMValidateHandleNoSecure (92dc87c3)
92dbe2d6        test    eax,eax
92dbe2d8        je      win32k!NtUserQueryWindow+0xd6 (92dbe2e0)
92dbe2da        push    eax
92dbe2db        call    win32k!ProcessHungWindow (92e0c43f)
92dbe2e0        call    win32k!LeaveCritEnterShared (92e026b8)
92dbe2e5        jmp     win32k!NtUserQueryWindow+0x104 (92dbe30e)

//查询号为7到来的case块
//不知所谓的功能,但是不影响理解意思.....
92dbe2e7        mov     eax,dword ptr [eax+0BCh]
92dbe2ed        xor     ecx,ecx
92dbe2ef        cmp     eax,dword ptr [win32k!gpqForeground (92f24e60)]
92dbe2f5        sete    cl
92dbe2f8        mov     esi,ecx
92dbe2fa        jmp     win32k!NtUserQueryWindow+0x104 (92dbe30e)

//查询号为8到来的case块
//+164是个spwndDefaultIme,IME让我想起了输入法?好吧,我不清楚。。。。。
92dbe2fc        mov     eax,dword ptr [eax+164h]
92dbe302        jmp     win32k!NtUserQueryWindow+0x84 (92dbe28e)

//查询号为9到来的case块
//spDefaultImc更加不知所云。。。谁有兴趣谁看去!  这里不理解阅读
92dbe304        mov     eax,dword ptr [eax+168h]
92dbe30a        jmp     win32k!NtUserQueryWindow+0x84 (92dbe28e)

//查询号为6到来的case块
92dbe30c        xor     esi,esi
//好吧,亲爱的你终于退出了.....
92dbe30e        call    win32k!UserSessionSwitchLeaveCrit (92dc356f)
92dbe313        pop     edi
92dbe314        mov     eax,esi
92dbe316        pop     esi
92dbe317        pop     ebp
92dbe318        ret     8

这个函数内容不多,但是涉及win32子系统的数据结构很多,也非常复杂,希望大家跟着我的分析自己在windows7下面去看看。
相信你通过我的这个分析结果,自己都能写出来一个这个函数了(至少我现在写出来完全没有障碍....)!

那么,为什么说了这么多,最终想干什么呢?实际就是想告诉大家win32子系统内部的一些数据结构而已,这些数据结构对于管理和维和win32子系统相当重要,所以有必要好好理解。你可以用这些数据结构去做好多好多事情,比如枚举进程(这个我想比你暴力枚举还要实际的多,因为没有哪个进程没窗口的。就算你摘掉了tagWND链表中的自己,那么你也休想逃过gSharedInfo+4位置保存的所有关于句柄的数组!就算你逃过了,那你要考虑的问题将是特别特别多,那这个算不算枚举进程最好的办法呢?在我目前的认知里我觉得是.....)。

好了,本篇结束,win32子系统入门的差不多了,后面我有时间的话就讲讲入门后能做些什么吧。

ps:截止现在,win32子系统系列是我花了两天时间分析出来的,感觉还是不太好分析的。因为win32子系统参考资料很少,所以什么东西你都要亲力亲为。后面我关于它的研究会放慢很对,因为我还要建设好论坛和出驱动方面的视频教程.....最后,希望大家多多支持(www.mengwuji.net)!
2013-5-29 16:06
0
雪    币: 47147
活跃值: (20485)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
5
是你本人写的文章吗?怎么还转帖了
2013-5-29 16:37
0
雪    币: 61
活跃值: (56)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
6
是我本人写的,不过由于我先公布到我的论坛了,所以不敢在这里写成原创的了......

原创的概念是出处原创还是又本人实现分析的才叫远程?
那我还是改成远程吧.....
2013-5-29 17:07
0
雪    币: 47147
活跃值: (20485)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
7
将4个帖合并成一帖了。
2013-5-30 14:59
0
雪    币: 61
活跃值: (56)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
thank you!
2013-5-30 18:18
0
雪    币: 371
活跃值: (72)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
9
前排膜拜精品~~~
2013-5-30 20:11
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
很有用,多谢楼主分享
2013-5-31 13:17
0
雪    币: 43
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
国人加油啊,绝对精华。大牛们支持无极深入研究。
2013-8-29 13:46
0
雪    币: 623
活跃值: (40)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
12
make
2013-8-29 14:19
0
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
额,汇编语言不太明白,,底层的东西还需要学习啊。楼主真辛苦啊,写这么长。。。等以后功力长进了再来膜拜
2013-8-29 14:46
0
雪    币: 494
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
无极普通会员了,俺还初级
2013-8-29 14:48
0
雪    币: 1594
活跃值: (113)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
标记下,需要在过来看
2013-12-13 14:07
0
游客
登录 | 注册 方可回帖
返回
//