前段时间弄了下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了