<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

文章分类

导航

订阅

D1蓝屏、指针和安全的代价

在从Hibernate恢复时,发生D1蓝屏,蓝屏的基本信息为:

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: 53646156, memory referenced
Arg2: 00000002, IRQL
Arg3: 00000000, value 0 = read operation, 1 = write operation
Arg4: b9d0beb9, address which referenced memory

这是非常常见的一种蓝屏,直接的错误就是在高IRQL(2,DISPATCH_LEVEL)访问了错误的地址,导致了页错误异常。

对于这个案例,被访问的内存地址是53646156,从表面看这个地址就很有问题,首先它不是一个内核态的地址;再一看,它很不像一个地址,倒像一串ASC代码,使用.FORMATS命令看一下:

kd> .formats 53646156
Evaluate expression:
  Hex:     53646156
  Decimal: 1399087446
  Octal:   12331060526
  Binary:  01010011 01100100 01100001 01010110
  Chars:   SdaV
  Time:    Sat May 03 07:24:06 2014
  Float:   low 9.80886e+011 high 0
  Double:  6.91241e-315

看来果真是一串ASC码,其内容为SdaV,哦,这岂不是在内核池中分配VAD(Virtual Address Descriptor)结构时使用的分配标记。看来是阴差阳错,谁把这个分配标记当作了地址。

是谁呢?蓝屏的第四个参数就是点这把火的那条指令的地址,即b9d0beb9,使用r命令可以时光倒流回当时的情景:

kd> r
eax=00000002 ebx=8a6b1880 ecx=53646156 edx=8a6b2398 esi=8a6b1888 edi=8984c008
eip=b9d0beb9 esp=aca0d27c ebp=aca0d2a8 iopl=0         nv up ei pl nz na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010206
psched!MpSend+0x97:
b9d0beb9 833901          cmp     dword ptr [ecx],1    ds:0023:53646156=????????

这是一条比较指令,把ECX寄存器所致指向的地址内容与1相比。CPU执行时,需要先读出这个地址的内容,而到这个地址去读的时候,出异常了。

看来是有人把ECX设置错了。反汇编它前面的指令:

kd> ub b9d0beb9
psched!MpSend+0x7b:
b9d0be9d ff158000d1b9    call    dword ptr [psched!_imp_KfAcquireSpinLock (b9d10080)]
b9d0bea3 88430c          mov     byte ptr [ebx+0Ch],al
b9d0bea6 0fb70f          movzx   ecx,word ptr [edi]
b9d0bea9 8b157c0ad1b9    mov     edx,dword ptr [psched!g_WanLinkTable (b9d10a7c)]
b9d0beaf 8b0c8a          mov     ecx,dword ptr [edx+ecx*4]
b9d0beb2 85c9            test    ecx,ecx
b9d0beb4 894df4          mov     dword ptr [ebp-0Ch],ecx
b9d0beb7 7405            je      psched!MpSend+0x9c (b9d0bebe)

注意观察对ECX寄存器的操作。从下向上追溯,倒数第2行,是将ECX的值赋给EBP-C这个局部变量,观察一下:

kd> dd ebp-0C l1
aca0d29c  53646156

这与出事时的ECX值相符合,看来栈上的数据还都完好。倒数第3行是测试一下ECX是否为零。

倒数第4行是对ECX的赋值语句,意思是把EDX这个数组(结构)的ECX号元素赋给ECX。因为这里的源和目标都使用了ECX寄存器,所以单从这一句是看不出执行这条指令时的ECX值了。好在从向上两行可以看到ECX的值是从EDI寄存器所指向的两字节内存单元中的得到的。因为从这一行到事发时,没有人操作EDI,所以上面r命令显示的EDI值8984c008仍是可信的:

kd> dw 8984c008 l8
8984c008  0061 0064 0073 0061 006d 0070 0008 0045

从此推测,movzx   ecx,word ptr [edi]这一句应该是把ECX赋值成了0061。观察上面这串数字,感觉也颇蹊跷,使用db命令显示:

kd> db 8984c008 l10
8984c008  61 00 64 00 73 00 61 00-6d 00 70 00 08 00 45 00  a.d.s.a.m.p...E.

