Windows内核开发陷阱 – NTSTATUS

STATUS_REPARSE不是“失败”

曾经在过滤驱动中想针对REPARSE的情况做特殊处理,实现代码如下:

if (NT_SUCCESS(status)) {
    /* file created */
} else if (STATUS_REPARSE == status) {
    /* to be repared  */
} else {
    ...
}

结果代码运行没达有预期效果,手工调试时才发现代码进入了NT_SUCCESS(status)分支。STATUS_REPARSE竟然表示是“成功”的!

这件事后才真正注意到status的问题,虽然在学习内核及驱动编程的时候书上都有相关介绍,但这些基础章节并没有引起重视就直接跳过了。但是像STATS_REPARSE这样的特例并不是少数,如STATUS_PENDING、STATUS_TIMEOUT等。

NTSTATUS的定义

再回顾一下NTSTATUS的定义: NTSTATUS是一个32位长的有符号整型数,即LONG型。其格式如下:

  3                   2                   1                   0
1 0|9|8|7 6 5 4 3 2 1 0 9 8 7 6|5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
---+-+-+-----------------------+-------------------------------
S  |C|N|    Facility           |               Code              
---------------------------------------------------------------

各部分的解释如下:

位数 注解
S 2 Severity: 分4个等级(Success, Informational, Warning, Error)
C 1 1:用户自定义的值 0:默认为系统定义(Microsoft)
N 1 保留位,始终为0值
Facility 12 错误类别或子系统的划分
Code 16 具体错误代码

Severity的4个等级的定义如下:

00 - Success
01 - Informational
10 - Warning
11 - Error

NT_XXXX宏分析

引用WDK代码(ntdef.h):

//
// Generic test for success on any status value (non-negative numbers
// indicate success).
//

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)

//
// Generic test for information on any status value.
//

#define NT_INFORMATION(Status) ((((ULONG)(Status)) >> 30) == 1)

//
// Generic test for warning on any status value.
//

#define NT_WARNING(Status) ((((ULONG)(Status)) >> 30) == 2)

//
// Generic test for error on any status value.
//

#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3)

由定义可以看出至少两点:

  1. NT_SUCCESS的定义和其它三者是有区别的,并不是根据Severity的值来判断的。NT_SUCCESS(status)其实包含了Severity == 0及Severity == 1的情形。

  2. NT_SUCCESS与NT_ERROR不是单纯的非黑即白的关系。新手常犯的错误就是主观上认为NT_SUCCESS()与!NT_ERROR(),或者NT_ERROR()与!NT_SUCCESS()是等价。

如果想确保一个irp确实是“成功”完成了,就要严格用STATUS_SUCCESS来判断,而不是NT_SUCCESS()。类似地,用NT_ERROR()可以保证代码的执行确实出现了错误,但是NT_WARNING()的情况即被漏掉了。

总之,必要的基本功始终是不可少的,像熟悉开发场景相关的返回值并弄懂这些返回值的确切含义等此类的苦活累活是必须要做且要做好的。

参考链接

  1. [MS-ERREF]: Windows Error Codes
  2. Windows Driver Kit (WDK)

<本文用时2小时,字体及表格调整花时较多。>

Windows内核开发陷阱: Nt vs Zw

在Windows平台上做过开发的人对Native API都不会陌生。但以Nt开头与以Zw开头的Native API到底有没有差别以及都有什么使用场景等一直以来都是让人感觉比较困扰的问题。另外内核态也有同样的一组Nt及Zw开头的内核支持函数,这两者间又是怎么一回事呢,这其中会有会有开发上有陷阱呢?!

我们先来解析用户层,然后研究下内核层其各自的实现:

用户层的Nt vs Zw

对用户层程序来说Nt与Zw开头函数实际上是一样的,只是同一个函数的两个名称而已。比如下面是对ntdll!NtCreateFile及ntdll!ZwCreateFile(Win7 X64)的反汇编:

1: kd> u ntdll!ZwCreateFile
ntdll!NtCreateFile:
00000000`77480400 4c8bd1          mov     r10,rcx
00000000`77480403 b852000000      mov     eax,52h
00000000`77480408 0f05            syscall
00000000`7748040a c3              ret

1: kd> u ntdll!ZwClose
ntdll!NtClose:
00000000`7747ffa0 4c8bd1          mov     r10,rcx
00000000`7747ffa3 b80c000000      mov     eax,0Ch
00000000`7747ffa8 0f05            syscall
00000000`7747ffaa c3              ret

syscall

用户层ntdll!ZwCreateFile或NtCreateFile会通过syscall切换到内核,最早的Windows系统是通过软中断0x2e来完成模式切换的。如何切换以及切换至何处是由CPU的MSR寄存器所决定的,由操作系统在启动时完成的初始化设定。

针对Win7 X64测试系统来说,此处的syscall指令将跳转由MSR_LSTAR所指向的固定地址:

