2023年12月17日发(作者:)

从崩溃分析到逆向工程---系统高级诊断技巧

前言:

系统在日常的使用中时不时的会罢工,死机,蓝屏,中毒等等.给我们在使用中添加了很多的不便,于是,就有了系统诊断技术,从最初的系统异常行为分析到崩溃转储分析,可以让我们深入到系统罢工的真正原因!!!!本文以此为题,结合作者的实验成果编写而成.如果你是那些不喜欢拘泥于表面现象,喜欢探索本源的技术人员,系统管理员,ITpro或是电脑发烧友,那么本人就是为你而写!!!

本文是在读者能够较为熟悉系统原理和对WinDbg基本操作的情况为前提的,如果你在这方面还是有所欠缺的话呢,请参阅文章:/?tid=55125&highlight=

正文:

最近到手一个BSOD demo,于是拿来测试,这个东东是去年BlackHat大会上Alex

Ionescu发布的一个windows的bug,这个bug不仅影响Windows XP,并且在Vista

SP1/Windows Server 2008中也依然存在。不过根据本人的测试,这个bug在windows

7中已被修复。

这个软体本身本身并不会传播,也不会盗取信息,其作用仅仅是蓝屏而已。顶多算个DoS软体并且并非所有蓝屏皆是由于此程序导致的。蓝屏的提示的错误信息为:INVALID_KERNEL_HANDLE

那么INVALID_KERNEL_HANDLE是什么意思呢?

我们可以用内核调试器WinDbg,输入命令:

1. .hh bug check 0x93

复制代码

,这个命令会转到WInDbg的帮助文件的bug check 0x93处如图:

运行!analyze -v后的显示信息

1.

2.

kd> !analyze -v

*******************************************************************************

3. *

*

4. * Bugcheck

Analysis

*

5. *

*

6. *******************************************************************************

7.

8.

9.

INVALID_KERNEL_HANDLE (93)

This message occurs if kernel code (server, redirector, other driver,

etc.)attempts to close a handle that is not a valid handle.

10. Arguments:

11. Arg1: 00000038, The handle that NtClose was called with.(NtClose函数调用句柄)

12. Arg2: 00000000, means a protected handle was closed.(意味着被保护的句柄被关闭)

13. Arg3: 00000000

14. Arg4: 00000000

复制代码

这里的信息可以省去打开帮助文件来查找同样信息的麻烦,有时候,这些文本描述给出了有关诊断步骤的建议。

1.

2.

3.

4.

5.

6.

7.

8.

9.

STACK_TEXT:

b9f0ec30 808da689 00000093 00000038 00000000 nt!KeBugCheckEx+0x1b

b9f0ec5c 808da783 e32f73d0 85ce2738 00000038 nt!ObpCloseHandleTableEntry+0xf1

b9f0eca4 808da8bb 00000038 00000000 00000000 nt!ObpCloseHandle+0x87

b9f0ecb8 80866428 00000038 b9f0ed44 80826f6d nt!NtClose+0x1d

b9f0ecb8 80826f6d 00000038 b9f0ed44 80826f6d nt!KiFastCallEntry+0xf8

b9f0ed34 bf8b44b8 00000038 00000030 b9f0ed58 nt!ZwClose+0x11

b9f0ed44 bf8b44f7 00000038 0012ff70 85ce2738 win32k!_CloseWindowStation+0x1e

b9f0ed58 80866428 00000038 0012ffc0 7c90e514

win32k!NtUserCloseWindowStation+0x29

10. b9f0ed58 7c90e514 00000038 0012ffc0 7c90e514 nt!KiFastCallEntry+0xf8

11. WARNING: Frame IP not in any known module. Following frames may be wrong.

12. 0012ffc0 00000000 00000000 00000000 00000000 0x7c90e514

复制代码

上面的栈痕迹显示了可执行的映像,先是调用了KiFastCallEntry这个NativeAPI进入内核,然后调用中的NtUserCloseWindowStation函数,而NtUserCloseWindowStation又依次调用了中的_CloseWindowStation等等,直到最后的ObpCloseHandleTableEntry函数执行后导致KeBugCheckEx从而BSOD。上面是程序调用函数的流程,比较的直观!!!(模块"nt"是Ntoskrnl)

这里顺带的说明一下,在编程的过程中,程序员所使用的应用层API实际上最终还是要使用系统层的API来实现相关的操作的,这层API被称为Native API.下图的Windows API架构图会给你直白的说明.