这很像是一个双字节字符串,其内容为adsamp。这样看来这个EDI的指向值得怀疑。那么EDI是如何来的呢?考虑到跳转影响,而且当前代码离入口不远,所以我们还是从入口开始分析,反汇编前30h条指令:

kd> u psched!MpSend l30
psched!MpSend:
01 b9d0be22 8bff            mov     edi,edi
02 b9d0be24 55              push    ebp
03 b9d0be25 8bec            mov     ebp,esp
04 b9d0be27 83ec20          sub     esp,20h
05 b9d0be2a 53              push    ebx
06 b9d0be2b 8b5d08          mov     ebx,dword ptr [ebp+8]
07 b9d0be2e 56              push    esi
08 b9d0be2f 33f6            xor     esi,esi
09 b9d0be31 46              inc     esi
0a b9d0be32 39b330020000    cmp     dword ptr [ebx+230h],esi
0b b9d0be38 7508            jne     psched!MpSend+0x20 (b9d0be42)
0c b9d0be3a 39b334020000    cmp     dword ptr [ebx+234h],esi
0d b9d0be40 740a            je      psched!MpSend+0x2a (b9d0be4c)
0e b9d0be42 b8010000c0      mov     eax,0C0000001h
0f b9d0be47 e94e0d0000      jmp     psched!MpSend+0xd78 (b9d0cb9a)
10 b9d0be4c 83bb8401000003  cmp     dword ptr [ebx+184h],3
11 b9d0be53 57              push    edi
12 b9d0be54 0f85fb060000    jne     psched!MpSend+0x733 (b9d0c555)
13 b9d0be5a 6681bb380200000008 cmp   word ptr [ebx+238h],800h
14 b9d0be63 0f8583060000    jne     psched!MpSend+0x6ca (b9d0c4ec)
15 b9d0be69 8b450c          mov     eax,dword ptr [ebp+0Ch]
16 b9d0be6c 8b7008          mov     esi,dword ptr [eax+8]
17 b9d0be6f f6460605        test    byte ptr [esi+6],5
18 b9d0be73 7405            je      psched!MpSend+0x58 (b9d0be7a)
19 b9d0be75 8b7e0c          mov     edi,dword ptr [esi+0Ch]
1a b9d0be78 eb0b            jmp     psched!MpSend+0x63 (b9d0be85)
1b b9d0be7a 6a00            push    0
1c b9d0be7c 56              push    esi
1d b9d0be7d ff15d801d1b9    call    dword ptr [psched!_imp__MmMapLockedPages (b9d101d8)]
1e b9d0be83 8bf8            mov     edi,eax
1f b9d0be85 837e140e        cmp     dword ptr [esi+14h],0Eh
20 b9d0be89 730a            jae     psched!MpSend+0x73 (b9d0be95)
21 b9d0be8b b8010000c0      mov     eax,0C0000001h
22 b9d0be90 e9040d0000      jmp     psched!MpSend+0xd77 (b9d0cb99)
23 b9d0be95 8d7308          lea     esi,[ebx+8]
24 b9d0be98 8bce            mov     ecx,esi
25 b9d0be9a 8975f8          mov     dword ptr [ebp-8],esi
26 b9d0be9d ff158000d1b9    call    dword ptr [psched!_imp_KfAcquireSpinLock (b9d10080)]
27 b9d0bea3 88430c          mov     byte ptr [ebx+0Ch],al
28 b9d0bea6 0fb70f          movzx   ecx,word ptr [edi]
29 b9d0bea9 8b157c0ad1b9    mov     edx,dword ptr [psched!g_WanLinkTable (b9d10a7c)]
2a b9d0beaf 8b0c8a          mov     ecx,dword ptr [edx+ecx*4]
2b b9d0beb2 85c9            test    ecx,ecx
2c b9d0beb4 894df4          mov     dword ptr [ebp-0Ch],ecx
2d b9d0beb7 7405            je      psched!MpSend+0x9c (b9d0bebe)
2e b9d0beb9 833901          cmp     dword ptr [ecx],1
2f b9d0bebc 740c            je      psched!MpSend+0xa8 (b9d0beca)
30 b9d0bebe 8ad0            mov     dl,al

