最近学习了APC相关的一些知识 (汗!古老到牛们都可能忘记了它的存在~_~),发现special kernal apc还是能做些事情,比如结束某个进程(嘿嘿^_^),当然是在R0了, “不要问我都能加载驱动了,什么不能干?因为我太TMD能**了,呸”某大牛的话,同样适用于本贴!-_-
选择微点做试验,并非偶然。微点主动防御软件是目前主防中的牛B者,她太X了,真正达到HOOK无极限,搞定她,能学到一些XX技巧。貌似网上流行的ARK软件和进程查杀软件不能直接结束她。(要是很容易放倒,也许就不会有太多人玩儿她了。。嘿嘿^_^!)
预备知识:PE结构;EPROCESS 、ETHREAD结构及获取方法;APC相关知识(俺学习总结的文档会放后面,愿与大伙交流学习)。
看点:EAT HOOK 恢复方法;special kernal apc使用方法;获取内核未导出函数地址方法。
基本流程:
1.恢复KeInsertQueueApc的EAT HOOK;
2.暴搜进程获取微点进程,根据微点进程枚举所有线程;
3.通过PsTerminateSystemThread函数查找到PspTerminateThreadByPointer,通过PspTerminateThreadByPointer找到PspExitThread,编写APC例程,例程内直接调用搜到的PspExitThread函数-_-;
4.调用已恢复KeInsertQueueApc对所有微点线程插APC;
5. 加载驱动,看着最新版免费试用90天的微点主防在XP SP2平台上消失。
附注:
代码写得很乱,想到哪写到哪。如果在阅读代码时出现不适切勿找偶!正常情况加载驱动,微点退出,如出现BSOD等现象,切勿找偶!
以下为俺机子上加载驱动后,windbg输出信息:
baseadress:400000
the functionName = KeInsertQueueApc
the functionaddress=0x0040E411
the functionrav =0x0000E411 the modulefullname is \WINDOWS\system32\ntoskrnl.exe
the database is 0x804D8000
the functionName = KeInsertQueueApc
the functionaddress=0xF9847B80
the functionrav =0x7936FB80 the hook address at 0x7936fb80
the pkeinsertqueueapc is 0x804e6411the keinsertqueueapc is 0xf9847b80restore the hook address at 0x0000e411 the Thread is 0x81CEBAD0
the Thread 0x81cebad0 is Terminated.
the Thread is 0x81CFBAE0
the Thread 0x81cfbae0 is Terminated.
the Thread is 0x81D2E8E8
the Thread 0x81d2e8e8 is Terminated.
the Thread is 0x81C35DA8
the Thread 0x81c35da8 is Terminated.
the Thread is 0x81C14DA8
the Thread 0x81c14da8 is Terminated. the Thread is 0x81E23B30
the Thread 0x81e23b30 is Terminated. the Thread is 0x81E4D9E8
the Thread 0x81e4d9e8 is Terminated.
the Thread is 0x81E444E0
the Thread 0x81e444e0 is Terminated.
the Thread is 0x81E23770
the Thread 0x81e23770 is Terminated.
the Thread is 0x81E234F8
the Thread 0x81e234f8 is Terminated.
the Thread is 0x81DCA348
the Thread 0x81dca348 is Terminated.
the Thread is 0x81D93598
the Thread 0x81d93598 is Terminated.
the Thread is 0x81C6E020
the Thread 0x81c6e020 is Terminated.
the Thread is 0x81C6ED20
the Thread 0x81c6ed20 is Terminated.
the Thread is 0x81BB0020
the Thread 0x81bb0020 is Terminated.
the Thread is 0x81BB0320
the Thread 0x81bb0320 is Terminated.
the Thread is 0x81C6A020
the Thread 0x81c6a020 is Terminated. the Thread is 0x81E41DA8
the Thread 0x81e41da8 is Terminated.
the Thread is 0x81E3F450
the Thread 0x81e3f450 is Terminated.
the Thread is 0x81E3EB38
the Thread 0x81e3eb38 is Terminated.
the Thread is 0x81E3E8C0
the Thread 0x81e3e8c0 is Terminated.
the Thread is 0x81E3E510
the Thread 0x81e3e510 is Terminated.
the Thread is 0x81E3DB38
the Thread 0x81e3db38 is Terminated.
the Thread is 0x81E3D8C0
the Thread 0x81e3d8c0 is Terminated.
the Thread is 0x81E3D648
the Thread 0x81e3d648 is Terminated.
the Thread is 0x81E3A258
the Thread 0x81e3a258 is Terminated.
the Thread is 0x81E39020
the Thread 0x81e39020 is Terminated.
the Thread is 0x81E39DA8
the Thread 0x81e39da8 is Terminated.
the Thread is 0x81E39AD0
the Thread 0x81e39ad0 is Terminated.
the Thread is 0x81E38020
the Thread 0x81e38020 is Terminated.
the Thread is 0x81E383A0
the Thread 0x81e383a0 is Terminated.
the Thread is 0x81E37020
the Thread 0x81e37020 is Terminated.
the Thread is 0x81E37DA8
the Thread 0x81e37da8 is Terminated.
the Thread is 0x81E36C08
the Thread 0x81e36c08 is Terminated.
the Thread is 0x81E364D8
the Thread 0x81e364d8 is Terminated.
the Thread is 0x81E355B0
the Thread 0x81e355b0 is Terminated.
the Thread is 0x81E2C020
the Thread 0x81e2c020 is Terminated.
the Thread is 0x81E2CD88
the Thread 0x81e2cd88 is Terminated.
the Thread is 0x81E2C918
the Thread 0x81e2c918 is Terminated.
the Thread is 0x81E23020
the Thread 0x81e23020 is Terminated.
the Thread is 0x81E23DA8
the Thread 0x81e23da8 is Terminated.
the Thread is 0x81E18560
the Thread 0x81e18560 is Terminated.
the Thread is 0x81D91950
the Thread 0x81d91950 is Terminated.
the Thread is 0x81D6C020
the Thread 0x81d6c020 is Terminated.
the Thread is 0x81D5BDA8
the Thread 0x81d5bda8 is Terminated.
==================正文开始================================= APC机制学习
1) APC允许用户程序和系统元件在一个进程的地址空间内某个线程的上下文中执行代码。
2) I/O管理器使用APC来完成一个线程发起的异步的I/O操作。例如:当一个设备驱动调用IoCompleteRequest来通知I/O管理器,它已经结束处理一个异步I/O请求时,I/O管理器排队一个apc到发起请求的线程。然后线程在一个较低IRQL级别,来执行APC. APC的作用是从系统空间拷贝I/O操作结果和状态信息到线程虚拟内存空间的一个缓冲中。
3) 使用APC可以得到或者设置一个线程的上下文和挂起线程的执行。 APC对象
在NT中,有两种类型的APC:用户模式和内核模式。用户APCs运行在用户模式下目标线程当前上下文中,并且需要从目标线程得到许可来运行。特别是,用户模式的APCs需要目标线程处在alertable等待状态才能被成功的调度执行。对于用户模式下,可以调用函数SleepEx,SignalObjectAndWait,WaitForSingleObjectEx,WaitForMultipleObjectsEx,MsgWaitForMultipleObjectsEx 都可以使目标线程处于alertable等待状态,从而让用户模式APC执行,原因是这些函数最终都是调用了内核中的KeWaitForSingleObject, KeWaitForMultipleObjects, KeWaitForMutexObject, KeDelayExecutionThread等函数。另外通过调用一个未公开的alert-test服务KeTestAlertThread,用户线程可以使用户模式APC执行。
当一个用户模式APC被投递到一个线程,调用上面的等待函数,如果返回等待状态STATUS_USER_APC,在返回用户模式时,内核转去控制APC例程,当APC例程完成后,再继续线程的执行.
内核模式APC执行在内核模式下,可以被划分为常规的和特殊的两类。当APC被投递到一个特殊的线程,特殊的内核模式APC不需要从线程得到许可来运行。然而,常规的内核模式APC在他们成功执行前,需要有特定的环境。此外,特殊的内核APC被尽可能快地执行,既只要APC_LEVEL级上有可调度的活动。在很多情况下,特殊的内核APC甚至能唤醒阻塞的线程。普通的内核APC仅在所有特殊APC都被执行完,并且目标线程仍在运行,同时该线程中也没有其它内核模式APC正执行时才执行。用户模式APC在所有内核模式APC执行完后才执行,并且仅在目标线程有alertable属性时才执行。
一个等待执行的APC都存在于一个线程执行体,由内核管理的队列中。系统中的每一个线程都包含两个APC队列,一个是为用户模式APC,另一个是为内核模式APC的。 NT通过一个成为KAPC的内核控制对象来描述一个APC. Windbg中kapc结构如下: kd> dt -v -r _kapc
Matched ntdll!_KAPC
ntdll!_KAPC
struct _KAPC, 14 elements, 0x30 bytes
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 Spare0 : Uint4B
+0x008 Thread : Ptr32 to struct _KTHREAD, 73 elements, 0x1c0 bytes
+0x000 Header : struct _DISPATCHER_HEADER, 6 elements, 0x10 bytes
+0x000 Type : UChar
+0x001 Absolute : UChar
+0x002 Size : UChar
+0x003 Inserted : UChar
+0x004 SignalState : Int4B
+0x008 WaitListHead : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x010 MutantListHead : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x018 InitialStack : Ptr32 to Void
+0x01c StackLimit : Ptr32 to Void
+0x020 Teb : Ptr32 to Void
+0x024 TlsArray : Ptr32 to Void
+0x028 KernelStack : Ptr32 to Void
+0x02c DebugActive : UChar
+0x02d State : UChar
+0x02e Alerted : [2] UChar
+0x030 Iopl : UChar
+0x031 NpxState : UChar
+0x032 Saturation : Char
+0x033 Priority : Char
+0x034 ApcState : struct _KAPC_STATE, 5 elements, 0x18 bytes
+0x000 ApcListHead : [2] struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x010 Process : Ptr32 to struct _KPROCESS, 29 elements, 0x6c bytes
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
+0x04c ContextSwitches : Uint4B
+0x050 IdleSwapBlock : UChar
+0x051 Spare0 : [3] UChar
+0x054 WaitStatus : Int4B
+0x058 WaitIrql : UChar
+0x059 WaitMode : Char
+0x05a WaitNext : UChar
+0x05b WaitReason : UChar
+0x05c WaitBlockList : Ptr32 to struct _KWAIT_BLOCK, 6 elements, 0x18 bytes
+0x000 WaitListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x008 Thread : Ptr32 to struct _KTHREAD, 73 elements, 0x1c0 bytes
+0x00c Object : Ptr32 to Void
+0x010 NextWaitBlock : Ptr32 to struct _KWAIT_BLOCK, 6 elements, 0x18 bytes
+0x014 WaitKey : Uint2B
+0x016 WaitType : Uint2B
+0x060 WaitListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x060 SwapListEntry : struct _SINGLE_LIST_ENTRY, 1 elements, 0x4 bytes
+0x000 Next : Ptr32 to struct _SINGLE_LIST_ENTRY, 1 elements, 0x4 bytes
+0x068 WaitTime : Uint4B
+0x06c BasePriority : Char
+0x06d DecrementCount : UChar
+0x06e PriorityDecrement : Char
+0x06f Quantum : Char
+0x070 WaitBlock : [4] struct _KWAIT_BLOCK, 6 elements, 0x18 bytes
+0x000 WaitListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x008 Thread : Ptr32 to struct _KTHREAD, 73 elements, 0x1c0 bytes
+0x00c Object : Ptr32 to Void
+0x010 NextWaitBlock : Ptr32 to struct _KWAIT_BLOCK, 6 elements, 0x18 bytes
+0x014 WaitKey : Uint2B
+0x016 WaitType : Uint2B
+0x0d0 LegoData : Ptr32 to Void
+0x0d4 KernelApcDisable : Uint4B
+0x0d8 UserAffinity : Uint4B
+0x0dc SystemAffinityActive : UChar
+0x0dd PowerState : UChar
+0x0de NpxIrql : UChar
+0x0df InitialNode : UChar
+0x0e0 ServiceTable : Ptr32 to Void
+0x0e4 Queue : Ptr32 to struct _KQUEUE, 5 elements, 0x28 bytes
+0x000 Header : struct _DISPATCHER_HEADER, 6 elements, 0x10 bytes
+0x010 EntryListHead : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x018 CurrentCount : Uint4B
+0x01c MaximumCount : Uint4B
+0x020 ThreadListHead : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x0e8 ApcQueueLock : Uint4B
+0x0f0 Timer : struct _KTIMER, 5 elements, 0x28 bytes
+0x000 Header : struct _DISPATCHER_HEADER, 6 elements, 0x10 bytes
+0x010 DueTime : union _ULARGE_INTEGER, 4 elements, 0x8 bytes
+0x018 TimerListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x020 Dpc : Ptr32 to struct _KDPC, 9 elements, 0x20 bytes
+0x024 Period : Int4B
+0x118 QueueListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x120 SoftAffinity : Uint4B
+0x124 Affinity : Uint4B
+0x128 Preempted : UChar
+0x129 ProcessReadyQueue : UChar
+0x12a KernelStackResident : UChar
+0x12b NextProcessor : UChar
+0x12c CallbackStack : Ptr32 to Void
+0x130 Win32Thread : Ptr32 to Void
+0x134 TrapFrame : Ptr32 to struct _KTRAP_FRAME, 35 elements, 0x8c bytes
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c DbgArgPointer : Uint4B
+0x010 TempSegCs : Uint4B
+0x014 TempEsp : Uint4B
+0x018 Dr0 : Uint4B
+0x01c Dr1 : Uint4B
+0x020 Dr2 : Uint4B
+0x024 Dr3 : Uint4B
+0x028 Dr6 : Uint4B
+0x02c Dr7 : Uint4B
+0x030 SegGs : Uint4B
+0x034 SegEs : Uint4B
+0x038 SegDs : Uint4B
+0x03c Edx : Uint4B
+0x040 Ecx : Uint4B
+0x044 Eax : Uint4B
+0x048 PreviousPreviousMode : Uint4B
+0x04c ExceptionList : Ptr32 to struct _EXCEPTION_REGISTRATION_RECORD, 2 elements, 0x8 bytes
+0x050 SegFs : Uint4B
+0x054 Edi : Uint4B
+0x058 Esi : Uint4B
+0x05c Ebx : Uint4B
+0x060 Ebp : Uint4B
+0x064 ErrCode : Uint4B
+0x068 Eip : Uint4B
+0x06c SegCs : Uint4B
+0x070 EFlags : Uint4B
+0x074 HardwareEsp : Uint4B
+0x078 HardwareSegSs : Uint4B
+0x07c V86Es : Uint4B
+0x080 V86Ds : Uint4B
+0x084 V86Fs : Uint4B
+0x088 V86Gs : Uint4B
+0x138 ApcStatePointer : [2] Ptr32 to struct _KAPC_STATE, 5 elements, 0x18 bytes
+0x000 ApcListHead : [2] struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x010 Process : Ptr32 to struct _KPROCESS, 29 elements, 0x6c bytes
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
+0x140 PreviousMode : Char
+0x141 EnableStackSwap : UChar
+0x142 LargeStack : UChar
+0x143 ResourceIndex : UChar
+0x144 KernelTime : Uint4B
+0x148 UserTime : Uint4B
+0x14c SavedApcState : struct _KAPC_STATE, 5 elements, 0x18 bytes
+0x000 ApcListHead : [2] struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x010 Process : Ptr32 to struct _KPROCESS, 29 elements, 0x6c bytes
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
+0x164 Alertable : UChar
+0x165 ApcStateIndex : UChar
+0x166 ApcQueueable : UChar
+0x167 AutoAlignment : UChar
+0x168 StackBase : Ptr32 to Void
+0x16c SuspendApc : struct _KAPC, 14 elements, 0x30 bytes
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 Spare0 : Uint4B
+0x008 Thread : Ptr32 to struct _KTHREAD, 73 elements, 0x1c0 bytes
+0x00c ApcListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x014 KernelRoutine : Ptr32 to void
+0x018 RundownRoutine : Ptr32 to void
+0x01c NormalRoutine : Ptr32 to void
+0x020 NormalContext : Ptr32 to Void
+0x024 SystemArgument1 : Ptr32 to Void
+0x028 SystemArgument2 : Ptr32 to Void
+0x02c ApcStateIndex : Char
+0x02d ApcMode : Char
+0x02e Inserted : UChar
+0x19c SuspendSemaphore : struct _KSEMAPHORE, 2 elements, 0x14 bytes
+0x000 Header : struct _DISPATCHER_HEADER, 6 elements, 0x10 bytes
+0x010 Limit : Int4B
+0x1b0 ThreadListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x1b8 FreezeCount : Char
+0x1b9 SuspendCount : Char
+0x1ba IdealProcessor : UChar
+0x1bb DisableBoost : UChar
+0x00c ApcListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x014 KernelRoutine : Ptr32 to void
+0x018 RundownRoutine : Ptr32 to void
+0x01c NormalRoutine : Ptr32 to void
+0x020 NormalContext : Ptr32 to Void
+0x024 SystemArgument1 : Ptr32 to Void
+0x028 SystemArgument2 : Ptr32 to Void
+0x02c ApcStateIndex : Char
+0x02d ApcMode : Char
+0x02e Inserted : UChar
kd> dt _kapc
ntdll!_KAPC
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x004 Spare0 : Uint4B
+0x008 Thread : Ptr32 _KTHREAD
+0x00c ApcListEntry : _LIST_ENTRY
+0x014 KernelRoutine : Ptr32
+0x018 RundownRoutine : Ptr32
+0x01c NormalRoutine : Ptr32
+0x020 NormalContext : Ptr32 Void
+0x024 SystemArgument1 : Ptr32 Void
+0x028 SystemArgument2 : Ptr32 Void
+0x02c ApcStateIndex : Char
+0x02d ApcMode : Char
+0x02e Inserted : UChar
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;
//------
APC环境
一个线程在它执行的任意时刻,假设当前的IRQL是在Passive级,它可能需要临时在其他的进程上下文中执行代码,为了完成这个操作,线程调用系统功能函数KeAttachProcess,在从这个调用返回时,线程执行在另一个进程的地址空间。先前所有在线程自己的进程上下文中等待执行的APC,由于这时其所属进程的地址空间不是当前可用的,因此他们不能被投递执行。然而,新的插入到这个线程的APC可以执行在这个新的进程空间。甚至当线程最后从新的进程中分离时,新的插入到这个线程的APC还可以在这个线程所属的进程上下文中执行。
NT中每个线程维护了两个APC环境或者说是状态。每一个APC环境包含了用户模式的APC队列和内核模式的APC队列,一个指向当前进程对象的指针和三个控制变量,用于指出:是否有未决的内核模式APC(KernelApcPending),是否有常规内核模式APC在进行中(KernelApcInProgress),是否有未决的用户模式的APC(UserApcPending). 这些APC的环境保存在线程对象的ApcStatePointer域中。这个域是由2个元素组成的数组。即:+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE
kd> dt _KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY
+0x010 Process : Ptr32 _KPROCESS
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
typedef struct _KAPC_STATE {
LIST_ENTRY ApcListHead[MaximumMode];
struct _KPROCESS *Process;
BOOLEAN KernelApcInProgress;
BOOLEAN KernelApcPending;
BOOLEAN UserApcPending;
} KAPC_STATE, *PKAPC_STATE, *PRKAPC_STATE;
主APC环境是位于线程对象的ApcState 域,即:
+0x034 ApcState : _KAPC_STATE
线程中等待在当前进程上下文中执行的APC保存在ApcState的队列中。无论何时,NT的APC派发器(dispatcher)和其他系统元件查询一个线程未决的APC时, 他们都会检查主APC环境,如果这里有任何未决的APC,就会马上被投递,或者修改它的控制变量稍后投递。
第二个APC环境是位于线程对象的SavedApcState域,当线程临时挂接到其他进程时,它是用来备份主APC环境的。+0x14c SavedApcState : struct _KAPC_STATE, 5 elements, 0x18 bytes
一个线程调用KeAttachProcess,在另外的进程上下文中执行后续的代码时,ApcState域的内容就被拷贝到SavedApcState域。然后ApcState域被清空,它的APC队列重新初始化,控制变量设置为0,当前进程域设置为新的进程。这些步骤成功的确保先前在线程所属的进程上下文地址空间中等待的APC,当线程运行在其它不同的进程上下文时,这些APC不被传送执行。随后,ApcStatePointer域数组内容被更新来反映新的状态,数组中第一个元素指向SavedApcState域,第二个元素指向ApcState域,表明线程所属进程上下文的APC环境位于SavedApcState域。线程的新的进程上下文的APC环境位于ApcState域。最后,当前进程上下文切换到新的进程上下文。
对于一个APC对象,决定当前APC环境的是ApcStateIndex域。ApcStateIndex域的值作为ApcStatePointer域数组的索引来得到目标APC环境指针。随后,目标APC环境指针用来在相应的队列中存放apc对象.
当线程从新的进程中脱离时(KeDetachProcess), 任何在新的进程地址空间中等待执行的未决的内核APCs被派发执行。随后SavedApcState 域的内容被拷贝回ApcState域。SavedApcState 域的内容被清空,线程的ApcStateIndex域被设为OriginalApcEnvironment,ApcStatePointer域更新,当前进程上下文切换到线程所属进程。 使用APCs
设备驱动程序使用两个主要函数来利用APC, 第一个是KeInitializeApc,用来初始化APC对象。这个函数接受一个驱动分配的APC对象,一个目标线程对象指针,APC环境索引(指出APC对象存放于哪个APC环境),APC的kernel,rundown和normal例程指针,APC类型(用户模式或者内核模式)和一个上下文参数。 函数声明如下:
NTKERNELAPI
VOID
KeInitializeApc (
IN PRKAPC Apc,
IN PKTHREAD Thread,
IN KAPC_ENVIRONMENT Environment,
IN PKKERNEL_ROUTINE KernelRoutine,
IN PKRUNDOWN_ROUTINE RundownRoutine OPTIONAL,
IN PKNORMAL_ROUTINE NormalRoutine OPTIONAL,
IN KPROCESSOR_MODE ApcMode,
IN PVOID NormalContext
);
typedef enum _KAPC_ENVIRONMENT {
OriginalApcEnvironment,
AttachedApcEnvironment,
CurrentApcEnvironment
} KAPC_ENVIRONMENT;
KeInitializeApc 首先设置APC对象的Type和Size域一个适当的值,然后检查参数Environment的值,如果是CurrentApcEnvironment,那么ApcStateIndex域设置为目标线程的ApcStateIndex域。否则,ApcStateIndex域设置为参数Environment的值。随后,函数直接用参数设置APC对象Thread,RundownRoutine,KernelRoutine域的值。为了正确地确定APC的类型,KeInitializeApc 检查参数NormalRoutine的值,如果是NULL,ApcMode域的值设置为KernelMode,NormalContext域设置为NULL。如果NormalRoutine的值不是NULL,这时候它一定指向一个有效的例程,就用相应的参数来设置ApcMode域和NormalContext域。最后,KeInitializeApc 设置Inserted域为FALSE.然后初始化APC对象,并没有把它存放到相应的APC队列中。
可以了解到APC对象如果缺少有效的NormalRoutine,就会被当作内核模式APC.尤其是它们会被认为是特殊的内核模式APC.
实际上,I/O管理器就是用这类的APC来完成异步I/O操作。相反地,APC对象定义了有效的NormalRoutine,并且ApcMode域是KernelMode,就会被当作常规的内核模式APCs,否则就会被当作是用户模式APCs
NTDDK.H中KernelRoutine, RundownRoutine, and NormalRoutine 的定义如下:
typedef
VOID
(*PKKERNEL_ROUTINE) (
IN struct _KAPC *Apc,
IN OUT PKNORMAL_ROUTINE *NormalRoutine,
IN OUT PVOID *NormalContext,
IN OUT PVOID *SystemArgument1,
IN OUT PVOID *SystemArgument2
);
typedef
VOID
(*PKRUNDOWN_ROUTINE) (
IN struct _KAPC *Apc
);
typedef
VOID
(*PKNORMAL_ROUTINE) (
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
);
//------------------ 通常,无论是什么类型,每个APC对象必须要包含一个有效的KernelRoutine 函数指针。当这个APC被NT的APC dispatcher传送执行时,这个例程首先被执行。用户模式的APC必须包含一个有效的NormalRoutine 函数指针,这个函数必须在用户内存区域。同样的,常规内核模式APC也必须包含一个有效的NormalRoutine,但是它就像KernelRoutine一样运行在内核模式。作为可选择的,任意类型的APC都可以定义一个有效的RundownRoutine,这个例程必须在内核内存区域,并且仅仅当系统需要释放APC队列的内容时,才被调用。例如线程退出时,在这种情况下,KernelRoutine和NormalRoutine都不执行,只有RundownRoutine执行。没有这个例程的APC对象会被删除。
投递APC到一个线程的动作,仅仅是操作系统调用KiDeliverApc完成的。执行APC实际上就是调用APC内的例程。
一旦APC对象完成初始化后,设备驱动调用KeInsertQueueApc来将APC对象存放到目标线程的相应的APC队列中。这个函数接受一个由KeInitializeApc完成初始化的APC对象指针,两个系统参数和一个优先级增量。跟传递给KeInitializeApc函数的参数context 一样,这两个系统参数只是在APC的例程执行时,简单的传递给APC的例程。
NTKERNELAPI
BOOLEAN
KeInsertQueueApc (
IN PRKAPC Apc,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2,
IN KPRIORITY Increment
);
//-----------------
在KeInsertQueueApc 将APC对象存放到目标线程相应的APC队列之前,它首先检查目标线程是否是APC queueable。如果不是,函数立即返回FALSE.如果是,函数直接用参数设置SystemArgument1域和SystemArgument2 域,随后,函数调用KiInsertQueueApc来将APC对象存放到相应的APC队列。
KiInsertQueueApc 仅仅接受一个APC对象和一个优先级增量。这个函数首先得到线程APC队列的spinlock并且持有它,防止其他线程修改当前线程的APC结构。随后,检查APC对象的Inserted 域。如果是TRUE,表明这个APC对象已经存放到APC队列中了,函数立即返回FALSE.如果APC对象的Inserted 域是FALSE.函数通过ApcStateIndex域来确定目标APC环境,然后把APC对象存放到相应的APC队列中,即将APC对象中的ApcListEntry 域链入到APC环境的ApcListHead域中。链入的位置由APC的类型决定。常规的内核模式APC,用户模式APC都是存放到相应的APC队列的末端。相反的,如果队列中已经存放了一些APC对象,特殊的内核模式APC存放到队列中第一个常规内核模式APC对象的前面。如果是内核定义的一个当线程退出时使用的用户APC,它也会被放在相应的队列的前面。然后,线程的主APC环境中的UserApcPending域被设置为TRUE。这时KiInsertQueueApc 设置APC对象的Inserted 域为TRUE,表明这个APC对象已经存放到APC队列中了。接下来,检查这个APC对象是否被排队到线程的当前进程上下文APC环境中,如果不是,函数立即返回TRUE。如果这是一个内核模式APC,线程主APC环境中的KernelApcPending域设置为TRUE。
WIN32 SDK文档中是这样描述APC的: 当一个APC被成功的存放到它的队列后,发出一个软中断,APC将会在线程被调度运行的下一个时间片执行。然而这不是完全正确的。这样一个软中断,仅仅是当一个内核模式的APC(无论是常规的内核模式APC还是特殊的内核模式APC)针对于调用线程时,才会发出。随后函数返回TRUE。
1)如果APC不是针对于调用线程,目标线程在Passive权限等级处在等待状态;
2)这是一个常规内核模式APC
3)这个线程不再临界区
4)没有其他的常规内核模式APC仍然在进行中
那么这个线程被唤醒,返回状态是STATUS_KERNEL_APC。但是等待状态没有aborted。 如果这是一个用户模式APC,KiInsertQueueApc检查判断目标线程是否是alertable等待状态,并且WaitMode域等于UserMode。如果是,主APC环境的UserApcPending 域设置为TRUE。等待状态返回STATUS_USER_APC,最后,函数释放spinlock,返回TRUE,表示APC对象已经被成功放入队列。
早期作为APC管理函数的补充,设备驱动开发者可以使用未公开的系统服务NtQueueApcThread来直接将一个用户模式的APC投递到某个线程。
这个函数内部实际上是调用了KeInitializeApc 和KeInsertQueueApc 来完成这个任务。
NT的APC派发器
NT检查是否线程有未决的APC. 然后APC派发器子程序KiDeliverApc在这个线程上下文执行来开始将未决的APC执行。注意,这个行为中断了线程的正常执行流程,首先将控制权给APC派发器,随后当KiDeliverApc完成后,继续线程的执行。
例如:当一个线程被调度运行时,最后一步,上下文切换函数 SwapContext 用来检查是否新的线程有未决的内核APC.如果是,SwapContext要么(1)请求一个APC级别的软中断来开始APC执行,由于新线程运行在低的IRQL(Passive级别¬)。
或者(2)返回TRUE,表示新的线程有未决的内核APC。
SwapContext的返回值仅仅是特定系统函数可用的,这些系统函数调用SwapContext来强制切换线程上下文到另一个线程. 然后,当这些系统函数经过一段时间再继续时,他们通常检查SwapContext 的返回值,如果是TRUE,他们就会调用APC派发器来投递内核APC到当前的线程. 例如:
系统函数KiSwapThread被等待服务用来放弃处理器,直到等待结束。这个函数内部调用SwapContext。当等待结束,继续从调用SwapContext处执行时,就会检查SwapContext的返回值。如果是TRUE,KiSwapThread会降低IRQL级别到APC级,然后调用KiDeliverApc来在当前线程执行内核APC.
对于用户APC, 内核调用APC派发器仅仅是当线程回到用户模式,并且线程的主APC环境的UserApcPending域为TRUE时。例如:当系统服务派发器KiSystemService完成一个系统服务请求正打算回到用户模式时,它会检查是否有未决的用户APC。在执行上,KiDeliverApc调用用户APC的KernelRoutine. 随后,KiInitializeUserApc函数被调用,用来设置线程的陷阱帧。所以从内核模式退出时,线程开始在用户模式下执行。
KiInitializeUserApc的函数的作用是拷贝当前线程先前的执行状态(当进入内核模式时,这个状态保存在线程内核栈创建的陷阱帧里),从内核栈到线程的用户模式栈,初始化用户模式APC。APC派发器子程序KiUserApcDispatcher在Ntdll.dll内。最后,加载陷阱帧的EIP寄存器和Ntdll.dll中KiUserApcDispatcher的地址。当陷阱帧最后释放时,内核将控制转交给KiUserApcDispatcher,这个函数调用APC的NormalRoutine例程,NormalRoutine函数地址以及参数都在栈中,当例程完成时,它调用NtContinue来让线程利用在栈中先前的上下文继续执行,仿佛什么事情也没有发生过。
当内核调用KiDeliverApc来执行一个用户模式APC时,线程中的PreviousMode域被设为UserMode. TrapFrame域指向线程的陷阱帧。当内核调用KiDeliverApc来执行内核APC时,线程中的PreviousMode域被设为KernelMode. TrapFrame域指向NULL。
注意,无论何时只要KernelRoutine被调用,传递给它的指针是一个局部的APC属性的副本,由于APC对象已经脱离了队列,所以可以安全的在KernelRoutine中释放APC内存。此外,这个例程在它的参数被传递给其他例程之前,有一个最后的机会来修改这些参数。 NTSYSAPI
NTSTATUS
NTAPI
NtQueueApcThread (
IN HANDLE Thread,
IN PKNORMAL_ROUTINE NormalRoutine,
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
); /*
TerminateThread.c
By 炉子[0GiNr]
http://hi.baidu.com/breakinglove_
http://0ginr.com
*/
#include "ntddk.h"
#include "LDasm.h" //网上很多的,自己找一个好了。
typedef enum _KAPC_ENVIRONMENT {
OriginalApcEnvironment,
AttachedApcEnvironment,
CurrentApcEnvironment,
InsertApcEnvironment
} KAPC_ENVIRONMENT;
NTKERNELAPI
VOID
KeInitializeApc (
PKAPC Apc,
PETHREAD Thread,
KAPC_ENVIRONMENT Environment,
PKKERNEL_ROUTINE KernelRoutine,
PKRUNDOWN_ROUTINE RundownRoutine,
PKNORMAL_ROUTINE NormalRoutine,
KPROCESSOR_MODE ProcessorMode,
PVOID NormalContext
);
NTKERNELAPI
BOOLEAN
KeInsertQueueApc (
PKAPC Apc,
PVOID SystemArgument1,
PVOID SystemArgument2,
KPRIORITY Increment
);
#define PS_CROSS_THREAD_FLAGS_SYSTEM 0x00000010UL
ULONG GetThreadFlagsOffset()
{
UCHAR *cPtr, *pOpcode;
ULONG Length;
USHORT Offset;
for (cPtr = (PUCHAR)PsTerminateSystemThread;
cPtr < (PUCHAR)PsTerminateSystemThread + 0x100;
cPtr += Length)
{
Length = SizeOfCode(cPtr, &pOpcode);
if (!Length) break;
if (*(USHORT *)pOpcode == 0x80F6) //f6804802000010 test byte ptr [eax+248h],10h
{
Offset=*(USHORT *)((ULONG)pOpcode+2);
return Offset;
//break;
}
}
return 0;
}
VOID KernelTerminateThreadRoutine(
IN PKAPC Apc,
IN OUT PKNORMAL_ROUTINE *NormalRoutine,
IN OUT PVOID *NormalContext,
IN OUT PVOID *SystemArgument1,
IN OUT PVOID *SystemArgument2
)
{
ULONG ThreadFlagsOffset=GetThreadFlagsOffset();
PULONG ThreadFlags;
DbgPrint("[TerminateThread] KernelTerminateThreadRoutine.\n");
ExFreePool(Apc);
if (ThreadFlagsOffset)
{
ThreadFlags=(ULONG *)((ULONG)(PsGetCurrentThread())+ThreadFlagsOffset);
*ThreadFlags=(*ThreadFlags)|PS_CROSS_THREAD_FLAGS_SYSTEM;
PsTerminateSystemThread(STATUS_SUCCESS); //o(∩_∩)o
}
else
{
//failed :'(
}
return; //never be here
}
BOOLEAN TerminateThread(PETHREAD Thread)
{
PKAPC Apc=NULL;
BOOLEAN blnSucceed=FALSE;
if (!MmIsAddressValid(Thread)) return FALSE; //error.
Apc=ExAllocatePool(NonPagedPool,sizeof(KAPC));
KeInitializeApc(Apc,
Thread,
OriginalApcEnvironment,
KernelTerminateThreadRoutine,
NULL,
NULL,
KernelMode,
NULL); //special apc
blnSucceed=KeInsertQueueApc(Apc,
NULL,
NULL,
0);
//add some code works like KeForceResumeThread here.
return blnSucceed;
}
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[TerminateThread] Unloaded\n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString)
{
DbgPrint("[TerminateThread] DriverEntry.\n");
TerminateThread((PETHREAD)0xff6f3c70); // for test
pDriverObj->DriverUnload = DriverUnload;
return STATUS_SUCCESS; //do NOT return an unsuccessful value here, or you need to wait for apc routine return.
}
kd> u nt! PsTerminateSystemThread
nt!PsTerminateSystemThread:
805c8a84 8bff mov edi,edi
805c8a86 55 push ebp
805c8a87 8bec mov ebp,esp
805c8a89 64a124010000 mov eax,fs:[00000124]
805c8a8f f6804802000010 test byte ptr [eax+0x248],0x10
805c8a96 7507 jnz nt!PsTerminateSystemThread+0x1b (805c8a9f)
805c8a98 b80d0000c0 mov eax,0xc000000d
805c8a9d eb09 jmp nt!PsTerminateSystemThread+0x24 (805c8aa8)
Mov eax ,fs:[00000124]其实是把CurrentThread 的地址赋给了eax;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: