进程线程篇——总结与提升
写在前面
??此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读 ,方便学习本教程。
??看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?系统调用篇学会了吗?练习做完了吗?没有的话就不要继续了。
?? 华丽的分割线 ??
模拟线程切换分析
??之前我们详细分析了模拟线程切换的本质,里面模拟了两处线程切换,一处模拟了时钟切换,一处模拟了主动切换,也就是通过API
的方式进行的切换。
??我们来看一下模拟时钟切换的部分:
while(TRUE) {
Sleep(20);
Scheduling();
}
??上面的Sleep(20)
就是代表我每20ms
继续调用Scheduling()
来实现线程切换,也就是说时钟周期是20ms
。
??模拟通过API
的方式进行的主动切换的就是如下函数:
void GMSleep(int MilliSeconds)
{
GMThread_t* GMThreadp;
GMThreadp = &GMThreadList[CurrentThreadIndex];
if (GMThreadp->Flags != 0) {
GMThreadp->Flags = GMTHREAD_SLEEP;
GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;
}
Scheduling();
return;
}
??而我们的每一个线程,都会调用GMSleep
这个函数,这个函数模拟的就是模拟调用WinAPI
:
void Thread1(void*) {
while(1){
printf("Thread1\n");
GMSleep(500);
}
}
??我假设上面的你都搞明白了。那么,接着上篇的课后练习,怎样实现线程的挂起和恢复呢?这个问题你思考了吗?没思考的话就不要继续了。
线程挂起恢复分析实现
??为了实现线程的挂起恢复函数,我们应该把思路放在它是如何实现线程调度上。让线程挂起,无非就是不让给这个线程CPU
时间了,看看下面负责调度的函数是怎样找到线程的:
void Scheduling(void)
{
int i;
int TickCount;
GMThread_t* SrcGMThreadp;
GMThread_t* DstGMThreadp;
TickCount = GetTickCount();
SrcGMThreadp = &GMThreadList[CurrentThreadIndex];
DstGMThreadp = &GMThreadList[0];
for (i = 1; GMThreadList[i].name; i++) {
if (GMThreadList[i].Flags & GMTHREAD_SLEEP) {
if (TickCount > GMThreadList[i].SleepMillsecondDot) {
GMThreadList[i].Flags = GMTHREAD_READY;
}
}
if (GMThreadList[i].Flags & GMTHREAD_READY) {
DstGMThreadp = &GMThreadList[i];
break;
}
}
CurrentThreadIndex = DstGMThreadp - GMThreadList;
SwitchContext(SrcGMThreadp, DstGMThreadp);
return;
}
??可以看到,只要模拟线程是GMTHREAD_READY
状态,它就会调换线程,也就是说我们改一改这个标志线程状态的参数一改,就能实现我想要的挂起和恢复,我们来实现它。
??首先,我们在ThreadSwitch.h
来添加两处声明:
bool SyspendThread(char* Name);
bool ResumeThread(char* Name);
??然后我们对它进行实现:
bool SyspendThread(char* Name)
{
for (int i=1;i
??为了方便观察,我们只保留了Thread1
和Thread2
,最终main.cpp
的内容如下:
#include "stdafx.h"
#include
#include "ThreadSwitch.h"
extern int CurrentThreadIndex;
extern GMThread_t GMThreadList[MAXGMTHREAD];
void Thread1(void*) {
while(1){
printf("Thread1\n");
GMSleep(500);
}
}
void Thread2(void*) {
while (1) {
printf("Thread2\n");
GMSleep(500);
}
}
int main()
{
RegisterGMThread("Thread1", Thread1, NULL);
RegisterGMThread("Thread2", Thread2, NULL);
//SyspendThread("Thread2");
//ResumeThread("Thread2");
while(TRUE) {
Sleep(20);
Scheduling();
}
return 0;
}
??我们接下来以动图的形式进行演示:
??上一篇留下的第0题到此就解决完毕了。
SwapContext 分析
??本分析对于逆向水平有一定的要求,如果不行的话建议做多一些有关IDA
的CrakeMe
练习,分析流程以做熟悉和练习。不过没有经验也无所谓,仔细看看本部分,回去重新做一遍。
??当你找到这个函数的是哦胡,你看到的应该是下面的情况:
SwapContext proc near ; CODE XREF: KiUnlockDispatcherDatabase(x)+72↑p
; KiSwapContext(x)+29↑p ...
or cl, cl
mov byte ptr es:[esi+2Dh], 2
pushf
loc_46A8E8: ; CODE XREF: KiIdleLoop()+5A↓j
mov ecx, [ebx] ; ebx = KPCR
cmp dword ptr [ebx+994h], 0
push ecx
jnz loc_46AA2D
cmp ds:_PPerfGlobalGroupMask, 0
jnz loc_46AA04
loc_46A905: ; CODE XREF: SwapContext+12C↓j
; SwapContext+13D↓j ...
mov ebp, cr0
mov edx, ebp
mov cl, [esi+2Ch]
mov [ebx+50h], cl
??既然分析它的参数是什么,首先我们得知道它们是如何传参的,传的是什么,这样我们才能解决上一篇留下的思考题。
??然后我们找到它的来自KiSwapContext
的一个引用,结果如下:
; __fastcall KiSwapContext(x)
@KiSwapContext@4 proc near ; CODE XREF: KiSwapThread()+41↑p
var_200FE4 = dword ptr -200FE4h
var_10 = dword ptr -10h
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
sub esp, 10h
mov [esp+10h+var_4], ebx
mov [esp+10h+var_8], esi
mov [esp+10h+var_C], edi
mov [esp+10h+var_10], ebp
mov ebx, ds:0FFDFF01Ch ; ebx = &_KPCR
mov esi, ecx ; esi = ecx = NextReadyThread
mov edi, [ebx+124h]
mov [ebx+124h], esi
mov cl, [edi+58h]
call SwapContext
mov ebp, [esp+10h+var_10]
mov edi, [esp+10h+var_C]
mov esi, [esp+10h+var_8]
mov ebx, [esp+10h+var_4]
add esp, 10h
retn
@KiSwapContext@4 endp
??但是这样也看不出来参数是什么,我们再往上找一级:
@KiSwapThread@0 proc near ; CODE XREF: KiAttachProcess(x,x,x,x)+F2↑p
; KeDelayExecutionThread(x,x,x):loc_42279A↑p ...
mov edi, edi
push esi
push edi
db 3Eh
mov eax, ds:0FFDFF020h
mov esi, eax
??我们找到了KiSwapThread
这个函数,明显望文生义就是用来切换线程用的内核函数。前面我们知道0xFFDFF000
这个地址存放的就是KPCR
结构体的地址,那么根据结构体可以知道0xFFDFF020
存放的就是KPRCB
这个结构体的首地址,最终分析得到如下结果:
@KiSwapThread@0 proc near ; CODE XREF: KiAttachProcess(x,x,x,x)+F2↑p
; KeDelayExecutionThread(x,x,x):loc_42279A↑p ...
mov edi, edi
push esi
push edi
db 3Eh
mov eax, ds:0FFDFF020h
mov esi, eax
mov eax, [esi+_KPRCB.NextThread] ; eax = NextThread
test eax, eax ; 测一测有没有
mov edi, [esi+_KPRCB.CurrentThread] ; edi = CurrentThread
jz short loc_429CAC ; 没有 NextThread 就跳
and [esi+_KPRCB.NextThread], 0 ; 把 NextThread 清零
jmp short loc_429CCF
; ---------------------------------------------------------------------------
loc_429CAC: ; CODE XREF: KiSwapThread()+14↑j
push ebx
movsx ebx, [esi+_KPRCB.Number]
xor edx, edx
mov ecx, ebx
call @KiFindReadyThread@8 ; KiFindReadyThread(x,x)
test eax, eax
jnz short loc_429CCE ; 如果找到了下一个线程就跳走
mov eax, [esi+_KPRCB.IdleThread]
xor edx, edx
inc edx
mov ecx, ebx
shl edx, cl
or _KiIdleSummary, edx
loc_429CCE: ; CODE XREF: KiSwapThread()+2C↑j
pop ebx
loc_429CCF: ; CODE XREF: KiSwapThread()+1A↑j
mov ecx, eax
call @KiSwapContext@4 ; KiSwapContext(x)
??经过简单的分析,我们很容易地判断出执行到KiSwapContext
前的ecx
为一个线程结构体,如下图所示:
??根据符号显示KiSwapContext
是只有一个参数,经过简单的分析可以得到下面的结果:
; __fastcall KiSwapContext(x)
@KiSwapContext@4 proc near ; CODE XREF: KiSwapThread()+41↑p
var_200FE4 = dword ptr -200FE4h
var_10 = dword ptr -10h
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
sub esp, 10h
mov [esp+10h+var_4], ebx
mov [esp+10h+var_8], esi
mov [esp+10h+var_C], edi
mov [esp+10h+var_10], ebp
mov ebx, ds:0FFDFF01Ch ; ebx = &_KPCR
mov esi, ecx ; esi = ecx = NextReadyThread
mov edi, [ebx+_KPCR.PrcbData.CurrentThread]
mov [ebx+_KPCR.PrcbData.CurrentThread], esi
mov cl, [edi+_KTHREAD.WaitIrql]
call SwapContext
mov ebp, [esp+10h+var_10]
mov edi, [esp+10h+var_C]
mov esi, [esp+10h+var_8]
mov ebx, [esp+10h+var_4]
add esp, 10h
retn
@KiSwapContext@4 endp
??执行到SwapContext
这个函数前,esi
成了下一个切换新线程,而edi
成了需要被切换的老线程,而ebx
是KPCR
结构体,也就是说,改函数一共有3个参数,每一个参数的含义我们都已经知道了,我们利用IDA
的F5
也可以得到一定的验证:
char __usercall SwapContext@(int *a1@, int a2@, int a3@)
??接下来我们分析一下在哪里实现了线程切换。既然操作系统线程切换是基于堆栈的,esp
换了,必然导致线程的切换,我们很容易跟到下面的汇编:
loc_46A94C: ; CODE XREF: SwapContext+67↑j
mov ecx, [ebx+_KPCR.TSS]
mov [ecx+_KTSS.Esp0], eax
mov esp, [esi+_KTHREAD.KernelStack]
mov eax, [esi+_KTHREAD.Teb]
mov [ebx+_KPCR.NtTib.Self], eax
??经历过堆栈的弹出恢复操作,再调用retn
,即可完成线程的切换。接下来我们看看什么时候切换的CR3
:
mov eax, [edi+_KTHREAD.ApcState.Process]
cmp eax, [esi+_KTHREAD.ApcState.Process]
mov [edi+_KTHREAD.IdleSwapBlock], 0
jz short loc_46A994
mov edi, [esi+_KTHREAD.ApcState.Process]
test word ptr [edi+_KTHREAD.Teb], 0FFFFh
jnz short loc_46A9CE
xor eax, eax
loc_46A975: ; CODE XREF: SwapContext+117↓j
lldt ax
xor eax, eax
mov gs, eax
assume gs:GAP
mov eax, [edi+_EPROCESS.Pcb.DirectoryTableBase]
mov ebp, [ebx+_KPCR.TSS]
mov ecx, dword ptr [edi+_KTHREAD.Iopl]
mov [ebp+_KTSS.CR3], eax
mov cr3, eax
mov [ebp+_KTSS.IoMapBase], cx
jmp short loc_46A994
; ---------------------------------------------------------------------------
align 4
loc_46A994: ; CODE XREF: SwapContext+86↑j
; SwapContext+AF↑j
mov eax, [ebx+_KPCR.NtTib.Self]
??经过分析,切换CR3
是需要条件的,它会判断新的线程和老的线程的Process
是不是一样的,然后决定是否处理:
mov eax, [edi+_KTHREAD.ApcState.Process]
cmp eax, [esi+_KTHREAD.ApcState.Process]
mov [edi+_KTHREAD.IdleSwapBlock], 0
jz short loc_46A994
??如果不相同的话,就会执行下面的代码切换CR3
:
mov eax, [edi+_EPROCESS.Pcb.DirectoryTableBase]
mov ebp, [ebx+_KPCR.TSS]
mov ecx, dword ptr [edi+_KTHREAD.Iopl]
mov [ebp+_KTSS.CR3], eax
mov cr3, eax
??看明白到这个地方的时候,第2题就解决了。一个CPU
一套寄存器,也就说明里面只能存储一个TSS
地址的寄存器,那么中断门提权时TSS
中存储的一定是当前线程的ESP0
和SS0
吗?我们接下来分析一下:
mov eax, [esi+_KTHREAD.InitialStack]
mov ecx, [esi+_KTHREAD.StackLimit]
sub eax, 210h
mov [ebx+_KPCR.NtTib.StackLimit], ecx
mov [ebx+_KPCR.NtTib.StackBase], eax
xor ecx, ecx
mov cl, [esi+_KTHREAD.NpxState]
and edx, 0FFFFFFF1h
or ecx, edx
or ecx, [eax+20Ch]
cmp ebp, ecx
jnz loc_46A9FC
lea ecx, [ecx+0]
loc_46A940: ; CODE XREF: SwapContext+11F↓j
test dword ptr [eax-1Ch], 20000h ; 检查是否为虚拟8086模式
jnz short loc_46A94C
sub eax, 10h
loc_46A94C: ; CODE XREF: SwapContext+67↑j
mov ecx, [ebx+_KPCR.TSS]
mov [ecx+_KTSS.Esp0], eax ; 将修正好的栈顶放入到 TSS 中
mov esp, [esi+_KTHREAD.KernelStack]
mov eax, [esi+_KTHREAD.Teb]
??可以看出在mov esp, [esi+_KTHREAD.KernelStack]
之前,上面的代码已经处理好并修正ESP0
,所以中断门提权时TSS
中存储的一定是当前线程的ESP0
和SS0
。至此,第3题解答完毕。
??在我测试的虚拟机中,fs
的段选择子都是0x3B
,但为什么不同的线程段选择子指向的TEB
却不一样呢?是因为它直接修改了GDT
表的内容,使它指向的地址是我们现在线程的TEB
,代码如下所示:
mov eax, [ebx+_KPCR.NtTib.Self]
mov ecx, [ebx+_KPCR.GDT]
mov [ecx+3Ah], ax
shr eax, 10h
mov [ecx+3Ch], al
mov [ecx+3Fh], ah
??至此,第4题解答完毕。我们来看看0环的ExceptionList
在哪里备份的:
pop ecx
mov [ebx+_KPCR.NtTib.ExceptionList], ecx
??第5题也就解决了,它把新线程的ExceptionList
存于KPCR
中。下面我们来看看IdleThread
如何查找:
??我们知道IdleThread
存储于KPCR
之中,我们通过结构体的方式进行查询,找到该成员存储的地址。
kd> dt _KPCR 0xffdff000
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x01c SelfPcr : 0xffdff000 _KPCR
+0x020 Prcb : 0xffdff120 _KPRCB
+0x024 Irql : 0x1c ''
+0x028 IRR : 4
+0x02c IrrActive : 0
+0x030 IDR : 0xffff20f8
+0x034 KdVersionBlock : 0x80546ab8 Void
+0x038 IDT : 0x8003f400 _KIDTENTRY
+0x03c GDT : 0x8003f000 _KGDTENTRY
+0x040 TSS : 0x80042000 _KTSS
+0x044 MajorVersion : 1
+0x046 MinorVersion : 1
+0x048 SetMember : 1
+0x04c StallScaleFactor : 0x64
+0x050 DebugActive : 0 ''
+0x051 Number : 0 ''
+0x052 Spare0 : 0 ''
+0x053 SecondLevelCacheAssociativity : 0 ''
+0x054 VdmAlert : 0
+0x058 KernelReserved : [14] 0
+0x090 SecondLevelCacheSize : 0
+0x094 HalReserved : [16] 0
+0x0d4 InterruptMode : 0
+0x0d8 Spare1 : 0 ''
+0x0dc KernelReserved2 : [17] 0
+0x120 PrcbData : _KPRCB
kd> dx -id 0,0,805539a0 -r1 ((ntkrnlpa!_KPRCB *)0xffdff120)
((ntkrnlpa!_KPRCB *)0xffdff120) : 0xffdff120 [Type: _KPRCB *]
[+0x000] MinorVersion : 0x1 [Type: unsigned short]
[+0x002] MajorVersion : 0x1 [Type: unsigned short]
[+0x004] CurrentThread : 0x80553740 [Type: _KTHREAD *]
[+0x008] NextThread : 0x0 [Type: _KTHREAD *]
[+0x00c] IdleThread : 0x80553740 [Type: _KTHREAD *]
[+0x010] Number : 0 [Type: char]
[+0x011] Reserved : 0 [Type: char]
??然后我们dt
一下这个结构体:
kd> dt _ETHREAD 0x80553740
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x1c0 CreateTime : _LARGE_INTEGER 0x0
+0x1c0 NestedFaultCount : 0y00
+0x1c0 ApcNeeded : 0y0
+0x1c8 ExitTime : _LARGE_INTEGER 0x0
+0x1c8 LpcReplyChain : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x1c8 KeyedWaitChain : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x1d0 ExitStatus : 0n0
+0x1d0 OfsChain : (null)
+0x1d4 PostBlockList : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x1dc TerminationPort : (null)
+0x1dc ReaperLink : (null)
+0x1dc KeyedWaitValue : (null)
+0x1e0 ActiveTimerListLock : 0
+0x1e4 ActiveTimerListHead : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x1ec Cid : _CLIENT_ID
+0x1f4 LpcReplySemaphore : _KSEMAPHORE
+0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
+0x208 LpcReplyMessage : (null)
+0x208 LpcWaitingOnPort : (null)
+0x20c ImpersonationInfo : (null)
+0x210 IrpList : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x218 TopLevelIrp : 0
+0x21c DeviceToVerify : (null)
+0x220 ThreadsProcess : (null)
+0x224 StartAddress : (null)
+0x228 Win32StartAddress : (null)
+0x228 LpcReceivedMessageId : 0
+0x22c ThreadListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x234 RundownProtect : _EX_RUNDOWN_REF
+0x238 ThreadLock : _EX_PUSH_LOCK
+0x23c LpcReplyMessageId : 0
+0x240 ReadClusterSize : 0
+0x244 GrantedAccess : 0x1f03ff
+0x248 CrossThreadFlags : 0
+0x248 Terminated : 0y0
+0x248 DeadThread : 0y0
+0x248 HideFromDebugger : 0y0
+0x248 ActiveImpersonationInfo : 0y0
+0x248 SystemThread : 0y0
+0x248 HardErrorsAreDisabled : 0y0
+0x248 BreakOnTermination : 0y0
+0x248 SkipCreationMsg : 0y0
+0x248 SkipTerminationMsg : 0y0
+0x24c SameThreadPassiveFlags : 0
+0x24c ActiveExWorker : 0y0
+0x24c ExWorkerCanWaitUser : 0y0
+0x24c MemoryMaker : 0y0
+0x250 SameThreadApcFlags : 0
+0x250 LpcReceivedMsgIdValid : 0y0
+0x250 LpcExitThreadCalled : 0y0
+0x250 AddressSpaceOwner : 0y0
+0x254 ForwardClusterOnly : 0 ''
+0x255 DisablePageFaultClustering : 0 ''
??在上面的结构体中+0x224 StartAddress
存储的就是线程开始执行的地址,也就是我们在3环调用CreateThread
传递的函数地址。但是对于这个线程比较特殊,直接全为空,那么我们如何找到函数地址呢?
??程序执行的时候,一定会用到堆栈。我们可以通过堆栈就可以定位程序的行为。我们把关注点放到KTHREAD
的KernelStack
上。涉及该成员的操作存在于线程切换中,我们来看看与堆栈操作相关的局部汇编代码:
pushf
mov ecx, [ebx] ; ebx = KPCR
cmp [ebx+_KPCR.PrcbData.DpcRoutineActive], 0
push ecx ;KPCR.NtTib.ExceptionList
……
mov esp, [esi+_KTHREAD.KernelStack]
……
pop ecx
xor eax, eax
retn
??先看看堆栈地址的地址是啥:
kd> dt _KTHREAD 0x80553740
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY [ 0x80553750 - 0x80553750 ]
+0x018 InitialStack : 0x8054af00 Void
+0x01c StackLimit : 0x80547f00 Void
+0x020 Teb : (null)
+0x024 TlsArray : (null)
+0x028 KernelStack : 0x8054ac4c Void
+0x02c DebugActive : 0 ''
+0x02d State : 0x2 ''
+0x02e Alerted : [2] ""
+0x030 Iopl : 0 ''
+0x031 NpxState : 0xa ''
+0x032 Saturation : 0 ''
+0x033 Priority : 16 ''
+0x034 ApcState : _KAPC_STATE
+0x04c ContextSwitches : 0x1736
+0x050 IdleSwapBlock : 0 ''
+0x051 Spare0 : [3] ""
+0x054 WaitStatus : 0n0
+0x058 WaitIrql : 0x2 ''
+0x059 WaitMode : 0 ''
+0x05a WaitNext : 0 ''
+0x05b WaitReason : 0 ''
+0x05c WaitBlockList : 0x805537b0 _KWAIT_BLOCK
+0x060 WaitListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
+0x068 WaitTime : 0x555
+0x06c BasePriority : 0 ''
+0x06d DecrementCount : 0 ''
+0x06e PriorityDecrement : 0 ''
+0x06f Quantum : -17 ''
+0x070 WaitBlock : [4] _KWAIT_BLOCK
+0x0d0 LegoData : (null)
+0x0d4 KernelApcDisable : 0
+0x0d8 UserAffinity : 0xffffffff
+0x0dc SystemAffinityActive : 0 ''
+0x0dd PowerState : 0 ''
+0x0de NpxIrql : 0 ''
+0x0df InitialNode : 0 ''
+0x0e0 ServiceTable : 0x80553fa0 Void
+0x0e4 Queue : (null)
+0x0e8 ApcQueueLock : 0
+0x0f0 Timer : _KTIMER
+0x118 QueueListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x120 SoftAffinity : 1
+0x124 Affinity : 1
+0x128 Preempted : 0 ''
+0x129 ProcessReadyQueue : 0 ''
+0x12a KernelStackResident : 0x1 ''
+0x12b NextProcessor : 0 ''
+0x12c CallbackStack : (null)
+0x130 Win32Thread : (null)
+0x134 TrapFrame : (null)
+0x138 ApcStatePointer : [2] 0x80553774 _KAPC_STATE
+0x140 PreviousMode : 0 ''
+0x141 EnableStackSwap : 0x1 ''
+0x142 LargeStack : 0 ''
+0x143 ResourceIndex : 0 ''
+0x144 KernelTime : 0x2d1a
+0x148 UserTime : 0
+0x14c SavedApcState : _KAPC_STATE
+0x164 Alertable : 0 ''
+0x165 ApcStateIndex : 0 ''
+0x166 ApcQueueable : 0x1 ''
+0x167 AutoAlignment : 0 ''
+0x168 StackBase : 0x8054af00 Void
+0x16c SuspendApc : _KAPC
+0x19c SuspendSemaphore : _KSEMAPHORE
+0x1b0 ThreadListEntry : _LIST_ENTRY [ 0x805539f0 - 0x805539f0 ]
+0x1b8 FreezeCount : 0 ''
+0x1b9 SuspendCount : 0 ''
+0x1ba IdealProcessor : 0 ''
+0x1bb DisableBoost : 0 ''
??再看看堆栈长什么样子:
kd> dd 0x8054ac4c
8054ac4c 00000000 ffdff980 80542af0 00000000
8054ac5c 0000000e 00000000 00000000 00000000
8054ac6c 00000000 00000000 00000000 00000000
8054ac7c 00000000 00000000 00000000 00000000
8054ac8c 00000000 00000000 00000000 00000000
8054ac9c 00000000 00000000 00000000 00000000
8054acac 00000000 00000000 00000000 00000000
8054acbc 00000000 00000000 00000000 00000000
??也就是说,第一个就是ExceptionList
,第二个就是Eflag
,第三个就是切换线程后跳转的地址,在这里也就是IdleThread
继续走的地址。这个地址肯定就在IdleThread
当中。我们u
一下:
kd> u 80542af0
ReadVirtual: 80542af0 not properly sign extended
80542af0 fb sti
ReadVirtual: 80542b00 not properly sign extended
80542af1 90 nop
ReadVirtual: 80542b01 not properly sign extended
80542af2 90 nop
ReadVirtual: 80542b02 not properly sign extended
80542af3 fa cli
ReadVirtual: 80542b03 not properly sign extended
80542af4 3b6d00 cmp ebp,dword ptr [ebp]
80542af7 740d je nt!KiIdleLoop+0x26 (80542b06)
80542af9 b102 mov cl,2
80542afb ff15a8864d80 call dword ptr [nt!_imp_HalClearSoftwareInterrupt (804d86a8)]
??也就是函数是KiIdleLoop
,我们通过IDA
看看该函数:
; _DWORD __cdecl KiIdleLoop()
@KiIdleLoop@0 proc near ; CODE XREF: KiSystemStartup(x)+2E2↓j
lea ebp, [ebx+980h]
jmp short loc_46AAF0
; ---------------------------------------------------------------------------
loc_46AAE8: ; CODE XREF: KiIdleLoop()+2D↓j
lea ecx, [ebx+0C50h]
call dword ptr [ecx]
loc_46AAF0: ; CODE XREF: KiIdleLoop()+6↑j
; KiIdleLoop()+65↓j
sti
nop
nop
cli
cmp ebp, [ebp+0]
jz short loc_46AB06
mov cl, 2
call ds:__imp_@HalClearSoftwareInterrupt@4 ; HalClearSoftwareInterrupt(x)
call KiRetireDpcList
loc_46AB06: ; CODE XREF: KiIdleLoop()+17↑j
cmp dword ptr [ebx+128h], 0
jz short loc_46AAE8
sti
mov esi, [ebx+128h]
mov edi, [ebx+124h]
or ecx, 1
mov [ebx+124h], esi
mov byte ptr es:[esi+2Dh], 2
mov dword ptr [ebx+128h], 0
push offset loc_46AB3F
pushf
jmp loc_46A8E8
; ---------------------------------------------------------------------------
loc_46AB3F: ; DATA XREF: KiIdleLoop()+54↑o
lea ebp, [ebx+980h]
jmp short loc_46AAF0
@KiIdleLoop@0 endp
??这个函数没有任何意义,就是让CPU
别闲着,执行一波无任何意义的代码。至此第6题解决。
??KiFindReadyThread
分析这块涉及算法,经查阅是通过二分法进行查找的。算法实现原理是我的知识盲区,我仅把流程说一下:首先该函数会解析KiReadySummary
,找到从左起第一个为1的位数,再用该位获取从KiDispatchReadListHead
中的第一个_KTHREAD
线程,将其从链表中摘除再判断如果摘除后该链表为空,则找到相应的KiReadySummary
位将其置0,然后将对应找到的线程结构体返回。至于其中的详细细节,可以参考这位博友的分析: 。如下是IDA
的伪C代码,仅供参考:
PETHREAD __fastcall KiFindReadyThread(ULONG ProcessorNumber, KPRIORITY LowPriority)
{
int v2; // ecx
unsigned int v3; // eax
unsigned int v4; // edx
int v5; // edx
int v6; // eax
_LIST_ENTRY *v7; // esi
PETHREAD ReadyThread; // eax
_LIST_ENTRY *v9; // ecx
_LIST_ENTRY *v10; // edi
v2 = 16;
v3 = KiReadySummary & ~((1 << LowPriority) - 1);
v4 = HIWORD(v3);
if ( !HIWORD(v3) )
{
v2 = 0;
v4 = v3;
}
if ( (v4 & 0xFFFFFF00) != 0 )
v2 += 8;
v5 = v2 + KiFindFirstSetLeft[v3 >> v2];
v6 = v3 << (31 - v5);
v7 = &KiDispatcherReadyListHead[2 * v5];
if ( !v6 )
return 0;
while ( v6 >= 0 )
{
LOBYTE(v5) = v5 - 1;
--v7;
v6 *= 2;
if ( !v6 )
return 0;
}
v9 = v7->Flink->Flink;
ReadyThread = &v7->Flink[-12];
v10 = ReadyThread->WaitListEntry.Blink;
v10->Flink = v9;
v9->Blink = v10;
if ( IsListEmpty(v7) )
KiReadySummary &= ~(1 << v5);
return ReadyThread;
}
??至此第7题解决。
??模拟线程切换与Windows
的线程切换有哪些区别?真正的线程有两个栈,一个内核0环的栈,一个是3环的栈,发生线程切换在0环;模拟线程切换没用到FS
、异常列表之类的东西,其他的区别可以自行总结。
??接下来是最后一题,我们走一下时钟中断的流程,中断都是在IDT
表中的,首先我们跟着走一下,首先定位该表,只需g
到_IDT
,效果如下所示:
_IDT dd offset _KiTrap00 ; DATA XREF: KiSystemStartup(x)+1D5↑o
db 0, 8Eh
word_5B8B02 dw 8 ; DATA XREF: KiSwapIDT()↓o
dd offset _KiTrap01
dd 88E00h
dd offset _KiTrap02
dd 88E00h
dd offset _KiTrap03
dd 8EE00h
dd offset _KiTrap04
dd 8EE00h
dd offset _KiTrap05
dd 88E00h
dd offset _KiTrap06
dd 88E00h
dd offset _KiTrap07
dd 88E00h
dd offset _KiTrap08
dd 88E00h
dd offset _KiTrap09
??时钟中断的中断号是0x30
,我们定位到这个函数,如何定位呢?看下面的图:
??然后跳转到这个函数:
; _DWORD __stdcall KiStartUnexpectedRange()
_KiStartUnexpectedRange@0 proc near ; DATA XREF: KiGetVectorInfo(x,x)+68↑o
; INIT:005B8C7C↓o
push 30h ; '0'
jmp _KiEndUnexpectedRange@0 ; KiEndUnexpectedRange()
_KiStartUnexpectedRange@0 endp
??我们看看跳转到哪里:
; _DWORD __stdcall KiEndUnexpectedRange()
_KiEndUnexpectedRange@0 proc near ; CODE XREF: KiStartUnexpectedRange()+5↑j
; _KiUnexpectedInterrupt1+5↑j ...
jmp cs:off_46632E
_KiEndUnexpectedRange@0 endp
; ---------------------------------------------------------------------------
off_46632E dd offset _KiUnexpectedInterruptTail
; DATA XREF: KiEndUnexpectedRange()↑r
??继续跟着,为了节约篇幅,只保留调用流程部分:
_KiUnexpectedInterruptTail proc near ; CODE XREF: KiEndUnexpectedRange()↑j
; DATA XREF: .text:off_46632E↑o
……
loc_466E7E: ; CODE XREF: Dr_kui_a+10↑j
; Dr_kui_a+7C↑j
inc dword ptr ds:0FFDFF5C4h
mov ebx, [esp+68h+var_68]
sub esp, 4
push esp
push ebx
push 1Fh
call ds:__imp__HalBeginSystemInterrupt@12 ; HalBeginSystemInterrupt(x,x,x)
or eax, eax
jnz short loc_466E9D
add esp, 8
jmp short loc_466EEC
; ---------------------------------------------------------------------------
loc_466E9D: ; CODE XREF: _KiUnexpectedInterruptTail+BF↑j
cli
call ds:__imp__HalEndSystemInterrupt@8 ; HalEndSystemInterrupt(x,x)
jmp short Kei386EoiHelper@0 ; Kei386EoiHelper()
_KiUnexpectedInterruptTail endp
public HalEndSystemInterrupt
HalEndSystemInterrupt proc near ; CODE XREF: sub_80010EF0+E8↑p
; sub_80017144+B3↓p
; DATA XREF: ...
arg_0 = byte ptr 4
movzx ecx, [esp+arg_0]
cmp byte ptr ds:0FFDFF024h, 2
jbe short loc_8001123E
mov eax, ds:dword_800176EC[ecx*4]
or eax, ds:0FFDFF030h
out 21h, al ; Interrupt controller, 8259A.
shr eax, 8
out 0A1h, al ; Interrupt Controller #2, 8259A
loc_8001123E: ; CODE XREF: HalEndSystemInterrupt+C↑j
mov ds:0FFDFF024h, cl
mov eax, ds:0FFDFF028h
mov al, ds:byte_80017784[eax]
cmp al, cl
ja short loc_80011256
retn 8
; ---------------------------------------------------------------------------
loc_80011256: ; CODE XREF: HalEndSystemInterrupt+35↑j
add esp, 0Ch
jmp ds:pKiUnexpectedInterrupt[eax*4]
HalEndSystemInterrupt endp ; sp-analysis failed
pKiUnexpectedInterrupt dd offset KiUnexpectedInterrupt
; DATA XREF: HalEndSystemInterrupt+3D↑r
; sub_80011260+3D↑r
dd offset sub_80016BDD
dd offset sub_80016A45
sub_80016A45 proc near ; CODE XREF: KfLowerIrql:loc_800110AC↑p
; KfReleaseSpinLock:loc_8001111C↑p ...
push dword ptr ds:0FFDFF024h
mov byte ptr ds:0FFDFF024h, 2
and dword ptr ds:0FFDFF028h, 0FFFFFFFBh
sti
call ds:KiDispatchInterrupt
cli
call sub_80011260
jmp ds:Kei386EoiHelper
sub_80016A45 endp
; _DWORD __stdcall KiDispatchInterrupt()
public _KiDispatchInterrupt@0
_KiDispatchInterrupt@0 proc near ; DATA XREF: .edata:off_58D2A8↓o
var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
mov ebx, ds:0FFDFF01Ch ; a1
lea eax, [ebx+980h]
cli
cmp eax, [eax]
jz short loc_46A85E
push ebp
push dword ptr [ebx]
mov dword ptr [ebx], 0FFFFFFFFh
mov edx, esp
mov esp, [ebx+988h]
push edx
mov ebp, eax
call KiRetireDpcList
pop esp
pop dword ptr [ebx]
pop ebp
loc_46A85E: ; CODE XREF: KiDispatchInterrupt()+F↑j
sti
cmp dword ptr [ebx+9ACh], 0
jnz short loc_46A8BE
cmp dword ptr [ebx+128h], 0
jz short locret_46A8BD
mov eax, [ebx+128h]
loc_46A877: ; CODE XREF: KiDispatchInterrupt()+9F↓j
sub esp, 0Ch
mov [esp+0Ch+var_4], esi
mov [esp+0Ch+var_8], edi
mov [esp+0Ch+var_C], ebp
mov esi, eax ; NewThread
mov edi, [ebx+124h] ; oldThread
mov dword ptr [ebx+128h], 0
mov [ebx+124h], esi
mov ecx, edi
mov byte ptr [edi+50h], 1
call @KiReadyThread@4 ; KiReadyThread(x)
mov cl, 1
call SwapContext
mov ebp, [esp+0Ch+var_C]
mov edi, [esp+0Ch+var_8]
mov esi, [esp+0Ch+var_4]
add esp, 0Ch
locret_46A8BD: ; CODE XREF: KiDispatchInterrupt()+3F↑j
retn
; ---------------------------------------------------------------------------
loc_46A8BE: ; CODE XREF: KiDispatchInterrupt()+36↑j
mov dword ptr [ebx+9ACh], 0
call _KiQuantumEnd@0 ; KiQuantumEnd()
or eax, eax
jnz short loc_46A877
retn
_KiDispatchInterrupt@0 endp
进程挂靠
??在讲编程的时候,我们都听过:一个进程可以包含多个线程,一个进程至少要有一个线程。进程为线程提供资源,也就是提供Cr3
的值,Cr3
中存储的是页目录表基址,Cr3
确定了,线程能访问的内存也就确定了。
??对于这一行代码:mov eax,dword ptr ds:[0x12345678]
,CPU
如何解析这个地址呢?CPU
解析线性地址时要通过页目录表来找对应的物理页,页目录表基址存在Cr3
寄存器中。当前的Cr3
的值来源于当前的进程结构体的_KPROCESS.DirectoryTableBase
当中。那么进程挂靠又是怎么回事呢?我们先来看个结构体:
kd> dt _KTHREAD
ntdll!_KTHREAD
……
+0x032 Saturation : Char
+0x033 Priority : Char
+0x034 ApcState : _KAPC_STATE
+0x04c ContextSwitches : Uint4B
+0x050 IdleSwapBlock : UChar
……
kd> dt _KAPC_STATE
ntdll!_KAPC_STATE
+0x000 ApcListHead : [2] _LIST_ENTRY
+0x010 Process : Ptr32 _KPROCESS
+0x014 KernelApcInProgress : UChar
+0x015 KernelApcPending : UChar
+0x016 UserApcPending : UChar
??ApcState
这个成员我们在逆向线程切换的时候遇到过,也就是解决我们第2题的时候,这个结构体的Process
成员就是存储的进程挂靠上的进程CR3
。可以打一个比方,EPROCESS
的DirectoryTableBase
存储的是亲父母,而ApcState
存储的是养父母,我想要资源时从养父母来拿。正常情况下,CR3
的值是由养父母提供的,但CR3
的值也可以改成和当前线程毫不相干的其他进程的DirectoryTableBase
。将当前CR3
的值改为其他进程,称为“进程挂靠”。
跨进程内存读写
??跨进程内存读写根据之前所学肯定必须切换CR3
,并且读取内存肯定会落实到类似如下汇编:
mov eax,dword ptr ds:[0x12345678]
mov dword ptr ds:[0x00401234],eax
??我们自己实现一个跨进程内存读写一个int
还好说,如果是一个指定长度的Buffer
,那咋办呢?
??我们都知道,应用程序的高2G
的内核空间是共用的,也就是说,无论是哪个应用程序,高2G
的内容寻址都能寻到的。那么我把读取进程的内存写到高2G
的空间,然后切换CR3
回去,然后重新把高2G缓存的东西写到指定Buffer
中,我们就完成了。上面的读的操作,写得操作也是类似的。
跨进程读
跨进程写
??我们下面来简单分析一下Windows
实现的跨进程读内存的函数NtReadVirtualMemory
和跨进程写NtWriteVirtualMemory
的函数,NtWriteVirtualMemory
和NtReadVirtualMemory
实现十分相似,我就只分析前者,下面的自行分析。为什么说是浅析是因为里面有大量的其他前置知识,比如APC
和内存管理。三环怎么进内核的我就不再赘述了,为了方便。为了缩短篇幅增加可读性,我会尽可能使用IDA
翻译的伪代码,你的伪代码结果应该和我的不一样,因为我进行了一些重命名操作。
NtReadVirtualMemory 浅析
??我们先定位到NtReadVirtualMemory
这个伪代码:
NTSTATUS __stdcall NtReadVirtualMemory(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T NumberOfBytesToRead, PSIZE_T NumberOfBytesRead)
{
_KTHREAD *v5; // edi
PSIZE_T v6; // ebx
int a7; // [esp+10h] [ebp-28h] BYREF
PRKPROCESS PROCESS; // [esp+14h] [ebp-24h] BYREF
KPROCESSOR_MODE AccessMode[4]; // [esp+18h] [ebp-20h]
NTSTATUS res; // [esp+1Ch] [ebp-1Ch]
CPPEH_RECORD ms_exc; // [esp+20h] [ebp-18h]
v5 = KeGetCurrentThread();
AccessMode[0] = v5->PreviousMode;
if ( AccessMode[0] )
{
if ( BaseAddress + NumberOfBytesToRead < BaseAddress
|| Buffer + NumberOfBytesToRead < Buffer
|| BaseAddress + NumberOfBytesToRead > MmHighestUserAddress
|| Buffer + NumberOfBytesToRead > MmHighestUserAddress )
{
return 0xC0000005;
}
v6 = NumberOfBytesRead;
if ( NumberOfBytesRead )
{
ms_exc.registration.TryLevel = 0;
if ( NumberOfBytesRead >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
*NumberOfBytesRead = *NumberOfBytesRead;
ms_exc.registration.TryLevel = -1;
}
}
else
{
v6 = NumberOfBytesRead;
}
a7 = 0;
res = 0;
if ( NumberOfBytesToRead )
{
res = ObReferenceObjectByHandle(ProcessHandle, 0x10u, PsProcessType, AccessMode[0], &PROCESS, 0);
if ( !res )
{
res = MmCopyVirtualMemory(
PROCESS,
BaseAddress,
v5->ApcState.Process,
Buffer,
NumberOfBytesToRead,
AccessMode[0],
&a7);
ObfDereferenceObject(PROCESS);
}
}
if ( v6 )
{
*v6 = a7;
ms_exc.registration.TryLevel = -1;
}
return res;
}
??我们可以看到,该函数实现内存拷贝是通过MmCopyVirtualMemory
这个函数实现的,我们点击去看看:
NTSTATUS __stdcall MmCopyVirtualMemory(PRKPROCESS PROCESS, PVOID BaseAddress, PRKPROCESS KPROCESS, char *buffer, SIZE_T Length, KPROCESSOR_MODE AccessMode, int *a7)
{
PRKPROCESS process; // ebx
_EPROCESS *eprocess; // ecx
NTSTATUS res; // esi
_EX_RUNDOWN_REF *RunRefa; // [esp+8h] [ebp+8h]
if ( !Length )
return 0;
process = PROCESS;
eprocess = PROCESS;
if ( PROCESS == KeGetCurrentThread()->ApcState.Process )
eprocess = KPROCESS;
RunRefa = &eprocess->RundownProtect;
if ( !ExAcquireRundownProtection(&eprocess->RundownProtect) )
return STATUS_PROCESS_IS_TERMINATING;
if ( Length <= 0x1FF )
goto LABEL_10;
res = MiDoMappedCopy(process, BaseAddress, KPROCESS, buffer, Length, AccessMode, a7);
if ( res == STATUS_WORKING_SET_QUOTA )
{
*a7 = 0;
LABEL_10:
res = MiDoPoolCopy(process, BaseAddress, KPROCESS, buffer, Length, AccessMode, a7);
}
ExReleaseRundownProtection(RunRefa);
return res;
}
??你可能看到一个新奇的函数ExAcquireRundownProtection
,这个函数是申请一个锁,从网上查阅翻译过来是停运保护(RundownProtection
)锁,名字怪怪的听起来怪怪的。
??这个不涉及我们的核心,我们继续分析,发现它内部又是通过MiDoMappedCopy
实现进程内存读取的:
NTSTATUS __stdcall MiDoMappedCopy(PRKPROCESS PROCESS, char *src, PRKPROCESS process, char *buffer, SIZE_T Length, KPROCESSOR_MODE AccessMode, int *a7)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v13 = 0;
src_0 = src;
buffer_1 = buffer;
v7 = 0xE000;
if ( Length <= 0xE000 )
v7 = Length;
v16 = &MemoryDescriptorList;
Length_1 = Length;
v19 = v7;
v20 = 0;
v14 = 0;
v15 = 0;
while ( Length_1 )
{
if ( Length_1 < v19 )
v19 = Length_1;
KeStackAttachProcess(PROCESS, &ApcState);
BaseAddress = 0;
v12 = 0;
v11 = 0;
ms_exc.registration.TryLevel = 0;
if ( src_0 == src && AccessMode )
{
v20 = 1;
if ( Length && (&src[Length] < src || &src[Length] > MmUserProbeAddress) )
ExRaiseAccessViolation();
v20 = 0;
}
MemoryDescriptorList.Next = 0;
MemoryDescriptorList.Size = 4 * (((src_0 & 0xFFF) + v19 + 0xFFF) >> 12) + 28;
MemoryDescriptorList.MdlFlags = 0;
MemoryDescriptorList.StartVa = (src_0 & 0xFFFFF000);
MemoryDescriptorList.ByteOffset = src_0 & 0xFFF;
MemoryDescriptorList.ByteCount = v19;
MmProbeAndLockPages(&MemoryDescriptorList, AccessMode, IoReadAccess);
v12 = 1;
BaseAddress = MmMapLockedPagesSpecifyCache(&MemoryDescriptorList, 0, MmCached, 0u, 0u, 0x20u);
if ( !BaseAddress )
{
v13 = 1;
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
}
KeUnstackDetachProcess(&ApcState);
KeStackAttachProcess(process, &ApcState);
if ( src_0 == src )
{
if ( AccessMode )
{
v20 = 1;
ProbeForWrite(buffer, Length, 1u);
v20 = 0;
}
}
v11 = 1;
qmemcpy(buffer_1, BaseAddress, v19);
ms_exc.registration.TryLevel = -1;
KeUnstackDetachProcess(&ApcState);
MmUnmapLockedPages(BaseAddress, &MemoryDescriptorList);
MmUnlockPages(&MemoryDescriptorList);
Length_1 -= v19;
src_0 += v19;
buffer_1 += v19;
}
*a7 = Length;
return STATUS_SUCCESS;
}
??KeStackAttachProcess
和KeUnstackDetachProcess
这俩函数与APC
相关,在这里你可以简单理解就是切换CR3
,实现进程挂靠和解除挂靠。我们注意一下下面的伪代码:
MemoryDescriptorList.Next = 0;
MemoryDescriptorList.Size = 4 * (((src_0 & 0xFFF) + v19 + 0xFFF) >> 12) + 28;
MemoryDescriptorList.MdlFlags = 0;
MemoryDescriptorList.StartVa = (src_0 & 0xFFFFF000);
MemoryDescriptorList.ByteOffset = src_0 & 0xFFF;
MemoryDescriptorList.ByteCount = v19;
MmProbeAndLockPages(&MemoryDescriptorList, AccessMode, IoReadAccess);
v12 = 1;
BaseAddress = MmMapLockedPagesSpecifyCache(&MemoryDescriptorList, 0, MmCached, 0u, 0u, 0x20u);
??MmMapLockedPagesSpecifyCache
这个函数就是映射里面描述的物理页,下面是微软对该函数的描述:
The MmMapLockedPagesSpecifyCache routine maps the physical pages that are described by an MDL to a virtual address, and enables the caller to specify the cache attribute that is used to create the mapping.
??上面的操作就算锁住物理页,并把它重新映射到高2G
地址,我们直接写到里面,就少了重新把高2G
的内容重新写到程序空间的步骤了。
进程创建浅析
??同理上面的浅析,我同样使用伪代码。进程创建浅析是为了知道内核是怎样创建进程的流程,具体细节请自行挖掘,我们定位到其内核函数NtCreateProcess
:
NTSTATUS __stdcall NtCreateProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, BOOLEAN InheritObjectTable, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort)
{
ULONG v8; // eax
v8 = (SectionHandle & 1) != 0;
if ( (DebugPort & 1) != 0 )
v8 |= 2u;
if ( InheritObjectTable )
v8 |= 4u;
return NtCreateProcessEx(
ProcessHandle,
DesiredAccess,
ObjectAttributes,
ParentProcess,
v8,
SectionHandle,
DebugPort,
ExceptionPort,
0);
}
??这个内核函数又会调用NtCreateProcessEx
实现功能,我们点进去看看:
NTSTATUS __stdcall NtCreateProcessEx(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, BOOLEAN InJob)
{
PHANDLE v9; // ecx
NTSTATUS result; // eax
if ( KeGetCurrentThread()->PreviousMode )
{
v9 = ProcessHandle;
if ( ProcessHandle >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
*ProcessHandle = *ProcessHandle;
}
else
{
v9 = ProcessHandle;
}
if ( ParentProcess )
result = PspCreateProcess(
v9,
DesiredAccess,
ObjectAttributes,
ParentProcess,
Flags,
SectionHandle,
DebugPort,
ExceptionPort,
InJob);
else
result = STATUS_INVALID_PARAMETER;
return result;
}
??这个函数又会调用PspCreateProcess
实现创建进程的任务,继续点击去看看:
?? 点击查看伪代码 ??
NTSTATUS __stdcall PspCreateProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ParentProcess, ULONG Flags, HANDLE SectionHandle, HANDLE DebugPort, HANDLE ExceptionPort, BOOLEAN InJob)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v41 = KeGetCurrentThread();
AccessMode[0] = v41->PreviousMode;
process = v41->ApcState.Process;
v64 = 0;
a4[0] = 0;
a4[1] = 0;
if ( (Flags & 0xFFFFFFF0) != 0 )
return STATUS_INVALID_PARAMETER;
if ( ParentProcess )
{
result = ObReferenceObjectByHandle(ParentProcess, 0x80u, PsProcessType, AccessMode[0], &eprocess, 0);
ParentProcess_1 = eprocess;
if ( result < 0 )
return result;
if ( InJob && !eprocess->Job )
{
ObfDereferenceObject(eprocess);
return STATUS_INVALID_PARAMETER;
}
ActiveProcessors = eprocess->Pcb.Affinity;
}
else
{
ParentProcess_1 = 0;
ActiveProcessors = KeActiveProcessors;
}
a3 = ActiveProcessors;
*NewIrql = PsMinimumWorkingSet;
v47 = PsMaximumWorkingSet;
v11 = ObCreateObject(AccessMode[0], PsProcessType, ObjectAttributes, *AccessMode, 0, 608, 0, 0, &Process);
if ( v11 < 0 )
goto LABEL_97;
NewProcess = Process;
memset(Process, 0, 0x260u);
NewProcess->RundownProtect.Count = 0;
NewProcess->ProcessLock.Value = 0;
InitializeListHead(&NewProcess->ThreadListHead);
PspInheritQuota(&NewProcess->Pcb, &ParentProcess_1->Pcb);
ObInheritDeviceMap(NewProcess, ParentProcess_1);
v13 = ParentProcess_1;
if ( ParentProcess_1 )
{
NewProcess->DefaultHardErrorProcessing = ParentProcess_1->DefaultHardErrorProcessing;
NewProcess->InheritedFromUniqueProcessId = v13->UniqueProcessId;
}
else
{
NewProcess->DefaultHardErrorProcessing = 1;
NewProcess->InheritedFromUniqueProcessId = 0;
}
if ( SectionHandle )
{
v14 = ObReferenceObjectByHandle(SectionHandle, 8u, MmSectionObjectType, AccessMode[0], &Object, 0);
v61 = Object;
v11 = v14;
if ( v14 < 0 )
goto LABEL_96;
v13 = ParentProcess_1;
}
else
{
v61 = 0;
if ( v13 != PsInitialSystemProcess )
{
if ( ExAcquireRundownProtection(&v13->RundownProtect) )
{
v15 = v13->SectionObject;
v61 = v15;
if ( v15 )
ObfReferenceObject(v15);
ExReleaseRundownProtection(&v13->RundownProtect);
}
if ( !v61 )
{
v11 = STATUS_PROCESS_IS_TERMINATING;
goto LABEL_96;
}
}
}
NewProcess->SectionObject = v61;
if ( DebugPort )
{
v11 = ObReferenceObjectByHandle(DebugPort, 2u, DbgkDebugObjectType, AccessMode[0], &v44, 0);
if ( v11 < 0 )
goto LABEL_96;
NewProcess->DebugPort = v44;
if ( (Flags & 2) != 0 )
_InterlockedOr(&NewProcess->584, 2u);
}
else if ( v13 )
{
DbgkCopyProcessDebugPort(NewProcess, v13);
}
if ( ExceptionPort )
{
v11 = ObReferenceObjectByHandle(ExceptionPort, 0, LpcPortObjectType, AccessMode[0], &v45, 0);
if ( v11 < 0 )
goto LABEL_96;
NewProcess->ExceptionPort = v45;
}
NewProcess->ExitStatus = STATUS_PENDING;
v11 = PspInitializeProcessSecurity(&ParentProcess_1->Pcb, NewProcess);
if ( v11 < 0 )
goto LABEL_96;
v16 = ParentProcess_1;
if ( ParentProcess_1 )
{
if ( !MmCreateProcessAddressSpace(*NewIrql, NewProcess, a4) )
goto LABEL_59;
}
else
{
NewProcess->ObjectTable = process->ObjectTable;
MmInitializeHandBuiltProcess(NewProcess, a4);
}
_InterlockedOr(&NewProcess->584, 0x40000u);
NewProcess->Vm.MaximumWorkingSetSize = v47;
KeInitializeProcess(&NewProcess->Pcb, 8, a3, a4, NewProcess->DefaultHardErrorProcessing & 4);
NewProcess->Pcb.ThreadQuantum = PspForegroundQuantum;
NewProcess->PriorityClass = 2;
if ( v16 )
{
v17 = v16->PriorityClass;
if ( v17 == 1 || v17 == 5 )
NewProcess->PriorityClass = v17;
v18 = ObInitProcess((Flags & 4) != 0 ? ParentProcess_1 : 0, NewProcess);
}
else
{
v18 = MmInitializeHandBuiltProcess2(&NewProcess->Pcb);
}
v11 = v18;
if ( v18 < 0 )
goto LABEL_96;
v58 = 0;
if ( SectionHandle )
{
v19 = MmInitializeProcessAddressSpace(&NewProcess->Pcb, 0, v61, &NewProcess->SeAuditProcessCreationInfo);
v11 = v19;
if ( v19 < 0 )
goto LABEL_96;
v58 = v19;
v11 = PspMapSystemDll(&NewProcess->Pcb, 0);
if ( v11 < 0 )
goto LABEL_96;
v64 = 1;
goto LABEL_58;
}
v20 = ParentProcess_1;
if ( !ParentProcess_1 )
goto LABEL_58;
if ( ParentProcess_1 == PsInitialSystemProcess )
{
v11 = MmInitializeProcessAddressSpace(&NewProcess->Pcb, 0, 0, 0);
if ( v11 >= 0 )
{
v24 = ExAllocatePoolWithTag(PagedPool, 8u, 0x61506553u);
NewProcess->SeAuditProcessCreationInfo.ImageFileName = v24;
if ( v24 )
{
*&v24->Name.Length = 0;
v24->Name.Buffer = 0;
goto LABEL_58;
}
goto LABEL_59;
}
LABEL_96:
ObfDereferenceObject(NewProcess);
goto LABEL_97;
}
NewProcess->SectionBaseAddress = ParentProcess_1->SectionBaseAddress;
v11 = MmInitializeProcessAddressSpace(&NewProcess->Pcb, v20, 0, 0);
v64 = 1;
if ( v11 < 0 )
goto LABEL_96;
v21 = ParentProcess_1->SeAuditProcessCreationInfo.ImageFileName;
if ( v21 )
{
v22 = v21->Name.MaximumLength + 8;
v23 = ExAllocatePoolWithTag(PagedPool, v22, 0x61506553u);
NewProcess->SeAuditProcessCreationInfo.ImageFileName = v23;
if ( v23 )
{
qmemcpy(v23, ParentProcess_1->SeAuditProcessCreationInfo.ImageFileName, v22);
NewProcess->SeAuditProcessCreationInfo.ImageFileName->Name.Buffer = &NewProcess->SeAuditProcessCreationInfo.ImageFileName[1].Name.Length;
goto LABEL_58;
}
LABEL_59:
v11 = STATUS_INSUFFICIENT_RESOURCES;
goto LABEL_96;
}
LABEL_58:
v25 = (NewProcess->Token.Value & 0xFFFFFFF8);
v26 = MmGetSessionId(NewProcess);
SeSetSessionIdToken(v25, v26);
v46[0] = NewProcess;
v46[1] = 0;
v27 = ExCreateHandle(PspCidTable, v46);
NewProcess->UniqueProcessId = v27;
if ( !v27 )
goto LABEL_59;
NewProcess->ObjectTable->UniqueProcessId = v27;
if ( SeDetailedAuditingWithToken(0) )
SeAuditProcessCreation(&NewProcess->Pcb);
if ( ParentProcess_1 )
{
v28 = ParentProcess_1->Job;
if ( v28 )
{
if ( (v28->LimitFlags & 0x1000) == 0 )
{
if ( (Flags & 1) != 0 )
{
v11 = (v28->LimitFlags & 0x800) != 0 ? 0 : STATUS_ACCESS_DENIED;
}
else
{
v11 = PspGetJobFromSet(v28, InJob, &NewProcess->Job);
if ( v11 < 0 )
goto LABEL_96;
v43 = NewProcess->Job;
v11 = PspAddProcessToJob(v43, &NewProcess->Pcb);
v29 = v43->Token;
if ( v29 )
{
v11 = SeSubProcessToken(v29, &v50, 0);
if ( v11 < 0 )
goto LABEL_96;
SeAssignPrimaryToken(&NewProcess->Pcb, v50);
ObfDereferenceObject(v50);
}
}
if ( v11 < 0 )
goto LABEL_96;
}
}
}
if ( ParentProcess_1 && v64 )
{
BaseAddress[0] = 0;
BaseAddress[1] = -1;
if ( SectionHandle )
{
v11 = MmCreatePeb(&NewProcess->Pcb, BaseAddress, &NewProcess->Peb);
if ( v11 < 0 )
{
NewProcess->Peb = 0;
goto LABEL_96;
}
}
else
{
LOBYTE(BaseAddress[0]) = 1;
v30 = ParentProcess_1->Peb;
NewProcess->Peb = v30;
MmCopyVirtualMemory(&process->Pcb, BaseAddress, &NewProcess->Pcb, v30, 8u, 0, &a7a);
}
}
v31 = v41;
--v41->KernelApcDisable;
ExAcquireFastMutexUnsafe(&PspActiveProcessMutex);
v32 = dword_48315C;
NewProcess->ActiveProcessLinks.Flink = &PsActiveProcessHead;
NewProcess->ActiveProcessLinks.Blink = v32;
v32->Flink = &NewProcess->ActiveProcessLinks;
dword_48315C = &NewProcess->ActiveProcessLinks;
ExReleaseFastMutexUnsafe(&PspActiveProcessMutex);
v34 = (*(v31 + 212))++ == -1;
if ( v34 && *(v31 + 52) != v31 + 52 )
{
*(v31 + 73) = 1;
LOBYTE(v33) = 1;
HalRequestSoftwareInterrupt(v33);
}
if ( !ParentProcess_1 || (v35 = PsInitialSystemProcess, ParentProcess_1 != PsInitialSystemProcess) )
v35 = *(v31 + 68);
v11 = SeCreateAccessStateEx(0, v35, &PassedAccessState, v37, DesiredAccess, (PsProcessType + 26));
if ( v11 < 0 )
goto LABEL_96;
v11 = ObInsertObject(NewProcess, &PassedAccessState, DesiredAccess, 1u, 0, &Handle);
v53 = v11;
SeDeleteAccessState(&PassedAccessState);
if ( v11 >= 0 )
{
NewProcess->GrantedAccess = 1;
PsSetProcessPriorityByClass(NewProcess, 0);
if ( !ParentProcess_1 || ParentProcess_1 == PsInitialSystemProcess )
{
NewProcess->GrantedAccess = 2035711;
}
else
{
v11 = ObGetObjectSecurity(NewProcess, &SecurityDescriptor, MemoryAllocated);
v53 = v11;
if ( v11 < 0 )
{
ObCloseHandle(Handle, AccessMode[0]);
goto LABEL_96;
}
SubjectSecurityContext.ProcessAuditId = NewProcess;
SubjectSecurityContext.PrimaryToken = PsReferencePrimaryToken(&NewProcess->Pcb);
SubjectSecurityContext.ClientToken = 0;
v62 = SeAccessCheck(
SecurityDescriptor,
&SubjectSecurityContext,
0,
0x2000000u,
0,
0,
(PsProcessType + 26),
AccessMode[0],
&NewProcess->GrantedAccess,
&AccessStatus);
ObFastDereferenceObject(&NewProcess->Token, SubjectSecurityContext.PrimaryToken);
ObReleaseObjectSecurity(SecurityDescriptor, MemoryAllocated[0]);
if ( !v62 )
NewProcess->GrantedAccess = 0;
NewProcess->GrantedAccess |= 0x1F07FBu;
}
KeQuerySystemTime(&NewProcess->CreateTime);
*ProcessHandle = Handle;
ms_exc.registration.TryLevel = -1;
if ( v58 )
v11 = v58;
goto LABEL_96;
}
LABEL_97:
if ( ParentProcess_1 )
ObfDereferenceObject(ParentProcess_1);
return v11;
}
??我们都知道,创建进程的时候一定会创建一个线程,被称之为主线程。上面都是初始化进程结构体,创建TEB
等操作,线程在哪里创建的呢?是因为这个是在3环调用的,我们来看看3环长啥样子:
BOOL __stdcall CreateProcessW(LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation)
{
return CreateProcessInternalW(
0,
lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation,
0);
}
??这个函数又调用了CreateProcessInternalW
这个函数,我们来继续点击去看看,创建线程的函数就在这里面,但是为了节省篇幅,我们就给出了局部代码:
BaseInitializeContext(&ThreadContext, v157, SectionInformation, UserStack.StackBase, 0);
v161 = BaseFormatObjectAttributes(&ObjectAttributes, v141, 0);
if ( v184 && v160 && v141 )
{
DirectoryInfo.CurDirRef = v141->nLength;
v200 = v141->lpSecurityDescriptor;
v201 = v141->bInheritHandle;
v200 = 0;
v161 = BaseFormatObjectAttributes(&ObjectAttributes, &DirectoryInfo.CurDirRef, 0);
}
v20 = NtCreateThread(
&ThreadHandle,
0x1F03FFu,
v161,
ProcessHandle,
&ClientId,
&ThreadContext,
&UserStack,
1u);
进程结束浅析
??结束函数没啥可分析了,给出如下IDA
伪代码:
NTSTATUS __stdcall NtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v2 = KeGetCurrentThread();
v3 = v2;
v8 = v2->ApcState.Process;
if ( ProcessHandle )
{
v11 = 1;
}
else
{
ProcessHandle = -1;
v11 = 0;
}
LOBYTE(AccessMode) = v2->PreviousMode;
result = ObReferenceObjectByHandle(ProcessHandle, 1u, PsProcessType, AccessMode, &AccessMode, 0);
v5 = AccessMode;
v6 = AccessMode;
if ( result >= 0 )
{
ProcessHandlea = &AccessMode[146];
if ( (AccessMode[146].Count & 0x2000) != 0 )
PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n", AccessMode, &AccessMode[93]);
RunRef = v5 + 32;
if ( ExAcquireRundownProtection(v5 + 32) )
{
if ( v11 )
_InterlockedOr(ProcessHandlea, 8u);
ProcessHandleb = 290;
v7 = PsGetNextProcessThread(v6, 0);
if ( v7 )
{
ProcessHandleb = 0;
do
{
if ( v7 != v3 )
PspTerminateThreadByPointer(v7, ExitStatus);
v7 = PsGetNextProcessThread(v6, v7);
}
while ( v7 );
}
ExReleaseRundownProtection(RunRef);
if ( v6 == v8 )
{
if ( v11 )
{
ObfDereferenceObject(v6);
PspTerminateThreadByPointer(v3, ExitStatus);
}
}
else if ( ExitStatus == DBG_TERMINATE_PROCESS )
{
DbgkClearProcessDebugObject(v6, 0);
}
if ( ProcessHandleb == 290 || v6[1].ThreadListHead.Flink && v11 )
{
ObClearProcessHandleTable(v6);
ProcessHandleb = 0;
}
ObfDereferenceObject(v6);
result = ProcessHandleb;
}
else
{
ObfDereferenceObject(v5);
result = STATUS_PROCESS_IS_TERMINATING;
}
}
return result;
}
??可以说,结束进程,也就是把它的所有的线程全部干掉,删掉进程相关记录,进程就被杀死了。
进程线程结构体扩展
??之前介绍进程线程相关结构体的时候主要介绍关键的成员,但是有些成员是在说明信息的时候还是比较重要的,或者没啥用处做个记录的,这里再重新补充一下,仅供了解:
KPROCESS
??其结构体如下所示:
kd> dt _KPROCESS
ntdll!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x010 ProfileListHead : _LIST_ENTRY
+0x018 DirectoryTableBase : [2] Uint4B
+0x020 LdtDescriptor : _KGDTENTRY
+0x028 Int21Descriptor : _KIDTENTRY
+0x030 IopmOffset : Uint2B
+0x032 Iopl : UChar
+0x033 Unused : UChar
+0x034 ActiveProcessors : Uint4B
+0x038 KernelTime : Uint4B
+0x03c UserTime : Uint4B
+0x040 ReadyListHead : _LIST_ENTRY
+0x048 SwapListEntry : _SINGLE_LIST_ENTRY
+0x04c VdmTrapcHandler : Ptr32 Void
+0x050 ThreadListHead : _LIST_ENTRY
+0x058 ProcessLock : Uint4B
+0x05c Affinity : Uint4B
+0x060 StackCount : Uint2B
+0x062 BasePriority : Char
+0x063 ThreadQuantum : Char
+0x064 AutoAlignment : UChar
+0x065 State : UChar
+0x066 ThreadSeed : UChar
+0x067 DisableBoost : UChar
+0x068 PowerState : UChar
+0x069 DisableQuantum : UChar
+0x06a IdealNode : UChar
+0x06b Flags : _KEXECUTE_OPTIONS
+0x06b ExecuteOptions : UChar
ProfileListHead
??性能分析相关,一般操作系统会自动处理,如下图所示,没啥用处:
ActiveProcessors
??当前活动的处理器,表示在哪个核运行。
ReadyListHead
??该进程处于就绪状态的所有进程链表。
SwapListEntry
??被交换到磁盘上的进程的链表,如果进程被交换出去就会挂到这里。
ThreadListHead
??当前进程下的所有线程的链表。
ProcessLock
??进程锁,用于同步,防止被同时修改用的,给操作系统用的。
ThreadQuantum
??线程默认的时间碎片。
State
??表示进程交换到磁盘和内存的状态。
ThreadSeed
??指示Affinity
陈述的亲核性最理想亲的核。
EPROCESS
??其结构体如下所示:
kd> dt _EPROCESS
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
+0x070 CreateTime : _LARGE_INTEGER
+0x078 ExitTime : _LARGE_INTEGER
+0x080 RundownProtect : _EX_RUNDOWN_REF
+0x084 UniqueProcessId : Ptr32 Void
+0x088 ActiveProcessLinks : _LIST_ENTRY
+0x090 QuotaUsage : [3] Uint4B
+0x09c QuotaPeak : [3] Uint4B
+0x0a8 CommitCharge : Uint4B
+0x0ac PeakVirtualSize : Uint4B
+0x0b0 VirtualSize : Uint4B
+0x0b4 SessionProcessLinks : _LIST_ENTRY
+0x0bc DebugPort : Ptr32 Void
+0x0c0 ExceptionPort : Ptr32 Void
+0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0c8 Token : _EX_FAST_REF
+0x0cc WorkingSetLock : _FAST_MUTEX
+0x0ec WorkingSetPage : Uint4B
+0x0f0 AddressCreationLock : _FAST_MUTEX
+0x110 HyperSpaceLock : Uint4B
+0x114 ForkInProgress : Ptr32 _ETHREAD
+0x118 HardwareTrigger : Uint4B
+0x11c VadRoot : Ptr32 Void
+0x120 VadHint : Ptr32 Void
+0x124 CloneRoot : Ptr32 Void
+0x128 NumberOfPrivatePages : Uint4B
+0x12c NumberOfLockedPages : Uint4B
+0x130 Win32Process : Ptr32 Void
+0x134 Job : Ptr32 _EJOB
+0x138 SectionObject : Ptr32 Void
+0x13c SectionBaseAddress : Ptr32 Void
+0x140 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x144 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
+0x148 Win32WindowStation : Ptr32 Void
+0x14c InheritedFromUniqueProcessId : Ptr32 Void
+0x150 LdtInformation : Ptr32 Void
+0x154 VadFreeHint : Ptr32 Void
+0x158 VdmObjects : Ptr32 Void
+0x15c DeviceMap : Ptr32 Void
+0x160 PhysicalVadList : _LIST_ENTRY
+0x168 PageDirectoryPte : _HARDWARE_PTE_X86
+0x168 Filler : Uint8B
+0x170 Session : Ptr32 Void
+0x174 ImageFileName : [16] UChar
+0x184 JobLinks : _LIST_ENTRY
+0x18c LockedPagesList : Ptr32 Void
+0x190 ThreadListHead : _LIST_ENTRY
+0x198 SecurityPort : Ptr32 Void
+0x19c PaeTop : Ptr32 Void
+0x1a0 ActiveThreads : Uint4B
+0x1a4 GrantedAccess : Uint4B
+0x1a8 DefaultHardErrorProcessing : Uint4B
+0x1ac LastThreadExitStatus : Int4B
+0x1b0 Peb : Ptr32 _PEB
+0x1b4 PrefetchTrace : _EX_FAST_REF
+0x1b8 ReadOperationCount : _LARGE_INTEGER
+0x1c0 WriteOperationCount : _LARGE_INTEGER
+0x1c8 OtherOperationCount : _LARGE_INTEGER
+0x1d0 ReadTransferCount : _LARGE_INTEGER
+0x1d8 WriteTransferCount : _LARGE_INTEGER
+0x1e0 OtherTransferCount : _LARGE_INTEGER
+0x1e8 CommitChargeLimit : Uint4B
+0x1ec CommitChargePeak : Uint4B
+0x1f0 AweInfo : Ptr32 Void
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm : _MMSUPPORT
+0x238 LastFaultCount : Uint4B
+0x23c ModifiedPageCount : Uint4B
+0x240 NumberOfVads : Uint4B
+0x244 JobStatus : Uint4B
+0x248 Flags : Uint4B
+0x248 CreateReported : Pos 0, 1 Bit
+0x248 NoDebugInherit : Pos 1, 1 Bit
+0x248 ProcessExiting : Pos 2, 1 Bit
+0x248 ProcessDelete : Pos 3, 1 Bit
+0x248 Wow64SplitPages : Pos 4, 1 Bit
+0x248 VmDeleted : Pos 5, 1 Bit
+0x248 OutswapEnabled : Pos 6, 1 Bit
+0x248 Outswapped : Pos 7, 1 Bit
+0x248 ForkFailed : Pos 8, 1 Bit
+0x248 HasPhysicalVad : Pos 9, 1 Bit
+0x248 AddressSpaceInitialized : Pos 10, 2 Bits
+0x248 SetTimerResolution : Pos 12, 1 Bit
+0x248 BreakOnTermination : Pos 13, 1 Bit
+0x248 SessionCreationUnderway : Pos 14, 1 Bit
+0x248 WriteWatch : Pos 15, 1 Bit
+0x248 ProcessInSession : Pos 16, 1 Bit
+0x248 OverrideAddressSpace : Pos 17, 1 Bit
+0x248 HasAddressSpace : Pos 18, 1 Bit
+0x248 LaunchPrefetched : Pos 19, 1 Bit
+0x248 InjectInpageErrors : Pos 20, 1 Bit
+0x248 VmTopDown : Pos 21, 1 Bit
+0x248 Unused3 : Pos 22, 1 Bit
+0x248 Unused4 : Pos 23, 1 Bit
+0x248 VdmAllowed : Pos 24, 1 Bit
+0x248 Unused : Pos 25, 5 Bits
+0x248 Unused1 : Pos 30, 1 Bit
+0x248 Unused2 : Pos 31, 1 Bit
+0x24c ExitStatus : Int4B
+0x250 NextPageColor : Uint2B
+0x252 SubSystemMinorVersion : UChar
+0x253 SubSystemMajorVersion : UChar
+0x252 SubSystemVersion : Uint2B
+0x254 PriorityClass : UChar
+0x255 WorkingSetAcquiredUnsafe : UChar
+0x258 Cookie : Uint4B
RundownProtect
??进程停运保护,可以防止他人杀死进程。
SessionProcessLinks
??进程的会话子系统相关。
Token
??该进程的令牌,与安全相关。
InheritedFromUniqueProcessId
??指示被谁创建该进程的pid
,这东西比较有用,可以找到父进程。
SeAuditProcessCreationInfo
??通过这个可以获取进程的全路径,我们举个例子:
kd> dt _EPROCESS 89a9b648
ntdll!_EPROCESS
……
+0x1f0 AweInfo : (null)
+0x1f4 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f8 Vm : _MMSUPPORT
……
kd> dx -id 0,0,805539a0 -r1 (*((ntdll!_SE_AUDIT_PROCESS_CREATION_INFO *)0x89a9b83c))
(*((ntdll!_SE_AUDIT_PROCESS_CREATION_INFO *)0x89a9b83c)) [Type: _SE_AUDIT_PROCESS_CREATION_INFO]
[+0x000] ImageFileName : 0x89cc8a18 [Type: _OBJECT_NAME_INFORMATION *]
kd> dx -id 0,0,805539a0 -r1 ((ntdll!_OBJECT_NAME_INFORMATION *)0x89cc8a18)
((ntdll!_OBJECT_NAME_INFORMATION *)0x89cc8a18) : 0x89cc8a18 [Type: _OBJECT_NAME_INFORMATION *]
[+0x000] Name : "\Device\HarddiskVolume1\Program Files\PalmInput\Extensions\Guard\2.6.0.49\PalmInputGuard.exe" [Type: _UNICODE_STRING]
Flags
??指示进程的状态,比较有用。
SubSystemMinorVersion / SubSystemMajorVersion
??指示支持的子系统版本,在PE
文件的信息中是有描述的。
ExitStatus
??进程退出状态。
ReadOperationCount
??调用ReadFile
的次数。
WriteOperationCount
??调用WriteFile
的次数。
OtherOperationCount
??调用其他与IO
读写文件相关的API
次数。
KTHREAD
??其结构体如下所示:
kd> dt _KTHREAD
ntdll!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY
+0x018 InitialStack : Ptr32 Void
+0x01c StackLimit : Ptr32 Void
+0x020 Teb : Ptr32 Void
+0x024 TlsArray : Ptr32 Void
+0x028 KernelStack : Ptr32 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 : _KAPC_STATE
+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 _KWAIT_BLOCK
+0x060 WaitListEntry : _LIST_ENTRY
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
+0x068 WaitTime : Uint4B
+0x06c BasePriority : Char
+0x06d DecrementCount : UChar
+0x06e PriorityDecrement : Char
+0x06f Quantum : Char
+0x070 WaitBlock : [4] _KWAIT_BLOCK
+0x0d0 LegoData : Ptr32 Void
+0x0d4 KernelApcDisable : Uint4B
+0x0d8 UserAffinity : Uint4B
+0x0dc SystemAffinityActive : UChar
+0x0dd PowerState : UChar
+0x0de NpxIrql : UChar
+0x0df InitialNode : UChar
+0x0e0 ServiceTable : Ptr32 Void
+0x0e4 Queue : Ptr32 _KQUEUE
+0x0e8 ApcQueueLock : Uint4B
+0x0f0 Timer : _KTIMER
+0x118 QueueListEntry : _LIST_ENTRY
+0x120 SoftAffinity : Uint4B
+0x124 Affinity : Uint4B
+0x128 Preempted : UChar
+0x129 ProcessReadyQueue : UChar
+0x12a KernelStackResident : UChar
+0x12b NextProcessor : UChar
+0x12c CallbackStack : Ptr32 Void
+0x130 Win32Thread : Ptr32 Void
+0x134 TrapFrame : Ptr32 _KTRAP_FRAME
+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE
+0x140 PreviousMode : Char
+0x141 EnableStackSwap : UChar
+0x142 LargeStack : UChar
+0x143 ResourceIndex : UChar
+0x144 KernelTime : Uint4B
+0x148 UserTime : Uint4B
+0x14c SavedApcState : _KAPC_STATE
+0x164 Alertable : UChar
+0x165 ApcStateIndex : UChar
+0x166 ApcQueueable : UChar
+0x167 AutoAlignment : UChar
+0x168 StackBase : Ptr32 Void
+0x16c SuspendApc : _KAPC
+0x19c SuspendSemaphore : _KSEMAPHORE
+0x1b0 ThreadListEntry : _LIST_ENTRY
+0x1b8 FreezeCount : Char
+0x1b9 SuspendCount : Char
+0x1ba IdealProcessor : UChar
+0x1bb DisableBoost : UChar
MutantListHead
??互斥体链表相关,具体细节在同步篇进行讲解。
Alertable
??指示线程是否被唤醒,具体细节在APC
篇进行讲解。
Priority
??线程优先级。
EnableStackSwap
??指示是否能将堆栈最为文件交换
ETHREAD
??结构体如下所示:
kd> dt _ETHREAD
ntdll!_ETHREAD
+0x000 Tcb : _KTHREAD
+0x1c0 CreateTime : _LARGE_INTEGER
+0x1c0 NestedFaultCount : Pos 0, 2 Bits
+0x1c0 ApcNeeded : Pos 2, 1 Bit
+0x1c8 ExitTime : _LARGE_INTEGER
+0x1c8 LpcReplyChain : _LIST_ENTRY
+0x1c8 KeyedWaitChain : _LIST_ENTRY
+0x1d0 ExitStatus : Int4B
+0x1d0 OfsChain : Ptr32 Void
+0x1d4 PostBlockList : _LIST_ENTRY
+0x1dc TerminationPort : Ptr32 _TERMINATION_PORT
+0x1dc ReaperLink : Ptr32 _ETHREAD
+0x1dc KeyedWaitValue : Ptr32 Void
+0x1e0 ActiveTimerListLock : Uint4B
+0x1e4 ActiveTimerListHead : _LIST_ENTRY
+0x1ec Cid : _CLIENT_ID
+0x1f4 LpcReplySemaphore : _KSEMAPHORE
+0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
+0x208 LpcReplyMessage : Ptr32 Void
+0x208 LpcWaitingOnPort : Ptr32 Void
+0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
+0x210 IrpList : _LIST_ENTRY
+0x218 TopLevelIrp : Uint4B
+0x21c DeviceToVerify : Ptr32 _DEVICE_OBJECT
+0x220 ThreadsProcess : Ptr32 _EPROCESS
+0x224 StartAddress : Ptr32 Void
+0x228 Win32StartAddress : Ptr32 Void
+0x228 LpcReceivedMessageId : Uint4B
+0x22c ThreadListEntry : _LIST_ENTRY
+0x234 RundownProtect : _EX_RUNDOWN_REF
+0x238 ThreadLock : _EX_PUSH_LOCK
+0x23c LpcReplyMessageId : Uint4B
+0x240 ReadClusterSize : Uint4B
+0x244 GrantedAccess : Uint4B
+0x248 CrossThreadFlags : Uint4B
+0x248 Terminated : Pos 0, 1 Bit
+0x248 DeadThread : Pos 1, 1 Bit
+0x248 HideFromDebugger : Pos 2, 1 Bit
+0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
+0x248 SystemThread : Pos 4, 1 Bit
+0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
+0x248 BreakOnTermination : Pos 6, 1 Bit
+0x248 SkipCreationMsg : Pos 7, 1 Bit
+0x248 SkipTerminationMsg : Pos 8, 1 Bit
+0x24c SameThreadPassiveFlags : Uint4B
+0x24c ActiveExWorker : Pos 0, 1 Bit
+0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit
+0x24c MemoryMaker : Pos 2, 1 Bit
+0x250 SameThreadApcFlags : Uint4B
+0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit
+0x250 LpcExitThreadCalled : Pos 1, 1 Bit
+0x250 AddressSpaceOwner : Pos 2, 1 Bit
+0x254 ForwardClusterOnly : UChar
+0x255 DisablePageFaultClustering : UChar
CrossThreadFlags
??表示线程的状态和身份,可以设置状态为系统线程可以使普通权限杀不死。
线程创建浅析
??之前我们分析创建进程的时候,是通过NtCreateThread
这个函数进行创建主线程,我们来看看里面有什么:
NTSTATUS __stdcall NtCreateThread(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, PCLIENT_ID ClientId, PCONTEXT ThreadContext, PINITIAL_TEB UserStack, BOOLEAN CreateSuspended)
{
struct _INITIAL_TEB *v8; // eax
int *v9; // ebx
int v11; // ecx
int v12[6]; // [esp+Ch] [ebp-38h] BYREF
_KTHREAD *v13; // [esp+24h] [ebp-20h]
CPPEH_RECORD ms_exc; // [esp+2Ch] [ebp-18h]
ms_exc.registration.TryLevel = 0;
v13 = KeGetCurrentThread();
if ( v13->PreviousMode )
{
if ( ThreadHandle >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
*ThreadHandle = *ThreadHandle;
if ( ClientId )
{
v12[5] = ClientId;
if ( ClientId >= MmUserProbeAddress )
*MmUserProbeAddress = 0;
if ( (ClientId & 3) != 0 )
ExRaiseDatatypeMisalignment();
LOBYTE(ClientId->UniqueProcess) = ClientId->UniqueProcess;
LOBYTE(ClientId->UniqueThread) = ClientId->UniqueThread;
}
if ( !ThreadContext )
{
ms_exc.registration.TryLevel = -1;
return STATUS_INVALID_PARAMETER;
}
if ( (ThreadContext & 3) != 0 )
ExRaiseDatatypeMisalignment();
v8 = MmUserProbeAddress;
if ( ThreadContext >= MmUserProbeAddress )
{
*MmUserProbeAddress = 0;
v8 = MmUserProbeAddress;
}
v9 = UserStack;
if ( (UserStack & 3) != 0 )
ExRaiseDatatypeMisalignment();
if ( UserStack >= v8 )
v8->PreviousStackBase = 0;
}
else
{
v9 = UserStack;
}
v12[0] = *v9;
v11 = v9[1];
v12[1] = v11;
if ( !v12[0] && !v11 )
qmemcpy(v12, v9, 0x14u);
ms_exc.registration.TryLevel = -1;
return PspCreateThread(
ThreadHandle,
DesiredAccess,
ObjectAttributes,
ProcessHandle,
0,
ClientId,
ThreadContext,
v12,
CreateSuspended,
0,
0);
}
??浏览一遍,发现最终是通过PspCreateThread
这个函数进行创建线程实现,点击去看看:
?? 点击查看伪代码 ??
MACRO_STATUS __stdcall PspCreateThread(PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, HANDLE ProcessHandle, PVOID a5, PCLIENT_ID ClientId, PCONTEXT ThreadContext, int a8, BOOLEAN CreateSuspended, int a10, int a11)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
CurrentThread = KeGetCurrentThread();
CurrentThread_1 = CurrentThread;
if ( a10 )
AccessMode[0] = 0;
else
AccessMode[0] = CurrentThread->PreviousMode;
v70 = 0;
v12 = 0;
v56 = 0;
if ( ProcessHandle )
{
result = ObReferenceObjectByHandle(ProcessHandle, 2u, PsProcessType, AccessMode[0], &Object, 0);
v12 = Object;
v56 = Object;
}
else if ( a10 )
{
v12 = a5;
ObfReferenceObject(a5);
v56 = a5;
result = STATUS_WAIT_0;
}
else
{
result = STATUS_INVALID_HANDLE;
}
if ( result >= STATUS_WAIT_0 )
{
if ( AccessMode[0] && v12 == PsInitialSystemProcess )
{
v14 = STATUS_INVALID_HANDLE;
LABEL_15:
ObfDereferenceObject(v12);
return v14;
}
v15 = ObCreateObject(AccessMode[0], PsThreadType, ObjectAttributes, *AccessMode, 0, 600, 0, 0, &v59);
if ( v15 < 0 )
{
v14 = v15;
goto LABEL_15;
}
thread = v59;
memset(v59, 0, 0x258u);
thread->RundownProtect.Count = 0;
thread->ThreadsProcess = v12;
thread->Cid.UniqueProcess = v12->UniqueProcessId;
v60[0] = thread;
v60[1] = 0;
v17 = ExCreateHandle(PspCidTable, v60);
thread->Cid.UniqueThread = v17;
if ( !v17 )
{
v18 = STATUS_INSUFFICIENT_RESOURCES;
LABEL_38:
ObfDereferenceObject(thread);
return v18;
}
thread->ReadClusterSize = MmReadClusterSize;
KeInitializeSemaphore(&thread->LpcReplySemaphore, 0, 1);
thread->ExitTime.HighPart = &thread->456;
thread->ExitTime.LowPart = &thread->456;
thread->IrpList.Blink = &thread->IrpList;
thread->IrpList.Flink = &thread->IrpList;
thread->PostBlockList.Blink = &thread->PostBlockList;
thread->PostBlockList.Flink = &thread->PostBlockList;
thread->ThreadLock.Value = 0;
KeInitializeSpinLock(&thread->ActiveTimerListLock);
thread->ActiveTimerListHead.Blink = &thread->ActiveTimerListHead;
thread->ActiveTimerListHead.Flink = &thread->ActiveTimerListHead;
v45 = &v12->RundownProtect;
if ( !ExAcquireRundownProtection(&v12->RundownProtect) )
{
v18 = STATUS_PROCESS_IS_TERMINATING;
goto LABEL_38;
}
if ( ThreadContext )
{
v18 = MmCreateTeb(&v12->Pcb, a8, &thread->Cid, &v70);
if ( v18 < 0 )
{
v19 = v45;
LABEL_37:
ExReleaseRundownProtection(v19);
goto LABEL_38;
}
ms_exc.registration.TryLevel = 0;
thread->StartAddress = ThreadContext->Eip;
thread->LpcReceivedMessageId = ThreadContext->Eax;
ms_exc.registration.TryLevel = -1;
v20 = KeInitThread(thread, 0, PspUserThreadStartup, 0, thread->StartAddress, ThreadContext, v70, v12);
}
else
{
v70 = 0;
_InterlockedOr(&thread->584, 0x10u);
thread->StartAddress = a10;
v20 = KeInitThread(thread, 0, PspSystemThreadStartup, a10, a11, 0, 0, v12);
}
v18 = v20;
if ( v20 < 0 )
{
if ( v70 )
{
MmDeleteTeb(&v12->Pcb, v70);
thread->Tcb.Teb = 0;
}
LABEL_36:
v19 = &v12->RundownProtect;
goto LABEL_37;
}
v21 = CurrentThread_1;
--CurrentThread_1->KernelApcDisable;
v53 = &v12->ProcessLock;
_ECX = &v12->ProcessLock;
_EDX = 2;
__asm { cmpxchg [ecx], edx }
if ( (v12->Flags & 8) != 0 )
{
v50 = &v12->ProcessLock;
_ECX = &v12->ProcessLock;
_EDX = 0;
__asm { cmpxchg [ecx], edx }
v26 = (*(v21 + 212))++ == -1;
if ( v26 && *(v21 + 52) != v21 + 52 )
{
*(v21 + 73) = 1;
LOBYTE(_ECX) = 1;
HalRequestSoftwareInterrupt(_ECX);
}
KeUninitThread(thread);
if ( v70 )
MmDeleteTeb(&v12->Pcb, v70);
v18 = STATUS_PROCESS_IS_TERMINATING;
goto LABEL_36;
}
v27 = v12->ActiveThreads;
v12->ActiveThreads = v27 + 1;
v28 = v12->ThreadListHead.Blink;
thread->ThreadListEntry.Flink = &v12->ThreadListHead;
thread->ThreadListEntry.Blink = v28;
v28->Flink = &thread->ThreadListEntry;
v12->ThreadListHead.Blink = &thread->ThreadListEntry;
KeStartThread(thread);
v48 = &v12->ProcessLock;
_ECX = &v12->ProcessLock;
_EDX = 0;
__asm { cmpxchg [ecx], edx }
v31 = CurrentThread_1;
v26 = CurrentThread_1->KernelApcDisable++ == -1;
if ( v26 && *(v31 + 52) != v31 + 52 )
{
CurrentThread_1->ApcState.KernelApcPending = 1;
LOBYTE(_ECX) = 1;
HalRequestSoftwareInterrupt(_ECX);
}
ExReleaseRundownProtection(&v12->RundownProtect);
if ( !v27 )
{
WmiTraceProcess(&v12->Pcb);
if ( PspCreateProcessNotifyRoutineCount )
{
v58 = &PspCreateProcessNotifyRoutine;
v51 = 8;
do
{
v32 = ExReferenceCallBackBlock(v58);
v33 = v32;
if ( v32 )
{
v34 = ExGetCallBackBlockRoutine(v32);
v34(v12->InheritedFromUniqueProcessId, v12->UniqueProcessId, 1);
ExDereferenceCallBackBlock(v58, v33);
}
v58 += 4;
--v51;
}
while ( v51 );
}
}
v35 = v12->Job;
if ( v35 && *(v35 + 196) && (v12->JobStatus & 5) == 0 )
{
_InterlockedOr(&v12->JobStatus, 4u);
--CurrentThread_1->KernelApcDisable;
Resource = (v35 + 32);
ExAcquireResourceSharedLite((v35 + 32), 1u);
v36 = *(v35 + 196);
if ( v36 )
IoSetIoCompletion(v36, *(v35 + 200), v12->UniqueProcessId, 0, 6, 0);
ExReleaseResourceLite(Resource);
v37 = CurrentThread_1;
v26 = CurrentThread_1->KernelApcDisable++ == -1;
if ( v26 && *(v37 + 52) != v37 + 52 )
{
*(v37 + 73) = 1;
LOBYTE(v37) = 1;
HalRequestSoftwareInterrupt(v37);
}
}
WmiTraceThread(thread, a8, 1);
if ( PspCreateThreadNotifyRoutineCount )
{
v57 = &PspCreateThreadNotifyRoutine;
v49 = 8;
do
{
v38 = ExReferenceCallBackBlock(v57);
RunRef = v38;
if ( v38 )
{
v39 = ExGetCallBackBlockRoutine(v38);
v39(thread->Cid.UniqueProcess, thread->Cid.UniqueThread, 1);
ExDereferenceCallBackBlock(v57, RunRef);
}
v57 += 4;
--v49;
}
while ( v49 );
}
ObReferenceObjectEx(thread, 2);
if ( ThreadContext )
{
P = ExAllocatePoolWithTag(NonPagedPool, 0x30u, 'aCsP');
if ( !P )
{
_InterlockedOr(&thread->584, 2u);
LABEL_64:
v18 = STATUS_INSUFFICIENT_RESOURCES;
LABEL_77:
KeReadyThread(thread);
ObDereferenceObjectEx(thread, 2);
return v18;
}
KeInitializeApc(P, &thread->Tcb, OriginalApcEnvironment, IopDeallocateApc, 0, dword_598B5C, 1, 0);
if ( !KeInsertQueueApc(P, BaseAddress, 0, 0) )
{
_InterlockedOr(&thread->584, 2u);
ExFreePoolWithTag(P, 0);
goto LABEL_64;
}
}
if ( CreateSuspended )
{
ms_exc.registration.TryLevel = 1;
KeSuspendThread(&thread->Tcb);
ms_exc.registration.TryLevel = -1;
if ( (thread->CrossThreadFlags & 1) != 0 )
KeForceResumeThread(&thread->Tcb);
}
if ( ThreadContext )
v40 = &CurrentThread_1->ApcState.Process->Pcb;
else
v40 = &v12->Pcb;
v63 = SeCreateAccessStateEx(0, v40, &PassedAccessState, v44, DesiredAccess, (PsThreadType + 26));
if ( v63 < 0 )
{
_InterlockedOr(&thread->584, 2u);
if ( CreateSuspended )
KeResumeThread(thread);
v18 = v63;
goto LABEL_77;
}
v63 = ObInsertObject(thread, &PassedAccessState, DesiredAccess, 0, 0, &Handle);
SeDeleteAccessState(&PassedAccessState);
if ( v63 >= 0 )
{
ms_exc.registration.TryLevel = 2;
*ThreadHandle = Handle;
if ( ClientId )
*ClientId = thread->Cid;
ms_exc.registration.TryLevel = -1;
}
else
{
_InterlockedOr(&thread->584, 2u);
if ( CreateSuspended )
KeResumeThread(thread);
}
KeQuerySystemTime(&CurrentTime);
v41 = CurrentTime.QuadPart >> 29;
thread->CreateTime.LowPart = 8 * CurrentTime.LowPart;
thread->CreateTime.HighPart = v41;
if ( (thread->CrossThreadFlags & 2) != 0 )
{
thread->GrantedAccess = 2032639;
}
else
{
v63 = ObGetObjectSecurity(thread, &SecurityDescriptor, MemoryAllocated);
if ( v63 < 0 )
{
_InterlockedOr(&thread->584, 2u);
if ( CreateSuspended )
KeResumeThread(thread);
KeReadyThread(thread);
ObfDereferenceObject(thread);
ObCloseHandle(Handle, AccessMode[0]);
goto LABEL_95;
}
SubjectSecurityContext.ProcessAuditId = v12;
SubjectSecurityContext.PrimaryToken = PsReferencePrimaryToken(&v12->Pcb);
SubjectSecurityContext.ClientToken = 0;
v42 = &thread->GrantedAccess;
v64 = SeAccessCheck(
SecurityDescriptor,
&SubjectSecurityContext,
0,
0x2000000u,
0,
0,
(PsThreadType + 26),
AccessMode[0],
&thread->GrantedAccess,
&AccessStatus);
ObFastDereferenceObject(&v12->Token, SubjectSecurityContext.PrimaryToken);
ObReleaseObjectSecurity(SecurityDescriptor, MemoryAllocated[0]);
if ( !v64 )
*v42 = 0;
*v42 |= 0x61u;
}
KeReadyThread(thread);
ObfDereferenceObject(thread);
LABEL_95:
result = v63;
}
return result;
}
??流程和进程线程创建了流程套路几乎一样,初始化线程结构体,创建TEB
,插入结构体,但里面有APC
相关的知识,具体细节就不再赘述了。
DPC
??DPC
是什么?它的英文全称为Deferred Procedure Call
,即延迟过程调用。它最初作用是设计为中断服务程序的一部分,用来解决中断服务处理时间过长的问题。因为每次触发中断,都会关中断,然后执行中断服务例程。由于关中断了,所以中断服务例程必须短小精悍,不能消耗过多时间,否则会导致系统丢失大量其他中断。但是有的中断,其中断服务例程要做的事情本来就很多,那怎么办?于是,可以在中断服务例程中先执行最紧迫的那部分工作,然后把剩余的相对来说不那么重要的工作移入到DPC
函数中去执行。
??每当触发一个中断时,中断服务例程可以在当前CPU
中插入一个DPC
,当执行完ISR
,退出ISR
后, CPU
就会扫描它的DPC
队列,依次执行里面的每个DPC
,当执行完DPC
后,才又回到当前线程的中断处继续执行。
??既然提到ISR
,那么它是什么?它的英文全称为Interrupt Service Routines
,即中断服务处理。在Windows
中如果有认为不太重要的操作会被打包成一个KDPC
结构体,如下所示:
kd> dt _KDPC
ntdll!_KDPC
+0x000 Type : Int2B
+0x002 Number : UChar
+0x003 Importance : UChar
+0x004 DpcListEntry : _LIST_ENTRY
+0x00c DeferredRoutine : Ptr32 void
+0x010 DeferredContext : Ptr32 Void
+0x014 SystemArgument1 : Ptr32 Void
+0x018 SystemArgument2 : Ptr32 Void
+0x01c Lock : Ptr32 Uint4B
??打包完毕后,会插入到KPCRB
的DpcListHead
成员中,等待触发调用时机。KPCRB
结构如下所示,只展示与DPC
相关的成员:
kd> dt _KPRCB
ntdll!_KPRCB
……
+0x4b0 DpcTime : Uint4B
+0x4b4 DebugDpcTime : Uint4B
……
+0x860 DpcListHead : _LIST_ENTRY
+0x868 DpcStack : Ptr32 Void
+0x86c DpcCount : Uint4B
+0x870 DpcQueueDepth : Uint4B
+0x874 DpcRoutineActive : Uint4B
+0x878 DpcInterruptRequested : Uint4B
+0x87c DpcLastCount : Uint4B
+0x880 DpcRequestRate : Uint4B
……
+0x8a0 DpcLock : Uint4B
……
+0x8c0 CallDpc : _KDPC
……
??非时钟中断的中断、时钟中断、主动调用API
,相关函数会去轮询DPC
链表进行回调。对于用户或者驱动创建的DPC
,还可以加定时器,到指定时间进行触发,但不会挂到KPCR
当中,会挂到时钟任务当中。
??介绍了上面的概念,我们来介绍结构体的相关成员含义:
KDPC
Type
??指明该结构体的类型,值为0x13
。
Number
??指明属于哪个KPCR
。
Importance
??优先级,取值0-2
,0最低,2最高,优先级越高越优先执行,初始化默认值为1。
DpcListEntry
??DPC
链表,和进程线程链表一样,挂到腰上。
DeferredRoutine
??DPC
的回调函数地址。
DeferredContext
??回调函数上下文,非必须。
SystemArgument1 / SystemArgument2
??回调函数的参数,非必须。
Lock
??DPC
结构体的锁。
DPC 的初始化
??学习DPC
,首先我们了解一下它的初始化,我们用IDA
定位到该函数:
; void __stdcall KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext)
public _KeInitializeDpc@12
_KeInitializeDpc@12 proc near ; CODE XREF: IopInitializeIrpStackProfiler()+29↑p
; VdmpDelayInterrupt(x)+26B↓p ...
Dpc = dword ptr 8
DeferredRoutine = dword ptr 0Ch
DeferredContext = dword ptr 10h
mov edi, edi
push ebp
mov ebp, esp
mov eax, [ebp+Dpc]
mov ecx, [ebp+DeferredRoutine]
and dword ptr [eax+1Ch], 0
mov [eax+0Ch], ecx
mov ecx, [ebp+DeferredContext]
mov word ptr [eax], 13h
mov byte ptr [eax+2], 0
mov byte ptr [eax+3], 1
mov [eax+10h], ecx
pop ebp
retn 0Ch
_KeInitializeDpc@12 endp
??这个函数十分简单,用伪代码展示如下:
void __stdcall KeInitializeDpc(PRKDPC Dpc, PKDEFERRED_ROUTINE DeferredRoutine, PVOID DeferredContext)
{
Dpc->Lock = 0;
Dpc->DeferredRoutine = DeferredRoutine;
Dpc->Type = 19;
Dpc->Number = 0;
Dpc->Importance = 1;
Dpc->DeferredContext = DeferredContext;
}
DPC 的插入
??然后我们来看看DPC
的插入流程,由于涉及IRQL
提权操作,我们先看一下枚举:
??为了增强可读性,如下是我重命名好的IDA
的伪代码:
BOOLEAN __stdcall KeInsertQueueDpc(PRKDPC Dpc, PVOID SystemArgument1, PVOID SystemArgument2)
{
_KPRCB *kprcb; // esi
bool Importance; // zf
_LIST_ENTRY *DpcListHead; // ecx
_LIST_ENTRY *DpcListEntry; // eax
_LIST_ENTRY *v9; // edx
_LIST_ENTRY *v10; // edx
KIRQL NewIrql; // [esp+Fh] [ebp-1h]
NewIrql = KfRaiseIrql(0x1Fu);
kprcb = MEMORY[0xFFDFF020];
_ECX = &Dpc->Lock;
_EDX = MEMORY[0xFFDFF020] + 0x8A0; // DpcLock
__asm { cmpxchg [ecx], edx }
++kprcb->DpcCount;
++kprcb->DpcQueueDepth;
Importance = Dpc->Importance == 2;
Dpc->SystemArgument1 = SystemArgument1;
Dpc->SystemArgument2 = SystemArgument2;
DpcListHead = &kprcb->DpcListHead;
DpcListEntry = &Dpc->DpcListEntry;
if ( Importance )
{
v9 = DpcListHead->Flink;
DpcListEntry->Flink = DpcListHead->Flink;
Dpc->DpcListEntry.Blink = DpcListHead;
v9->Blink = DpcListEntry;
DpcListHead->Flink = DpcListEntry;
}
else
{
v10 = kprcb->DpcListHead.Blink;
DpcListEntry->Flink = DpcListHead;
Dpc->DpcListEntry.Blink = v10;
v10->Flink = DpcListEntry;
kprcb->DpcListHead.Blink = DpcListEntry;
}
if ( !kprcb->DpcRoutineActive
&& !kprcb->DpcInterruptRequested
&& (Dpc->Importance
|| kprcb->DpcQueueDepth >= kprcb->MaximumDpcQueueDepth
|| kprcb->DpcRequestRate < kprcb->MinimumDpcRate) )
{
LOBYTE(DpcListHead) = 2;
kprcb->DpcInterruptRequested = 1;
HalRequestSoftwareInterrupt(DpcListHead);
}
KfLowerIrql(NewIrql);
return 1;
}
??可以看出,如果DPC
的优先级高,就会插到DPC
链表的前面,如果低就会插到末尾。在多核的情况下,其伪代码会有所不一样,如下所示:
BOOLEAN __stdcall KeInsertQueueDpc(PRKDPC Dpc, PVOID SystemArgument1, PVOID SystemArgument2)
{
unsigned __int8 Number; // al
_KPRCB *kpcrb; // esi
char v5; // bl
bool v6; // zf
_LIST_ENTRY *v7; // ecx
_LIST_ENTRY *v8; // eax
_LIST_ENTRY *v9; // edx
_LIST_ENTRY *v10; // edx
signed __int32 v12; // [esp+Ch] [ebp-8h]
KIRQL NewIrql; // [esp+13h] [ebp-1h]
NewIrql = KfRaiseIrql(0x1Fu);
Number = Dpc->Number;
if ( Number < 0x20u )
{
v5 = Dpc;
kpcrb = KeGetPcr()->Prcb;
}
else
{
kpcrb = KiProcessorBlock[Number];
v5 = Number - 32;
}
KiAcquireSpinLock();
v12 = _InterlockedCompareExchange(&Dpc->Lock, &kpcrb->DpcLock, 0);
if ( !v12 )
{
++kpcrb->DpcCount;
++kpcrb->DpcQueueDepth;
v6 = Dpc->Importance == 2;
Dpc->SystemArgument1 = SystemArgument1;
Dpc->SystemArgument2 = SystemArgument2;
v7 = &kpcrb->DpcListHead;
v8 = &Dpc->DpcListEntry;
if ( v6 )
{
v9 = v7->Flink;
v8->Flink = v7->Flink;
Dpc->DpcListEntry.Blink = v7;
v9->Blink = v8;
v7->Flink = v8;
}
else
{
v10 = kpcrb->DpcListHead.Blink;
v8->Flink = v7;
Dpc->DpcListEntry.Blink = v10;
v10->Flink = v8;
kpcrb->DpcListHead.Blink = v8;
}
if ( !kpcrb->DpcRoutineActive && !kpcrb->DpcInterruptRequested )
{
if ( kpcrb == KeGetPcr()->Prcb )
{
if ( Dpc->Importance
|| kpcrb->DpcQueueDepth >= kpcrb->MaximumDpcQueueDepth
|| kpcrb->DpcRequestRate < kpcrb->MinimumDpcRate )
{
LOBYTE(v7) = 2;
kpcrb->DpcInterruptRequested = 1;
HalRequestSoftwareInterrupt(v7); //处理 DPC
}
}
else if ( Dpc->Importance == 2 || kpcrb->DpcQueueDepth >= kpcrb->MaximumDpcQueueDepth )
{
kpcrb->DpcInterruptRequested = 1;
KiIpiSend(1 << v5, 2u);
}
}
}
KiReleaseSpinLock(&kpcrb->DpcLock);
KfLowerIrql(NewIrql);
return v12 == 0;
}
??这里提一句,想要获取多核调试环境极其内核文件,必须把虚拟机的配置配置成多核再重新安装操作系统。但是我给的符号是单核的,多核的我也踏破铁鞋也找不到,上面的伪代码一些结构体我是通过单核的生成的,至于函数是参考资料命名的。如果您有多核的符号,希望您能够提供,感谢支持。
??对于多核的情况,具体细节将会在同步篇进行讲解。含有SpinLock
函数的这个东西成为自旋锁。发现这份代码和单核的会多一些判断和一个函数KiIpiSend
,这个函数作用是通知另一个KPCR
执行DPC
,了解即可。
DPC 的移除
??对于移除DPC
代码很简单,如下是其伪代码:
BOOLEAN __stdcall KeRemoveQueueDpc(PRKDPC Dpc)
{
unsigned int *Lock; // esi
_disable(); // cli
Lock = Dpc->Lock;
if ( Lock )
{
--*(Lock - 12);
RemoveEntryList(&Dpc->DpcListEntry);
Dpc->Lock = 0;
}
_enable(); // sti
return Lock != 0;
}
??对于多核下,代码如下,和单核区别不大,只是加入了自旋锁:
BOOLEAN __stdcall KeRemoveQueueDpc(PRKDPC Dpc)
{
unsigned int *Lock; // edi
_disable();
Lock = Dpc->Lock;
if ( Lock )
{
KiAcquireSpinLock(Dpc->Lock);
if ( Lock == Dpc->Lock )
{
--*(Lock - 12);
RemoveEntryList(&Dpc->DpcListEntry);
Dpc->Lock = 0;
}
KiReleaseSpinLock(Lock);
}
_enable();
return Lock != 0;
}
DPC 的执行
??执行DPC
是通过KiRetireDpcList
执行的,而调用该函数的是通过KiDispatchInterrupt
和KiIdleLoop
执行的,我们通过伪代码进行简单了解:
int __usercall KiRetireDpcList@(_KPCR *kpcr@, _LIST_ENTRY *DPCListHead@)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v13 = 0;
if ( PPerfGlobalGroupMask && (*(PPerfGlobalGroupMask + 4) & 0x80) != 0 )
v13 = 1;
do
{
MEMORY[0xFFDFFBC4] = &v11; // 这里有错误,原汇编:
// mov ds:0FFDFF994h, esp
// 可能是插件的 Bug
// 指向:DpcRoutineActive
do
{
dpc = DPCListHead->Flink;
v3 = DPCListHead->Flink->Flink;
DPCListHead->Flink = v3;
v3->Blink = DPCListHead;
dpc = (dpc - 4); // 挂到腰上了,把指针指到头部
DeferredRoutine = dpc->DeferredRoutine;
SystemArgument1 = *&dpc->SystemArgument1;
DeferredContext = dpc->DeferredContext;
dpc_1 = dpc;
dpc->Lock = 0;
--kpcr->PrcbData.DpcQueueDepth;
_enable();
if ( v13 ) // 日志性能分析,忽略
{
v6 = WmiGetCpuClock(DeferredRoutine);
DeferredRoutine = v7;
v11 = v6;
v12 = v7;
}
result = (DeferredRoutine)(dpc_1, DeferredContext, SystemArgument1, HIDWORD(SystemArgument1));// 执行 DPC
if ( v13 )
result = PerfInfoLogDpc(v12, v11, SHIDWORD(v11));
_disable();
}
while ( !IsListEmpty(DPCListHead) );
kpcr->PrcbData.DpcRoutineActive = 0;
kpcr->PrcbData.DpcInterruptRequested = 0;
}
while ( !IsListEmpty(DPCListHead) );
return result;
}
DPC 的使用
??既然学习了DPC
,没有应用肯定不行,我们做几个实验体验一下DPC
。我们新建一个驱动项目,怎么配置我就不说了,代码如下:
#include
#include
KDPC dpc = { 0 };
VOID DPCRoutine(_In_ struct _KDPC* Dpc, _In_opt_ PVOID DeferredContext,
_In_opt_ PVOID SystemArgument1, _In_opt_ PVOID SystemArgument2)
{
DbgPrint("DPC Running……\n");
}
NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
DbgPrint("Unloaded Successfully!");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint("Loaded Successfully!");
DriverObject->DriverUnload = UnloadDriver;
KeInitializeDpc(&dpc, DPCRoutine, NULL);
KeInsertQueueDpc(&dpc, NULL, NULL);
return STATUS_SUCCESS;
}
??编译然后加载后,就会出现如下结果:
??那么继续搞得高级点,不是驱动或者用户创建的DPC
支持定时器吗?我们来搞一下:
#include
#include
KDPC dpc = { 0 };
KTIMER timer = { 0 };
LARGE_INTEGER duringtime = { 0 };
VOID DPCRoutine(_In_ struct _KDPC* Dpc, _In_opt_ PVOID DeferredContext,
_In_opt_ PVOID SystemArgument1, _In_opt_ PVOID SystemArgument2)
{
DbgPrint("DPC Running……\n");
KeSetTimer(&timer, duringtime, &dpc);
}
VOID UnloadDriver(PDRIVER_OBJECT DriverObject)
{
KeCancelTimer(&timer);
DbgPrint("Unloaded Successfully!");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint("Loaded Successfully!");
DriverObject->DriverUnload = UnloadDriver;
KeInitializeTimer(&timer);
KeInitializeDpc(&dpc, DPCRoutine, NULL);
duringtime.QuadPart = -30 * 1000 * 1000; //负号表示相对时间,此处间隔为3秒
KeSetTimer(&timer, duringtime, &dpc);
return STATUS_SUCCESS;
}
??实现的效果如下:
??上面实现的就是DPC
定时器。
Windows 架构
??学习系统内核,最好了解一些该系统的系统架构,示意图如下,来自潘爱民的《Windows内核原理与实现》:
??如有兴趣了解其细节,请自行翻找,注意该书是基于WRK
的,里面的结构体相关或者代码相关的请以逆向结果为准。