从#15行可以看到,第二参数(EBP+C)的值赋给了EAX,下一行把EAX结构的偏移8的字段赋给了ESI。通过kv命令显示参数:

kd> kv 1
ChildEBP RetAddr  Args to Child             
aca0d2a8 ba509985 8a6b1880 87c04418 00000000 psched!MpSend+0x97 (FPO: [Non-Fpo])

可见,第二个参数的值是87c04418,观察它的值:

kd> dd 87c04418
87c04418  ???????? ???????? ???????? ????????

这说明这个地址没有被包含在DUMP文件中。不过这已经不是很重要。分析到这,我们基本可以推断这个蓝屏与调用MPSend的第二个参数密切有关。

MPSend是网络小端口驱动程序中常见的一个发送函数,以下是它的函数原型:

NDIS_STATUS
MPSend(
 IN NDIS_HANDLE    MiniportAdapterContext,
 IN PNDIS_PACKET   Packet,
 IN UINT     Flags
 )

MSDN中也有这个函数原型的定义,即:

NDIS_STATUS MiniportSend(
  NDIS_HANDLE MiniportAdapterContext,
  PNDIS_PACKET Packet,
  UINT Flags
);

也就是说,第二个参数是指向NDIS_PACKET结构的指针。MSDN中给出的这个结构的定义是:

typedef struct _NDIS_PACKET {
  NDIS_PACKET_PRIVATE Private;
  union {
    struct {
      UCHAR MiniportReserved[2*sizeof(PVOID)];
      UCHAR WrapperReserved[2*sizeof(PVOID)];
    };
    struct {
      UCHAR MiniportReservedEx[3*sizeof(PVOID)];
      UCHAR WrapperReservedEx[sizeof(PVOID)];
    };
    struct {
      UCHAR MacReserved[4*sizeof(PVOID)];
    };
  };
  ULONG_PTR Reserved[2];
  UCHAR ProtocolReserved[1];
} NDIS_PACKET, *PNDIS_PACKET, **PPNDIS_PACKET;

其中,NDIS_PACKET_PRIVATE结构的定义是:

typedef struct _NDIS_PACKET_PRIVATE
{
    UINT                PhysicalCount;    // number of physical pages in packet.
    UINT                TotalLength;     // Total amount of data in the packet.
    PNDIS_BUFFER        Head;            // first buffer in the chain
    PNDIS_BUFFER        Tail;            // last buffer in the chain

    // if Head is NULL the chain is empty; Tail doesn't have to be NULL also

    PNDIS_PACKET_POOL   Pool;            // so we know where to free it back to
    UINT                Count;
    ULONG               Flags;           
    BOOLEAN             ValidCounts;
    UCHAR               NdisPacketFlags;    // See fPACKET_xxx bits below
    USHORT              NdisPacketOobOffset;
} NDIS_PACKET_PRIVATE, * PNDIS_PACKET_PRIVATE;

其中的NDIS_BUFFER结构其实就是描述内存分配用的MDL结构:

typedef MDL NDIS_BUFFER, *PNDIS_BUFFER;

终上,有关的访问流程是:

#15行把参数Packet的地址赋给EAX,#16行是把Packet->Head成员赋给ESI。而#19行是把NDIS_BUFFER(MDL )结构的MappedSystemVa成员赋给EDI。而这个EDI所指向的内存数据是有问题的。

kd> dt _MDL
nt!_MDL
   +0x000 Next             : Ptr32 _MDL
   +0x004 Size             : Int2B
   +0x006 MdlFlags         : Int2B
   +0x008 Process          : Ptr32 _EPROCESS
   +0x00c MappedSystemVa   : Ptr32 Void
   +0x010 StartVa          : Ptr32 Void
   +0x014 ByteCount        : Uint4B
   +0x018 ByteOffset       : Uint4B

那么是谁向psched模块的MpSend传递了错误的参数呢?首先看一下pshed模块:

