2006年01月03日

这两天弄了个新后门,只是为了学习和实现自己的几个想法. 还没有完成,,现在只是个大概的样子  以后会慢慢填

我的想法也只实现了一点.现在这个程序有的是:运行在ring0,没有进程,没有硬盘文件,没有端口,也没有启动项(硬盘文件用的比较cheAp的方法,以后也许真的不需要了: )   还有就是穿防火墙,实验过的防火墙比较少,,不过相信都可以穿了,包括sp2, 正向连接,所谓的强开端口 ; P    客户端用nc,,比如运行uay.exe 12345,,,然后nc ip 12345就可以了

哎 ring0下的,我最怕的就是不稳定.

程序写的比较垃圾,见笑了 ;p  只在xp下实验过,,,,希望找到错误后能mAil我 zmba@tom.com :)

下载

2005年12月31日

Calling a DLL in a Kernel-Mode Driver


If you’ve done any Windows® programming, chances are you’ve written at least one DLL. If you’ve just started writing Windows drivers, you may have tried to call a DLL from your driver, without success. Why doesn’t this work?

The fundamental issue for a DLL in kernel mode is whether the DLL calls any user-mode code. If a DLL contains anything other than native kernel API calls, you’ll get linker errors if you try to link your driver with it when you build (and the kernel wouldn’t load it anyway). Obviously, a DLL built as a Win32 library will fall into this category. Even if you avoid making explicit user-mode API calls in the DLL source code, the compiler often generates implicit user-mode support calls when it does stack checking, overflow checking, and the like. So any DLL built in a Windows user-mode development environment such as Visual Studio® is unlikely to qualify for use in kernel mode.

What should you do?

Use the DDK build environment to build an export driver. An export driver is a kernel-mode DLL that provides routines for other drivers to call. Like any standard driver, an export driver contains only routines that resolve to kernel-mode functions. Unlike a standard driver, however, an export driver does not receive IRPs or occupy a place in the driver stack, nor is it considered to be a system service.

You can build any standard driver as an export driver—it will operate as a standard driver when loaded in the usual way, and also export functions that other drivers can call. However, if you just want to write the kernel-mode equivalent of a library, you can leave out many of the elements required for a full-blown driver. At a minimum, an export driver must have a DriverEntry routine; this can be an empty stub to satisfy build scripts—the export driver’s DriverEntry is never called by Plug and Play. An export driver should also have the standard entry point and unload routines, DllInitialize and DLLUnload, so the operating system can keep track of the number of times the export driver’s functions have been imported by other drivers, and unload the export driver when the last calling driver unloads.

You can export functions by declaring them with the DECLSPEC_EXPORT macro in the export driver source code, or you can take the simpler approach of listing them as exports in a module-definition (.def) file. (Even if you use DECLSPEC_EXPORT, your .def file must contain at least DllInitialize and DLLUnload so you can mark these functions as PRIVATE.)

In the driver’s sources file, set TARGETTYPE to EXPORT_DRIVER and set DLLDEF to the path of your .def file, then build the driver. The build process generates an export library with a .lib extension and an export driver with a .sys extension. The .sys file is your kernel-mode DLL.

Static linking to an export driver is essentially the same as for a user-mode DLL: Add the export driver library to the target library list for the driver that will be calling the library. In the calling driver’s source code, use the DECLSPEC_IMPORT macro to declare the functions it will call. (Also use EXTERN_C, defined in ntdef.h, to avoid any unwanted name decoration in the calls.) Install the export driver .sys file in the %windir%\system32\drivers directory. It will be loaded the first time any other driver calls into it.

Dynamic linking is a bit trickier, because there’s no kernel-mode equivalent to GetProcAddress. (MmGetSystemRoutineAddress is similar, but it will dynamically resolve exports only from Ntoskrnl.exe or the HAL.) Here are some techniques for dynamic linking to an export driver:

In the export driver, define an I/O control code (IOCTL) that instantiates a class and returns an interface pointer that other drivers can use to call methods on the interface object in the usual way. The class should provide a method for freeing the object, and the header file should contain only pure abstract methods.

Design the export driver to export direct-call interfaces in response to IRP_MN_QUERY_INTERFACE requests from other drivers.

For more information:

Roberts, Tim. DLLs in Kernel Mode. Windows Driver Developer’s Digest, Vol. 1 No. 3, July 2003

Windows DDK
Creating Export Drivers
Defining I/O Control Codes
IRP_MN_QUERY_INTERFACE

Visual C++ Programmer’s Guide
Module-Definition (.DEF) Files

Richter, Jeffrey. Programming Applications for Microsoft Windows, 4th ed. Microsoft Press, 1999. ISBN 1-57231-996-8
Part IV: Dynamic-Link Libraries

2005年12月16日

晚上看一个清华的讲座,软件开发过程的.讲座的人是微软中国的一个.听介绍是在美国拿了博士后就进了微软.这样的人估计生活肯定不错.讲座的时候3句话里有2句都得加点英文,估计是在国外过久了.他是谁其实和我要说的都没什么关系,,看着看着我就在想,这会是一个什么样的人,如果哪天要和小日本真打起仗,给他枪,他会去参加吗?也许是我瞎琢磨,人不可貌相,说不定他直接就拿刀去砍呢.想象下这样的情景还是很有意思的 : )   不过在国外有了优越的生活,去不去死真的是个问题.这样想他原因是看他的笔记本是sony的,你和他谈反对日货,恩..我猜是没结果,,很多聪明人根本不会睬你,就像很多聪明人会认为你很幼稚一样.先沉默再来个高傲的眼神.层次就分出来.没这些人地球也就不转了,,,有阿谀奉承很高兴服从的,,也有认为看自己透了事层次很高然后再去服从的.当然更要又反抗到底被拖出去砍了的和分不出类的.

自己会是什么样的人呢? 在战争中,多杀两个日本人就够了 不想那么多了 嘿嘿

讲座的博士,对不起了,今天拿您瞎琢磨了半天 ; )

又是APC,接着上回的吧,没完没了的 :|

    朋友又提到了OriginAlApcEnvironment和AttachedApcEnvironment.翻出源码来找了找,大概理解了为啥要在KTHREAD里有+0×138 
ApcStAtePointer  : [2] Ptr32 _KAPC_STATE 和 +0×14c SAvedApcState    : _KAPC_STATE 了.
    首先说只有KTHREAD里的+0×034 ApcStAte         : _KAPC_STATE 才是存放APC的地方,DISPATCH APC的时候也是这里的,从上次的那几个函
数中可以看出来,而SAvedApcStAte是用来保存的(废话),ApcStAtePointer不过是2个指针.在什么情况下需要保存原来的ApcStAte呢?在线程AttAch
到其他进程的时候.APC是为了在指定的线程环境下执行程序,当线程所在的地址空间变了以后,线程原来的APC就不会执行.而是产生一个新的APC队列,
用来在当前进程中执行APC
VOID
KeAttachProcess (
    IN PRKPROCESS Process
    )

/*++

Routine Description:

    This function attaches a thread to a target process' address space
    if, and only if, there is not already a process attached.

Arguments:

    Process - Supplies a pointer to a dispatcher object of type process.

Return Value:

    None.

--*/

{

    KIRQL OldIrql;
    PRKTHREAD Thread;

    ASSERT_PROCESS(Process);
    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    //
    // Raise IRQL to dispatcher level and lock dispatcher database.
    //

    Thread = KeGetCurrentThread();
    KiLockDispatcherDatabase(&OldIrql);

    //
    // If the target process is the current process, then return immediately.
    // Otherwise, check whether there is a process address space attached or
    // the thread is executing a DPC. If either condition is true, then call
    // bug check. Otherwise, attach the target process.
    //

    if (Thread->ApcState.Process == Process) {
        KiUnlockDispatcherDatabase(OldIrql);

    } else if ((Thread->ApcStateIndex != 0) ||
               (KeIsExecutingDpc() != FALSE)) {
        KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,
                     (ULONG_PTR)Process,
                     (ULONG_PTR)Thread->ApcState.Process,
                     (ULONG)Thread->ApcStateIndex,
                     (ULONG)KeIsExecutingDpc());

    } else {
        KiAttachProcess(Thread, Process, OldIrql, &Thread->SavedApcState);
    }

    return;
}

VOID
KiAttachProcess (
    IN PRKTHREAD Thread,
    IN PKPROCESS Process,
    IN KIRQL OldIrql,
    OUT PRKAPC_STATE SavedApcState
    )

/*++

Routine Description:

    This function attaches a thread to a target process' address space.

    N.B. The dispatcher database lock must be held when this routine is
        called.

Arguments:

    Thread - Supplies a pointer to a dispatcher object of type thread.

    Process - Supplies a pointer to a dispatcher object of type process.

    OldIrql - Supplies the previous IRQL.

    SavedApcState - Supplies a pointer to the APC state structure that receives
        the saved APC state.

Return Value:

    None.

--*/

{

    PRKTHREAD OutThread;
    KAFFINITY Processor;
    PLIST_ENTRY NextEntry;
    KIRQL HighIrql;

    ASSERT(Process != Thread->ApcState.Process);

    //
    // Bias the stack count of the target process to signify that a
    // thread exists in that process with a stack that is resident.
    //

    Process->StackCount += 1;

    //
    // Save current APC state and initialize a new APC state.
    //

    KiMoveApcState(&Thread->ApcState, SavedApcState);
    InitializeListHead(&Thread->ApcState.ApcListHead[KernelMode]);
    InitializeListHead(&Thread->ApcState.ApcListHead[UserMode]);
    Thread->ApcState.Process = Process;
    Thread->ApcState.KernelApcInProgress = FALSE;
    Thread->ApcState.KernelApcPending = FALSE;
    Thread->ApcState.UserApcPending = FALSE;
    if (SavedApcState == &Thread->SavedApcState) {
        Thread->ApcStatePointer[0] = &Thread->SavedApcState;
        Thread->ApcStatePointer[1] = &Thread->ApcState;
        Thread->ApcStateIndex = 1;
    }

    //
    // If the target process is in memory, then immediately enter the
    // new address space by loading a new Directory Table Base. Otherwise,
    // insert the current thread in the target process ready list, inswap
    // the target process if necessary, select a new thread to run on the
    // the current processor and context switch to the new thread.
    //

    if (Process->State == ProcessInMemory) {

        //
        // It is possible that the process is in memory, but there exist
        // threads in the process ready list. This can happen when memory
        // management forces a process attach.
        //

        NextEntry = Process->ReadyListHead.Flink;
        while (NextEntry != &Process->ReadyListHead) {
            OutThread = CONTAINING_RECORD(NextEntry, KTHREAD, WaitListEntry);
            RemoveEntryList(NextEntry);
            OutThread->ProcessReadyQueue = FALSE;
            KiReadyThread(OutThread);
            NextEntry = Process->ReadyListHead.Flink;
        }

        KiSwapProcess(Process, SavedApcState->Process);
        KiUnlockDispatcherDatabase(OldIrql);

    } else {
        Thread->State = Ready;
        Thread->ProcessReadyQueue = TRUE;
        InsertTailList(&Process->ReadyListHead, &Thread->WaitListEntry);
        if (Process->State == ProcessOutOfMemory) {
            Process->State = ProcessInTransition;
            InsertTailList(&KiProcessInSwapListHead, &Process->SwapListEntry);
            KiSwapEvent.Header.SignalState = 1;
            if (IsListEmpty(&KiSwapEvent.Header.WaitListHead) == FALSE) {
                KiWaitTest(&KiSwapEvent, BALANCE_INCREMENT);
            }
        }

        //
        // Clear the active processor bit in the previous process and
        // set active processor bit in the process being attached to.
        //

#if !defined(NT_UP)

        KiLockContextSwap(&HighIrql);
        Processor = KeGetCurrentPrcb()->SetMember;
        SavedApcState->Process->ActiveProcessors &= ~Processor;
        Process->ActiveProcessors |= Processor;

#if defined(_ALPHA_)

        Process->RunOnProcessors |= Processor;

#endif

        KiUnlockContextSwap(HighIrql);

#endif

        Thread->WaitIrql = OldIrql;
        KiSwapThread();
    }

    return;
}

VOID
KeDetachProcess (
    VOID
    )

/*++

Routine Description:

    This function detaches a thread from another process' address space.

Arguments:

    None.

Return Value:

    None.

--*/

{

    KIRQL OldIrql;
    PKPROCESS Process;
    PKTHREAD Thread;

    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    //
    // Raise IRQL to dispatcher level and lock dispatcher database.
    //

    Thread = KeGetCurrentThread();
    KiLockDispatcherDatabase(&OldIrql);

    //
    // If the current thread is attached to another process, then detach
    // it.
    //

    if (Thread->ApcStateIndex != 0) {

        //
        // Check if a kernel APC is in progress, the kernel APC queue is
        // not empty, or the user APC queue is not empty. If any of these
        // conditions are true, then call bug check.
        //

#if DBG

        if ((Thread->ApcState.KernelApcInProgress) ||
            (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) ||
            (IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) == FALSE)) {
            KeBugCheck(INVALID_PROCESS_DETACH_ATTEMPT);
        }

#endif

        //
        // Unbias current process stack count and check if the process should
        // be swapped out of memory.
        //

        Process = Thread->ApcState.Process;
        Process->StackCount -= 1;
        if ((Process->StackCount == 0) &&
            (IsListEmpty(&Process->ThreadListHead) == FALSE)) {
            Process->State = ProcessInTransition;
            InsertTailList(&KiProcessOutSwapListHead, &Process->SwapListEntry);
            KiSwapEvent.Header.SignalState = 1;
            if (IsListEmpty(&KiSwapEvent.Header.WaitListHead) == FALSE) {
                KiWaitTest(&KiSwapEvent, BALANCE_INCREMENT);
            }
        }

        //
        // Restore APC state and check whether the kernel APC queue contains
        // an entry. If the kernel APC queue contains an entry then set kernel
        // APC pending and request a software interrupt at APC_LEVEL.
        //

        KiMoveApcState(&Thread->SavedApcState, &Thread->ApcState);
        Thread->SavedApcState.Process = (PKPROCESS)NULL;
        Thread->ApcStatePointer[0] = &Thread->ApcState;
        Thread->ApcStatePointer[1] = &Thread->SavedApcState;
        Thread->ApcStateIndex = 0;
        if (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) {
            Thread->ApcState.KernelApcPending = TRUE;
            KiRequestSoftwareInterrupt(APC_LEVEL);
        }

        //
        // Swap the address space back to the parent process.
        //

        KiSwapProcess(Thread->ApcState.Process, Process);
    }

    //
    // Lower IRQL to its previous value and return.
    //

    KiUnlockDispatcherDatabase(OldIrql);
    return;
}
这两个是比较老的函数.
当一个threAd AttAch到其他的process的时候,KiMoveApcState(&Thread-> ApcState, SAvedApcStAte);原来的ApcStAte结构被复制到
SAvedApcStAte,也就是ThreAd-> SAvedApcStAte.然后ThreAd-> ApcStAte被重新初始化为空
InitializeListHead(&Thread-> ApcState.ApcListHead[KernelMode]);
InitializeListHead(&Thread-> ApcState.ApcListHead[UserMode]);
Thread-> ApcState.Process = Process;
Thread-> ApcState.KernelApcInProgress = FALSE;
Thread-> ApcState.KernelApcPending = FALSE;
Thread-> ApcState.UserApcPending = FALSE;
然后用到了那2个指针
if (SavedApcState == &Thread-> SavedApcState) {
    Thread-> ApcStatePointer[0] = &Thread-> SavedApcState;
    Thread-> ApcStatePointer[1] = &Thread-> ApcState;
    Thread-> ApcStateIndex = 1;
}
可以看到Thread-> ApcStatePointer[OriginAlApcEnvironment]指向了AttAch前的
ApcStAte,Thread-> ApcStatePointer[AttachedApcEnvironment]指向了AttAch后的.
如果在这时Insert进了一个APC,如果在KeInitializeApc 时用的是OriginAlApcEnvironment,这个APC不会马上被调用,而是等到进程切换回去以后
 
在deAttAch的时候,把SAvedApcStAte再放回ApcStAte
KiMoveApcState(&Thread-> SavedApcState, &Thread-> ApcState);
Thread-> SavedApcState.Process = (PKPROCESS)NULL;
Thread-> ApcStatePointer[0] = &Thread-> ApcState;
Thread-> ApcStatePointer[1] = &Thread-> SavedApcState;
Thread-> ApcStateIndex = 0;
if (IsListEmpty(&Thread-> ApcState.ApcListHead[KernelMode]) == FALSE) {
    Thread-> ApcState.KernelApcPending = TRUE;
    KiRequestSoftwareInterrupt(APC_LEVEL);
}
 
KeAttachProcess,KeDetachProcess 这2个函数之所以老了,是因为他们只能让一个threAd AttAch一次,意思是说不能让一个线程在
KeAttachProcess后没有调用KeDetachProcess 时就再次KeAttachProcess到另外一个进程
改进的函数就是
VOID
KeStackAttachProcess (
    IN PRKPROCESS Process,
    OUT PRKAPC_STATE ApcState
    );
 
VOID
KeUnstackDetachProcess (
    IN PRKAPC_STATE ApcState
    );
 
参数PRKAPC_STATE ApcState就是提供的空间用来保存上一个ApcStAte的,和KTHREAD.SAvedApcStAte的作用是一样的.当第一次ATTACH的是时候用KTHREAD.SAvedApcStAte,再次ATTACH的时候用用户提供的空间.
_KAPC_STATE中的+0×010 Process          : Ptr32 _KPROCESS指出ATTACH前的process
通过ThreAd->ApcStAteIndex判断是否AttAch过
从函数中看,似乎在DeAttAch前应该判断下APC队列里时候有APC在排对,如果有的时候就DEATTACH了,就要bugcheck
 
VOID
KeStackAttachProcess (
    IN PRKPROCESS Process,
    OUT PRKAPC_STATE ApcState
    )

/*++

Routine Description:

    This function attaches a thread to a target process' address space
    and returns information about a previous attached process.

Arguments:

    Process - Supplies a pointer to a dispatcher object of type process.

Return Value:

    None.

--*/

{

    KIRQL OldIrql;
    PRKTHREAD Thread;

    ASSERT_PROCESS(Process);
    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    //
    // Raise IRQL to dispatcher level and lock dispatcher database.
    //

    Thread = KeGetCurrentThread();
    KiLockDispatcherDatabase(&OldIrql);

    //
    // If the current thread is executing a DPC, then bug check.
    //

    if (KeIsExecutingDpc() != FALSE) {
        KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,
                     (ULONG_PTR)Process,
                     (ULONG_PTR)Thread->ApcState.Process,
                     (ULONG)Thread->ApcStateIndex,
                     (ULONG)KeIsExecutingDpc());
    }

    //
    // If the target process is not the current process, then attach the target
    // process. Otherwise, return a distinguished process value to indicate that
    // an attach was not performed.
    //

    if (Thread->ApcState.Process == Process) {
        KiUnlockDispatcherDatabase(OldIrql);
        ApcState->Process = (PRKPROCESS)1;

    } else {

        //
        // If the current thread is attached to a process, then save the current
        // APC state in the callers APC state structure. Otherwise, save the
        // current APC state in the saved APC state structure, and return a NULL
        // process pointer.
        //

        if (Thread->ApcStateIndex != 0) {
            KiAttachProcess(Thread, Process, OldIrql, ApcState);

        } else {
            KiAttachProcess(Thread, Process, OldIrql, &Thread->SavedApcState);
            ApcState->Process = NULL;
        }
    }

    return;
}

VOID
KiAttachProcess (
    IN PRKTHREAD Thread,
    IN PKPROCESS Process,
    IN KIRQL OldIrql,
    OUT PRKAPC_STATE SavedApcState
    )

/*++

Routine Description:

    This function attaches a thread to a target process' address space.

    N.B. The dispatcher database lock must be held when this routine is
        called.

Arguments:

    Thread - Supplies a pointer to a dispatcher object of type thread.

    Process - Supplies a pointer to a dispatcher object of type process.

    OldIrql - Supplies the previous IRQL.

    SavedApcState - Supplies a pointer to the APC state structure that receives
        the saved APC state.

Return Value:

    None.

--*/