0: kd> rdmsr 0xc0000082
msr[c0000082] = fffff800`03082ec0
0: kd> u fffff800`03082ec0
nt!KiSystemCall64:
fffff800`03082ec0 0f01f8          swapgs
fffff800`03082ec3 654889242...000 mov   qword ptr gs:[10h],rsp
fffff800`03082ecc 65488b242...000 mov   rsp,qword ptr  gs:[1A8h]
fffff800`03082ed5 6a2b            push    2Bh
fffff800`03082ed7 65ff34251...000 push    qword ptr gs:[10h]
fffff800`03082edf 4153            push    r11
fffff800`03082ee1 6a33            push    33h
fffff800`03082ee3 51              push    rcx
......
fffff800`02c7bfce 6690            xchg    ax,ax
fffff800`02c7bfd0 fb              sti
fffff800`02c7bfd1 48898be0010000  mov     qword ptr [rbx+1E0h],rcx
fffff800`02c7bfd8 8983f8010000    mov     dword ptr [rbx+1F8h],eax
nt!KiSystemServiceStart:
fffff800`02c7bfde 4889a3d8010000  mov     qword ptr [rbx+1D8h],rsp
......

nt!KiSystemCall64主要的工作是:

  1. 参数及用户栈的保存(内核态处理完打好返回用户层)
  2. 内核栈切换、内核线程的初始化设定等
  3. 准备工作做好后,最重要的是调用相应的内核服务(比如nt!NtXXX)

内核层的Nt vs Zw

对内核层来说,Zw与Nt函数虽然参数及功能上都是一致的,但二者对参数的处理及对调用环境的认同是不一样的。以nt!NtClose及nt!ZwClose的代码为例(Win7 X64):

1: kd> u nt!NtClose
nt!NtClose [d:\w7rtm\minkernel\ntos\ob\obclose.c @ 430]:
fffff800`0338e2a0 48895c2408      mov     qword ptr [rsp+8],rbx
fffff800`0338e2a5 57              push    rdi
fffff800`0338e2a6 4883ec20        sub     rsp,20h
fffff800`0338e2aa 65488....010000 mov   rax,qword ptr gs:[188h]
fffff800`0338e2b3 48833....e9ff00 cmp     qword ptr [fffff800`03227450],0
fffff800`0338e2bb 488bd9          mov     rbx,rcx
fffff800`0338e2be 0fb6b8f6010000  movzx   edi,byte ptr [rax+1F6h]
fffff800`0338e2c5 0f8520a70200    jne     nt!NtClose+0x2a74b (fffff800`033b89eb)
fffff800`0338e2cb 400fb6d7        movzx   edx,dil
fffff800`0338e2cf 488bcb          mov     rcx,rbx
fffff800`0338e2d2 488b5c2430      mov     rbx,qword ptr [rsp+30h]
fffff800`0338e2d7 4883c420        add     rsp,20h
fffff800`0338e2db 5f              pop     rdi
fffff800`0338e2dc e91ffdffff      jmp     nt!ObpCloseHandle (fffff800`0338e000)

看nt!NtClose没有针对栈做任何准备工作而是直接进入了任务的处理:即调用nt!ObpCloseHandel。类似NtCreateFile也是类似的流程。

下面再来看nt!ZwClose:

1: kd> u nt!ZwClose
nt!ZwClose [o:\w7rtm.obj.amd64fre\minkernel\ntos\ke\mp\objfre\amd64\sysstubs.asm @ 267]:
fffff800`03073640 488bc4          mov     rax,rsp
fffff800`03073643 fa              cli
fffff800`03073644 4883ec10        sub     rsp,10h
fffff800`03073648 50              push    rax
fffff800`03073649 9c              pushfq
fffff800`0307364a 6a10            push    10h
fffff800`0307364c 488d059d300000  lea     rax,[nt!KiServiceLinkage (fffff800`030766f0)]
fffff800`03073653 50              push    rax
fffff800`03073654 b80c000000      mov     eax,0Ch
fffff800`03073659 e9e2670000      jmp     nt!KiServiceInternal (fffff800`03079e40)