kd> lmvm psched
start    end        module name
b9d03000 b9d13e00   psched     (pdb symbols)          d:\symbols\psched.pdb\72F13E8E57F04ADA961EFC51F1587E9B1\psched.pdb
    Loaded symbol image file: psched.sys
    Mapped memory image file: C:\WINDOWS\system32\drivers\psched.sys
    Image path: psched.sys
    Image name: psched.sys
    Timestamp:        Wed Aug 04 10:04:16 2004 (41107C60)
    CheckSum:         0001B55A
    ImageSize:        00010E00
    File version:     5.1.2600.2180
    Product version:  5.1.2600.2180
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        3.6 Driver
    File date:        00000000.00000000
    Translations:     0409.04b0
    CompanyName:      Microsoft Corporation
    ProductName:      Microsoft® Windows® Operating System
    InternalName:     PSCHED.SYS
    OriginalFilename: PSCHED.SYS
    ProductVersion:   5.1.2600.2180
    FileVersion:      5.1.2600.2180 (xpsp_sp2_rtm.040803-2158)
    FileDescription:  MS QoS Packet Scheduler
    LegalCopyright:   © Microsoft Corporation. All rights reserved.

接下来看一下栈回溯:

kd> kvn 100
 # ChildEBP RetAddr  Args to Child             
00 aca0d2a8 ba509985 8a6b1880 87c04418 00000000 psched!MpSend+0x97 (FPO: [Non-Fpo])
01 aca0d2d0 b14c0b50 88eb5428 87c04418 b14bf2c3 NDIS!ndisMSendX+0x1d6 (FPO: [Non-Fpo])
02 aca0d2dc b14bf2c3 87c04418 88eb5428 8984c0b7 ICsrvr+0x5b50 (FPO: [2,0,0])
03 aca0d640 b14beb61 87c04418 8a682130 87f03160 ICsrvr+0x42c3 (FPO: [Non-Fpo])
04 aca0d668 b14c0d7c 87f03160 87f031f0 0000000e ICsrvr+0x3b61 (FPO: [Non-Fpo])
05 aca0d688 ba52be2d 886b1300 87f03160 87f031f0 ICsrvr+0x5d7c (FPO: [Non-Fpo])
06 aca0d6f0 ba51ee54 013d70f8 aca0d710 00000001 NDIS!ethFilterDprIndicateReceivePacket+0x4a8 (FPO: [Non-Fpo])
07 aca0d718 ba50999d 8993c0e4 89963c20 8a56c008 NDIS!ndisMLoopbackPacketX+0x154 (FPO: [Non-Fpo])
08 aca0d734 b17472b1 89a7f830 89963c20 8a56c008 NDIS!ndisMSendX+0x181 (FPO: [Non-Fpo])
09 aca0d758 b1747355 0056c00e faffffef 89963c20 tcpip!ARPSendBCast+0x2a8 (FPO: [Non-Fpo])
0a aca0d784 b174564a 8a56c008 aca0d700 00000001 tcpip!ARPTransmit+0x125 (FPO: [Non-Fpo])
0b aca0d7b4 b174578f 8a3bd758 faffffef 89963c20 tcpip!SendIPPacket+0x193 (FPO: [Non-Fpo])
0c aca0d900 b17499c4 b17839b4 87b97468 88fb3134 tcpip!IPTransmit+0x289e (FPO: [Non-Fpo])
0d aca0d9a0 b174978b 87f221b8 87b97468 885eb8c0 tcpip!UDPSend+0x41b (FPO: [Non-Fpo])
0e aca0d9c4 b17497f1 00a0d9e8 885ebfa0 88fb3174 tcpip!TdiSendDatagram+0xd5 (FPO: [Non-Fpo])
0f aca0d9fc b1748149 885eb8c0 885eb978 885eb8c0 tcpip!UDPSendDatagram+0x4f (FPO: [Non-Fpo])
10 aca0da18 804ee0ef 89a04f18 885eb8c0 884776f8 tcpip!TCPDispatchInternalDeviceControl+0xff (FPO: [Non-Fpo])
11 aca0da28 b1722cc7 b1722798 89a2ef08 885eb8c0 nt!IopfCallDriver+0x31 (FPO: [0,0,0])
WARNING: Stack unwind information not available. Following frames may be wrong.
12 aca0da2c b1722798 89a2ef08 885eb8c0 89a2efc0 FireTDI+0x6cc7
13 aca0da48 b1722bc3 89a2ef08 885e6c07 b1722e47 FireTDI+0x6798
14 aca0db10 b16b3b5e 8872f008 aca0db64 00000085 FireTDI+0x6bc3
15 aca0dc38 805afda5 aca0dcf0 b16b22a2 b16b2728 afd!AfdFastIoDeviceControl+0x2a7 (FPO: [Non-Fpo])
16 aca0dd34 8053cbc8 000020f0 000026e8 00000000 nt!ObReferenceObjectByHandle+0x249 (FPO: [Non-Fpo])
17 aca0dd34 7c90eb94 000020f0 000026e8 00000000 nt!KiFastCallEntry+0xf8 (FPO: [0,0] TrapFrame @ aca0dd64)
18 039cfd14 00000000 00000000 00000000 00000000 0x7c90eb94

在上面的栈回溯中,首先可以明显的看出有两个没有调试符号的模块,一个是ICsrvr,另一个是FireTDI。

kd> lmvm ICsrvr
start    end        module name
b14bb000 b14de7a0   ICsrvr     (no symbols)          
    Loaded symbol image file: ICsrvr.sys
    Mapped memory image file: C:\WINDOWS\system32\drivers\ICsrvr.sys
    Image path: ICsrvr.sys
    Image name: ICsrvr.sys
    Timestamp:        Thu Jul 20 12:36:22 2006 (44BF4086)
    CheckSum:         0002B6A4
    ImageSize:        000237A0
    File version:     0.0.0.0
    Product version:  0.0.0.0
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        3.7 Driver
    File date:        00000000.00000000
    Translations:     0409.04b0
    CompanyName:      
    ProductName:      VPN Client (Windows 2000)
    InternalName:     icsrvr.sys
    OriginalFilename: icsrvr.sys
    ProductVersion:   
    FileVersion:      
    FileDescription:  VPN Client Service Driver
    LegalCopyright:   

也就是说,ICsrvr是用于VPN(Virutal Private Network)。再看一下另一个:

kd> lmvm FireTDI
start    end        module name
b171c000 b1742000   FireTDI    (no symbols)          
    Loaded symbol image file: FireTDI.sys
    Mapped memory image file: C:\WINDOWS\system32\drivers\FireTDI.sys
    Image path: FireTDI.sys
    Image name: FireTDI.sys
    Timestamp:        Wed Feb 28 04:04:54 2007 (45E4D536)
    CheckSum:         000184FA
    ImageSize:        00026000
    File version:     6.0.1.153
    Product version:  6.0.1.153
    File flags:       0 (Mask 3F)
    File OS:          4 Unknown Win32
    File type:        3.6 Driver
    File date:        00000000.00000000
    Translations:     0409.04e4
    CompanyName:      McAfee, Inc.
    ProductName:      McAfee Host Intrusion Prevention
    InternalName:     FireTDI
    OriginalFilename: FireTDI.sys
    ProductVersion:   6.0.1.153
    FileVersion:      6.0.1.153
    FileDescription:  McAfee HIP Application Firewall Driver
    LegalCopyright:   Copyright© 2000-2007 McAfee, Inc. All Rights Reserved.

看来这个McAfee公司的一个驱动程序,其功能是所谓的Host Intrusion Prevention,说白了就是检测侵入到主机中的恶意软件,发现和防止它们干坏事。FireTDI中的TDI是Transport Driver Interface 的缩写,是Windows系统定义的网络传输层向外提供服务的接口。

再看看所在的进程吧:

kd> !thread
GetPointerFromAddress: unable to read from 80557fb4
THREAD 88cf8020  Cid 02e8.0d34  Teb: 7ff5d000 Win32Thread: e427a380 RUNNING on processor 0
Not impersonating
GetUlongFromAddress: unable to read from 80557fc4
Owning Process            89838758       Image:         svchost.exe
Attached Process          N/A            Image:         N/A
ffdf0000: Unable to get shared data
Wait Start TickCount      4687958     
Context Switch Count      29                 LargeStack
ReadMemory error: Cannot get nt!KeMaximumIncrement value.
UserTime                  00:00:00.000
KernelTime                00:00:00.000
Win32 Start Address 0x74f0742e
Start Address 0x7c810679
Stack Init aca0e000 Current aca0dc50 Base aca0e000 Limit aca0a000 Call 0
Priority 9 BasePriority 8 PriorityDecrement 0 DecrementCount 16
<以下是栈回溯,省略>

看来是某个系统服务发起了网络有关的操作。从栈回溯的中间部分可以看出,这个线程是在发送一个UDP报文。而在发送过程中,调用了两个第三方的驱动程序FireTDI和ICSrvr,把报文交给它们做过滤和检查,理由是为了安全。

安全重要,毋容置疑。但是问题是我们把保证安全这个重任交给了谁?它足够衷心吧?它值得我们信赖吧?它会不会无事生非和节外生枝呢?

今天的计算机世界还没有秩序,处在文明出现前的混乱不堪状态。因此,今天的安全软件和“不安全”软件使用着类似的技术,在几乎平等的条件下相互肉搏。这难为了安全软件。但是,令主人难以接受的是,很多安全软件在黔驴技穷的时候,居然时常在主人那里耍脾气。它们不管主人在干什么,就恣意为所欲为。因此,当你在WORD里执行一个Paste操作,也可能要等待几秒钟:WORD突然失去响应,甚至整个系统都差不多回到了Windows 1.0的年代,移动窗口像搬砖头一样。而安全软件的图标在那里活泼的闪烁,似乎在说:“我在帮你做安全检查呢^-^”。

也有时候,安全检查者走了火,于是直接把系统搞蓝屏了。

也有时候,安全检查者失去了方向,于是所在的进程永远挂在那里,成为著名的杀不掉的进程,因为“安全”检查的人把线程挂在内核态。

也有时候,安全检查者没看清楚,搞错了人,把好人杀掉了,有用的文件没了,甚至系统起不来了。

也有时侯,系统莫名其妙的挂住了,朴实的用户和客户支持都糊里糊涂的以为是中了病毒,其实是......

更难以接受的是,当你无法忍受它的愚蠢,想将其杀掉时,它已经准备好了N种方法。对于它的较低版本,你或许还可以见到它起来时(因为感觉明显的缓慢),使用任务管理器就把那个占CPU最高的进程杀掉。但是它没几天就升级了,想用任务管理器杀掉它不可能了。但是或许可以在系统服务里找到它,将其禁止,不要担心它下次起不来,它有N+1种方法把自己跑起来。但过了些天后,它可能又自动升级了M个版本,更加鲁棒了(robust)。非得用调试器(WinDBG)附加上去,然后关掉WinDBG,让其与调试器同归于尽。这应该是重启和Log Off外最顶级的杀进程的方法了吧。但是没多久,这种方法也被它发现了,不过它也没有什么好方法抵制,但是它使出了“鱼死网破”的方法,一旦你试图把调试器附加上去,整个系统就在秒一级做响应了,估计是把Windows子系统搞混乱了......

于是很能理解,真正深刻理解了今天安全软件的人都不愿意使用它们。安全领域的著名女将Joanna Rutkowska在一次接受记者采访时就说,我从来不安装什么安全软件。但是她承认“MS Kernel Debugger can be very useful tool for system compromise detection.”

 

还是回到这个蓝屏吧,因为是小型转储,所以没能分析的水落石出。现已经把转储方式改为内核转储......其实一种更好的方式是启用崩溃时中断到调试器,即/CrashDebug(参见《软件调试》P476)。

另外,通过小型转储也可能看出更多线索,我把这个转储文件上传到如下地址:http://advdbg.org/img/dump/d1.dmp。感兴趣的朋友可以试一试,尤其是对网络和防火墙熟悉的高手们。

posted on 2008年9月14日 17:31 由 Raymond

Powered by Community Server Powered by CnForums.Net