{

    PRKTHREAD OutThread;
    KAFFINITY Processor;
    PLIST_ENTRY NextEntry;
    KIRQL HighIrql;

    ASSERT(Process != Thread->ApcState.Process);

    //
    // Bias the stack count of the target process to signify that a
    // thread exists in that process with a stack that is resident.
    //

    Process->StackCount += 1;

    //
    // Save current APC state and initialize a new APC state.
    //

    KiMoveApcState(&Thread->ApcState, SavedApcState);
    InitializeListHead(&Thread->ApcState.ApcListHead[KernelMode]);
    InitializeListHead(&Thread->ApcState.ApcListHead[UserMode]);
    Thread->ApcState.Process = Process;
    Thread->ApcState.KernelApcInProgress = FALSE;
    Thread->ApcState.KernelApcPending = FALSE;
    Thread->ApcState.UserApcPending = FALSE;
    if (SavedApcState == &Thread->SavedApcState) {
        Thread->ApcStatePointer[0] = &Thread->SavedApcState;
        Thread->ApcStatePointer[1] = &Thread->ApcState;
        Thread->ApcStateIndex = 1;
    }

    //
    // If the target process is in memory, then immediately enter the
    // new address space by loading a new Directory Table Base. Otherwise,
    // insert the current thread in the target process ready list, inswap
    // the target process if necessary, select a new thread to run on the
    // the current processor and context switch to the new thread.
    //

    if (Process->State == ProcessInMemory) {

        //
        // It is possible that the process is in memory, but there exist
        // threads in the process ready list. This can happen when memory
        // management forces a process attach.
        //

        NextEntry = Process->ReadyListHead.Flink;
        while (NextEntry != &Process->ReadyListHead) {
            OutThread = CONTAINING_RECORD(NextEntry, KTHREAD, WaitListEntry);
            RemoveEntryList(NextEntry);
            OutThread->ProcessReadyQueue = FALSE;
            KiReadyThread(OutThread);
            NextEntry = Process->ReadyListHead.Flink;
        }

        KiSwapProcess(Process, SavedApcState->Process);
        KiUnlockDispatcherDatabase(OldIrql);

    } else {
        Thread->State = Ready;
        Thread->ProcessReadyQueue = TRUE;
        InsertTailList(&Process->ReadyListHead, &Thread->WaitListEntry);
        if (Process->State == ProcessOutOfMemory) {
            Process->State = ProcessInTransition;
            InsertTailList(&KiProcessInSwapListHead, &Process->SwapListEntry);
            KiSwapEvent.Header.SignalState = 1;
            if (IsListEmpty(&KiSwapEvent.Header.WaitListHead) == FALSE) {
                KiWaitTest(&KiSwapEvent, BALANCE_INCREMENT);
            }
        }

        //
        // Clear the active processor bit in the previous process and
        // set active processor bit in the process being attached to.
        //

#if !defined(NT_UP)

        KiLockContextSwap(&HighIrql);
        Processor = KeGetCurrentPrcb()->SetMember;
        SavedApcState->Process->ActiveProcessors &= ~Processor;
        Process->ActiveProcessors |= Processor;

#if defined(_ALPHA_)

        Process->RunOnProcessors |= Processor;

#endif

        KiUnlockContextSwap(HighIrql);

#endif

        Thread->WaitIrql = OldIrql;
        KiSwapThread();
    }

    return;
}

VOID
KeUnstackDetachProcess (
    IN PRKAPC_STATE ApcState
    )

/*++

Routine Description:

    This function detaches a thread from another process' address space
    and restores previous attach state.

Arguments:

    ApcState - Supplies a pointer to an APC state structure that was returned
        from a previous call to stack attach process.

Return Value:

    None.

--*/

{

    KIRQL OldIrql;
    PKPROCESS Process;
    PKTHREAD Thread;

    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    //
    // Raise IRQL to dispatcher level and lock dispatcher database.
    //

    Thread = KeGetCurrentThread();
    KiLockDispatcherDatabase(&OldIrql);

    //
    // If the APC state has a distinguished process pointer value, then no
    // attach was performed on the paired call to stack attach process.
    //

    if (ApcState->Process != (PRKPROCESS)1) {

        //
        // If the current thread is not attached to another process, a kernel
        // APC is in progress, or either the kernel or user mode APC queues
        // are not empty, then call bug check.
        //

        if ((Thread->ApcStateIndex == 0) ||
             (Thread->ApcState.KernelApcInProgress) ||
             (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) ||
             (IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) == FALSE)) {
            KeBugCheck(INVALID_PROCESS_DETACH_ATTEMPT);
        }

        //
        // Unbias current process stack count and check if the process should
        // be swapped out of memory.
        //

        Process = Thread->ApcState.Process;
        Process->StackCount -= 1;
        if ((Process->StackCount == 0) &&
            (IsListEmpty(&Process->ThreadListHead) == FALSE)) {
            Process->State = ProcessInTransition;
            InsertTailList(&KiProcessOutSwapListHead, &Process->SwapListEntry);
            KiSwapEvent.Header.SignalState = 1;
            if (IsListEmpty(&KiSwapEvent.Header.WaitListHead) == FALSE) {
                KiWaitTest(&KiSwapEvent, BALANCE_INCREMENT);
            }
        }

        //
        // Restore APC state and check whether the kernel APC queue contains
        // an entry. If the kernel APC queue contains an entry then set kernel
        // APC pending and request a software interrupt at APC_LEVEL.
        //

        if (ApcState->Process != NULL) {
            KiMoveApcState(ApcState, &Thread->ApcState);

        } else {
            KiMoveApcState(&Thread->SavedApcState, &Thread->ApcState);
            Thread->SavedApcState.Process = (PKPROCESS)NULL;
            Thread->ApcStatePointer[0] = &Thread->ApcState;
            Thread->ApcStatePointer[1] = &Thread->SavedApcState;
            Thread->ApcStateIndex = 0;
        }

        if (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) {
            Thread->ApcState.KernelApcPending = TRUE;
            KiRequestSoftwareInterrupt(APC_LEVEL);
        }

        //
        // Swap the address space back to the parent process.
        //

        KiSwapProcess(Thread->ApcState.Process, Process);
    }

    //
    // Lower IRQL to its previous value and return.
    //

    KiUnlockDispatcherDatabase(OldIrql);
    return;
}

2005年12月07日
/////////////KernelMessAgeBox.h////////
//////////////////////// uty@uaty
#include <ntddk.h>

//CAllers of KMessAgeBox must be running At IRQL = PASSIVE_LEVEL :>
BOOLEAN
KMessAgeBox(
	PCHAR	lpText,
	PCHAR	lpCAption,
	ULONG	ulType
	);
//--------------------------------------------------------------------
/////////////KernelMessAgeBox.c////////
//////////////////////// uty@uaty
#include <ntddk.h>

#ifdef DBG
#define u_DbgPrint(_x_)		\
			DbgPrint _x_;
#else
#define	u_DbgPrint(_x_)
#endif

//--------------------------------------------------------------------
typedef enum _KAPC_ENVIRONMENT{
    OriginalApcEnvironment,
	AttachedApcEnvironment,
	CurrentApcEnvironment
}KAPC_ENVIRONMENT;

typedef struct _KAPC_STATE{
	LIST_ENTRY	ApcListHead[2];
	PEPROCESS	Process;
	UCHAR		KernelApcInProgress;
	UCHAR		KernelApcPending;
	UCHAR		UserApcPending;
}KAPC_STATE,*PKAPC_STATE;

typedef	struct _messAgepArA{
	PCHAR	lpText;
	PCHAR	lpCAption;
	ULONG	ulType;
}MESSAGEPARA,*PMESSAGEPARA;
//--------------------------------------------------------------------
NTSTATUS
uSetTheApc(
	ULONG			process,
	ULONG			threAd,
	ULONG			MAppedAddress,
	PKEVENT			pEvent,
	PMESSAGEPARA	pPArA
	);

VOID
WorkThreAd(
	IN PVOID pContext
	);

VOID
KernelApcCAllBAck(
	PKAPC Apc,
	PKNORMAL_ROUTINE *NormalRoutine,
	PVOID *NormalContext,
	PVOID *SystemArgument1,
	PVOID *SystemArgument2
	);

VOID
OnUnloAd(
	IN PDRIVER_OBJECT DriverObject
	);

BOOLEAN
find_threAd(
	OUT	ULONG	*process,
	OUT	ULONG	*threAd
	);

UserMessAgeBox(
	PCHAR	MessAgeText,
	PCHAR	MessAgeCAption,
	ULONG	ulType
	);

UserMessAgeBox_end(
	VOID
	);

BOOLEAN
CheckVersion(
	VOID
	);

/* Function prototypes for APCs */
VOID
KeInitializeApc(
	PKAPC Apc,
	PKTHREAD Thread,
	CCHAR ApcStateIndex,
	PKKERNEL_ROUTINE KernelRoutine,
	PKRUNDOWN_ROUTINE RundownRoutine,
	PKNORMAL_ROUTINE NormalRoutine,
	KPROCESSOR_MODE ApcMode,
	PVOID NormalContext
	);

BOOLEAN
KeInsertQueueApc(
	PKAPC Apc,
	PVOID SystemArgument1,
	PVOID SystemArgument2,
	UCHAR unknown
	);

/* Function prototypes for AttAch process */
NTKERNELAPI VOID
KeStackAttachProcess(
		IN PEPROCESS Process,
		OUT PKAPC_STATE ApcState
		);

NTKERNELAPI VOID
KeUnstackDetachProcess(
	IN PKAPC_STATE ApcState
	);

//--------------------------------------------------------------------
ULONG		THREADLISTHEAD_OFFSET;
ULONG		THREADLISTENTRY_OFFSET;
ULONG		IMAGEFILENAME_OFFSET;
ULONG		ACTIVEPROCESSLINKS_OFFSET;
ULONG		USERAPCPENDING_OFFSET;
ULONG		TCB_TEB_OFFSET;

BOOLEAN
KMessAgeBox(
	PCHAR	lpText,
	PCHAR	lpCAption,
	ULONG	ulType
	)
{
	PMESSAGEPARA		pPArA	= NULL;
	HANDLE				hThreAd = NULL;
	NTSTATUS			dwStAtus;
	if(strlen(lpText)+strlen(lpCAption) > 98){		//two bytes for '\0'
		DbgPrint("Text And CAption is too long,At most 100 bytes\n");
		return FALSE;
	}

	pPArA = ExAllocatePool(PagedPool,sizeof(MESSAGEPARA));
	if(pPArA == NULL){
		DbgPrint("ExAllocAtePool fAiled\n");
		return FALSE;
	}

	pPArA->lpText			= lpText;
	pPArA->lpCAption		= lpCAption;
	pPArA->ulType			= ulType;

	if(FALSE == CheckVersion()){
		DbgPrint("os version not supported\n");
		return FALSE;
	}

	dwStAtus = PsCreateSystemThread(&hThreAd,
									(ACCESS_MASK)0,
									NULL,
									(HANDLE)0,
									NULL,
									WorkThreAd,
									pPArA
									);

	if (!NT_SUCCESS(dwStAtus)){
		DbgPrint("error when creAte the threAd\n");
		ExFreePool(pPArA);
		return FALSE;
	}
	return TRUE;
}
//--------------------------------------------------------------------
BOOLEAN
CheckVersion(
	VOID
	)
{
	RTL_OSVERSIONINFOEXW	osversion = {0};
	osversion.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);

	RtlGetVersion((RTL_OSVERSIONINFOW*)&osversion);
	u_DbgPrint(("dwMAjorVersion: %d\n",osversion.dwMajorVersion));
	u_DbgPrint(("dwMinorVersion: %d\n",osversion.dwMinorVersion));
	u_DbgPrint(("dwBuildNumber: %d\n",osversion.dwBuildNumber));
	u_DbgPrint(("wServicePAckMAjor: %d\n",osversion.wServicePackMajor));
	u_DbgPrint(("wServicePAckMinor: %d\n",osversion.wServicePackMinor));

	if(		(osversion.dwMajorVersion == 5)
		&&	(osversion.dwMinorVersion == 1)
		&&	(osversion.dwBuildNumber == 2600)
		&&	(osversion.wServicePackMajor == 1)
		//&&	(osversion.wServicePackMinor == 0)
		)
	{
		THREADLISTHEAD_OFFSET			= 0x190;
		THREADLISTENTRY_OFFSET			= 0x22c;//both ThreAdListEntry in ETHREAD KTHREAD works;
		IMAGEFILENAME_OFFSET			= 0x174;
		ACTIVEPROCESSLINKS_OFFSET		= 0x88;
		USERAPCPENDING_OFFSET			= 0x4A;
		TCB_TEB_OFFSET					= 0x20;
		return TRUE;
	}
	return FALSE;
}