运行

1. !process

复制代码

查看系统崩溃时的进程信息,显示是文件,这是我们的BSOD测试文件.

1.

2.

kd> !process

GetPointerFromAddress: unable to read from 808817d4

3.

4.

PROCESS 83f90020 SessionId: none Cid: 0b90 Peb: 7ffdd000 ParentCid: 077c

DirBase: 0b831300 ObjectTable: e32f73d0 HandleCount:

Accessible>

5.

6.

7.

8.

9.

Image:

VadRoot 85d08290 Vads 38 Clone 0 Private 73. Modified 2. Locked 0.

DeviceMap e265e568

Token e29d8030

ReadMemory error: Cannot get nt!KeMaximumIncrement value.

10. ffdf0000: Unable to get shared data

11. ElapsedTime 00:00:00.000

12. UserTime 00:00:00.000

13. KernelTime 00:00:00.000

14. QuotaPoolUsage[PagedPool] 28396

15. QuotaPoolUsage[NonPagedPool] 1520

16. Working Set Sizes (now,min,max) (347, 50, 345) (1388KB, 200KB, 1380KB)

17. PeakWorkingSetSize 349

18. VirtualSize 13 Mb

19. PeakVirtualSize 13 Mb

20. PageFaultCount 346

21. MemoryPriority BACKGROUND

22. BasePriority 8

23. CommitCharge 110

24.

25. THREAD 86188440 Cid 0b90.08e0 Teb: 7ffdf000 Win32Thread: e3c004a0

RUNNING on processor 0

复制代码

运行

1. !vm

复制代码

查看系统是否耗尽了虚拟内存。

1.

2.

3.

4.

5.

6.

7.

8.

9.

kd> !vm

*** Virtual Memory Usage ***

GetUlongFromAddress: unable to read from 808817c8

Physical Memory: 0 ( 0 Kb)

GetUlongFromAddress: unable to read from 80881300

************ NO PAGING FILE *********************

10. 80881220: Unable to get paged pool info

11. GetUlongPtrFromAddress: unable to read from 80872f88

12. GetUlongPtrFromAddress: unable to read from 808815ec

13. GetPointerFromAddress: unable to read from 808812c4

14. GetPointerFromAddress: unable to read from 808754c8

15. GetUlongFromAddress: unable to read from 8087ce40

16. GetPointerFromAddress: unable to read from 8087cdb4

17. GetUlongFromAddress: unable to read from 8087cb94

18. GetUlongFromAddress: unable to read from 80872f10

19. GetUlongFromAddress: unable to read from 80872f20

20. GetUlongFromAddress: unable to read from 808817bc

21. GetUlongFromAddress: unable to read from 8088177c

22. GetUlongFromAddress: unable to read from 8087c9f0

23. GetUlongFromAddress: unable to read from 8087c840

24. GetUlongFromAddress: unable to read from 8087c83c

25. GetUlongFromAddress: unable to read from 8087c844

26. GetUlongFromAddress: unable to read from 8087c840

27. GetUlongFromAddress: unable to read from 8087c83c

28. GetUlongFromAddress: unable to read from 8087ca9c

29. GetUlongPtrFromAddress: unable to read from 80873be4

30. GetUlongPtrFromAddress: unable to read from 80875540

31. GetUlongFromAddress: unable to read from 8087ca58

32. GetUlongFromAddress: unable to read from 8087ca40

33. Error reading free nonpaged PTEs 8087c9f4

34. GetUlongFromAddress: unable to read from 8087ca50

35. Available Pages: 0 ( 0 Kb)

36. ResAvail Pages: 0 ( 0 Kb)

37.

38. ********** Running out of physical memory **********

39.

40. Locked IO Pages: 0 ( 0 Kb)

41. Free System PTEs: 0 ( 0 Kb)

42.

43. ********** Running out of system PTEs **************

44.

45. GetUlongFromAddress: unable to read from 8087c9d8

46. GetUlongFromAddress: unable to read from 8087cbf0

47. Free NP PTEs: 0 ( 0 Kb)

48. Free Special NP: 0 ( 0 Kb)

49. Modified Pages: 0 ( 0 Kb)

50. Modified PF Pages: 0 ( 0 Kb)

51. 808843c0: Unable to get pool descriptor

52. GetUlongFromAddress: unable to read from 808738b0

53. NonPagedPool Usage: 0 ( 0 Kb)

54. NonPagedPool Max: 0 ( 0 Kb)

55. GetUlongFromAddress: unable to read from 808738ac

56. PagedPool Usage: 0 ( 0 Kb)

57. PagedPool Maximum: 0 ( 0 Kb)

58. GetUlongFromAddress: unable to read from 808853e8

59. Unable to read _LIST_ENTRY @ 808811f8

60. Session Commit: 0 ( 0 Kb)

61. Shared Commit: 0 ( 0 Kb)

62. Special Pool: 0 ( 0 Kb)

63. Shared Process: 0 ( 0 Kb)

64. PagedPool Commit: 0 ( 0 Kb)

65. Driver Commit: 0 ( 0 Kb)

66. Committed pages: 146662 ( 586648 Kb)

67. Commit limit: 0 ( 0 Kb)

68.

69. ********** Number of committed pages is near limit ********

70. GetUlongFromAddress: unable to read from 8087cab8

71. GetUlongFromAddress: unable to read from 8087cabc

72.

73. Unable to read/NULL value _LIST_ENTRY @ 80882f58

74.

75. ProcessCommitUsage could not be calculated

复制代码

这一项命令卡伊查看该系统是否耗尽了虚拟内存,换页内存池或非换页内存池.如果虚拟内存被耗尽的话呢,则以提交的页面将接近提交限制,所以你可以检查进程列表,看哪一个进程报告了很高的提交内存量,以期望找到一个可能的内存泄漏错误!!!从上面的信息可以得知,这个bug没有引起内存池的泄露.

运行

1. lm kv m win32k

复制代码

命令可确定驱动程序的安装的版本和文件的其他详细信息。不过如果版本信息没有出现的话,那么很可能就是在崩溃时这些信息可能被换出物理内存了,这样的话只有从映像文件的属性来查看了!!!

1.

2.

3.

kd> lm kv m win32k

start end module name

bf800000 bf9c2d80 win32k (pdb

symbols) c:

4.

5.

6.

7.

Loaded symbol image file:

Mapped memory image file: c:

Image path:

Image name:

8.

9.

Timestamp: Fri Apr 17 17:58:42 2009 (49E852D2)

CheckSum: 001D0731

10. ImageSize: 001C2D80

11. File version: 5.1.2600.3556

12. Product version: 5.1.2600.3556

13. File flags: 0 (Mask 3F)

14. File OS: 40004 NT Win32

15. File type: 3.7 Driver

16. File date: 00000000.00000000

17. Translations: 0410.04b0

18. CompanyName: Microsoft Corporation

19. ProductName: Sistema operativo Microsoft? Windows?

20. InternalName:

21. OriginalFilename:

22. ProductVersion: 5.1.2600.3556

23. FileVersion: 5.1.2600.3556 (xpsp_sp2_gdr.090417-1237)

24. FileDescription: Driver Win32 multiutente

25. LegalCopyright: ? Microsoft Corporation. Tutti i diritti riservati.

复制代码

显示了完整的驱动信息,有过编程经验的人都知道是Windows XP的多用户管理驱动文件,就网上的FAQ文档来看到话,大部分的Win32k BSOD都是硬件或者是软体丢失篡改所引起的,但是通过上述对栈痕迹的分析又不像是这么回事,那么该怎么办呢?

没办法了,逆向分析吧,逆向分析简单说就是通过逆向工具反汇编事故软体,通过分析软体的反汇编代码来确定软体的运行流程,这个事故软体也就是几十kb大小,应该不难分析,ok,打开IDApro,将软体反汇编得到如下代码:

1.

2.

3.

4.

5.

6.

7.

8.

9.

push esi

push 0 ; lpsa

push 37Fh ; dwDesiredAccess

push 0 ; dwReserved

push 0 ; lpwinsta

call ds:CreateWindowStationW

push 2 ; dwFlags

mov esi, eax

push 2 ; dwMask

10. push esi ; hObject

11. call ds:SetHandleInformation

12. push esi ; hWinSta

13. call ds:CloseWindowStation

14. xor eax, eax

15. pop esi

16. retn

复制代码

IDApro的静态分析能力很强,在汇编指令的后面讲函数的名称都详细的做了注释,理解起来比较的容易,这里我来解释一下,push,指令是堆栈操作指令,将值压入堆栈,堆栈操作是以"后进先出"的方式进行数据操作.call是过程调用指令,这里是调用函数.简单的说就是将函数所需要的值压入栈,然后调用!mov 指令为双操作数指令,两个操作数中必须有一个是寄存器.可以理解为编程中的赋值语句,顺序是从右到左.

代码中只调用了三个API

WindowStationW

dleInformation

indowStation

程序的原理知道了,那么这时什么Bug呢?这里就要参考Alex Ionescu的文档Pointers and Handles---A Story of Unchecked Assumptions in the Windows Kernel,这个文档是Black Hat USA'08大会上发布的,爆出的是windows操作系统的一个DoS(denial of service拒绝服务 ) Bug,下面我们来了解这个Bug是如何形成的.

Bug的关键词是Protected Handle Close意思是被写保护的句柄被关闭.

引用原文(关键的三步):

Protect From Close

with SetHandleInformation or NtSetInformationObject

for protecting against accidental close of critical handles

ACL check done -- anyone can deprotect the handle

mostly a debugging/reliability feature -- not security

What Happens at Close

handled by ObpCloseHandleEntry

cases: return STATUS_HANDLE_NOT_CLOSABLE

s being debugged or FLG_ENABLE_CLOSE_EXCEPTIONS global flag is set

or handle has debug information: RaiseException(STATUS_HANDLE_NOT_CLOSABLE)

What if Kernel Closes It?

would expect the operation to succeed anyway -- kernel is “god”

... remember this is a debugging/reliability feature

we’d probably want to know if a critical handle in our driver was

what happens? Crash!KeBugCheckEx(INVALID_KERNEL_HANDLE)

为了方便英语不是很好的朋友下面给出我的翻译:

防止句柄被关闭

1.调用SetHandleInformation或者NtSetInformationObject函数来保护句柄

2.运用于保护关键的句柄被意外的关闭

3.没有访问控制表的保护机制---任何人都可关闭句柄

4.所以大部分是调试/可靠的特征是非安全的

当尝试关闭时是否会发生什么

1.通过ObpCloseHandleEntry函数来逻辑处理句柄

2.通常会返回 STATUS_HANDLE_NOT_CLOSABLE

3.进程被调试或者通过设置FLG_ENABLE_CLOSE_EXCEPTIONS全局标记或者句柄的调试信息为STATUS_HANDLE_NOT_CLOSABLE

如果是内核将句柄关闭呢?

1.你将会预见这个过程会成功,因为内核就是系统的上帝

2.但是,你要记住这里有调试中/可靠的特征

3.因此我们想要知道的是如果驱动中的关键句柄被关闭的话......

4.然后会发生什么呢?崩溃!系统直接调用KeBugCheckEx函数,出现BSOD(INVALID_KERNEL_HANDLE)

实现手法:

1. 利用CreateWindowStation函数来创建一个窗口站(window station)对象

2. 利用SetHandleInformation函数来保护这个句柄

3. 利用CloseWindowStation函数来关闭这个对象

其实我们知道关闭句柄的话通过调用CloseHandle函数就ok了,那么为什么作者这里没有提及呢?原因是CloseHandle和NtClose函数 是在用户模式下调用并且将会把PM(Protected Mode保护模式)值 设为1.在这个保护模式下即使句柄被保护,系统也不会崩溃.有一种误解就是句柄只能同归调用CloseHandle函数来关闭,其实这是不正确的,因为通过 CloseWindowStation函数也是可以的.Win32k模块通过OkayToClose 机制来限制CloseHandle函数的调用.进一步说,就是当终端程序启动时,终端的API会初始化,然后调用csrss程序,此程序会调用NtUserConsoleControl函数的同时会调用PsSetProcessWin32WindowStation函数来缓存此句柄,但是当转到新的窗口站时,PsSetProcessWin32WindowStation函数将会检查句柄是否是过期的,如果是的话就会调用ZwClose关闭缓存句柄!!!!哈哈,说到这大家也明白了吧,最终关键的句柄还是被关闭了,上面已经讲过如果关键的句柄被关闭会出现什么现象呢?那就是崩溃!!!

这里真相大白了,原来是windows的处理机制的bug导致的,探索的进程是艰辛的,

尤其是对我这样的初学者来说,这也算是学习逆向工程和系统调试的一个成果吧,最近一直抽不出时间来写文章,不过还是忙里偷闲的写了这片烂文