Quantcast
Channel: 看雪安全论坛
Viewing all articles
Browse latest Browse all 9556

【原创】win32子系统之一:用户层句柄分析

$
0
0
名称: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位系统

Viewing all articles
Browse latest Browse all 9556

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>