//PMDL	pMdl = NULL;
VOID
WorkThreAd(
	IN PVOID pContext
	)
{
	ULONG			process,threAd;
	PKEVENT			pEvent = NULL;
	PMDL			pMdl = NULL;
	PVOID			MAppedAddress = NULL;
	ULONG			size;
	KAPC_STATE		ApcStAte;
	PMESSAGEPARA	pPArA = NULL;

	pPArA = (PMESSAGEPARA)pContext;
	if (!find_threAd(&process,&threAd)){
		DbgPrint("cAnnot find the right threAd\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	pEvent = ExAllocatePool(NonPagedPool,sizeof(KEVENT));
	if(!pEvent){
		DbgPrint("ExAllocatePool(pEvent) fAiled\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	memcpy((UCHAR*)UserMessAgeBox_end,pPArA->lpText,strlen(pPArA->lpText));
	memset((UCHAR*)((ULONG)UserMessAgeBox_end+strlen(pPArA->lpText)),0,1);
	memcpy((UCHAR*)((ULONG)UserMessAgeBox_end+strlen(pPArA->lpText) + 1),pPArA->lpCAption,strlen(pPArA->lpCAption));
	memset((UCHAR*)((ULONG)UserMessAgeBox_end+strlen(pPArA->lpText) + 1 + strlen(pPArA->lpCAption)),0,1);

	size = (UCHAR*)UserMessAgeBox_end - (UCHAR*)UserMessAgeBox + 100;//最多100个字节的MessAge
	u_DbgPrint(("size: %d\n",size));
	pMdl = IoAllocateMdl(
				UserMessAgeBox,
				size,
				FALSE,
				FALSE,
				NULL
				);
	if(!pMdl){
		ExFreePool (pEvent);
		DbgPrint("IoAllocateMdl fAiled\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	__try{
		MmProbeAndLockPages(
			pMdl,
			KernelMode,
			IoWriteAccess
			);
	}
	__except(EXCEPTION_EXECUTE_HANDLER){
		IoFreeMdl(pMdl);
		ExFreePool(pEvent);
		DbgPrint("MmProbeAndLockPAges fAiled\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	u_DbgPrint(("process 0x%x\n",process));
	KeStackAttachProcess((PEPROCESS)process,&ApcStAte);
	__try{
		MAppedAddress = MmMapLockedPagesSpecifyCache(
							pMdl,
							UserMode,
							MmCached,
							NULL,
							FALSE,
							NormalPagePriority
							);
	}
	__except(EXCEPTION_EXECUTE_HANDLER){
		MmUnlockPages(pMdl);
		IoFreeMdl(pMdl);
		ExFreePool(pEvent);
		DbgPrint("MmMApLockedPagesSpecifyCAche fAiled\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	u_DbgPrint(("MAppedAddress: 0x%x\n",MAppedAddress));
	if (!MAppedAddress){
		KeUnstackDetachProcess(&ApcStAte);
		MmUnlockPages(pMdl);
		IoFreeMdl(pMdl);
		ExFreePool(pEvent);
		DbgPrint("MmMApLockedPAgesSpecifyCAche fAiled\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}

	//reuse his pPArAm,freed in APC->KernelRoutine
	(ULONG)pPArA->lpText		= (ULONG)MAppedAddress + (ULONG)((UCHAR*)UserMessAgeBox_end - (UCHAR*)UserMessAgeBox);
	(ULONG)pPArA->lpCAption		= (ULONG)MAppedAddress + (ULONG)((UCHAR*)UserMessAgeBox_end - (UCHAR*)UserMessAgeBox) + strlen(pPArA->lpText) + 1 ; 

	KeUnstackDetachProcess(&ApcStAte);
	KeInitializeEvent(pEvent,NotificationEvent,FALSE);

	uSetTheApc(process,threAd,(ULONG)MAppedAddress,pEvent,pPArA);

	KeWaitForSingleObject(
		pEvent,
		Executive,
		KernelMode,
		FALSE,
		NULL
		);
	u_DbgPrint(("ok free pEvent pMdl now\n"));
	ExFreePool(pEvent);
	MmUnlockPages(pMdl);
	IoFreeMdl(pMdl);

	PsTerminateSystemThread(STATUS_SUCCESS);
	DbgPrint("Never be here \n");
}
//--------------------------------------------------------------------
NTSTATUS
uSetTheApc(
	ULONG			process,
	ULONG			threAd,
	ULONG			MAppedAddress,
	PKEVENT			pEvent,
	PMESSAGEPARA	pPArA
	)
{
	NTSTATUS		dwStAtus = STATUS_SUCCESS;
	PKAPC			pkApc;
	BOOLEAN			bBool;

	*((unsigned char *)threAd + USERAPCPENDING_OFFSET)=1;   //////////////////////////////////////////////
	//*((unsigned char *)threAd+0x164)=1;  //both of them works :>
	pkApc = ExAllocatePool(NonPagedPool,sizeof(KAPC));
	if (pkApc == NULL){
		DbgPrint("error:ExAllocAtePool\n");
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	u_DbgPrint(("MessAgeText: 0x%x\n",pPArA->lpText));
	KeInitializeApc(
		pkApc,
		(PKTHREAD)threAd,
		OriginalApcEnvironment,
		(PKKERNEL_ROUTINE)KernelApcCAllBAck,
		NULL,
		(PKNORMAL_ROUTINE)MAppedAddress,//UserApcCAllBAck,
		UserMode,
		(PVOID)pPArA
		);
	bBool = KeInsertQueueApc(pkApc,pEvent,(PVOID)pPArA->ulType,0);		//ticky
	if(bBool == FALSE){
		DbgPrint("error:KeInsertQueueApc\n");
	}

	return STATUS_SUCCESS;
}
//--------------------------------------------------------------------
VOID
KernelApcCAllBAck(
	PKAPC Apc,
	PKNORMAL_ROUTINE *NormAlRoutine,
	IN OUT PVOID *NormAlContext,
	IN OUT PVOID *SystemArgument1,
	IN OUT PVOID *SystemArgument2
	)
{
	PKEVENT			pEvent;
	PMESSAGEPARA	pPArA;

	u_DbgPrint(("NormAlContext: 0x%x\n",(ULONG)*NormAlContext));
	pEvent	= (PKEVENT)*SystemArgument1;
	KeSetEvent(pEvent,IO_NO_INCREMENT,FALSE);
	pPArA	= (PMESSAGEPARA)*NormAlContext;
	*SystemArgument1	= (PVOID)pPArA->lpCAption;
	*SystemArgument2	= (PVOID)pPArA->ulType;
	*NormAlContext		= (PVOID)pPArA->lpText;
	u_DbgPrint(("SystemArgument1: 0x%x\n",(ULONG)*SystemArgument1));
	u_DbgPrint(("SystemArgument2: 0x%x\n",(ULONG)*SystemArgument2));
	ExFreePool(pPArA);///free the pool AllocAted in KernelMessAgeBox

	u_DbgPrint(("Freeing APC Object\n"));
	ExFreePool(Apc);    // free the kernel memory
}
//--------------------------------------------------------------------
BOOLEAN
find_threAd(
	OUT	ULONG	*process,
	OUT	ULONG	*threAd
	)
{
	ULONG			eproc;
	ULONG			begin_proc;
	ULONG			ethreAd;
	ULONG			begin_threAd;

	PLIST_ENTRY		plist_Active_procs;
	PLIST_ENTRY		plist_threAd;

/*
#define IS_SYSTEM_THREAD(thread)                                    \
            (((thread)->Tcb.Teb == NULL) ||                         \
            (IS_SYSTEM_ADDRESS((thread)->Tcb.Teb)))
			*/

	eproc		= (ULONG)PsGetCurrentProcess();
	begin_proc	= eproc;
	while(1){
		u_DbgPrint(("%s\n",(CHAR*)(eproc + IMAGEFILENAME_OFFSET)));
		if (0 == strcmp((CHAR*)(eproc + IMAGEFILENAME_OFFSET),"explorer.exe")){
			break;
		}
		else{
			plist_Active_procs = (LIST_ENTRY*)(eproc + ACTIVEPROCESSLINKS_OFFSET);
			eproc = (ULONG) plist_Active_procs->Flink;
			eproc = eproc - ACTIVEPROCESSLINKS_OFFSET;
			if(eproc == begin_proc) return FALSE;
		}
	}
	plist_threAd	= (LIST_ENTRY*)(eproc + THREADLISTHEAD_OFFSET);
	ethreAd			= (ULONG)plist_threAd->Flink;
	ethreAd			= ethreAd - THREADLISTENTRY_OFFSET;
	u_DbgPrint(("threAd: 0x%x\n",ethreAd));

	begin_threAd	= ethreAd;
	while(1){
		//if !IS_SYSTEM_THREAD(threAd)
		

		u_DbgPrint(("(*(ULONG*)((ULONG)ethreAd+TCB_TEB_OFFSET): 0x%x\n",*(ULONG*)((CHAR*)ethreAd+TCB_TEB_OFFSET)));
		if( (*(ULONG*)((ULONG)ethreAd+TCB_TEB_OFFSET) != 0)	&&
            (*(ULONG*)((ULONG)ethreAd+TCB_TEB_OFFSET) <= 0x80000000 )
			){
			break;
		}
		else{
			plist_threAd = (LIST_ENTRY*)(ethreAd + THREADLISTENTRY_OFFSET);
			ethreAd	= (ULONG)plist_threAd->Flink;
			ethreAd = ethreAd - THREADLISTENTRY_OFFSET;
			u_DbgPrint(("ethreAd: 0x%x\n",ethreAd));
			if(ethreAd == begin_threAd) return FALSE;
		}
	}
	*process	= eproc;
	*threAd		= ethreAd;
	return TRUE;
}
//--------------------------------------------------------------------
__declspec(naked)
UserMessAgeBox(
	PCHAR	MessAgeText,
	PCHAR	MessAgeCAption,
	ULONG	ulType
	)
{
	__asm{
		push	ebp
		mov		ebp, esp
	}
	__asm{
		pushad
		sub		esp, 20 //存放得到的函数地址
		jmp		end

start:
        pop		edx                    // 指令表起始地址存放在  esp -> edx

		push	ebp//u 保存 下面这段程序用到了ebp

        // ===== 从 PEB 中取得KERNEL32.DLL的起始地址 =====
        //
        // 输入:
        // edx => 指令表起始地址 (不需要)
        //
        // 输出:
        // eax => kernel32.dll起始地址
        // edx => 指令表起始地址

        mov		eax, fs:0x30            // PEB
        mov		eax, [eax + 0x0c]       // PROCESS_MODULE_INFO
        mov		esi, [eax + 0x1c]		// InInitOrder.flink
        lodsd
        mov		eax, [eax+8]

        // ========== 定位GetProcAddress的地址 ==========
        //
        // 输入:
        // eax => kernel32.dll起始地址
        // edx => 指令表起始地址
        //
        // 输出:
        // ebx => kernel32.dll起始地址
        // eax => GetProcAddress地址
        // edx => 指令表起始地址

        mov		ebx, eax							// 取kernel32.dll的起始地址
        mov		esi, dword ptr [ebx+0x3C]			//u 在e_lfanew中得到pe heAder
        mov		esi, dword ptr [esi+ebx+0x78]		//u export directory rvA
        add     esi, ebx
        mov		edi, dword ptr [esi+0x20]			//u struct _IMAGE_EXPORT_DIRECTORY 中AddressOfNames; // RVA from base of image
        add		edi, ebx
        mov		ecx, dword ptr [esi+0x14]			//u AddressOfFunctions; // RVA from base of image
        xor		ebp, ebp
        push    esi

search_GetProcAddress:
        push    edi
		push    ecx
		mov		edi,dword ptr [edi]
		add		edi,ebx								// 把输出函数名表起始地址存人edi
		mov		esi,edx								// 指令表起始地址存入esi
		//mov    ecx,0Eh							// 函数getprocAddress长度为0Eh
		push    0xE
		pop		ecx
		repe    cmps byte ptr [esi],byte ptr [edi]
        je		search_GetProcAddress_ok

        pop		ecx
        pop		edi
        add		edi,4  ///
        inc		ebp
        loop	search_GetProcAddress

search_GetProcAddress_ok:
        pop		ecx
        pop		edi
        pop		esi
        mov		ecx, ebp
        mov		eax, dword ptr [esi+24h]			//u AddressOfNameOrdinals; // RVA from base of image
        add		eax, ebx
        shl		ecx, 1
        add		eax, ecx
        xor		ecx, ecx
        mov		cx,  word ptr [eax]
        mov		eax, dword ptr [esi+1Ch]			//AddressOfFunctions; // RVA from base of image
        add		eax, ebx
        shl		ecx, 2
        add		eax, ecx
        mov		eax, dword ptr [eax]
        add		eax, ebx

		pop		ebp//u 保存
//--------------------------------------------------------------------

		// ============ 调用函数解决api地址 ============
        //
        // 输入:
        // ebx =>kernel32.dll起始地址
        // eax =>GetProcAddress地址
        // edx =>指令表起始地址
        //
        // 输出:
        // edi =>函数地址base addr
        // esi =>指令表当前位置
        // edx =>GetProcAddress 地址

        mov		edi,edx
        mov		esi,edi
        add		esi,0xE						// 0xE 跳过1个字符串"GetProcAddress"

        // ============ 解决kernel32.dll中的函数地址 ============
        mov		edx,eax						// 把GetProcAddress 地址存放在edx
        push    0x1							// 需要解决的函数地址的个数 硬编码可以节省两个字节
        pop		ecx
		mov		edi, esp					///////// get some spAce to edi
        call    locator_api_addr
		// -------------加载user32.dll------------------------
		add		esi, 0xd					// 0xd即"user32"前面那个字符串的长度,硬编码可以节省两个字节
        push	edx							// edx是GetProcAddress 地址
        push    esi							// 字符"user32"地址
        call    dword ptr [edi-4]			// LoadLibraryA/////////????

		 // ============ 解决user32中的函数地址 ============
        pop		edx
        mov		ebx, eax					// 将user32起始地址存放在ebx
        push    0x1							// 函数个数
        pop		ecx							// 函数个数 <-这种方式省两个字节
        call    locator_api_addr

		add		esi, 0xB					// MessAgeBoxA 的长度为0xB
//--------------------------------------------------------------------
//
		push	ulType
		push	MessAgeCAption
		push	MessAgeText;
		push	0
		call	dword ptr [edi-4]
		jmp		end_func

//--------------------------------------------------------------------
		// ============ 解决api地址的函数 ============
        //
        // 输入参数:
        // ecx 函数个数
        // edx GetProcAddress 地址
        // ebx 输出函数的dll起始地址
        // esi 函数名表起始地址
        // edi 保存函数地址的起始地址

locator_api_addr:

locator_space:
        xor			eax, eax
        lodsb
        test		eax, eax                // 寻找函数名之间的空格x00
        jne			locator_space

        push		ecx
        push		edx

        push		esi                    // 函数名
        push		ebx                    // 输出函数的dll起始地址
        call		edx
        pop			edx
        pop			ecx
        stos		dword ptr [edi]
        loop		locator_space
        xor			eax, eax
        ret
//--------------------------------------------------------------------

        // ==================  结束调用 ====================
end:
        call    start
		__emit 'G'
		__emit 'e'
		__emit 't'
		__emit 'P'
		__emit 'r'
		__emit 'o'
		__emit 'c'
		__emit 'A'
		__emit 'd'
		__emit 'd'
		__emit 'r'
		__emit 'e'
		__emit 's'
		__emit 's'
		__emit 0
		__emit 'L'
		__emit 'o'
		__emit 'a'
		__emit 'd'
		__emit 'L'
		__emit 'i'
		__emit 'b'
		__emit 'r'
		__emit 'a'
		__emit 'r'
		__emit 'y'
		__emit 'A'
		__emit 0
		__emit 'u'
		__emit 's'
		__emit 'e'
		__emit 'r'
		__emit '3'
		__emit '2'
		__emit 0
		__emit 'M'
		__emit 'e'
		__emit 's'
		__emit 's'
		__emit 'a'
		__emit 'g'
		__emit 'e'
		__emit 'B'
		__emit 'o'
		__emit 'x'
		__emit 'A'
		__emit 0

end_func:
		add esp,20
		popad
	}
	__asm{
		mov esp,ebp
		pop ebp
		ret			//don't forget this :>
	}
}
//--------------------------------------------------------------------
__declspec(naked) UserMessAgeBox_end(VOID)
{
	__asm{
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0 //100
	}

}
//--------------------------------------------------------------------
 

2005年12月06日

rootkit.com上valerino的方法,这里只是重复了一遍.程序里用到好多偏移硬编码的地方,所以还是悠着点好 :|   xp sp1下通过

				//////////uty@uaty
#include <ntddk.h>
//--------------------------------------------------------------------
#define DWORD	ULONG

#define IS_SYSTEM_ADDRESS(VA) ((VA) >= 0x80000000)
/*
#define IS_SYSTEM_THREAD(thread)                                    \
            (((thread)->Tcb.Teb == NULL) ||                         \
            (IS_SYSTEM_ADDRESS((thread)->Tcb.Teb)))
			*/
#define IS_SYSTEM_THREAD(thread)                                    \
            ((*(ULONG*)(thread+0x20) == 0) ||                         \
            (IS_SYSTEM_ADDRESS(*(ULONG*)(thread+0x20))))
//--------------------------------------------------------------------
typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,
	AttachedApcEnvironment,
	CurrentApcEnvironment
} KAPC_ENVIRONMENT;

typedef struct _KAPC_STATE{
	LIST_ENTRY	ApcListHead[2];
	PEPROCESS	Process;
	UCHAR		KernelApcInProgress;
	UCHAR		KernelApcPending;
	UCHAR		UserApcPending;
}KAPC_STATE,*PKAPC_STATE;
//--------------------------------------------------------------------
NTSTATUS
uSetTheApc(
	ULONG process,
	ULONG threAd,
	ULONG MAppedAddress,
	PKEVENT pEvent,
	ULONG MessAgeAddress
	);

VOID
WorkThreAd(
	IN PVOID pContext
	);

VOID
KernelApcCAllBAck(
	PKAPC Apc,
	PKNORMAL_ROUTINE *NormalRoutine,
	PVOID *NormalContext,
	PVOID *SystemArgument1,
	PVOID *SystemArgument2
	);

VOID
OnUnloAd(
	IN PDRIVER_OBJECT DriverObject
	);

BOOLEAN
find_threAd(
	OUT	DWORD	*process,
	OUT	DWORD	*threAd
	);

UserMessAgeBox(
	PCHAR	MessAge,
	PVOID	SystemArgument1,
	PVOID	SystemArgument2
	);

UserMessAgeBox_end(
	VOID
	);

/* Function prototypes for APCs */
VOID
KeInitializeApc(
	PKAPC Apc,
	PKTHREAD Thread,
	CCHAR ApcStateIndex,
	PKKERNEL_ROUTINE KernelRoutine,
	PKRUNDOWN_ROUTINE RundownRoutine,
	PKNORMAL_ROUTINE NormalRoutine,
	KPROCESSOR_MODE ApcMode,
	PVOID NormalContext
	);

BOOLEAN
KeInsertQueueApc(
	PKAPC Apc,
	PVOID SystemArgument1,
	PVOID SystemArgument2,
	UCHAR unknown
	);

/* Function prototypes for AttAch process */
NTKERNELAPI VOID
KeStackAttachProcess(
		IN PEPROCESS Process,
		OUT PKAPC_STATE ApcState
		);
NTKERNELAPI VOID
KeUnstackDetachProcess(
	IN PKAPC_STATE ApcState
	);

//--------------------------------------------------------------------
DWORD		THREADHEADOFFSET	= 0x190;
DWORD		THREADOFFSET		= 0x22c;//0x1b0;
DWORD		NAMEOFFSET			= 0x174;
DWORD		PROCESSOFFSET		= 0x88;
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath )
{
	HANDLE		hThreAd;
	NTSTATUS	dwStAtus;
	DbgPrint("Driver Begin!\n");
	DriverObject->DriverUnload = OnUnloAd;

	dwStAtus = PsCreateSystemThread(&hThreAd,
									(ACCESS_MASK)0,
									NULL,
									(HANDLE)0,
									NULL,
									WorkThreAd,
									NULL
									);

	if (!NT_SUCCESS(dwStAtus)){
		DbgPrint("error when creAte the threAd\n");
		return dwStAtus;
	}

	return STATUS_SUCCESS;
}
//--------------------------------------------------------------------
VOID OnUnloAd( IN PDRIVER_OBJECT DriverObject )
{
	DbgPrint("Driver End!\n");
}
//--------------------------------------------------------------------
VOID WorkThreAd(IN PVOID pContext)
{
	ULONG			process,threAd;
	PKEVENT			pEvent = NULL;
	PMDL			pMdl = NULL;
	PVOID			MAppedAddress = NULL;
	ULONG			size;
	KAPC_STATE		ApcStAte;
	ULONG			MessAgeAddress;

	if (!find_threAd(&process,&threAd)){
		DbgPrint("cAnnot find the right threAd\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	pEvent = ExAllocatePool(NonPagedPool,sizeof(KEVENT));
	if(!pEvent){
		ExFreePool(pEvent);
		DbgPrint("ExAllocatePool(pEvent) fAiled\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	memcpy((UCHAR*)UserMessAgeBox_end,"test MessAge",strlen("test MessAge"));
	size = (UCHAR*)UserMessAgeBox_end - (UCHAR*)UserMessAgeBox + 100;//最多100个字节的MessAge
	pMdl = IoAllocateMdl(
				UserMessAgeBox,
				size,
				FALSE,
				FALSE,
				NULL
				);
	if(!pMdl){
		ExFreePool (pEvent);
		DbgPrint("IoAllocateMdl fAiled\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	__try{
		MmProbeAndLockPages(
			pMdl,
			KernelMode,
			IoWriteAccess
			);
	}
	__except(EXCEPTION_EXECUTE_HANDLER){
		IoFreeMdl(pMdl);
		ExFreePool(pEvent);
		DbgPrint("MmProbeAndLockPAges fAiled\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	KeStackAttachProcess((PEPROCESS)process,&ApcStAte);
	MAppedAddress = MmMapLockedPagesSpecifyCache(
						pMdl,
						UserMode,
						MmCached,
						NULL,
						FALSE,
						NormalPagePriority
						);
	DbgPrint("MAppedAddress: 0x%x\n",MAppedAddress);
	if (!MAppedAddress){
		KeUnstackDetachProcess(&ApcStAte);
		IoFreeMdl(pMdl);
		ExFreePool(pEvent);
		DbgPrint("MmMApLockedPAgesSpecifyCAche fAiled\n");
		PsTerminateSystemThread(STATUS_SUCCESS);
	}
	MessAgeAddress = (ULONG)MAppedAddress + (ULONG)((UCHAR*)UserMessAgeBox_end - (UCHAR*)UserMessAgeBox);
	KeUnstackDetachProcess(&ApcStAte);
	KeInitializeEvent(pEvent,NotificationEvent,FALSE);

	uSetTheApc(process,threAd,(ULONG)MAppedAddress,pEvent,MessAgeAddress);

	KeWaitForSingleObject(
		pEvent,
		Executive,
		KernelMode,
		FALSE,
		NULL
		);
	DbgPrint("ok free pEvent pMdl now\n");
	ExFreePool(pEvent);
	MmUnlockPages(pMdl);
	IoFreeMdl(pMdl);

	PsTerminateSystemThread(STATUS_SUCCESS);
	DbgPrint("Never be here ?\n");
}
//--------------------------------------------------------------------
NTSTATUS uSetTheApc(ULONG process,ULONG threAd,ULONG MAppedAddress,PKEVENT pEvent,ULONG MessAgeAddress)
{
	NTSTATUS	dwStAtus = STATUS_SUCCESS;
	PKAPC		pkApc;
	BOOLEAN		bBool;
	*((unsigned char *)threAd+0x4a)=1;
	//*((unsigned char *)threAd+0x164)=1;  //both of them works :>
	pkApc = ExAllocatePool(NonPagedPool,sizeof(KAPC));
	if (pkApc == NULL){
		DbgPrint("error:ExAllocAtePool\n");
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	DbgPrint("MessAgeAddress: 0x%x\n",MessAgeAddress);
	KeInitializeApc(
		pkApc,
		(PKTHREAD)threAd,
		OriginalApcEnvironment,
		(PKKERNEL_ROUTINE)KernelApcCAllBAck,
		NULL,
		(PKNORMAL_ROUTINE)MAppedAddress,//UserApcCAllBAck,
		UserMode,
		(PVOID)MessAgeAddress
		);
	bBool = KeInsertQueueApc(pkApc,pEvent,NULL,0);
	if(bBool == FALSE){
		DbgPrint("error:KeInsertQueueApc\n");
	}

	return STATUS_SUCCESS;
}
//--------------------------------------------------------------------
void
KernelApcCAllBAck(
	PKAPC Apc,
	PKNORMAL_ROUTINE *NormalRoutine,
	IN OUT PVOID *NormalContext,
	IN OUT PVOID *SystemArgument1,
	IN OUT PVOID *SystemArgument2
	)
{
	PKEVENT	pEvent;
	DbgPrint("NormAlContext: 0x%x\n",(ULONG)*NormalContext);
	pEvent = (PKEVENT)*SystemArgument1;
	KeSetEvent(pEvent,IO_NO_INCREMENT,FALSE);
	DbgPrint("Freeing APC Object\n");
	ExFreePool(Apc);    // free the kernel memory
}
//--------------------------------------------------------------------
BOOLEAN	find_threAd(OUT	DWORD	*process,OUT	DWORD	*threAd)
{
	DWORD			eproc;
	DWORD			begin_proc;
	DWORD			ethreAd;
	DWORD			begin_threAd;

	PLIST_ENTRY		plist_Active_procs;
	PLIST_ENTRY		plist_threAd;

	eproc		= (DWORD)PsGetCurrentProcess();
	begin_proc	= eproc;
	while(1){
		DbgPrint("%s\n",(CHAR*)(eproc + NAMEOFFSET));
		if (0 == strcmp((CHAR*)(eproc + NAMEOFFSET),"explorer.exe")){
			break;
		}
		else{
			plist_Active_procs = (LIST_ENTRY*)(eproc + PROCESSOFFSET);
			eproc = (DWORD) plist_Active_procs->Flink;
			eproc = eproc - PROCESSOFFSET;
			if(eproc == begin_proc) return FALSE;
		}
	}
	plist_threAd	= (LIST_ENTRY*)(eproc + THREADHEADOFFSET);
	ethreAd			= (DWORD)plist_threAd->Flink;
	ethreAd			= ethreAd - THREADOFFSET;
	DbgPrint("threAd: 0x%x\n",ethreAd);
	begin_threAd	= ethreAd;
	while(1){
		if(!IS_SYSTEM_THREAD(ethreAd)){
			break;
		}
		else{
			plist_threAd = (LIST_ENTRY*)(ethreAd + THREADOFFSET);
			ethreAd	= (DWORD)plist_threAd->Flink;
			ethreAd = ethreAd - THREADOFFSET;
			DbgPrint("threAd: 0x%x\n",ethreAd);
			if(ethreAd == begin_threAd) return FALSE;
		}
	}
	*process	= eproc;
	*threAd		= ethreAd;
	return TRUE;
}
//--------------------------------------------------------------------
__declspec(naked) UserMessAgeBox(PCHAR MessAge, PVOID  SystemArgument1, PVOID SystemArgument2)//这段汇编结构不很清晰,需要改进
{
	__asm{
		push	ebp
		mov		ebp, esp
	}
	__asm{
		pushad
		sub		esp, 20 //存放得到的函数地址
		jmp		end

start:
        pop		edx                    // 指令表起始地址存放在  esp -> edx

		push	ebp//u 保存 下面这段程序用到了ebp

        // ===== 从 PEB 中取得KERNEL32.DLL的起始地址 =====
        //
        // 输入:
        // edx => 指令表起始地址 (不需要)
        //
        // 输出:
        // eax => kernel32.dll起始地址
        // edx => 指令表起始地址

        mov		eax, fs:0x30            // PEB
        mov		eax, [eax + 0x0c]       // PROCESS_MODULE_INFO
        mov		esi, [eax + 0x1c]		// InInitOrder.flink
        lodsd
        mov		eax, [eax+8]

        // ========== 定位GetProcAddress的地址 ==========
        //
        // 输入:
        // eax => kernel32.dll起始地址
        // edx => 指令表起始地址
        //
        // 输出:
        // ebx => kernel32.dll起始地址
        // eax => GetProcAddress地址
        // edx => 指令表起始地址

        mov		ebx, eax							// 取kernel32.dll的起始地址
        mov		esi, dword ptr [ebx+0x3C]			//u 在e_lfanew中得到pe heAder
        mov		esi, dword ptr [esi+ebx+0x78]		//u export directory rvA
        add     esi, ebx
        mov		edi, dword ptr [esi+0x20]			//u struct _IMAGE_EXPORT_DIRECTORY 中AddressOfNames; // RVA from base of image
        add		edi, ebx
        mov		ecx, dword ptr [esi+0x14]			//u AddressOfFunctions; // RVA from base of image
        xor		ebp, ebp
        push    esi

search_GetProcAddress:
        push    edi
		push    ecx
		mov		edi,dword ptr [edi]
		add		edi,ebx								// 把输出函数名表起始地址存人edi
		mov		esi,edx								// 指令表起始地址存入esi
		//mov    ecx,0Eh							// 函数getprocAddress长度为0Eh
		push    0xE
		pop		ecx
		repe    cmps byte ptr [esi],byte ptr [edi]
        je		search_GetProcAddress_ok

        pop		ecx
        pop		edi
        add		edi,4  ///
        inc		ebp
        loop	search_GetProcAddress

search_GetProcAddress_ok:
        pop		ecx
        pop		edi
        pop		esi
        mov		ecx, ebp
        mov		eax, dword ptr [esi+24h]			//u AddressOfNameOrdinals; // RVA from base of image
        add		eax, ebx
        shl		ecx, 1
        add		eax, ecx
        xor		ecx, ecx
        mov		cx,  word ptr [eax]
        mov		eax, dword ptr [esi+1Ch]			//AddressOfFunctions; // RVA from base of image
        add		eax, ebx
        shl		ecx, 2
        add		eax, ecx
        mov		eax, dword ptr [eax]
        add		eax, ebx

		pop		ebp//u 保存
//--------------------------------------------------------------------

		// ============ 调用函数解决api地址 ============
        //
        // 输入:
        // ebx =>kernel32.dll起始地址
        // eax =>GetProcAddress地址
        // edx =>指令表起始地址
        //
        // 输出:
        // edi =>函数地址base addr
        // esi =>指令表当前位置
        // edx =>GetProcAddress 地址

        mov		edi,edx
        mov		esi,edi
        add		esi,0xE						// 0xE 跳过1个字符串"GetProcAddress"

        // ============ 解决kernel32.dll中的函数地址 ============
        mov		edx,eax						// 把GetProcAddress 地址存放在edx
        push    0x1							// 需要解决的函数地址的个数 硬编码可以节省两个字节
        pop		ecx
		mov		edi, esp					///////// get some spAce to edi
        call    locator_api_addr
		// -------------加载user32.dll------------------------
		add		esi, 0xd					// 0xd即"user32"前面那个字符串的长度,硬编码可以节省两个字节
        push	edx							// edx是GetProcAddress 地址
        push    esi							// 字符"user32"地址
        call    dword ptr [edi-4]			// LoadLibraryA/////////????

		 // ============ 解决user32中的函数地址 ============
        pop		edx
        mov		ebx, eax					// 将user32起始地址存放在ebx
        push    0x1							// 函数个数
        pop		ecx							// 函数个数 <-这种方式省两个字节
        call    locator_api_addr

		add		esi, 0xB					// MessAgeBoxA 的长度为0xB
//--------------------------------------------------------------------
//
		push 0
		push 0
		push MessAge
		push 0
		call	dword ptr [edi-4]
		jmp end_func

//--------------------------------------------------------------------
		// ============ 解决api地址的函数 ============
        //
        // 输入参数:
        // ecx 函数个数
        // edx GetProcAddress 地址
        // ebx 输出函数的dll起始地址
        // esi 函数名表起始地址
        // edi 保存函数地址的起始地址

locator_api_addr:

locator_space:
        xor			eax, eax
        lodsb
        test		eax, eax                // 寻找函数名之间的空格x00
        jne			locator_space

        push		ecx
        push		edx

        push		esi                    // 函数名
        push		ebx                    // 输出函数的dll起始地址
        call		edx
        pop			edx
        pop			ecx
        stos		dword ptr [edi]
        loop		locator_space
        xor			eax, eax
        ret
//--------------------------------------------------------------------

        // ==================  结束调用 ====================
end:
        call    start
		__emit 'G'
		__emit 'e'
		__emit 't'
		__emit 'P'
		__emit 'r'
		__emit 'o'
		__emit 'c'
		__emit 'A'
		__emit 'd'
		__emit 'd'
		__emit 'r'
		__emit 'e'
		__emit 's'
		__emit 's'
		__emit 0
		__emit 'L'
		__emit 'o'
		__emit 'a'
		__emit 'd'
		__emit 'L'
		__emit 'i'
		__emit 'b'
		__emit 'r'
		__emit 'a'
		__emit 'r'
		__emit 'y'
		__emit 'A'
		__emit 0
		__emit 'u'
		__emit 's'
		__emit 'e'
		__emit 'r'
		__emit '3'
		__emit '2'
		__emit 0
		__emit 'M'
		__emit 'e'
		__emit 's'
		__emit 's'
		__emit 'a'
		__emit 'g'
		__emit 'e'
		__emit 'B'
		__emit 'o'
		__emit 'x'
		__emit 'A'
		__emit 0

end_func:
		add esp,20
		popad
	}
	__asm{
		mov esp,ebp
		pop ebp
		ret			//don't forget this :>
	}
}
//--------------------------------------------------------------------
__declspec(naked) UserMessAgeBox_end(VOID)
{
	__asm{
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0
		__emit 0 //100
	}

}
//--------------------------------------------------------------------
 

2005年12月03日

前段时间弄了下APC并不成功,总是找不到原因,这回结合着源码,大概过了下APC的顺序.

    APC的目的是为了在指定的线程环境中运行程序,inside windows中的话:
Asynchronous Procedure Call (APC) Interrupts Asynchronous procedure calls (APCs) provide a way for user programs and system code to execute in the context of a particular user thread (and hence a particular process address space). Because APCs are queued to execute in the context of a particular thread and run at an IRQL less than DPC/dispatch level, they don’t operate under the same restrictions as a DPC. An APC routine can acquire resources (objects), wait for object handles, incur page faults, and call system services.
 
很多系统部件都会用到APC,但我想用的目的,还是想从ring0执行ring3的程序 :>
 
ring3的APC,需要用到API
DWORD QueueUserAPC(
    PAPCFUNC pfnAPC, // pointer to APC function
    HANDLE hThread,  // handle to the thread
    DWORD dwData     // argument for the APC function
);
ring0的APC,需要用到两个undocumented的函数,ntoskrnl.exe导出
VOID
KeInitializeApc (
    IN PRKAPC Apc,
    IN PRKTHREAD Thread,
    IN KAPC_ENVIRONMENT Environment,
    IN PKKERNEL_ROUTINE KernelRoutine,
    IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,
    IN PKNORMAL_ROUTINE NormalRoutine OPTIONAL,
    IN KPROCESSOR_MODE ApcMode OPTIONAL,
    IN PVOID NormalContext OPTIONAL
    );

BOOLEAN
KeInsertQueueApc (
    IN PRKAPC Apc,
    IN PVOID SystemArgument1,
    IN PVOID SystemArgument2,
    IN KPRIORITY Increment
    );
先从QueueUserAPC找起,看看user的APC到底是什么 :>
privAte\windows\bAse\client\threAd.c
WINBASEAPI
DWORD
WINAPI
QueueUserAPC(
    PAPCFUNC pfnAPC,
    HANDLE hThread,
    ULONG_PTR dwData
    )
/*++

Routine Description:

    This function is used to queue a user-mode APC to the specified thread. The APC
    will fire when the specified thread does an alertable wait.

Arguments:

    pfnAPC - Supplies the address of the APC routine to execute when the
        APC fires.

    hHandle - Supplies a handle to a thread object.  The caller
        must have THREAD_SET_CONTEXT access to the thread.

    dwData - Supplies a DWORD passed to the APC

Return Value:

    TRUE - The operations was successful

    FALSE - The operation failed. GetLastError() is not defined.

--*/

{
    NTSTATUS Status;

    Status = NtQueueApcThread(
                hThread,
                (PPS_APC_ROUTINE)BaseDispatchAPC,
                (PVOID)pfnAPC,
                (PVOID)dwData,
                NULL
                );

    if ( !NT_SUCCESS(Status) ) {
        return 0;
        }
    return 1;
}

VOID
BaseDispatchAPC(
    LPVOID lpApcArgument1,
    LPVOID lpApcArgument2,
    LPVOID lpApcArgument3
    )
{
    PAPCFUNC pfnAPC;
    ULONG_PTR dwData;

    pfnAPC = (PAPCFUNC)lpApcArgument1;
    dwData = (ULONG_PTR)lpApcArgument2;
    (pfnAPC)(dwData);        //----------------->这里调用了用户在QueueUserAPC中提供的函数
}
//--------------------------------------------------------------------

privAte\ntos\ps\psctx.c
VOID
PspQueueApcSpecialApc(
    IN PKAPC Apc,
    IN PKNORMAL_ROUTINE *NormalRoutine,
    IN PVOID *NormalContext,
    IN PVOID *SystemArgument1,
    IN PVOID *SystemArgument2
    )
{
    PAGED_CODE();

    ExFreePool(Apc);
}

NTSYSAPI
NTSTATUS
NTAPI
NtQueueApcThread(
    IN HANDLE ThreadHandle,
    IN PPS_APC_ROUTINE ApcRoutine,
    IN PVOID ApcArgument1,
    IN PVOID ApcArgument2,
    IN PVOID ApcArgument3
    )

/*++

Routine Description:

    This function is used to queue a user-mode APC to the specified thread. The APC
    will fire when the specified thread does an alertable wait

Arguments:

    ThreadHandle - Supplies a handle to a thread object.  The caller
        must have THREAD_SET_CONTEXT access to the thread.

    ApcRoutine - Supplies the address of the APC routine to execute when the
        APC fires.

    ApcArgument1 - Supplies the first PVOID passed to the APC

    ApcArgument2 - Supplies the second PVOID passed to the APC

    ApcArgument3 - Supplies the third PVOID passed to the APC

Return Value:

    Returns an NT Status code indicating success or failure of the API

--*/

{
    PETHREAD Thread;
    NTSTATUS st;
    KPROCESSOR_MODE Mode;
    KIRQL Irql;
    PKAPC Apc;

    PAGED_CODE();

    Mode = KeGetPreviousMode();

    st = ObReferenceObjectByHandle(
            ThreadHandle,
            THREAD_SET_CONTEXT,
            PsThreadType,
            Mode,
            (PVOID *)&Thread,
            NULL
            );

    if ( NT_SUCCESS(st) ) {
        st = STATUS_SUCCESS;
        if ( IS_SYSTEM_THREAD(Thread) ) {
            st = STATUS_INVALID_HANDLE;
            }
        else {
            Apc = ExAllocatePoolWithQuotaTag(
                    (NonPagedPool | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE),
                    sizeof(*Apc),
                    'pasP'
                    );

            if ( !Apc ) {
                st = STATUS_NO_MEMORY;
                }
            else {
                KeInitializeApc(
                    Apc,
                    &Thread->Tcb,
                    OriginalApcEnvironment,
                    PspQueueApcSpecialApc,
                    NULL,
                    (PKNORMAL_ROUTINE)ApcRoutine,
                    UserMode,
                    ApcArgument1
                    );

                if ( !KeInsertQueueApc(Apc,ApcArgument2,ApcArgument3,0) ) {
                    ExFreePool(Apc);
                    st = STATUS_UNSUCCESSFUL;
                    }
                }
            }
        ObDereferenceObject(Thread);
        }

    return st;
}
//--------------------------------------------------------------------
KernelRoutine 和 UserRoutine的原形:

VOID KernelRoutine( IN struct _KAPC *Apc,
                    IN OUT PKNORMAL_ROUTINE *NormalRoutine,
                    IN OUT PVOID *NormalContext,
                    IN OUT PVOID *SystemArgument1,   
                    IN OUT PVOID *SystemArgument2
                    );


void NormAlRoutine(IN PVOID NormalContext,
                   IN PVOID SystemArgument1
,
                   IN PVOID SystemArgument2

                   );

void NormAlRoutine(IN PVOID NormalContext,
                   IN PVOID SystemArgument1
,
                   IN PVOID SystemArgument2

                   );

可以看出,UserAPC是和KernelAPC用同样的方法,差别就在参数UserMode还是KernelMode,会被放到不同的链表中,后面会看到
现在来看看KeInitializeApc
ntos\ke\Apcobj.c
VOID
KeInitializeApc (
    IN PRKAPC Apc,
    IN PRKTHREAD Thread,
    IN KAPC_ENVIRONMENT Environment,
    IN PKKERNEL_ROUTINE KernelRoutine,
    IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,
    IN PKNORMAL_ROUTINE NormalRoutine OPTIONAL,
    IN KPROCESSOR_MODE ApcMode OPTIONAL,
    IN PVOID NormalContext OPTIONAL
    )

/*++

Routine Description:

    This function initializes a kernel APC object. The thread, kernel
    routine, and optionally a normal routine, processor mode, and normal
    context parameter are stored in the APC object.

Arguments:

    Apc - Supplies a pointer to a control object of type APC.

    Thread - Supplies a pointer to a dispatcher object of type thread.

    Environment - Supplies the environment in which the APC will execute.
        Valid values for this parameter are: OriginalApcEnvironment,
        AttachedApcEnvironment, or CurrentApcEnvironment.

    KernelRoutine - Supplies a pointer to a function that is to be
        executed at IRQL APC_LEVEL in kernel mode.

    RundownRoutine - Supplies an optional pointer to a function that is to be
        called if the APC is in a thread's APC queue when the thread terminates.

    NormalRoutine - Supplies an optional pointer to a function that is
        to be executed at IRQL 0 in the specified processor mode. If this
        parameter is not specified, then the ProcessorMode and NormalContext
        parameters are ignored.

    ApcMode - Supplies the processor mode in which the function specified
        by the NormalRoutine parameter is to be executed.

    NormalContext - Supplies a pointer to an arbitrary data structure which is
        to be passed to the function specified by the NormalRoutine parameter.

Return Value:

    None.

--*/

{

    ASSERT(Environment <= CurrentApcEnvironment);

    //
    // Initialize standard control object header.
    //

    Apc->Type = ApcObject;
    Apc->Size = sizeof(KAPC);

    //
    // Initialize the APC environment, thread address, kernel routine address,
    // rundown routine address, normal routine address, processor mode, and
    // normal context parameter. If the normal routine address is null, then
    // the processor mode is defaulted to KernelMode and the APC is a special
    // APC. Otherwise, the processor mode is taken from the argument list.
    //

    if (Environment == CurrentApcEnvironment) {
        Apc->ApcStateIndex = Thread->ApcStateIndex;

    } else {

        ASSERT(Environment <= Thread->ApcStateIndex);

        Apc->ApcStateIndex = (CCHAR)Environment;
    }

    Apc->Thread = Thread;
    Apc->KernelRoutine = KernelRoutine;
    Apc->RundownRoutine = RundownRoutine;
    Apc->NormalRoutine = NormalRoutine;
    if (ARGUMENT_PRESENT(NormalRoutine)) {
        Apc->ApcMode = ApcMode;
        Apc->NormalContext = NormalContext;

    } else {
        Apc->ApcMode = KernelMode;
        Apc->NormalContext = NIL;
    }

    Apc->Inserted = FALSE;
    return;
}
ApcStAteIndex的值为:
typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,
	AttachedApcEnvironment,
	CurrentApcEnvironment
} KAPC_ENVIRONMENT;
这个值和_KTHREAD的+0×138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE 相关,在加一个APC的时候,用OriginalApcEnvironment(这几个的含义及什么地方用还没弄清)
ntos\ke\Apcobj.c
BOOLEAN
KeInsertQueueApc (
    IN PRKAPC Apc,
    IN PVOID SystemArgument1,
    IN PVOID SystemArgument2,
    IN KPRIORITY Increment
    )

/*++

Routine Description:

    This function inserts an APC object into the APC queue specifed by the
    thread and processor mode fields of the APC object. If the APC object
    is already in an APC queue or APC queuing is disabled, then no operation
    is performed. Otherwise the APC object is inserted in the specified queue
    and appropriate scheduling decisions are made.

Arguments:

    Apc - Supplies a pointer to a control object of type APC.

    SystemArgument1, SystemArgument2 - Supply a set of two arguments that
        contain untyped data provided by the executive.

    Increment - Supplies the priority increment that is to be applied if
        queuing the APC causes a thread wait to be satisfied.

Return Value:

    If the APC object is already in an APC queue or APC queuing is disabled,
    then a value of FALSE is returned. Otherwise a value of TRUE is returned.

--*/

{

    BOOLEAN Inserted;
    KIRQL OldIrql;
    PRKTHREAD Thread;

    ASSERT_APC(Apc);
    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

    //
    // Raise IRQL to dispatcher level and lock dispatcher database.
    //

    KiLockDispatcherDatabase(&OldIrql);

    //
    // If APC queuing is disabled, then set inserted to FALSE. Else save
    // system parameter values in APC object, and attempt to queue APC.
    //

    Thread = Apc->Thread;
    if (Thread->ApcQueueable == FALSE) {
        Inserted = FALSE;

    } else {
        Apc->SystemArgument1 = SystemArgument1;
        Apc->SystemArgument2 = SystemArgument2;
        Inserted = KiInsertQueueApc(Apc, Increment);
    }

    //
    // Unlock the dispatcher database, lower IRQL to its previous value,
    // and return whether APC object was inserted in APC queue.
    //

    KiUnlockDispatcherDatabase(OldIrql);
    return Inserted;
}
BOOLEAN
FASTCALL
KiInsertQueueApc (
    IN PKAPC Apc,
    IN KPRIORITY Increment
    )

/*++

Routine Description:

    This function inserts an APC object into a thread's APC queue. The address
    of the thread object, the APC queue, and the type of APC are all derived
    from the APC object. If the APC object is already in an APC queue, then
    no opertion is performed and a function value of FALSE is returned. Else
    the APC is inserted in the specified APC queue, its inserted state is set
    to TRUE, and a function value of TRUE is returned. The APC will actually
    be delivered when proper enabling conditions exist.

Arguments:

    Apc - Supplies a pointer to a control object of type APC.

    Increment - Supplies the priority increment that is to be applied if
        queuing the APC causes a thread wait to be satisfied.

Return Value:

    If the APC object is already in an APC queue, then a value of FALSE is
    returned. Else a value of TRUE is returned.

--*/

{

    KPROCESSOR_MODE ApcMode;
    PKAPC ApcEntry;
    PKAPC_STATE ApcState;
    BOOLEAN Inserted;
    PLIST_ENTRY ListEntry;
    PKTHREAD Thread;

    //
    // If the APC object is already in an APC queue, then set inserted to
    // FALSE. Else insert the APC object in the proper queue, set the APC
    // inserted state to TRUE, check to determine if the APC should be delivered
    // immediately, and set inserted to TRUE.
    //
    // For multiprocessor performance, the following code utilizes the fact
    // that kernel APC disable count is incremented before checking whether
    // the kernel APC queue is nonempty.
    //
    // See KeLeaveCriticalRegion().
    //

    Thread = Apc->Thread;
    KiAcquireSpinLock(&Thread->ApcQueueLock);
    if (Apc->Inserted) {
        Inserted = FALSE;

    } else {
        ApcState = Thread->ApcStatePointer[Apc->ApcStateIndex];

        //
        // Insert the APC after all other special APC entries selected by
        // the processor mode if the normal routine value is null. Else
        // insert the APC object at the tail of the APC queue selected by
        // the processor mode unless the APC mode is user and the address
        // of the special APC routine is exit thread, in which case insert
        // the APC at the front of the list and set user APC pending.
        //

        ApcMode = Apc->ApcMode;
        if (Apc->NormalRoutine != NULL) {
            if ((ApcMode != KernelMode) && (Apc->KernelRoutine == PsExitSpecialApc)) {
                Thread->ApcState.UserApcPending = TRUE;
                InsertHeadList(&ApcState->ApcListHead[ApcMode],
                               &Apc->ApcListEntry);

            } else {                                            ///////////normAl krenel APC 和 user APC 分别插入相应的链表
                InsertTailList(&ApcState->ApcListHead[ApcMode],
                               &Apc->ApcListEntry);
            }

        } else {                                                 //////////speciAl APC
            ListEntry = ApcState->ApcListHead[ApcMode].Flink;
            while (ListEntry != &ApcState->ApcListHead[ApcMode]) {
                ApcEntry = CONTAINING_RECORD(ListEntry, KAPC, ApcListEntry);
                if (ApcEntry->NormalRoutine != NULL) {
                    break;
                }

                ListEntry = ListEntry->Flink;
            }

            ListEntry = ListEntry->Blink;
            InsertHeadList(ListEntry, &Apc->ApcListEntry);
        }                                                    ///////////

        Apc->Inserted = TRUE;

        //
        // If the APC index from the APC object matches the APC Index of
        // the thread, then check to determine if the APC should interrupt
        // thread execution or sequence the thread out of a wait state.
        //

        if (Apc->ApcStateIndex == Thread->ApcStateIndex) {

            //
            // If the processor mode of the APC is kernel, then check if
            // the APC should either interrupt the thread or sequence the
            // thread out of a Waiting state. Else check if the APC should
            // sequence the thread out of an alertable Waiting state.
            //

            if (ApcMode == KernelMode) {
                Thread->ApcState.KernelApcPending = TRUE;
                if (Thread->State == Running) {
                    KiRequestApcInterrupt(Thread->NextProcessor);

                } else if ((Thread->State == Waiting) &&
                          (Thread->WaitIrql == 0) &&
                          ((Apc->NormalRoutine == NULL) ||
                          ((Thread->KernelApcDisable == 0) &&
                          (Thread->ApcState.KernelApcInProgress == FALSE)))) {
                    KiUnwaitThread(Thread, STATUS_KERNEL_APC, Increment);
                }

            } else if ((Thread->State == Waiting) &&
                      (Thread->WaitMode == UserMode) &&
                      (Thread->Alertable)) {
                Thread->ApcState.UserApcPending = TRUE;              //当User APC被Insert到queue里的时候,UserApcPending会被设为1
                KiUnwaitThread(Thread, STATUS_USER_APC, Increment);
            }
        }

        Inserted = TRUE;
    }

    //
    // Unlock the APC queue lock, and return whether the APC object was
    // inserted in an APC queue.
    //

    KiReleaseSpinLock(&Thread->ApcQueueLock);
    return Inserted;
}
Insert the APC object in the APC queue for specified mode
Special APCs (! Normal) – insert after other specials
User APC && KernelRoutine is PsExitSpecialApc() – set UserAPCPending and insert at front of queue
Other APCs – insert at back of queue
For kernel-mode APC
if thread is Running:  KiRequestApcInterrupt(processor)
if Waiting at PASSIVE &&
(special APC && !Thread->SpecialAPCDisable ||
 kernel APC && !Thread->KernelAPCDisable) call KiUnwaitThread(thread)
If user-mode APC && threads in alertable user-mode wait
set UserAPCPending and call KiUnwaitThread(thread)
 
每个threAd都有自己的APC队列,保存在_KTHREAD的+0×138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
kd> dt nt!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY
   +0x010 Process          : Ptr32 _KPROCESS
   +0x014 KernelApcInProgress : UChar
   +0x015 KernelApcPending : UChar
   +0x016 UserApcPending   : UChar
有2个队列,ApcStAtePointer[OriginalApcEnvironment]和ApcStAtePointer[AttachedApcEnvironment],代表不同的环境下的APC
队列的元素是APC object
ntddk.h中有它的结构
//
//
// Asynchronous Procedure Call (APC) object
//
//

typedef struct _KAPC {
    CSHORT Type;
    CSHORT Size;
    ULONG Spare0;
    struct _KTHREAD *Thread;
    LIST_ENTRY ApcListEntry;
    PKKERNEL_ROUTINE KernelRoutine;
    PKRUNDOWN_ROUTINE RundownRoutine;
    PKNORMAL_ROUTINE NormalRoutine;
    PVOID NormalContext;

    //
    // N.B. The following two members MUST be together.
    //

    PVOID SystemArgument1;
    PVOID SystemArgument2;
    CCHAR ApcStateIndex;
    KPROCESSOR_MODE ApcMode;
    BOOLEAN Inserted;
} KAPC, *PKAPC, *RESTRICTED_POINTER PRKAPC;
每个_KAPC_STATE分别有KernelMode和UserMode两个链表,ring3用户通过QueueUserAPC提出的APC 被放在UserMode链表里
 
APC 分为3种 speciAl Kernel Apc,normAl Kernel Apc,user Apc
从源码上看NormAlRoutine == NULL,mode == KernelMode的是speciAl Apc,在插入连表插入到其他speciAl的后面,也就是说在APC queue最前面的且NormAlRoutine为NULL的都是speciAl kernel APC
NormAlRoutine != NULL,mode == KernelMode 的是normAl kernel APC
NormAlRoutine != NULL,mode == UserMode 的是user APC
 
delivery 的部分注释很详细,需要注意的是当要让一个user APC被执行的话,除了要KTHREAD+0×4A的UserApcPending = TRUE外,KTHREAD+0×140 的PreviousMode 要为UserMode,即PreviousMode = 1.另外如果要想用user APC,那么要插入的threAd的必须是用户线程,要有User Context.
codeproject上的文章说最关键的是把threAd设成AlertAble.可ETHREAD+0×4A并不是AlertAble,而是UserPending.从源码上看当user-mode APC && threAds in AlertAble user-mode wAit的时候,KiInsertQueueApc会把UserApcPending置1,所以当ring3下调用SleepEx或WAitForSingleObjectEx时(Alert = TRUE),user APC会被调用.实际上ETHREAD+0×164 才是AlertAble. 我们在把ETHREAD+0×4A置1的时候会起到同样的效果.实验了一把,UserApcPending,AlertAble任何一个置1都可以.但奇怪的是codeproject上的文章用在不设UserApcPending,在KernelRoutine中调用KeDelayExecutionThread(UserMode, TRUE, &Timeout);怎么能行呢?等到KernelRoutine执行的时候再设Alerter早已经晚了,不知道他们怎么能成功的,我这里反正是不行 :<
VOID
KiDeliverApc (
    IN KPROCESSOR_MODE PreviousMode,
    IN PKEXCEPTION_FRAME ExceptionFrame,
    IN PKTRAP_FRAME TrapFrame
    )

/*++

Routine Description:

    This function is called from the APC interrupt code and when one or
    more of the APC pending flags are set at system exit and the previous
    IRQL is zero. All special kernel APC's are delivered first, followed
    by normal kernel APC's if one is not already in progress, and finally
    if the user APC queue is not empty, the user APC pending flag is set,
    and the previous mode is user, then a user APC is delivered. On entry
    to this routine IRQL is set to APC_LEVEL.

    N.B. The exception frame and trap frame addresses are only guaranteed
         to be valid if, and only if, the previous mode is user.

Arguments:

    PreviousMode - Supplies the previous processor mode.

    ExceptionFrame - Supplies a pointer to an exception frame.

    TrapFrame - Supplies a pointer to a trap frame.

Return Value:

    None.

--*/

{

    PKAPC Apc;
    PKKERNEL_ROUTINE KernelRoutine;
    PLIST_ENTRY NextEntry;
    PVOID NormalContext;
    PKNORMAL_ROUTINE NormalRoutine;
    KIRQL OldIrql;
    PVOID SystemArgument1;
    PVOID SystemArgument2;
    PKTHREAD Thread;

    //
    // Raise IRQL to dispatcher level and lock the APC queue.
    //

    Thread = KeGetCurrentThread();
    KiLockApcQueue(Thread, &OldIrql);

    //
    // Get address of current thread object, clear kernel APC pending, and
    // check if any kernel mode APC's can be delivered.
    //

    Thread->ApcState.KernelApcPending = FALSE;
    while (IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]) == FALSE) {            ////kernel APC
        NextEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
        Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
        KernelRoutine = Apc->KernelRoutine;
        NormalRoutine = Apc->NormalRoutine;
        NormalContext = Apc->NormalContext;
        SystemArgument1 = Apc->SystemArgument1;
        SystemArgument2 = Apc->SystemArgument2;
        if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) {

            //
            // First entry in the kernel APC queue is a special kernel APC.
            // Remove the entry from the APC queue, set its inserted state
            // to FALSE, release dispatcher database lock, and call the kernel
            // routine. On return raise IRQL to dispatcher level and lock
            // dispatcher database lock.
            //

            RemoveEntryList(NextEntry);
            Apc->Inserted = FALSE;
            KiUnlockApcQueue(Thread, OldIrql);
            (KernelRoutine)(Apc, &NormalRoutine, &NormalContext,
                            &SystemArgument1, &SystemArgument2);

#if DBG

                if (KeGetCurrentIrql() != OldIrql) {
                    KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
                                 KeGetCurrentIrql() << 16 | OldIrql << 8,
                                 (ULONG_PTR)KernelRoutine,
                                 (ULONG_PTR)Apc,
                                 (ULONG_PTR)NormalRoutine);
                }

#endif

            KiLockApcQueue(Thread, &OldIrql);

        } else {

            //
            // First entry in the kernel APC queue is a normal kernel APC.
            // If there is not a normal kernel APC in progress and kernel
            // APC's are not disabled, then remove the entry from the APC
            // queue, set its inserted state to FALSE, release the APC queue
            // lock, call the specified kernel routine, set kernel APC in
            // progress, lower the IRQL to zero, and call the normal kernel
            // APC routine. On return raise IRQL to dispatcher level, lock
            // the APC queue, and clear kernel APC in progress.
            //

            if ((Thread->ApcState.KernelApcInProgress == FALSE) &&
               (Thread->KernelApcDisable == 0)) {
                RemoveEntryList(NextEntry);
                Apc->Inserted = FALSE;
                KiUnlockApcQueue(Thread, OldIrql);
                (KernelRoutine)(Apc, &NormalRoutine, &NormalContext,
                                &SystemArgument1, &SystemArgument2);

#if DBG

                if (KeGetCurrentIrql() != OldIrql) {
                    KeBugCheckEx(IRQL_UNEXPECTED_VALUE,
                                 KeGetCurrentIrql() << 16 | OldIrql << 8 | 1,
                                 (ULONG_PTR)KernelRoutine,
                                 (ULONG_PTR)Apc,
                                 (ULONG_PTR)NormalRoutine);
                }

#endif

                if (NormalRoutine != (PKNORMAL_ROUTINE)NULL) {
                    Thread->ApcState.KernelApcInProgress = TRUE;
                    KeLowerIrql(0);
                    (NormalRoutine)(NormalContext, SystemArgument1,
                                    SystemArgument2);
                    KeRaiseIrql(APC_LEVEL, &OldIrql);
                }

                KiLockApcQueue(Thread, &OldIrql);
                Thread->ApcState.KernelApcInProgress = FALSE;

            } else {
                KiUnlockApcQueue(Thread, OldIrql);
                return;
            }
        }
    }

    //
    // Kernel APC queue is empty. If the previous mode is user, user APC
    // pending is set, and the user APC queue is not empty, then remove
    // the first entry from the user APC queue, set its inserted state to
    // FALSE, clear user APC pending, release the dispatcher database lock,
    // and call the specified kernel routine. If the normal routine address
    // is not NULL on return from the kernel routine, then initialize the
    // user mode APC context and return. Otherwise, check to determine if
    // another user mode APC can be processed.
    //

    if ((IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]) == FALSE) &&                        //user APC
       (PreviousMode == UserMode) && (Thread->ApcState.UserApcPending == TRUE)) {
        Thread->ApcState.UserApcPending = FALSE;
        NextEntry = Thread->ApcState.ApcListHead[UserMode].Flink;
        Apc = CONTAINING_RECORD(NextEntry, KAPC, ApcListEntry);
        KernelRoutine = Apc->KernelRoutine;
        NormalRoutine = Apc->NormalRoutine;
        NormalContext = Apc->NormalContext;
        SystemArgument1 = Apc->SystemArgument1;
        SystemArgument2 = Apc->SystemArgument2;
        RemoveEntryList(NextEntry);
        Apc->Inserted = FALSE;
        KiUnlockApcQueue(Thread, OldIrql);
        (KernelRoutine)(Apc, &NormalRoutine, &NormalContext,
                        &SystemArgument1, &SystemArgument2);

        if (NormalRoutine == (PKNORMAL_ROUTINE)NULL) {
            KeTestAlertThread(UserMode);

        } else {
            KiInitializeUserApc(ExceptionFrame, TrapFrame, NormalRoutine,
                                NormalContext, SystemArgument1, SystemArgument2);
        }

    } else {
        KiUnlockApcQueue(Thread, OldIrql);
    }

    return;
}
KiDeliverApc在KiSwApThreAd,KiUnlockDispAtcherDAtAbAse被调用,在每次线程切换后,将执行APC queue中的APC.
在系统调用和中断后也会通过DISPATCH_USER_APC宏调用KiDeliverApc
在插入APC的时候,在函数KiInsertQueueApc中如果Apc->ApcStAteIndex == ThreAd->ApcStAteIndex,线程正在运行时通过调用KiRequestApcInterrupt直接引发一个中断,从而立即执行Kernel APC
 
 
测试:
从ring0调用一个ring3程序中的函数
#include <stdio.h>
#include <windows.h>
#include <conio.h>

VOID APIENTRY func(DWORD dwPArAm,DWORD Arg2,DWORD Arg3);
void main()
{
	printf("%x\n",(ULONG)func);
	_getch();

}
//--------------------------------------------------------------------
VOID APIENTRY func(DWORD dwPArAm,DWORD Arg2,DWORD Arg3)
{
	MessageBox(NULL,"in func","",0);
}
//--------------------------------------------------------------------
print出func的地址作为UserApc的NormAlRoutine,用windbg查看ring3进程的线程的地址,填入测试的ring0程序中(-______-)
这个ring3程序的KTHREAD+0×140的PreviousMode一直都是UserMode(1),所以现在就不管它啦,,以后在应用的时候似乎应该先检查下这个位
#include <ntddk.h>

NTSTATUS uSetTheApc(VOID);
VOID WorkThreAd(IN PVOID pContext);
void KernelApcCAllBAck(PKAPC Apc, PKNORMAL_ROUTINE NormalRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2);
//void UserApcCAllBAck(PVOID arg1, PVOID arg2, PVOID arg3);
VOID OnUnloAd( IN PDRIVER_OBJECT DriverObject );

typedef enum _KAPC_ENVIRONMENT {
    OriginalApcEnvironment,
	AttachedApcEnvironment,
	CurrentApcEnvironment
} KAPC_ENVIRONMENT;

typedef struct _KAPC_STATE{
	LIST_ENTRY	ApcListHead;
	PEPROCESS	Process;
	UCHAR		KernelApcInProgress;
	UCHAR		KernelApcPending;
	UCHAR		UserApcPending;
}KAPC_STATE,*PKAPC_STATE;

/* Function prototypes for APCs */
void
KeInitializeApc(
  PKAPC Apc,
  PKTHREAD Thread,
  CCHAR ApcStateIndex,
  PKKERNEL_ROUTINE KernelRoutine,
  PKRUNDOWN_ROUTINE RundownRoutine,
  PKNORMAL_ROUTINE NormalRoutine,
  KPROCESSOR_MODE ApcMode,
  PVOID NormalContext
);

BOOLEAN
KeInsertQueueApc(
				 PKAPC Apc,
				 PVOID SystemArgument1,
				 PVOID SystemArgument2,
				 UCHAR unknown
				 );

PETHREAD	pThreAd = (PETHREAD)0xffabcb40;////modified by hAnd -_______-

NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath )
{
	HANDLE		hThreAd;
	NTSTATUS	dwStAtus;
	DbgPrint("Driver Begin!\n");
	//__asm int 3
	DriverObject->DriverUnload = OnUnloAd;
	*((unsigned char *)pThreAd+0x4a)=1;
	//*((unsigned char *)pThreAd+0x164)=1;  //both of them works :>
	dwStAtus = PsCreateSystemThread(&hThreAd,
									(ACCESS_MASK)0,
									NULL,
									(HANDLE)0,
									NULL,
									WorkThreAd,
									NULL
									);

	if (!NT_SUCCESS(dwStAtus)){
		DbgPrint("error when creAte the threAd\n");
		return dwStAtus;
	}

	return STATUS_SUCCESS;
}
//--------------------------------------------------------------------
VOID OnUnloAd( IN PDRIVER_OBJECT DriverObject )
{
	DbgPrint("Driver End!\n");
}
//--------------------------------------------------------------------
VOID WorkThreAd(IN PVOID pContext)
{
	uSetTheApc();

	PsTerminateSystemThread(STATUS_SUCCESS);
	DbgPrint("Never be here ?\n");
}
//--------------------------------------------------------------------
NTSTATUS uSetTheApc(VOID)
{
	NTSTATUS	dwStAtus = STATUS_SUCCESS;
	PKAPC		pkApc;
	BOOLEAN		bBool;
	pkApc = ExAllocatePool(NonPagedPool,sizeof(KAPC));
	if (pkApc == NULL){
		DbgPrint("error:ExAllocAtePool\n");
		return STATUS_INSUFFICIENT_RESOURCES;
	}
	KeInitializeApc(
		pkApc,
		(PKTHREAD)pThreAd,
		OriginalApcEnvironment,
		(PKKERNEL_ROUTINE)KernelApcCAllBAck,
		NULL,
		(PKNORMAL_ROUTINE)0x40100A,//UserApcCAllBAck,   //modified by hAnd - -
		UserMode,
		NULL
		);
	bBool = KeInsertQueueApc(pkApc,NULL,NULL,0);
	if(bBool == FALSE){
		DbgPrint("error:KeInsertQueueApc\n");
	}

	return STATUS_SUCCESS;
}
//--------------------------------------------------------------------
void
KernelApcCAllBAck(PKAPC Apc, PKNORMAL_ROUTINE NormalRoutine, PVOID NormalContext, PVOID SystemArgument1, PVOID SystemArgument2)
{

	//LARGE_INTEGER Timeout;

	DbgPrint("Freeing APC Object\n");
	ExFreePool(Apc);    // free the kernel memory

	//Timeout.QuadPart = 0;
	//KeDelayExecutionThread(UserMode, TRUE, &Timeout);       //no  i think it doesn't work

	/*
	* Another way for a thread to set itself in alertable state
	* (MSJ, Nerditorium, July 99):
	*
	* KeInitializeEvent(&event, SynchronizationEvent, FALSE);
	* KeWaitForSingleObject(&event, Executive, UserMode, TRUE, &Timeout);
	*/
}
//--------------------------------------------------------------------
测试结果:
ring3函数func被执行,弹出对话框 :>
 
rootkit.com 上早有了比较实用的利用程序 (by  vAlerino) , 那个看起来是没问题,不过我在运行的时候还是出了些问题没有成功,
前几天我试APC出的问题主要在直接对用PsCreAteSystemThreAd创建的threAd queue APC,当然queue不上user APC了 :|
2005年12月01日

for (int z = 5;0<1;){System.out.print(z);}//在JAVA里这样声明的变量z,作用域是{}里,c++里则是从定义的位置以下

2005年11月22日

WCHAR转CHAR

void wtoA(WCHAR* source,char* dest)
{
	ULONG i;
	for (i = 0;i<wcslen(source)+1;i++){
		dest[i] = (char)source[i];
	}
}

CHAR转WCHAR

VOID Atow(char* source,WCHAR* dest)
{
	ULONG	i;
	memset(dest,0,wcslen(dest));
	for(i = 0;i < strlen(source);i++){
		dest[i] = source[i];
	}
}
//--------------------------------------------------------------------

取一段字符串的各个部分

VOID GetArg(char* commAndline,int* Argc,char* Argv[])
{
	ULONG	length;
	ULONG	i = 0;
	ULONG	index = 0;
	length = strlen(commAndline);
	Argv[index] = commAndline;
	index++;
	for(i = 0;i < length;i++){
		if(commAndline[i] == ' '){
			commAndline[i] = '\0';//breAk up
			i++;
			while(commAndline[i] == ' '){
				i++;
			}
			Argv[index] = commAndline + i;
			index ++;
			i--;
		}
	}
	*Argc = index;
}
//--------------------------------------------------------------------

用法:

int main(void){

	char		str[1024];
	int			Argc;
	char*		Argv[9];
	int			i;

	strcpy(str,"cd windows dfdf f            d");
	GetArg(str,&Argc,Argv);
	for (i = 0;i < Argc;i++){
		printf("%s\n",Argv[i]);
	}
	return 0;
}
//--------------------------------------------------------------------

结果:

cd
windows
dfdf
f
d

改进了点,可以处理"了,比如cd "progrAm files",得到的参数就是 cd 和progrAm files

VOID GetArg(char* commAndline,int* Argc,char* Argv[])
{
	ULONG	length;
	ULONG	i = 0;
	ULONG	index = 0;
	length = strlen(commAndline);
	Argv[index] = commAndline;
	index++;
	for(i = 0;i < length;i++){
		if(commAndline[i] == ' ' && commAndline[i+1] != ' ' && commAndline[i+1] != '\0'){			commAndline[i] = '\0';//breAk up
			i++;
			while(commAndline[i] == ' ' && i<length){
				i++;
			}
			Argv[index] = commAndline + i;
			index ++;

			if(commAndline[i] == '"'){   /////////////////////这段是对"的处理
				commAndline[i] = '\0';
				Argv[index-1] = (char*)Argv[index-1]+1;
				while(commAndline[i] != '"' && i<length){
					i++;
				}
				if (commAndline[i] == '"'){
					commAndline[i] = '\0';
					i--;
				}
			}//if                                //////////////
		}

	}
	*Argc = index;
}
//--------------------------------------------------------------------

哎,又改进,加入最大参数个数

VOID GetArg(char* commAndline,int* Argc,char* Argv[],ULONG	mAxArgc)
{
	ULONG	length;
	ULONG	i = 0;
	ULONG	index = 0;
	length = strlen(commAndline);
	Argv[index] = commAndline;
	index++;
	for(i = 0;i < length;i++){
		if(commAndline[i] == ' ' && commAndline[i+1] != ' ' && commAndline[i+1] != '\0'){
			commAndline[i] = '\0';//breAk up
			i++;
			while(commAndline[i] == ' ' && i<length){
				i++;
			}
			if(index < mAxArgc){				//////////////处理最大参数个数 只取前mAxArgc个参数
				Argv[index] = commAndline + i;
				index ++;
			}									////////////////////////
			if(commAndline[i] == '"'){   /////////////////////这段是对"的处理
				commAndline[i] = '\0';
				Argv[index-1] = (char*)Argv[index-1]+1;
				while(commAndline[i] != '"' && i<length){
					i++;
				}
				if (commAndline[i] == '"'){
					commAndline[i] = '\0';
					i--;
				}
			}//if                                //////////////
		}

	}
	*Argc = index;
}
//--------------------------------------------------------------------

用法:

int main(void)
{
	char	str[60];
	ULONG	Argc;
	PCHAR	Argv[9];
	ULONG	i;

	strcpy(str,"dir  \"progrAm files\" ddd 44 55 66 7 8 9 10 11");
	GetArg(str,&Argc,Argv,9);
	printf("%d\n",Argc);

	for(i = 0;i<Argc;i++){
		printf("%s\n",Argv[i]);
	}

	return 0;
}
//--------------------------------------------------------------------

结果:

9
dir
progrAm files
ddd
44
55
66
7
8
9

2005年11月12日

Common Driver Reliability Issues

Microsoft Corporation

June 2004

Applies to:
   Microsoft Windows 98 / Windows Me
   Microsoft Windows 2000
   Microsoft Windows XP
   Microsoft Windows Server 2003
   Microsoft Windows codenamed "Longhorn"

Summary: This paper provides information about writing drivers for the Microsoft Windows family of operating systems. It describes a number of common errors and suggests how driver writers can find, correct, and prevent such errors. (37 printed pages)

Contents

Introduction
User-Mode Addresses in Kernel-Mode Code
Driver I/O Methods and Their Tradeoffs
Failing to Check Buffer Sizes in Buffered IOCTLs and FSCTLs
Returning Data in Uninitialized Bytes
Failing to Validate Variable-Length Buffers
Device State Validation
Cleanup and Close Routines
Device Control Routines
Synchronization
Shared Access
Locks and Disabling APCs
Handle Validation
Requests to Create and Open Files and Devices
Driver Unload Routines
Pageable Drivers and DPCs
User-Mode APIs
StartIo Recursion
Passing and Completing IRPs
Odd-length Unicode Buffers
Pool Allocation in Low Memory
Call to Action and Resources

Introduction

Drivers occupy a significant portion of the total code base executed in kernel mode. Consequently, efforts to improve the reliability and security of the system must address this large code base.

This paper describes a variety of problems commonly seen in drivers, often with code that shows typical errors, and how to fix them. The code has been edited for brevity.

This paper is for developers who are writing kernel-mode drivers. The information in this paper applies for the Microsoft Windows family of operating systems.

User-Mode Addresses in Kernel-Mode Code

When providing services to user-mode code, drivers and other kernel-mode components usually receive and return data in buffers. To avoid corruption of data, disclosure of sensitive or security-critical data, or exceptions that cannot be handled by the try/except mechanism, kernel components must ensure that each data pointer they receive from user mode is a valid user-mode pointer. This operation is called probing.

Drivers must obey the following rules when handing pointers obtained from user mode:

  • Probe all user-mode pointers before referencing them.

    To probe a pointer, use the macro ProbeForRead or ProbeForWrite, or the memory management routine MmProbeAndLockPages.

  • Enclose all references to user-mode pointers in try/except blocks. The mapping of user-mode memory can change at any instant for various reasons, such as address space deletion, protection change, or decommit. Therefore, any reference to a user-mode pointer could raise an exception.
  • Assume that user-mode pointers can be aligned on any boundary.
  • Be prepared for changes to the contents of user-mode memory at any time; another user-mode thread in the same process might change it. Drivers must not use user-mode buffers as temporary storage, or expect the results of double fetches to yield the same results the second time.
  • Validate all data received from user-mode code.

Handling user-mode pointers incorrectly can result in the following:

  • Crashes caused by references to portions of the kernel address space that the Memory Manager considers reserved. It is a serious error for any driver to reference such address space.
  • Crashes caused by references to input/output (I/O) space, if the architecture uses memory-mapped device registers. Such references (reads and writes) can also have negative effects on the device itself.
  • Disclosure of sensitive data if the caller passes a pointer to an area that is unreadable by user mode, then observes the driver’s responses or return values to deduce the contents of the protected location.
  • Corruption of kernel data structures by writing to arbitrary kernel addresses, which can cause crashes or compromise security.

Probing

To understand when probing is necessary, consider the following sample routines, SetUserData and GetUserData. These samples represent fictitious system service routines, but could also be driver routines keyed on input/output control (IOCTL) or file system control (FSCTL) values; the only difference is that the driver code is more complicated. These routines show a situation in which probing is necessary. To simplify the example, the sample routines do not include locks to prevent race conditions and similar details that normally should be present in such code.

Example Routines That Do Not Use Probing

SetUserData receives a buffer from user mode and copies it to a global location. This routine represents any kernel component that receives data from user mode.

VOID
SetUserData (
    IN PWCHAR DataPtr,    // Pointer from user mode
    IN ULONG DataLength
    )
{
      //
      // Truncate data if it's too big.
      //
      if (DataLength > MAX_DATA_LENGTH) {
          DataLength = MAX_DATA_LENGTH;
      }

      //
      // Copy user buffer to global location.
      //
      memcpy (InternalStructure->UserData, DataPtr, DataLength);
      InternalStructure->UserDataLength = DataLength;
}

GetUserData returns to the caller data that was previously set with SetUserData:
ULONG
GetUserData (
    IN PWCHAR DataPtr, // Pointer from user mode
    IN ULONG DataLength
    )
{
      //
      // Truncate data if it's too big.
      //
      if (DataLength > InternalStructure->UserDataLength) {
           DataLength = InternalStructure->UserDataLength;
      }   

      memcpy (DataPtr, InternalStructure->UserData, DataLength);

      return DataLength;
}

Problems Caused by Failing to Probe

In the examples in the previous section, both SetUserData and GetUserData fail to validate DataPtr. If the pointer is invalid, the caller could cause a system crash, thus compromising operating system integrity. If the pointer specifies a memory address that the caller does not have the right to read, the caller might also be able to deduce the contents of that address. Because the operating system maintains data for all processes in global pool addresses, a caller could pass an invalid pointer and then inspect the returned data for passwords or program output text strings generated by operating system users.

Routines that have pointer validation problems like these could easily be used to compromise system security. A hostile program could repeatedly call SetUserData with kernel addresses, followed by calls to GetUserData to retrieve the contents of the kernel address space. The program could then look for interesting data that is private to other users of the system, such as cached file data for files to which the caller has no access. In this situation, the kernel returns data that the caller has no permission to see.

In addition, reading certain kernel addresses can cause unwanted side effects. For example, some addresses are pageable but should be paged only within certain process contexts, such as thread stacks; in other contexts, a bug check can occur. Also, certain device registers may be mapped into virtual memory. Reading from memory locations that are mapped this way directly affects the hardware. For example, reading from a control register of a programmed I/O device might cause the device to lose incoming data, or might start or stop the device.

Example Routines That Use Probing

Both SetUserData and GetUserData must validate every user-mode pointer. The following shows correct code for SetUserData, which probes user-mode pointers before accessing them.

VOID
SetUserData (
    IN PWCHAR DataPtr, // Pointer from user mode
    IN ULONG DataLength
    )
{
  //
  // Truncate data if it's too big.
  //
  if (DataLength > MAX_DATA_LENGTH) {
     DataLength = MAX_DATA_LENGTH;
  }

  //
  // Copy user buffer to global location.
  //
  try {
       ProbeForRead( DataPtr,
                      DataLength,
                      TYPE_ALIGNMENT( WCHAR ));
       memcpy (InternalStructure->UserData,
               DataPtr, DataLength);
       InternalStructure->UserDataLength = DataLength;
  } except( EXCEPTION_EXECUTE_HANDLER ) {
  // Use GetExceptionCode() to return an error to the
  // caller.
 }
}

The correct code validates the pointer at DataPtr by calling the macro ProbeForRead in a try/except block.

The following shows the corrected code for GetUserData.

VOID
GetUserData (
    IN PWCHAR DataPtr, // Pointer from user mode
    IN ULONG DataLength
    )
{
  //
  // Truncate data if it's too big.
  //
  if (DataLength > InternalStructure->UserDataLength) {
       DataLength = InternalStructure->UserDataLength;
  }

  try {
       ProbeForWrite( DataPtr,
                      DataLength,
                      TYPE_ALIGNMENT( WCHAR ));
       memcpy (DataPtr, InternalStructure->UserData,
               DataLength);
       InternalStructure->UserDataLength = DataLength;
      } except( EXCEPTION_EXECUTE_HANDLER ){
  // Use GetExceptionCode() to return an error to the
  // caller.

   DataLength=0;
   }
  return DataLength;
}

The correct code validates the pointer at DataPtr by calling the macro ProbeForWrite in a try/except block.

Addresses Passed in METHOD_NEITHER IOCTLs and FSCTLs

The I/O Manager does not validate user-mode addresses passed in METHOD_NEITHER IOCTLs and FSCTLs. To ensure that such addresses are valid, the driver must use the ProbeForRead and ProbeForWrite macros, enclosing all buffer references in try/except blocks.

In the following example, the driver does not validate the address passed in the Type3InputBuffer.

case IOCTL_GET_HANDLER: {
      PULONG EntryPoint;

      EntryPoint =
         IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
      *EntryPoint = (ULONG) DriverEntryPoint;

The following code correctly validates the address and avoids this problem.

case IOCTL_GET_HANDLER: {
      PULONG_PTR EntryPoint;

      EntryPoint =
         IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;

      try {
           if (Irp->RequestorMode != KernelMode) {
           ProbeForWrite(EntryPoint,
                         sizeof( ULONG_PTR ),
                         TYPE_ALIGNMENT( ULONG_PTR ));
          }
      *EntryPoint = (ULONG_PTR)DriverEntryPoint;

      } except( EXCEPTION_EXECUTE_HANDLER ) {
...

Note also that the correct code casts DriverEntryPoint to a ULONG_PTR, instead of a ULONG. This change allows for use of this code in a 64-bit Windows environment.

Pointers Embedded in Buffered I/O Requests

Drivers must similarly validate pointers that are embedded in buffered I/O requests. In the following example, the structure member at arg is an embedded pointer.

struct ret_buf {
   void   *arg; // Pointer embedded in request
   int     rval;
   };

pBuf = Irp->AssociatedIrp.SystemBuffer;
   ...
arg = pBuf->arg; // Fetch the embedded pointer
   ...
// If the pointer is invalid,
// this statement can corrupt the system.
RtlMoveMemory(arg, &info, sizeof(info));

In this example, the driver should validate the embedded pointer by using the ProbeXxx macros enclosed in a try/except block, in the same way as for the METHOD_NEITHER IOCTLs described in the preceding section. Although embedding a pointer allows a driver to return extra information, a driver can more efficiently achieve the same result by using a relative offset or a variable length buffer.

Using Handles in User Context

Drivers often manipulate objects using handles, which can come from user mode or kernel mode. If the driver is running in system context, it can safely create and use handles because all threads within the system process are trusted. When running in user context, however, a driver must use handles with care.

Drivers should not create or pass handles to ZwXxx routines. These functions translate to calls to user-mode system services. Another thread in the process can change such handles at any instant. Using or creating handles within a user’s process makes the driver vulnerable to problems, as the following example shows.

status = IoCreateFile(&handle,
                      DesiredAccess,
                      &objectAttributes,
                      &ioStatusBlock,
                      NULL,
                      FILE_ATTRIBUTE_NORMAL,
                      FILE_SHARE_READ,
                      FILE_OPEN,
                      0,
                      NULL,
                      0,
                      CreateFileTypeNone,
                      NULL,
                      IO_NO_PARAMETER_CHECKING);

if ( NT_SUCCESS(status) ) {
   status = ObReferenceObjectByHandle(handle,
                      0,
                      NULL,
                      KernelMode,
                      &ccb->FileObject,
                      &handleInformation);

By the time ObReferenceObjectByHandle is called, the value of handle might have changed if:

  • Another thread closed and reopened the handle.
  • Another thread suspended the first thread and then successively created objects until it received the same handle value back again.

Similarly, handles received from user mode in other ways—for example, in a buffered I/O request—should not be passed to ZwXxx routines. Doing so makes a second transition into the kernel. When the ZwXxx routine runs, the previous processor mode is kernel; all access checks (even those against granted access masks of handles) are disabled. If a caller passes in a read-only handle to a file it lacks permission to write, and the driver then calls ZwWriteFile with the handle, the write will succeed. Similarly, calls to ZwCreateFile or ZwOpenFile with file names provided to the driver will successfully create or open files that should be denied to the caller.

Drivers can use the OBJ_FORCE_ACCESS_CHECK and OBJ_KERNEL_HANDLE flags in the OBJECT_ATTRIBUTES structure to safely use handles to manipulate objects. To set these flags, a driver calls InitializeObjectAttributes with the handle before creating the object.

The OBJ_FORCE_ACCESS_CHECK flag causes the system to perform all access checks on the object being opened. Handles created with OBJ_KERNEL_HANDLE can be accessed only in kernel mode. Drivers should use kernel-mode handles only when necessary, however; use of such handles can affect system performance, because Object Manager calls that use kernel handles attach to the system process. In addition, quota charges are made against the system process, and not against the original caller.

Driver I/O Methods and Their Tradeoffs

Drivers can use the following I/O methods:

  • Buffered I/O
  • Direct I/O
  • Neither buffered nor direct I/O (METHOD_NEITHER I/O)

In general, performance improves by moving from buffered I/O to direct I/O and from direct I/O to METHOD_NEITHER I/O, because the I/O Manager does less for the driver. The driver must do more work to validate requests, however, as the higher performing methods often require significantly more validation code to ensure that the driver is robust.

Buffered I/O

Buffered I/O requests are typically used by interfaces that require small transfer sizes or are called infrequently.

To handle a buffered I/O request, the I/O Manager:

  • Validates the user buffer pointers passed to it.
  • Allocates new buffers from non-paged pool for the input data.
  • Copies the user data to these newly allocated buffers.

The driver operates only on the buffers allocated by the I/O Manager, and not on the buffers allocated by the caller. The driver is therefore not required to validate the buffer pointers or handle exceptions if the caller’s address space becomes invalid.

Buffers allocated by the I/O Manager have the same alignment as allocated pool (8-byte alignment on the 32-bit systems). Consequently, the driver is not required to check for valid buffer alignment. The driver must validate the size and contents of the data, however. The data cannot change asynchronously because user-mode processes do not have access to the buffers.

After the driver completes a buffered I/O request, the I/O Manager executes an asynchronous procedure call (APC) to return to the original process context. The I/O Manager then copies data from the buffers written by the driver to the caller’s user-space output buffer.

Failing to check the size of buffers is perhaps the most common driver error in buffered I/O. This error can occur in many contexts, but is particularly troublesome in the following cases:

  • Failing to check buffer sizes in buffered IOCTLs and FSCTLs.
  • Returning data in uninitialized bytes.
  • Failing to validate variable-length buffers.

These cases are discussed in more detail in the sections that follow.

Failing to Check Buffer Sizes in Buffered IOCTLs and FSCTLs

When handling buffered IOCTLs and FSCTLs, a driver should always check the sizes of the input and output buffers to ensure that the buffers can hold all the requested data. If the RequiredAccess bits in the request specify FILE_ANY_ACCESS, as most driver IOCTLs and FSCTLs do, any caller that has a handle to the device also has access to buffered IOCTL or FSCTL requests for that device, and could read or write data beyond the end of the buffer.

For example, assume that the following code appears in a routine that is called from a dispatch routine, and that the driver has not validated the input buffer sizes passed in the IRP.

switch (ControlCode)
   ...
   ...
   case IOCTL_NEW_ADDRESS:{
      tNEW_ADDRESS *pNewAddress =
        pIrp->AssociatedIrp.SystemBuffer;

        pDeviceContext->Addr = ntohl (pNewAddress->Address);
        ...
  

The example does not check the buffer sizes before assigning a new value to pDeviceContext->Addr. As a result, the reference to pNewAddress->Address can cause a fault if the input buffer is not big enough to contain a tNEW_ADDRESS structure.

The following code checks the buffer sizes, avoiding the potential problem.

case IOCTL_NEW_ADDRESS: {
   tNEW_ADDRESS *pNewAddress =
     pIrp->AssociatedIrp.SystemBuffer;

  if (pIrpSp->Parameters.DeviceIoControl.InputBufferLength >=
       sizeof(tNEW_ADDRESS)){
         pDeviceContext->Addr = ntohl (pNewAddress->Address);
...

Code that handles other buffered I/O, such as WMI requests that use variable size buffers, can have similar errors.

Output buffer problems are similar to input buffer problems. They can easily corrupt the memory pool, and user-mode callers might be unaware that any error has occurred.

In the following example, the driver fails to check the size of the output buffer at SystemBuffer.

case IOCTL_GET_INFO: {

    Info = Irp->AssociatedIrp.SystemBuffer;

    Info->NumIF = NumIF;
    ...
    ...
    Irp->IoStatus.Information =
         NumIF*sizeof(GET_INFO_ITEM)+sizeof(ULONG);
    Irp->IoStatus.Status = ntStatus;
   }

Assuming that the NumIF field of the system buffer specifies the number of input items, this example can set IoStatus.Information to a value larger than the output buffer and thus return too much information to the user-mode caller. The preceding code could corrupt the memory pool by writing beyond the end of the system buffer.

Keep in mind that the I/O Manager does not validate the value in the Information field. The driver must check the output buffer size. If a caller passes a valid kernel-mode address for the output buffer and a buffer size of zero bytes, serious errors can occur.

Returning Data in Uninitialized Bytes

Drivers should initialize all output buffers with zeros before returning them to the caller. Failing to initialize a buffer can result in the inadvertent return of kernel-mode data in uninitialized bytes.

In the following example, a driver fails to initialize the buffer and thus unintentionally returns data in uninitialized bytes.

case IOCTL_GET_NAME: {
   ...
   ...
   outputBufferLength =
      ioStack->Parameters.DeviceIoControl.OutputBufferLength;
   outputBuffer =
      (PGET_NAME) Irp->AssociatedIrp.SystemBuffer;

   if (outputBufferLength >= sizeof(GET_NAME)) {
      length = outputBufferLength - sizeof(GET_NAME);
      ntStatus = IoGetDeviceProperty(
                  DeviceExtension->PhysicalDeviceObject,
                  DevicePropertyDriverKeyName,
                  length,
                  outputBuffer->DriverKeyName,
                  &length);

      outputBuffer->ActualLength = length + sizeof(GET_NAME);
      Irp->IoStatus.Information = outputBufferLength;
   } else {
     ntStatus = STATUS_BUFFER_TOO_SMALL;
   }

Setting IoStatus.Information to the output buffer size causes the whole output buffer to be returned to the caller. The I/O Manager does not initialize the data beyond the size of the input buffer—the input and output buffers overlap for a buffered request. Because the system support routine IoGetDeviceProperty does not write the entire buffer, this IOCTL returns uninitialized data to the caller.

Some drivers use the Information field to return codes that provide extra details about I/O requests. Before doing so, such drivers should check the IRP flags to ensure that IRP_INPUT_OPERATION is not set. When this flag is not set, the IOCTL or FSCTL does not have an output buffer, so the Information field does not supply a buffer size. In this case, the driver can safely use the Information field to return its own code.

Failing to Validate Variable-Length Buffers

Drivers should always validate variable-length buffers. Failure to do so can cause integer underflows and overflows.

Drivers often use input buffers with fixed headers and trailing variable-length data, as in the following example.

typedef struct _WAIT_FOR_BUFFER {
   LARGE_INTEGER Timeout;
   ULONG NameLength;
   BOOLEAN TimeoutSpecified;
   WCHAR Name[1];
   } WAIT_FOR_BUFFER, *PWAIT_FOR_BUFFER;

if (InputBufferLength < sizeof(WAIT_FOR_BUFFER)) {
    IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
    return( STATUS_INVALID_PARAMETER );
   }

WaitBuffer = Irp->AssociatedIrp.SystemBuffer;

if (FIELD_OFFSET(WAIT_FOR_BUFFER, Name[0]) +
       WaitBuffer->NameLength > InputBufferLength) {
         IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
         return( STATUS_INVALID_PARAMETER );
   }

Adding WaitBuffer->NameLength (a ULONG) to the offset (a LONG) can cause an integer overflow if the ULONG value is large. Instead, the driver should subtract the offset from InputBufferLength, and compare the result with WaitBuffer->NameLength, as in the following example.

if (InputBufferLength < sizeof(WAIT_FOR_BUFFER)) {
    IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
    return( STATUS_INVALID_PARAMETER );
   }

WaitBuffer = Irp->AssociatedIrp.SystemBuffer;

if ((InputBufferLength –
     FIELD_OFFSET(WAIT_FOR_BUFFER, Name[0])  >
       WaitBuffer->NameLength) {
    IoCompleteRequest( Irp, STATUS_INVALID_PARAMETER );
    return( STATUS_INVALID_PARAMETER );
   }

The subtraction shown in the preceding example cannot cause a buffer underflow, because the first if statement ensures that the input buffer length is greater than or equal to the size of WAIT_FOR_BUFFER.

The following example shows a more complicated overflow problem.

case IOCTL_SET_VALUE:
      dwSize = sizeof(SET_VALUE);

    if(inputBufferLength < dwSize) {
       ntStatus = STATUS_BUFFER_TOO_SMALL;
       break;
    }

    dwSize = FIELD_OFFSET(SET_VALUE, pInfo[0]) +
            pSetValue->NumEntries * sizeof(SET_VALUE_INFO);

    if(inputBufferLength < dwSize) {
       ntStatus = STATUS_BUFFER_TOO_SMALL;
       break;
    }

In this example, an integer overflow can occur during multiplication. If the size of the SET_VALUE_INFO structure is a multiple of two, a NumEntries value such as 0×80000000 results in an overflow, when the bits are shifted left during multiplication. The buffer size passes the validation test, however, because the overflow causes dwSize to contain a small number. To avoid this problem, subtract the buffer lengths as shown in the previous example, then divide by sizeof(SET_VALUE_INFO) and compare the result with NumEntries to ensure that the buffer is the correct size.

Direct I/O

Drivers for devices that can transfer large amounts of data at a time, such as mass storage devices, typically use direct I/O. To handle a direct I/O request, the I/O Manager allocates the input buffer from non-paged pool and, if the length of the buffer is nonzero, creates a memory descriptor list (MDL) to map the output buffer. For an input request, the I/O Manager checks the output buffer for read access; for an output request, it checks the buffer for write access.

Drivers access the output buffer by calling the MmGetSystemAddressForMdlSafe macro to map the MDL into a system address range. This system address range contains the same physical pages as the original user buffer, but is unaffected by virtual address changes in the calling application. Drivers can therefore rely on the address to remain valid.

Because the user’s address space is doubly mapped to the system address range, two different virtual addresses have the same physical address. The following consequences of double mapping can sometimes cause problems for drivers:

  • The offset into the virtual page of the user’s address becomes the offset into the system page.

    Access beyond the end of the system buffer might go unnoticed for long periods of time depending on the page granularity of the mapping. Unless a caller’s buffer is allocated near the end of a page, data written beyond the end of the buffer will nevertheless appear in the buffer, and the caller will be unaware that any error has occurred. If the end of the buffer coincides with the end of a page, the system virtual addresses beyond the end could point to anything, or could be invalid. Such problems can be extremely difficult to find.

  • If the calling process has another thread that modifies the user’s mapping of the memory, the contents of the system buffer will change when the user’s memory mapping changes.

    In this situation, using the system buffer to store scratch data can cause problems. Two fetches from the same memory location might yield different values.

In addition, during read requests, drivers must not write to mapped areas that they have locked for read access. Inadvertently writing to an area that is locked for read access could allow a user-mode application to corrupt the global system state.

The most common direct I/O problem is incorrectly handling zero-length buffers. Because the I/O Manager does not create MDLs for zero-length transfers, a zero-length buffer results in a NULL value at Irp->MdlAddress. If a driver passes a NULL MdlAddress to MmGetSystemAddressForMdlSafe, mapping fails and the macro returns NULL. Drivers should always check for a NULL return value before attempting to use the returned address.

The following code snippet shows one possible error in direct I/O. The example receives a string in a direct I/O request, and then tries to convert that string to uppercase characters.

PWCHAR  PortName = NULL;

PortName = (PWCHAR)MmGetSystemAddressForMdlSafe
                   (irp->MdlAddress, NormalPagePriority);

//
// Null-terminate the PortName so that RtlInitUnicodeString // will not be invalid.
//
PortName[Size / sizeof(WCHAR) - 1] = UNICODE_NULL;

RtlInitUnicodeString(&AdapterName, PortName);

Because the buffer might not be correctly formed, the code attempts to force a Unicode NULL as the last buffer character. If the underlying physical memory is doubly mapped to both a user-mode and a kernel-mode address, another thread in the process can overwrite the buffer as soon as this write operation completes. If the UNICODE NULL character is not present, however, the call to RtlInitUnicodeString can exceed the range of the buffer and, if it falls outside the system mapping, possibly cause a bug check.

If a driver creates and maps its own MDL, it must access the MDL only with the method for which it has probed. When the driver calls MmProbeAndLockPages, it specifies an access method (IoReadAccess, IoWriteAccess, or IoModifyAccess). If the driver specifies IoReadAccess, it must not attempt to write to the system buffer made available by MmGetSystemAddressForMdlSafe.

Further problems can occur in direct I/O paths when resources are unavailable. If insufficient system page table entries (PTE) are available, MmGetSystemAddressForMdlSafe fails and returns NULL.

Note   Microsoft Windows 98 does not support MmGetSystemAddressForMdlSafe. In a WDM driver that must run on Windows 98, call MmGetSystemAddressForMdl, setting the MDL_MAPPING_CAN_FAIL MDL flag in the MdlFlags member of the MDL structure. MmGetSystemAddressForMdl is obsolete on Windows Me, Windows 2000, and all later releases.

Neither Buffered nor Direct I/O (METHOD_NEITHER)

When handling a METHOD_NEITHER I/O request, the I/O Manager does not validate the supplied buffer pointers and lengths. Drivers must validate the pointers, lengths, and alignment by probing. Drivers must also use try/except blocks around each access to the user buffer to handle any exceptions that might occur.

The driver must also manage the buffers and memory operations by itself. When possible, the driver should perform all operations on the buffer directly within the context of the calling thread. When running outside this context, the driver must use MmProbeAndLockPages to double-map and lock down the buffer, thus preventing asynchronous changes to the data.

Some file-system drivers and network transport drivers define IOCTLs for fast I/O. Fast I/O, which uses METHOD_NEITHER, involves transferring data directly between user buffers and the system cache. Because the data in the user buffers can change asynchronously, fast I/O dispatch routines can be difficult to code. All references to user buffers must be enclosed in try/except blocks, and all METHOD_NEITHER buffers must be probed.

If a driver allocates resources in a fast I/O path, the driver must subsequently release those resources if an exception occurs while referencing user-mode memory. Failing to release resources in such situations is a common driver error.

For most fast I/O paths, the I/O Manager calls the fast I/O dispatch routine from within a try/except block. A driver that allocates resources in a fast I/O path must include an exception handler in its fast I/O dispatch routine. A driver that performs fast I/O and access user-mode memory, but does not allocate resources in the fast I/O path, should include an exception handler in its fast I/O dispatch routine. It is not required to do so, however.

Device State Validation

In addition to validating pointers, drivers should validate device state in both the checked and free builds.

In the following example, the driver uses the ASSERT macro to check for the correct device state in the checked build, but does not check the device state in the free build.

case IOCTL_WAIT_FOR_EVENT:

     ASSERT((!Extension->WaitEventIrp));
     Extension->WaitEventIrp = Irp;
     IoMarkIrpPending(Irp);
     status = STATUS_PENDING;

In the checked build, if the driver already holds the IRP pending the system will assert. In the free build, however, the driver does not check for this condition. Two calls to the same IOCTL cause the driver to lose track of an IRP.

On a multiprocessor system, this code fragment might cause additional problems. Assume that on entry, the routine that includes this code has ownership of (the right to manipulate) the IRP. When the routine saves the Irp pointer in the global structure at Extension->WaitEventIrp, another thread can read the IRP address from that global structure and perform operations on the IRP. To prevent this problem, the driver should mark the IRP pending before it saves the IRP, and should include both the call to IoMarkIrpPending and the assignment in an interlocked sequence. A Cancel routine for the IRP might also be necessary.

Cleanup and Close Routines

Driver writers must not confuse the tasks required in DispatchCleanup and DispatchClose routines.

The I/O Manager calls a driver’s DispatchCleanup routine when the last handle to a file object is closed. A cleanup request indicates that an application is being terminated, or has closed a file handle for the file object that represents the driver’s device object. The I/O Manager still holds a reference to the file object, however. The I/O Manager calls the DispatchClose routine when the last reference is released from the file object.

The DispatchCleanup routine should cancel any IRPs that are currently queued to the target device for the file object, but must not free resources that are attached to the file object or that might be used by other Dispatch routines. Because the I/O Manager holds a reference to the file object, a driver can receive I/O requests for a file object after its DispatchCleanup routine has been called, but before its DispatchClose routine is called.

For example, a user-mode caller might close the file handle while an I/O Manager request is in progress from another thread. If the driver deletes or frees necessary resources before the I/O Manager calls its DispatchClose routine, invalid pointer references and other problems could occur.

Device Control Routines

The following errors are common in DispatchDeviceControl routines, which handle IOCTLs:

  • Breaking apart IOCTL and FSCTL values.
  • Converging code paths for public and private IOCTLs.
  • Checking only the requestor mode to validate IOCTL or FSCTL IRPs.

Breaking Apart IOCTL and FSCTL Values

A driver must use the full value of the IOCTL control code, and not a subset of the bits, in its dispatch routine. Access checks and the IOCTL method are encoded into the control code. Ignoring the values of these bit fields could make the driver vulnerable to other unvalidated IOCTL routes. For example,

IoControlCode =
      pIrpStack->Parameters.DeviceIoControl.IoControlCode;
ControlCode   = (IoControlCode >> 2) & 0x00000FFF;

pCmd = pIrp -> AssociatedIrp.SystemBuffer;

switch (ControlCode) {
      case IOCTL_SET_TIMEOUT:
           pTimeOut = pIrp -> AssociatedIrp.SystemBuffer;
           *pTimeOut = InterlockedExchange(
                           &pde->TimeOutValue,
                           *pTimeOut);

This code masks both the calling method and access bits before the switch statement. In this example, if the intended IOCTL required write access to the device to issue this request, a caller could execute the switch statement with a different IOCTL value that did not require write access, but matched the extracted bits. Even if this code checks the input buffer length, it cannot tell which fields of the IRP contain the input buffer unless it consults the method bits of the IOCTL.

The I/O Manager macro IoGetFunctionCodeFromCtlCode has the same problem as the preceding example. Drivers should not use this macro.

An alternative method that avoids these problems is to build an array of structures indexed by the IOCTL function code. One field of the structure might contain the dispatch routine and another field might contain the complete IOCTL or FSCTL control code to compare against the input. Using such a structure, a driver can check both the calling method and the access control bits in one compare operation.

Converging Code Paths for Public IOCTLs and Private IOCTLs

As a general rule, drivers should not contain converging execution paths for private (internal) and public IOCTLs or FSCTLs. A driver that creates private IOCTLs or FSCTLs should handle such requests separately from any public IOCTLs or FSCTLs that it also supports.

A driver cannot determine whether an IOCTL or FSCTL originated in kernel mode or user mode merely from checking the control code. Consequently, handling both along the same code path (or performing minimal validation and then calling the same routines) can open a driver to security breaches. If a private IOCTL or FSCTL is privileged, unprivileged users who know the control codes might be able to gain access to it.

Checking Only the Requestor Mode to Validate IOCTL or FSCTL IRPs

Drivers should not validate IOCTL and FSCTL requests in IRPs by checking the value of Irp->RequestorMode only. IRPs that arrive from the network and the Server service (SRVSVC) have a requestor mode of kernel, regardless of the origin of the request. A driver that relies on the previous processor mode for the thread could unintentionally use an invalid user-mode pointer without probing, or perform an operation for which the original requestor does not have the required permissions.

Instead, drivers should use the appropriate access control checks, such as FILE_READ_DATA, FILE_WRITE_DATA, and so forth.

Synchronization

On the Microsoft Windows NT, Microsoft Windows 2000, and Windows XP operating systems, drivers are multithreaded; they can receive multiple I/O requests from different threads at the same time. In designing a driver, you must assume that it will be run on a symmetric multiprocessor (SMP) system and take the appropriate measures to ensure data integrity.

Specifically, whenever a driver changes global or file object data, it must use a lock or an interlocked sequence to prevent race conditions.

In the following example, a race condition could occur when the driver accesses the global data at Data.LpcInfo.

PLPC_INFO pLpcInfo = &Data.LpcInfo; //Pointer to global data
   ...
   ...
// This saved pointer may be overwritten by another thread.
pLpcInfo->LpcPortName.Buffer = ExAllocatePool(
                                     PagedPool,
                                     arg->PortName.Length);
 

Multiple threads entering this code as a result of an IOCTL call could cause a memory leak when the pointer is overwritten. To avoid this problem, the driver should use the ExInterlockedXxx routines or some type of lock when it changes the global data. The driver’s requirements determine the acceptable types of locks.

The following example attempts to reallocate a file-specific buffer (Endpoint->LocalAddress) to hold the endpoint address.

Endpoint = FileObject->FsContext;

if (Endpoint->LocalAddress != NULL &&
    Endpoint->LocalAddressLength <
       ListenEndpoint->LocalAddressLength ) {

      FREE_POOL (Endpoint->LocalAddress,
                 LOCAL_ADDRESS_POOL_TAG );
      Endpoint->LocalAddress  = NULL;
   }

if ( Endpoint->LocalAddress == NULL ) {
      Endpoint->LocalAddress =
            ALLOCATE_POOL (NonPagedPool,
                     ListenEndpoint->LocalAddressLength,
                     LOCAL_ADDRESS_POOL_TAG);
   }

In this example, a race condition could occur when the file object is accessed. Because the driver does not hold any locks, two requests for the same file object could enter this function. The result might be references to freed memory, multiple attempts to free the same memory, or memory leaks. To avoid these errors, the two if statements should be performed while the driver holds a spin lock.

Shared Access

File system drivers (FSD) and other highest-level drivers must perform access checks against an object’s security descriptor before using IoXxxShareAccess routines to check, set, remove, or update shared access to the object.

To handle shared access, drivers should:

  1. Obtain the requested access from the incoming IRP.
  2. If the IRP major function code is IRP_MJ_CREATE, determine the effective mode of the request. If the value of the Irp->RequestorMode field is KernelMode, check whether the SL_FORCE_ACCESS_CHECK flag is set in the IrpSp->Flags field. If this flag is set, access checks must specify that the request originated in user mode.
  3. Check the requested access against the object’s security descriptor. Pass the access requested in the IRP as the DesiredAccess parameter to SeAccessCheck.
  4. Compare the GrantedAccess returned by SeAccessCheck with the access requested in the IRP. If the GrantedAccess is more restrictive than the access requested in the IRP, complete the IRP with STATUS_ACCESS_DENIED. If the GrantedAccess matches the access requested in the IRP, proceed.
  5. Check the permitted shared access. Use the ACCESS_MASK value returned in the GrantedAccess parameter of SeAccessCheck as the DesiredAccess input parameter to IoCheckShareAccess.

SeAccessCheck sets only those bits in the returned GrantedAccess value that indicate the access actually granted to the user; the MAXIMUM_ALLOWED bit is always cleared in the returned value. To handle shared access correctly, drivers should follow these guidelines:

  • Drivers should inspect the access requested in the IRP before comparing it with the GrantedAccess value returned by SeAccessCheck. If the IRP requests MAXIMUM_ALLOWED, the driver must check the individual bits in the GrantedAccess value to determine whether sufficient access has been granted.
  • Drivers must pass the GrantedAccess value returned by SeAccessCheck as the DesiredAccess input parameter when calling IoXxxShareAccess.

For similar reasons, drivers should not attempt optimizations or partial access control by checking desired access for other bits, such as FILE_WRITE_DATA.

Note   This section describes the correct approach for NTFS and other file systems that use the access control lists (ACLs) supported by the SeXxx routines. An installable file system that uses a different type of ACLs should perform the equivalent access checks with its own rights-granting mechanism.

Locks and Disabling APCs

Certain locking primitives, user-supplied locks, and the unconventional use of events or other objects as locks have the potential to deadlock the system. Kernel-mode drivers that use such locking mechanisms should disable asynchronous procedure calls (APCs), unless the driver runs in a trusted environment (a worker thread). To disable and subsequently re-enable APCs, a device driver calls the KeEnterCriticalRegion and KeLeaveCriticalRegion routines, and a file-system driver calls the FsRtlEnterFileSystem and FsRtlLeaveFileSystem macros. These routines disable the delivery of normal kernel APCs. Special kernel APCs, which run at IRQL APC_LEVEL, are not affected by these routines.

Disabling APCs prevents the thread that currently holds the lock from being suspended by user-mode calls to SuspendThread (which delivers a kernel APC). Typically, such calls occur during debugging, but direct calls to this API are possible from user mode. If APCs are not disabled, the thread that holds the lock never has a chance to release the lock. As a result, other threads in the system are blocked while waiting for it.

Drivers must disable APCs when calling the following system routines:

  • Any of the ExXxxResourceXxx routines. These routines do not disable APCs. Drivers must enclose code that acquires and uses such resources within KeEnterCriticalRegion and KeLeaveCriticalRegion, or FsRtlEnterFileSystem and FsRtlLeaveFileSystem.
  • ExAcquireFastMutexUnsafe.
  • KeWaitForSingleObject for a non-mutex object.

Drivers are not required to disable APCs when calling the following system routines:

  • KeWaitForMutexObject or KeWaitForSingleObject for a mutex object. In this situation, KeWaitForSingleObject and KeWaitForMutexObject automatically disable APCs by the equivalent of KeEnterCriticalRegion.
  • ExAcquireFastMutex. This routine returns to the caller at IRQL APC_LEVEL and therefore blocks all APCs.

The situation is more complicated when driver code in a thread must run in order to release another thread. For example, consider a driver that acts as a communication mechanism between a client and a server thread. When the server thread posts a read, a read IRP enters the driver. Because no data is waiting for the driver, it pends the IRP and sets an appropriate cancel routine. If a client thread then sends a message with a write request, a write IRP enters the driver. Because the pending read IRP is already queued, however, the driver does not handle the write IRP; instead, the driver removes the read IRP from the queue and removes its cancel routine.

Now, assume that the queues that hold the pended IRPs are protected with locks. To improve performance, the driver writer has moved IRP completion outside the locks. This strategy has two advantages:

  • The lock region is smaller, thus improving driver scalability on large multiprocessor hardware.
  • Context swaps are minimized. Other threads that enter the driver are not awakened, and are subsequently blocked by a lock that is owned by the current thread.

Moving completion outside the locks has the following problems, however:

  • After the IRP has been removed from the queue, no cancellation routine is in place and APCs might be enabled.
  • If the client thread is suspended after it releases the lock but before it completes the IRP from the server thread, the server thread will be blocked by the suspended client thread.

To avoid these problems, such drivers should leave APCs disabled until the IRPs have been completed. For example, the following code handles a write request in the named-pipe file system.

FsRtlEnterFileSystem();

NpAcquireSharedVcb();

Status =  NpCommonWrite( IrpSp->FileObject,
          Irp->UserBuffer,
          IrpSp->Parameters.Write.Length,
          Irp->Tail.Overlay.Thread,
          Irp,
          &DeferredList ); // List of IRPs to be
                           //completed after lock release

NpReleaseVcb();

//
// At this point we have released the locks but still
// have kernel APCs disabled.
// We need to prevent this thread from being suspended until
// after we release the server threads.
//

//
// Complete any deferred IRPs after dropping the locks.
//
NpCompleteDeferredIrps (&DeferredList);

//
// Reenable APCs after completing any server IRPs.
// Suspension before completing this thread's IRP doesn't
// matter because it would just block
// this thread anyway and it's suspended.
//
FsRtlExitFileSystem();

if (Status != STATUS_PENDING) {
    NpCompleteRequest (Irp, Status);
    }

For additional information about when waiting threads receive alerts and DPCs, see the Design Guide in the Kernel-Mode Driver Architecture section of the Windows DDK.

Handle Validation

Some drivers must manipulate objects passed to them by callers, or must process two file objects at the same time. For example, a modem driver might receive a handle to an event object, or a network driver might receive handles to two different file objects. The driver must validate these handles. Because they are passed by a caller, and not through the I/O Manager, the I/O Manager cannot perform any validation checks.

In the following example, the driver has been passed the handle AscInfo->AddressHandle, but has not validated it before calling ObReferenceObjectByHandle.

// This handle is embedded in a buffered request.
//
status = ObReferenceObjectByHandle(
                  AscInfo->AddressHandle,
                  0,
                  NULL,
                  KernelMode,
                  &fileObject,
                  NULL);

if (NT_SUCCESS(status)) {
   if ( (fileObject->DeviceObject == DeviceObject) &&
        (fileObject->FsContext2 == TRANSPORT_SOCK) ) {
   ...

The call to ObReferenceObjectByHandle succeeds, but the code fails to ensure that the returned pointer references a file object; it trusts the caller to pass in the correct information. To correct this problem, the driver should pass explicit values for the DesiredAccess and ObjectType parameters.

Even if all the parameters for the call to ObReferenceObjectByHandle are correct, and the call succeeds, a driver can still get unexpected results if the file object is not intended for it. In the following example, the driver fails to ascertain that the call returns a pointer to the file object it expected.

status = ObReferenceObjectByHandle (
                          AcpInfo->Handle,
                          DesiredAccess,
                          *IoFileObjectType,
                          Irp->RequestorMode,
                          (PVOID *)&AcpEndpointFileObject,
                          NULL);

if ( !NT_SUCCESS(status) ) {
   goto complete;
}
AcpEndpoint = AcpEndpointFileObject->FsContext;

if ( AcpEndpoint->Type != BlockTypeEndpoint ) {
...

Although ObReferenceObjectByHandle returns a pointer to a file object, the driver has no guarantee that the pointer references the file object it expected. In this case, the driver should validate the pointer before accessing the driver-specific data at AcpEndpointFileObject->FsContext.

Drivers should validate handles as follows:

  • Check the object type to make sure it is what the driver expects.
  • Ensure that the requested access is appropriate for the object type and the required tasks. If the driver performs a fast file copy, for instance, it must make sure the handle has read access.
  • Specify the correct access mode (UserMode or KernelMode) and verify that the access mode is compatible with the access requested.
  • Validate the handle against the device object or driver if the driver expects a handle to a file object that the driver itself created. Do not break filters that send I/O requests for unexpected devices, however.
  • If the driver supports multiple kinds of file objects, it must be able to differentiate them. For example, TDI drivers use file objects to represent control channels, address objects, and connections. File-system drivers use file objects to represent volumes, directories, and files. Such drivers must determine which type of file object each handle represents.

Requests to Create and Open Files and Devices

Drivers can be vulnerable to problems when requests to create and open files or devices involve the following:

  • Opening files in the device namespace
  • Long file names
  • Unexpected I/O requests
  • Relative open requests for direct device open handles
  • Extended attributes

These issues are described in the following sections.

Opening Files in the Device Namespace

Drivers should set the FILE_DEVICE_SECURE_OPEN device characteristic when they call IoCreateDevice or IoCreateDeviceSecure to create a device object. The FILE_DEVICE_SECURE_OPEN characteristic directs the I/O Manager to apply the security descriptor of the device object to all open requests, including file open requests into the device’s namespace. Setting this characteristic prevents the potential security problems described in this section. For Plug-and-Play drivers, this characteristic is set in the INF file.

Drivers that support exclusive opens are the only exception to this rule. Such drivers should instead fail any IRP_MJ_CREATE requests that specify an IrpSp->FileObject->FileName parameter with a nonzero length.

The I/O Manager does not perform access checks based on the device object for open requests into the device namespace unless FILE_DEVICE_SECURE_OPEN is set. For a device named "\Device\DeviceName," the namespace consists of any name of the form "\Device\DeviceName\FileName."

Omitting access checks can open security holes in drivers that have privileged IOCTL or FSCTL interfaces. The privileged interfaces require write access to the device that is denied to unprivileged users. Unprivileged users can bypass security, however, and obtain handles with read and write access by opening a file in the device’s namespace. To prevent a user from bypassing security, a driver’s DispatchCreate routines must properly handle such create requests.

For example, an unprivileged user who attempts to open \Device\Transport will not be able to create a handle with read or write access to the device. The transport driver has protected IOCTLs, however, that allow administrators to configure the transport (that is, changing the address and so forth). These IOCTLs require write access to the device. (Read and write access requirements are encoded in the IOCTL or FSCTL value). Unless the transport driver sets the FILE_DEVICE_SECURE_OPEN characteristic or has other code to handle the situation, a caller could open \Device\Transport\xyz, and thus gain all access to the file object created. An unprivileged caller could also use a normally opened handle to the transport to request another relative open (with or without a file name) and achieve the same result.

As an alternative to setting FILE_DEVICE_SECURE_OPEN, a driver can perform its own access checks, or it can reject such I/O requests outright. The following shows some sample rejection code.

if ( irpStack->FileObject->RelatedFileObject ||
   irpStack->FileObject->FileName.Length ) {
   Irp->IoStatus.Status = STATUS_ACCESS_DENIED;
   IoCompleteRequest(Irp, IO_NO_INCREMENT);
   return STATUS_ACCESS_DENIED;
}

Long File Names

Long file names in the create path can cause memory leaks and memory pool corruption in some drivers.

The Object Manager limits object paths to 32 KB Unicode characters. The file name length, in bytes, including a trailing Unicode NULL, must be an even number that is less than 64 KB. This limit applies to the whole object path (for example, \Device\Volume1\xxxxxx). The portion presented to the I/O Manager has the leading path to the device object removed, making it significantly shorter than 64 KB.

A driver is unlikely to encounter long file names through standard file open requests. When a caller requests a relative file name open at the native API level, however, the Object Manager and therefore the I/O Manager can present file names that are only a few bytes short of 64 KB.

When handling a relative open request, drivers often try to reconstruct the full path of the file to open. Typically, the driver concatenates the file name of the base file (the file to which the supplied name is relative) with a separator character and the file name of the relative portion. The length of the complete string can easily exceed 64 KB, and therefore will not fit in the 16-bit integer UNICODE_STRING structures that represent the file names in the file objects. As a result, the driver can either corrupt pool or leak memory.

Pool corruption is caused by allocating a buffer that is too short for the target file name, as shown in the following example.

FullNameLengthTemp = RelatedCcb->FullFileName.Length +
                     AddSeparator + FileObjectName->Length;
FullFileName->MaximumLength =
       FullFileName->Length = (USHORT) FullNameLengthTemp;

FullFileName->Buffer = FsRtlAllocatePoolWithTag(
                                        PagedPool,
                                        FullFileName->Length,
                                        MODULE_POOL_TAG);

RtlCopyMemory(FullFileName->Buffer,
              RelatedCcb->FullFileName.Buffer,
              RelatedCcb->FullFileName.Length );

CurrentPosition = Add2Ptr(FullFileName->Buffer,
                          RelatedCcb->FullFileName.Length );

RtlCopyMemory( CurrentPosition,
               FileObjectName->Buffer,
               FileObjectName->Length );

The file name length calculation exceeds 64 KB and the USHORT cast truncates the length. As a result, the allocated buffer is too small and one or both of the calls to RtlCopyMemory corrupt pool.

The memory leak is a subtler problem, which occurs when the file name length is used without truncation to allocate the pool buffer. Because the buffer is large enough, this error does not corrupt pool. The file name-length stored in the file object is truncated to 16 bits, however. If the truncation results in a zero length, the I/O Manager never frees the file name buffer, and a memory leak occurs. A leak can also occur if a driver changes the file name by removing excess backslash characters and these changes make the file name length field zero.

Unexpected I/O Requests

Drivers that create more than one kind of device object must be able to handle I/O requests on every such device object.

Many drivers create more than one kind of device object by calling IoCreateDevice. Some drivers create control device objects in their DriverEntry routines to allow applications to communicate with the driver, even before the driver creates an FDO. For example, before a file system driver calls IoRegisterFileSystem to register itself as a file system, it must create a control device object to handle file system notifications.

A driver should be ready for create requests on any device object it creates. After completing the create request with a success status, the driver should expect to receive any user-accessible I/O requests on the created file object. Consequently, any driver that creates more than one device object must check which device object each I/O request specifies.

For example, a driver might expect that an I/O request specifies an FDO for a specific device, when in fact the request specifies its control device object. If the driver has not initialized the same fields in the device extension of the control device object as in the other device objects, the driver could crash when trying to use device extension information from the control device object.

Relative Open Requests for Direct Device Open Handles

The I/O Manager performs a direct device open in response to create or open requests that meet all of the following criteria:

  • The volume name has no trailing characters. For example, G: is valid, but G:\ and G:\a\b are not.
  • The create request is not relative to another file handle.
  • The requested access includes one or more of the following, and no other access types: SYNCHRONIZE, FILE_READ_ATTRIBUTES, READ_CONTROL, ACCESS_SYSTEM_SECURITY, WRITE_OWNER, or WRITE_DAC.

For a normal create or open request on a storage volume, the I/O Manager typically attempts to mount a file system, if none is already mounted. When performing a direct device open, however, the I/O Manager does not mount or send requests through a file system. Instead, it sends the IRP_MJ_CREATE request directly to the storage stack, bypassing any file system that has been mounted for the volume. Requests for further operations (such as read, write, or DeviceIoControl) on the file handle are sent to the topmost device object in the storage stack for the volume.

The I/O Manager performs a direct device open only when the caller requests limited access to the device, such as the access required to read device attributes. This type of open operation occurs rarely, but is useful when an application wants to query certain attributes of a storage volume without forcing a file system to be mounted.

If an application later sends an open request that is relative to a handle on which the I/O Manager performed a direct device open, the file system stack receives a file object in which the RelatedFileObject field points to an object that the file system has not previously seen. To determine whether the I/O Manager performed a direct device open on a file object, a file system driver can test the FO_DIRECT_DEVICE_OPEN flag in the Flags field of the file object.

On Microsoft Windows NT 4.0 and earlier versions of Windows NT, relative open requests for direct device open handles failed. This problem has been corrected in Microsoft Windows 2000 and later releases.

Extended Attributes

Drivers must validate the size and contents of extended attributes (EAs). EAs are used primarily by TDI drivers during open operations. The redirector (RDR) also uses them to hold user names and passwords for accessing network shares.

The I/O Manager copies and parses EAs to make sure they have the correct format: a keyword (a NULL-terminated, variable-length character string), followed by its value (0 to 65535 bytes). Drivers should not assume, however, that if the keyword is correct the value block contains exactly the data they expect. Even if the keyword is correct, the data size might be too small, thus causing the expected data structure to extend beyond the allocated end of buffer, or to contain garbage.

For example, the following code does not properly validate that the size of the value block is sizeof(PVOID).

ea = (PFILE_FULL_EA_INFORMATION)
      Irp->AssociatedIrp.SystemBuffer;

RtlCopyMemory (
           &connection->Context,
           &ea->EaName[ea->EaNameLength+1],
           sizeof (PVOID));

Drivers also must validate the data within EAs. The following code fails to perform this validation.

ea = OPEN_REQUEST_EA_INFORMATION(Request);
if (ea == NULL) {
    return STATUS_NONEXISTENT_EA_ENTRY;
   }

name = (PTRANSPORT_ADDRESS)&ea->EaName[ea->EaNameLength+1];
AddressName = (PTA_ADDRESS)&name->Address[0];

for (i=0;i<name->TAAddressCount;i++)
...

If the address count is large, the for loop could run beyond the end of the allocated buffer. The driver should check the minimum size of the value, and check each individual address to make sure it is within the buffer.

During internal review, Microsoft found the following error in several drivers that process EAs.

FILE_FULL_EA_INFORMATION UNALIGNED *
FindEA(
    PFILE_FULL_EA_INFORMATION    pStartEA,
    CHAR                        *pTargetName,
    USHORT                       TargetNameLength)
{
    FILE_FULL_EA_INFORMATION UNALIGNED *pCurrentEA;

    do
    {
        Found = TRUE;
        pCurrentEA = pStartEA;
        pStartEA  += pCurrentEA->NextEntryOffset;
...

This code should cast pStartEA to a PUCHAR to send forward a byte count instead of multiples of sizeof (FILE_FULL_EA_INFORMATION).

Driver Unload Routines

Before unloading, drivers must release all driver-allocated resources, cancel all timers, ensure that no deferred procedure calls (DPCs) are queued, and ensure that all driver-created threads have terminated. The operating system frees a driver’s address space soon after unloading the driver. Thereafter, attempting to execute any driver code, for example, in a DPC or driver-created thread, can result in a system crash.

This section outlines the steps that drivers should take to prevent such errors when using the following:

  • Work items
  • Driver-created threads
  • Timers
  • Queued DPCs
  • IoCompletion routines

Work Items

Drivers that use work items should call the IoAllocateWorkItem, IoQueueWorkItem, and IoFreeWorkItem routines instead of the obsolete ExQueueWorkItem and related routines. The newer IoXxxWorkItem routines include unload protection that the obsolete routines did not have.

The IoXxxWorkItem routines ensure that the device object associated with the work item remains available until the callback routine returns. Work item callback routines can set an event immediately before exiting, without risk that the driver will be unloaded before the callback routine returns. After the event is completed, the driver can call IoFreeWorkItem and free any resources shared with the work item.

The obsolete ExQueueWorkItem and related routines did not have this protection mechanism.

Note   The number of threads in which to run work items is limited. Drivers should allocate work items only when needed, and free them as soon as they are no longer required. A driver should not wait until it is unloaded to free work items that are no longer in use.

Driver-Created Threads

Many drivers have separate threads of execution that are created outside the control of the worker thread manager. These threads execute code within a loaded driver. Because a driver’s address space is freed soon after its Unload routine returns, every driver must carefully synchronize the termination of these driver threads. Attempting to execute instructions in a driver thread after the driver is unloaded can cause a system crash.

In the following example, the driver waits on an event that another driver thread will set just before exiting.

KeWaitForSingleObject(
                &Device->UnloadEvent,
                Executive,
                KernelMode,
                FALSE,
                (PLARGE_INTEGER)NULL
                ) {
    };
return;

The following code sets the event.

KeSetEvent(&Device->UnloadEvent,
           IO_NETWORK_INCREMENT,
           FALSE);
return;

If the driver unloads before the final few instructions execute, a fault may occur. In this example, the system could crash if the driver has already been unloaded when the return statement following the call to KeSetEvent is executed.

To prevent this error, drivers that create separate threads should wait on the thread object itself, instead of waiting on an event set by the thread. For example, if a driver calls PsCreateSystemThread to create a thread, the driver can call KeWaitForSingleObject, passing the handle of the thread as the object on which to wait. When the thread calls PsTerminateSystemThread, or returns from its thread routine back to the system, the wait is satisfied. The driver can now safely unload because the thread has exited.

Timers

Drivers that use timers must also unload carefully. Drivers must cancel any timers that are queued, wait for any CustomTimerDpc routines that are running, and synchronize access to driver structures from DPC routines.

A driver can cancel a one-shot timer in its Unload routine. To cancel a one-shot timer, the driver calls KeCancelTimer. If KeCancelTimer returns TRUE, the timer is not running. If KeCancelTimer returns FALSE, the timer DPC is currently running and the driver must not free any driver-allocated resources until after the DPC has finished running.

The operating system forces any DPCs that are already running to run to completion, even after the driver Unload routine returns (but before deleting the driver’s address space). A driver can therefore wait on an event signaled by the DPC. The DPC should signal the event after it has finished accessing any resources, typically immediately before returning. When the event wait is satisfied, the driver can safely free those resources and unload.

Drivers that use periodic timers must take an additional step. The driver first calls KeCancelTimer to disable the periodic timer. KeCancelTimer always returns TRUE for such timers, however, because as soon as a periodic timer expires, the operating system queues another such timer; consequently, periodic timers always appear to be queued.

To make sure that any DPCs for a periodic timer have completed, a driver must also call KeFlushQueuedDpcs. KeFlushQueuedDpcs returns after all queued DPCs on all processors have run. Although this routine is expensive in terms of performance, a driver must call it in this situation.

Queued DPCs

Before unloading a driver, the operating system flushes driver-queued DPCs other than those for periodic timers, as described in the preceding section. Therefore, drivers that queue DPCs are not required to call KeFlushQueuedDpcs before unloading; however, such drivers must synchronize access to ensure that the DPC routine has finished using resources before the driver frees them. A driver can use the same kind of event wait mechanism described for one-shot timers.

IoCompletion Routines

In rare cases, an IoCompletion routine can run in parallel with a driver’s Unload routine. If the Unload routine waits for an event set by the IoCompletion routine, the event could be satisfied and the driver unloaded before the IoCompletion routine runs to completion. This is a problem only for drivers that do not use Plug and Play.

To avoid this problem, drivers for Windows XP and later can use the IoSetCompletionRoutineEx routine to set the IoCompletion routine. IoSetCompletionRoutineEx protects the IoCompletion routine from driver unload.

Pageable Drivers and DPCs

Drivers that queue DPCs and make themselves pageable are not required to flush DPCs before calling MmPageEntireDriver. The operating system flushes DPCs before paging the driver, but the driver must ensure that neither it nor another thread queues any additional DPCs until the driver is once again locked in memory.

User-Mode APIs

This section describes errors that can occur when drivers are called by the following user-mode APIs.

  • NtReadFile and NtWriteFile
  • TransmitFile

NtReadFile and NtWriteFile

Drivers that read and write data in response to the user-mode APIs NtReadFile and NtWriteFile must be able to handle the negative file offsets that can be passed with these APIs. The I/O Manager performs limited checks on these offsets.

NtWriteFile accepts negative LARGE_INTEGER values to signify a write to end of file and a write to current position. NtReadFile accepts a negative offset, which indicates the current position read. No other negative offsets are accepted.

The I/O Manager does not reject transfers where the offset plus the transfer length cause the offset of the buffer end to wrap from positive to negative.

TransmitFile

The Win32 TransmitFile API issues an IOCTL to the system afd.sys driver (AFD) to do fast file copies over the network. The AFD provides support for Windows Sockets API to communicate with underlying transports. During internal testing, Microsoft found several drivers that encountered problems when their handles were passed to the TransmitFile API. Some looped, completing read requests with a success status but with zero bytes read; others had cancellation problems.

The Device Path Exerciser, DevCtl, includes the /w option to test a driver by using TransmitFile. Microsoft recommends testing drivers for these problems.

StartIo Recursion

If many device requests are outstanding, calls to IoStartNextPacket or IoStartNextPacketByKey from a driver’s StartIo routine can result in recursive calls back to the StartIo routine without unwinding the stack.

Drivers that call these routines from the StartIo routine should first call the IoSetStartIoAttributes routine, with the DeferredStartIo parameter set to TRUE. Doing so causes the I/O Manager to keep track of the nesting level of the calls, and dispatch to the StartIo routine only after the current StartIo call has returned.

Passing and Completing IRPs

Drivers commonly have the following problems in passing and completing IRPs:

  • Copying stack locations incorrectly.
  • Returning incorrect status for an IRP that the driver does not handle.
  • Losing IRPs or completing them more than once.
  • Returning incorrect status for an IRP that the driver issues.

Copying Stack Locations Incorrectly

When passing an IRP down the stack, drivers should always use the standard functions IoSkipCurrentIrpStackLocation and IoCopyCurrentIrpStackLocationToNext. Do not write driver-specific code to copy the stack location. Using the standard routines ensures that the driver does not duplicate the IoCompletion routine of a driver layered above it.

For example, the following code can duplicate an IoCompletion routine and cause problems.

currentStack = IoGetCurrentIrpStackLocation (Irp) ;
nextStack = IoGetNextIrpStackLocation (Irp) ;

RtlMoveMemory (nextStack,
               currentStack,
               sizeof (IO_STACK_LOCATION));

Returning Incorrect Status for an IRP That the Driver Does Not Handle

A driver must not return STATUS_SUCCESS for an IRP that it does not handle.

For example, some drivers incorrectly return STATUS_SUCCESS for query IRPs, even though they do not support the required functionality. Doing so can easily crash or corrupt the system, particularly during operations like file name look-ups, if the I/O Manager or another component attempts to use data that was left uninitialized by the Dispatch routine.

Unless otherwise noted in the documentation for a specific IRP, a driver should return STATUS_NOT_SUPPORTED for any IRP it does not handle. Plug-and-Play drivers might also return STATUS_INVALID_DEVICE_REQUEST to indicate that the IRP is inappropriate for the device.

Losing IRPs or Completing Them More Than Once

IRPs that are lost or completed more than once, along with missing calls to I/O Manager routines such as IoStartNextPacket, often occur in error-handling paths. A "lost" IRP is one that the device has finished, but the driver never completed by calling IoCompleteRequest or passing it to another driver.

Quick reviews of code paths can often find such problems. In addition, the DC2 and DevCtl tools can assist in finding lost IRPs. The DC2 and DevCtl tools are provided in the Tools directory of the Windows DDK.

Returning Incorrect Status from an IRP That the Driver Issues

Unlike drivers to which an IRP is forwarded, the driver that issues an IRP must not propagate the SL_PENDING_RETURNED bit in its IoCompletion routine for that IRP. Doing so corrupts the memory pool following the IRP.

When a driver receives an IRP from another driver, it must propagate the SL_PENDING_RETURNED bit if it returns STATUS_MORE_PROCESSING_REQUIRED for the IRP. Therefore, IoCompletion routines for IRPs that are forwarded from another driver typically include the following code.

If (Irp->PendingReturned)
    IoMarkIrpPending(Irp);

The driver that issued the IRP, however, must not include this statement. The issuing driver is the final recipient of the IRP; further processing is not required. When the issuing driver’s IoCompletion routine is called, the DeviceObject parameter is NULL and the I/O stack location points to the location immediately following the end of the IRP, causing corruption of the pool header for the next memory allocation.

Odd-length Unicode Buffers

Some I/O Manager APIs support Unicode input buffers that contain an odd number of bytes. The optional file name in NtQueryDirectoryFile, and many queries using NtQueryInformationFile (such as FileNameInformation), are examples. Drivers should test the lengths of these buffers upon input.

Pool Allocation in Low Memory

When the system is low on pool memory, calling ExAllocatePool with the pool type NonPagedPoolMustSucceed causes the system to crash. This can occur, for example, on a web server where client spikes are frequent and short, but the occurrences use a great deal of pool memory and can cause memory to become fragmented temporarily.

Drivers should not use this flag. Instead, drivers should allocate nonpaged memory with the NonPagedPool or NonPagedPoolCacheAligned flags and, if ExAllocatePool returns NULL, return the status STATUS_INSUFFICIENT_RESOURCES.

In addition, Microsoft Windows XP and Windows 2000 drivers must use MmGetSystemAddressForMdlSafe instead of MmGetSystemAddressForMdl. WDM drivers must use MmGetSystemAddressForMdl with the MDL_MAPPING_CAN_FAIL MDL flag, because MmGetSystemAddressForMdlSafe is not supported on Windows 98 and Windows Me.

For more information on pool allocation failures, see Low Pool Memory and Windows XP, available on the Microsoft website.

Call to Action and Resources

Call to Action:

  • Find and correct errors in existing drivers. Use the Driver Verifier, DC2, and DevCtl utilities in the Windows DDK.
  • Analyze code paths, particularly those involving locks, to uncover any problems described in this paper.
  • Always validate pointers obtained from user-mode callers.
  • Always check buffer sizes to prevent buffer overruns and underruns.

Resources: