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++里则是从定义的位置以下