1: kd> u nt!KiServiceInternal
nt!KiServiceInternal:
fffff800`02c7be40 4883ec08        sub     rsp,8
fffff800`02c7be44 55              push    rbp
fffff800`02c7be45 4881ec58010000  sub     rsp,158h
fffff800`02c7be4c 488da...000000  lea     rbp,[rsp+80h]
fffff800`02c7be54 48899dc0000000  mov     qword ptr [rbp+0C0h],rbx
fffff800`02c7be5b 4889bdc8000000  mov     qword ptr [rbp+0C8h],rdi
fffff800`02c7be62 4889b5d0000000  mov     qword ptr [rbp+0D0h],rsi
fffff800`02c7be69 fb              sti
fffff800`02c7be6a 65488b1c2588010000 mov   rbx,qword ptr gs:[188h]
fffff800`02c7be73 0f0d8bd8010000  prefetchw [rbx+1D8h]
fffff800`02c7be7a 0fb6bbf6010000  movzx   edi,byte ptr [rbx+1F6h]
fffff800`02c7be81 40887da8        mov     byte ptr [rbp-58h],dil
fffff800`02c7be85 c683f601000000  mov     byte ptr [rbx+1F6h],0
fffff800`02c7be8c 4c8b93d8010000  mov     r10,qword ptr [rbx+1D8h]
fffff800`02c7be93 4c8995b8000000  mov     qword ptr [rbp+0B8h],r10
fffff800`02c7be9a 4c8d1d3d010000  lea     r11,[nt!KiSystemServiceStart (fffff800`02c7bfde)]
fffff800`02c7bea1 41ffe3          jmp     r11

从代码上来nt!ZwClose前面做的工作则有些类似syscall,如果将syscall称作是硬模式切换的话,那nt!ZwCreate所做的可以称算是软模式切换。前者 是从用户态切换到内核态,涉及到栈及context的切换,转换成本很高。而后者则是从内核态到内核态的切换,其实仅是调用关系,均在同一个内核栈上。

nt!ZwXXX函数最终都会切换到相应的nt!NtXXX函数上,因为系统设定所有的任务都是由系统服务nt!NtXXX来实际处理。

从上面的对比不难看出,nt!ZwXXX函数做了一些看似没必要的切换工作,针对 内核驱动来说貌似完全没有必要。比如打开一个文件用nt!NtCreateFile或nt!IoCreateFile等函数更能节省稀缺的内核栈资源的使用,从这点上来看确实如此。

但二者细微的差别却可能导致严重的资源泄露问题。比如一个内核句柄,即在打开文件时指定了OBJ_KERNEL_HANDLE标志,在使用完毕要释放的时候,如果正巧是在用户context(进程上下文),比如IRP_MJ_CREATE或IRP_MJ_CLEANUP等irp的处理程序中,如果调用nt!NtClose的话,则会导致此句柄的泄露,然后此文件会一直处于打开(被占用)的状态。

其原因是nt!NtClose在处理句柄关闭时默认此句柄属于前一个模式空间(PreviousMode)。相反nt!ZwClose正因为多做了“软模式切换”,其切换过程中会修改PreviousMode为KernelMode,因为nt!ZwClose被调用时的线程已经处于内核线程模式(由用户线程通过syscall切换过来的),此时内核句柄用nt!ZwClose就能够正确关闭。

那么我们这里又有了一个新问题:如果反过来用nt!ZwClose来关闭一个本属于用户进程空间的句柄,会不会出错?

答案是不会的。因为nt!ObpCloseHandle在释放句柄前会针对内核句柄做检查,只有在满足全部条件的情况才会当作内核句柄来处理:

#define KERNEL_HANDLE_MASK ((ULONG_PTR)((LONG)0x80000000))

#define IsKernelHandle(H,M)                           \
    (((KERNEL_HANDLE_MASK & (ULONG_PTR)(H)) ==        \
       KERNEL_HANDLE_MASK) &&                         \
     ((M) == KernelMode) &&                           \
     ((H) != NtCurrentThread()) &&                    \
     ((H) != NtCurrentProcess()))

Object Manager通过句柄的第31位来区分是不是内核句柄,所以即使PreviousMode是KernelMode的情形也只会操作当前用户进程的句柄表(CurrentProcess->ObjectTable)而不是内核句柄表(nt!ObpKernelHandleTable)。

小结

不妨简单一点,用这样几个“简单粗暴”的公式来理解它们之间的关系,以NtClose/ZwClose为例:

  • ntdll!ZwClose(用户层) = ntdll!NtClose (用户层)
  • ntdll!ZwClose = syscall(硬切换) + nt!ZwClose(内核层)
  • nt!ZwClose = 软切换 + nt!NtClose(内核层)

魔鬼总在细节之中 (Devils in the details) !

<一直认为对这部分掌握得比较熟,但真正动笔的时候才发现很多不确定的地方。理解只有够深度够准确够熟练才是真正的理解!本文用时2.5小时。>

参考链接

  1. Nt vs. Zw - Clearing Confusion On The Native API
  2. MSDN: PreviousMode
  3. MSDN: Using Nt and Zw Versions of the Native System Services Routines
  4. MSDN: What Does the Zw Prefix Mean?
  5. Windows 7 x64 下的 System Call
  6. 看雪学院:windows 7 x64 KiSystemCall64 笔记

KEVENT,ERESOURCE及Thread Boost

在唤醒KEVENT时,函数KeSetEvent的第二个参数(KPRIORITY Increment)是用来临时提高被唤醒线程的调试优先级的,此举可以让睡眠在此KEVENT上的线程尽快得到处理以提高系统整体性能。

同样ERESOURCE也有类似的机制,在放锁时也会调高等待列表中处于饥饿状态的线程的优先级。

不过除此之外,ERESOURCE还有一种反向的Boost机制,即让等待者在试图获取锁时调高此锁当前拥有者线程的调度优先级,以加快属主线程的处理并尽可能早的释放锁资源。

反向操作却有可能遭遇这样一个问题:作为锁拥有者的线程未必是同步的,很有可能在获取ERESOURCE锁之后就因为异步处理事件(如Workitem,异步I/O请求)在不释放锁的情况下就退出(锁的释放将由异步事件来做),在此情况下ERESOURCE的属主线程指针就变成非法的了。为此Windows内核提供了一种机制来阻止问题的发生:线程等结构体一般都是至少8字节对齐的(最后3位均为0),通过将最后两位设为1的方式(|0x03)来甄别此指针数据是用户自己所指定的还是默认的系统线程,以此来避免访问已经释放的内存。

从Win7之后,FastFat及Ntfs都有对ERESOURCE的OwnerThread的处理,主要是异步的Noncached i/o事件的处理,其目的就是处理线程在获取ERESOURCE之后退出的情况。

FastMutex的陷阱

相对ERESOURCE来说,FastMutex的使用比较方便,不用考虑禁APC问题,也不用考虑FastMutex结构体的释放,用完即可以三不管。对于竞争不激烈且读写不明显的情况下,用FastMutex还是不错的。

不过使用过程中要注意其隐藏的陷阱:

1,ExAcquireFastMutex 会提升IRQL至APC_LEVEL,且保存原来的IRQL用以在ExReleaseFastMutex时恢复。所以在嵌套获取FastMutex时要特别注意放锁一定要依加锁顺序反向操作,不然会导致IRQL紊乱。为此系统特别提供了Unsafe操作函数(ExAcquireFastMutexUnsafe及ExReleaseFastMutexUnsafe),这两个函数不会操作IRQL,但使用上却有前提条件。

2,FastMutex的实现是通过原子锁与Gate对象来实现的,在没有竞争的情况下,通过原子锁可以速实现加锁及解锁,此外Gate对象是不可重入的,不像Event等(Dispatch Object)及ERESOURCE等可重入的锁(同一个线程可以同时加锁多次不会死锁)。

至于FastMutex为什么要提升IRQL至APC_LEVEL的原因,和文件系统调用FsRtlEnterFileSystem禁APC的原因是一样的,直接目的是防止当前线程被挂起(Suspend)。设想在没有禁止APC的情况下调用ExAcquireFastMutexUnsafe加锁之后线程随时可能被挂起,一旦被挂起的话系统中试图获取该锁的其它线程则只能等待,不管此线程是否高优先级或关键的系统任务。同理可以理解Event,ERESOURCE等为什么也需要禁止APC的原因。

除了FastMutex之外,Windows内核还实现了另外两种Mutex: Regular Mutex及Guarded Mutex。不过从Windows 8开始,GuardedMutext和FastMutex的实现是一样的,二者并无差别。Regular Mutex通过KeInitializeMutex初始化,加锁及放锁是通过KeWaitForXXXObject及KeReleaseMutex来操作的。我们知道 Event也是通过KeWaitForXXXObject来加锁的,但是Regular Mutex的加锁操作却会禁止当前线程的Kernel APC,Event却不会,所以在操作Event之前要显示地进入临界区或禁止APC操作以防止可能的死锁发生。

需要注意的是禁止Kernel APC并不会影响i/o操作,但是FastMutex及GauardedMutex不止是禁止Kernel APC,还会禁止Special APC,这样的话i/o操作是不允许的,因为i/o完成例程是在Special APC中做的。这些细微差别在使用中必须考虑到。

脑残的PsSetCreateProcessNotifyRoutine

Vista之前的系统,XP及Server 2003只有8个坑,就是说前8个调用成功之后,后续的所有调用都会失败,看来通过注册callback的方式来监控进程非常不靠谱。

从内核角度来说,尽量少的调用不仅提高系统响应时间,也降低了不靠谱的驱动所可能带来问题,但限制为区区8个总不是解决办法吧,比如Symantec家的SEP动不动就占3个,腾讯家(Tencent)更牛B,安全管家要用几个,QQ也要用,就连浏览器也要搞个驱动并也要注册个进程创建的回调通知,这也太让人无语了吧。

好在从Vista开始,这个限制从8个变成了64个,翻了整整8倍,呵呵,这下够用了?!

下面是一个典型的XP系统实例,8个回调会被占满了,后起的驱动们只能想其它办法了:

0: kd> x nt!PspCreateProcessNotifyRoutine 80564a40 nt!PspCreateProcessNotifyRoutine = <no type information> 0: kd> dd 80564a40 80564a40  e101894f e15e4eb7 e2031ff6 e1e8b56f 80564a50  e20a8d9f e51c641f e52246b7 e5ad82cf 80564a60  00000008 00000000 86a85d58 8656fac0 80564a70  86b9b5a0 86be5208 86be45a0 00000000 80564a80  00000000 00000000 00000000 00000000 80564a90  00000000 00000000 00000000 00000000 80564aa0  00000000 6d6f7441 00000000 00000001 80564ab0  00000000 00000000 00000000 00000000 0: kd> .for (r $t0=0; $t0 < 8; r $t0=$t0+1) {r $t1 = poi($t0 * 4 + nt!PspCreateProcessNotifyRoutine); .if ($t1 == 0) {.continue;}; r $t1 = $t1 & 0xFFFFFFF8; r $t1 = poi($t1 + 4); r $t0; r $t1; u $t1; ln $t1;} $t0=00000000 $t1=f7310790 TsFltMgr+0x10790: f7310790 55              push    ebp f7310791 8bec            mov     ebp,esp f7310793 83e4f8          and     esp,0FFFFFFF8h f7310796 83ec14          sub     esp,14h f7310799 53              push    ebx f731079a 56              push    esi f731079b 8b35a07b31f7    mov     esi,dword ptr [TsFltMgr+0x17ba0 (f7317ba0)] f73107a1 57              push    edi $t0=00000001 $t1=f771a788 ghcore+0x3788: f771a788 8bff            mov     edi,edi f771a78a 55              push    ebp f771a78b 8bec            mov     ebp,esp f771a78d 33c0            xor     eax,eax f771a78f 390564b471f7    cmp     dword ptr [ghcore+0x4464 (f771b464)],eax f771a795 7508            jne     ghcore+0x379f (f771a79f) f771a797 39056cb471f7    cmp     dword ptr [ghcore+0x446c (f771b46c)],eax f771a79d 741a            je      ghcore+0x37b9 (f771a7b9) $t0=00000002 $t1=ed1a3b00 SYMEVENT+0xcb00: ed1a3b00 8bff            mov     edi,edi ed1a3b02 55              push    ebp ed1a3b03 8bec            mov     ebp,esp ed1a3b05 51              push    ecx ed1a3b06 53              push    ebx ed1a3b07 8b5d08          mov     ebx,dword ptr [ebp+8] ed1a3b0a 56              push    esi ed1a3b0b 33f6            xor     esi,esi $t0=00000003 $t1=ecfeb120 QQFrmMgr+0xe120: ecfeb120 8bff            mov     edi,edi ecfeb122 55              push    ebp ecfeb123 8bec            mov     ebp,esp ecfeb125 51              push    ecx ecfeb126 51              push    ecx ecfeb127 53              push    ebx ecfeb128 8b5d0c          mov     ebx,dword ptr [ebp+0Ch] ecfeb12b 56              push    esi $t0=00000004 $t1=ecd098a0 SysPlant+0x78a0: ecd098a0 55              push    ebp ecd098a1 8bec            mov     ebp,esp ecd098a3 83e4f8          and     esp,0FFFFFFF8h ecd098a6 51              push    ecx ecd098a7 53              push    ebx ecd098a8 56              push    esi ecd098a9 57              push    edi ecd098aa a12480d1ec      mov     eax,dword ptr [SysPlant+0x16024 (ecd18024)] $t0=00000005 $t1=866b41d0 866b41d0 90              nop 866b41d1 90              nop 866b41d2 e9e7c4f466      jmp     QMUdisk+0x26be (ed6006be) 866b41d7 90              nop 866b41d8 90              nop 866b41d9 90              nop 866b41da 90              nop 866b41db 90              nop $t0=00000006 $t1=ecaf8070 BHDrvx86+0x6c070: ecaf8070 8bff            mov     edi,edi ecaf8072 55              push    ebp ecaf8073 8bec            mov     ebp,esp ecaf8075 807d1000        cmp     byte ptr [ebp+10h],0 ecaf8079 750f            jne     BHDrvx86+0x6c08a (ecaf808a) ecaf807b 8b450c          mov     eax,dword ptr [ebp+0Ch] ecaf807e 8b0d48e0b8ec    mov     ecx,dword ptr [BHDrvx86+0x102048 (ecb8e048)] ecaf8084 50              push    eax $t0=00000007 $t1=ee461eba dekfs+0x4eba: ee461eba 8bff            mov     edi,edi ee461ebc 55              push    ebp ee461ebd 8bec            mov     ebp,esp ee461ebf 83ec10          sub     esp,10h ee461ec2 807d1000        cmp     byte ptr [ebp+10h],0 ee461ec6 53              push    ebx ee461ec7 56              push    esi ee461ec8 57              push    edi

Server 2008 X64系统已经可以支持64个注册回调了,这个限制是硬编码在

函数PsSetCreateProcessNotifyRoutine中的:

0: kd> u nt!PspSetCreateProcessNotifyRoutine nt!PspSetCreateProcessNotifyRoutine: fffff800`01c3fab0 48895c2408      mov     qword ptr [rsp+8],rbx fffff800`01c3fab5 48896c2410      mov     qword ptr [rsp+10h],rbp fffff800`01c3faba 4889742418      mov     qword ptr [rsp+18h],rsi …… fffff800`01c3fad4 bb01000000      mov     ebx,1 …… fffff800`01c3fb3e e89d3cf1ff      call    nt!ExDereferenceCallBackBlock (fffff800`01b537e0) fffff800`01c3fb43 4403e3          add     r12d,ebx fffff800`01c3fb46 4183fc40        cmp     r12d,40h fffff800`01c3fb4a 72ae            jb      nt!PspSetCreateProcessNotifyRoutine+0x4a (fffff800`01c3fafa) fffff800`01c3fb4c 66019fb4010000  add     word ptr [rdi+1B4h],bx fffff800`01c3fb53 7518            jne     nt!PspSetCreateProcessNotifyRoutine+0xbd (fffff800`01c3fb6d) ……

此系统上并未安装腾讯管家等类似的防毒程序,但是64个注册回调已有6个被使用,差不多全是M$自家给占用的:

0: kd> x nt!PspCreateProcessNotifyRoutine fffff800`019f8ee0 nt!PspCreateProcessNotifyRoutine = <no type information> 0: kd> dq fffff800`019f8ee0 fffff800`019f8ee0  fffff880`00008c4f fffff880`006d7acf fffff800`019f8ef0  fffff880`006f694f fffff880`007ff5cf fffff800`019f8f00  fffff880`098168af fffff880`0a30216f fffff800`019f8f10  00000000`00000000 00000000`00000000 fffff800`019f8f20  00000000`00000000 00000000`00000000 fffff800`019f8f30  00000000`00000000 00000000`00000000 fffff800`019f8f40  00000000`00000000 00000000`00000000 fffff800`019f8f50  00000000`00000000 00000000`00000000 0: kd> .for (r $t0=0; $t0 < 0x40; r $t0=$t0+1) {r $t1=poi($t0 * 8 + nt!PspCreateProcessNotifyRoutine); .if ($t1 == 0) {.continue}; r $t0; r $t1 = $t1 & 0xFFFFFFFFFFFFFFF0; r $t1 = poi($t1 + 8); r $t1; u $t1; ln $t1;} $t0=0000000000000000 $t1=fffff8000188dbd0 nt!ViCreateProcessCallback: fffff800`0188dbd0 fff3            push    rbx fffff800`0188dbd2 4883ec40        sub     rsp,40h fffff800`0188dbd6 833dc3a2160000  cmp     dword ptr [nt!ViVerifierEnabled (fffff800`019f7ea0)],0 fffff800`0188dbdd 0f8583b6fcff    jne     nt! ?? ::FNODOBFM::`string'+0x21140 (fffff800`01859266) fffff800`0188dbe3 4883c440        add     rsp,40h fffff800`0188dbe7 5b              pop     rbx fffff800`0188dbe8 c3              ret fffff800`0188dbe9 90              nop (fffff800`0188dbd0)   nt!ViCreateProcessCallback   |  (fffff800`0188dbf0)   nt!IopAllocateFileObjectExtension Exact matches: nt!ViCreateProcessCallback = <no type information> $t0=0000000000000001 $t1=fffffa6000e52ffc ksecdd!CredMarshalTargetInfo+0x8cc: fffffa60`00e52ffc 4883ec28        sub     rsp,28h fffffa60`00e53000 488364244800    and     qword ptr [rsp+48h],0 fffffa60`00e53006 4584c0          test    r8b,r8b fffffa60`00e53009 488bc2          mov     rax,rdx fffffa60`00e5300c 7546            jne     ksecdd!CredMarshalTargetInfo+0x924 (fffffa60`00e53054) fffffa60`00e5300e 488d542448      lea     rdx,[rsp+48h] fffffa60`00e53013 488bc8          mov     rcx,rax fffffa60`00e53016 ff15dc00feff    call    qword ptr [ksecdd!BCryptDestroySecret+0x191c8 (fffffa60`00e330f8)] (fffffa60`00e52730)   ksecdd!CredMarshalTargetInfo+0x8cc   |  (fffffa60`00e535f4)   ksecdd!AcquireCredentialsHandleW $t0=0000000000000002 $t1=fffffa600106f830 tcpip+0x69830: fffffa60`0106f830 4d85c0          test    r8,r8 fffffa60`0106f833 7405            je      tcpip+0x6983a (fffffa60`0106f83a) fffffa60`0106f835 e9860b0000      jmp     tcpip+0x6a3c0 (fffffa60`010703c0) fffffa60`0106f83a e911130000      jmp     tcpip+0x6ab50 (fffffa60`01070b50) fffffa60`0106f83f 90              nop fffffa60`0106f840 90              nop fffffa60`0106f841 90              nop fffffa60`0106f842 90              nop $t0=0000000000000003 $t1=fffffa600074e06c CI!I_PEProcessNotify: fffffa60`0074e06c 4584c0          test    r8b,r8b fffffa60`0074e06f 7528            jne     CI!I_PEProcessNotify+0x2d (fffffa60`0074e099) fffffa60`0074e071 53              push    rbx fffffa60`0074e072 4883ec20        sub     rsp,20h fffffa60`0074e076 488bda          mov     rbx,rdx fffffa60`0074e079 ff15f1d1f5ff    call    qword ptr [CI!_imp_IoGetCurrentProcess (fffffa60`006ab270)] fffffa60`0074e07f 488bc8          mov     rcx,rax fffffa60`0074e082 ff1530d0f5ff    call    qword ptr [CI!_imp_PsIsProtectedProcess (fffffa60`006ab0b8)] (fffffa60`0074e06c)   CI!I_PEProcessNotify   |  (fffffa60`0074e0a0)   CI!RSA32Alloc Exact matches: CI!I_PEProcessNotify = <no type information> $t0=0000000000000004 $t1=fffffa6002fac964 dekfs+0x6964: fffffa60`02fac964 48895c2408      mov     qword ptr [rsp+8],rbx fffffa60`02fac969 48896c2410      mov     qword ptr [rsp+10h],rbp fffffa60`02fac96e 56              push    rsi fffffa60`02fac96f 57              push    rdi fffffa60`02fac970 4154            push    r12 fffffa60`02fac972 4155            push    r13 fffffa60`02fac974 4156            push    r14 fffffa60`02fac976 4883ec50        sub     rsp,50h $t0=0000000000000005 $t1=fffffa600c545b2d peauth!I_PEProcessNotify: fffffa60`0c545b2d 48895c2408      mov     qword ptr [rsp+8],rbx fffffa60`0c545b32 57              push    rdi fffffa60`0c545b33 4883ec20        sub     rsp,20h fffffa60`0c545b37 e8cc6bfdff      call    peauth!WARBIRD::Stub_VerifyVerifierCheckSum (fffffa60`0c51c708) fffffa60`0c545b3c 90              nop fffffa60`0c545b3d 360000          add     byte ptr ss:[rax],al fffffa60`0c545b40 00fd            add     ch,bh fffffa60`0c545b42 140a            adc     al,0Ah (fffffa60`0c545b2d)   peauth!I_PEProcessNotify   |  (fffffa60`0c545fe1)   peauth!PEReturnCertChain Exact matches: peauth!I_PEProcessNotify = <no type information>

ERESOURCE:和你玩躲猫猫

我是一直都知晓你的存在的,虽然你从未现身,任何时候都像操控着一切的隐形之手,但在我心里,你是世界的主宰,规则的制定者,高高在上的上帝。

我只是一个小螺丝钉,每天只是重复地做着我被要求的工作。关于我的出生,我并不清楚,因为我的创造者-“瓶瓶”老爷向来沉默寡言,从不多说话,并且一直都是忙忙碌碌。我也只能在他有空的时候,和他有一句没一句得聊聊天,这才从他口中得知关于我身世一星半点的信息。

听“瓶瓶”老爷说,他是最不愿意制造我们这些螺丝钉的。他说,每制造出一个,他的负担就会增加了一个,虽然我们都是他的长工,每时每刻不停得为他工作,但我们的衣食住行还是要“瓶瓶”老爷为我们打点才行,这也是忙碌的他在空闲时经常向我们发的牢骚。不过,尽管他爱发些不着边的牢骚,我们还是很喜欢他,毕竟他创造了并一直照顾着我们,我们真心地愿意为他打长工,哪怕是一辈子,只要这个世界存在并运转着。

但就在前些日子,世界不在像以前那么可靠了,原因是那只隐形的上帝之手在规则上所做的一个很小的改动,改动虽小,对我来说却是灾难,从此我的世界整个都变成瓦蓝瓦蓝的了(Blue Screen Of Death)。生活如此简单的我,与世无争,咱这井水犯不着任何一条河水,真想不到哎,躺着也会中枪。

我只是重复着做一丁点儿的事,说来也很简单:以Paging I/O的方式去从文件里读数据出来。至于为什么要以Paging I/O方式,我曾问过“瓶瓶”老爷,他说的话很长很长,很深奥很深奥,我只记住了一点点,希望我的复述你能听得明白:

我要读的文件是于用户层中打开的,而我却生活在内核里,我的另一个哥们会将用户层的HANDLE转成FileObject并交给我,之后用户层会将这个HANDLE释放掉(CloseHandle),这时候我虽然手上有Fileobject这把钥匙,但通向Cached I/O及Direct I/O的路全给封上了,只有Paging I/O这条黑路让我去走,我的职责如此,即便是刀山火海,也只能认了。但自从世界变成Win7之后,我总会遇到麻烦,连Paging I/O这条黑道有时也不让我走了,特别是看起来更炫的Win8世界,任何时候都是瓦蓝瓦蓝的。

当时我很伤心,世界一片惨蓝,我以为再也见不着太阳了。可“瓶瓶”老爷又让我重获新生。所以我更感激他了,我发誓愿意为他打二辈子长工,最多只能二辈子,不能再多了,因为我们所处的这个世界一直是很二很二的。

在世界又恢复至正常后,“瓶瓶”老爷曾对我说,或者是老爷他自言自语地说:Win7世界的NTFS动了点手脚,在获取文件ERESOURCE锁后,它会改变此ERESOURCE的Owner Thread’s Pointer(参见ExSetResourceOwnerPointer)!虽然我知道在Filter中是不应该操作此ERESOURCE锁的,但针对这种情况,也是不得已而为之,做个简单的Paging I/O也不是什么危险或惹事的操作。

“瓶瓶”老爷现在让我获取两次共享锁并在释放锁时多做个判断,你想看看我最得意时的状态吗?

0: kd&gt; dt _ERESOURCE 0xfffffa80&#x60;02b62050; !locks -v 0xfffffa80&#x60;02b62050;
nt!_ERESOURCE
+0x000 SystemResourcesList : _LIST_ENTRY [ 0xfffffa80&#x60;02b620f8 - 0xfffffa80&#x60;029030d0 ]
+0x010 OwnerTable       : 0xfffffa80&#x60;03ba48c0 _OWNER_ENTRY
+0x018 ActiveCount      : 1
+0x01a Flag             : 0
+0x020 SharedWaiters    : (null)
+0x028 ExclusiveWaiters : (null)
+0x030 OwnerEntry       : _OWNER_ENTRY
+0x040 ActiveEntries    : 2
+0x044 ContentionCount  : 0
+0x048 NumberOfSharedWaiters : 0
+0x04c NumberOfExclusiveWaiters : 0
+0x050 Reserved2        : (null)
+0x058 Address          : (null)
+0x058 CreatorBackTraceIndex : 0
+0x060 SpinLock         : 0

Resource @ 0xfffffa8002b62050    Shared 2 owning threads
&lt;strong&gt;     Threads: fffffa800196ab53-01&lt;*&gt; *** Actual Thread fffffa800196ab50&lt;/strong&gt;

THREAD fffffa800196ab50  Cid 1364.1368  Teb: 000007fffffde000 Win32Thread: fffff900c229fc20 RUNNING on processor 0
IRP List:
fffffa80042c2c40: (0006,0118) Flags: 00060043  Mdl: fffffa80045c7340
Not impersonating
DeviceMap                 fffff8a00256f460
Owning Process            fffffa80019b9b30       Image:         notepad.exe
Attached Process          N/A            Image:         N/A
Wait Start TickCount      8907           Ticks: 0
Context Switch Count      219                 LargeStack
UserTime                  00:00:00.000
KernelTime                00:00:00.592
Win32 Start Address 0x00000000ff383570
Stack Init fffff880171f1db0 Current fffff880171f0940
Base fffff880171f2000 Limit fffff880171e9000 Call 0
Priority 10 BasePriority 8 UnusualBoost 0 ForegroundBoost 2 IoPriority 2 PagePriority 5
Child-SP          RetAddr           Call Site
fffff880&#x60;171f17b0 fffff880&#x60;159d69de Foobar!FbDirectReadWriteSingle+0x2a1 [Foobar\read.c @ 332]
fffff880&#x60;171f1840 fffff880&#x60;159d73a8 Foobar!FbDirectReadWrite+0x85e [Foobar\read.c @ 628]
fffff880&#x60;171f1930 fffff800&#x60;01cbb1b5 Foobar!FbDispatchRead+0x18 [Foobar\read.c @ 1172]
fffff880&#x60;171f1960 fffff800&#x60;01cbac89 nt!IoPageRead+0x255
fffff880&#x60;171f19f0 fffff800&#x60;01ca165a nt!MiIssueHardFault+0x255
fffff880&#x60;171f1ac0 fffff800&#x60;01c920ee nt!MmAccessFault+0x146a
fffff880&#x60;171f1c20 00000000&#x60;ff384061 nt!KiPageFault+0x16e (TrapFrame @ fffff880&#x60;171f1c20)
00000000&#x60;000fde80 00000000&#x60;00000000 0xff384061

&lt;strong&gt;              fffffa800196ab50-01&lt;*&gt; &lt;/strong&gt;

THREAD fffffa800196ab50  Cid 1364.1368  Teb: 000007fffffde000 Win32Thread: fffff900c229fc20
……

哈哈,不论你怎么躲,也躲不开我了,因为我知道了你所有的藏身之所。现在想改变规则了?你想怎么改呢?

我是个简单的小螺丝钉,我不惧怕任何权威,我吃苦耐劳,我要求很少,只要不让我的世界一片惨蓝,做再多的事我也愿意。

现在我的生命力更顽强了,我不仅会点自检,还会些自愈。面对这个美好的世界,只想说一句话,那就是:活着真好。

最后,我的id是“螺丝钉小P”。你的呢?我知道你有多个马甲的,嘿嘿!

摘自“螺丝钉小P”的日记(DEC/12/2012)

 

Pages:  1 2 3 4 5