tangptr@126.com 发表于 2015-8-2 20:03:18

TP的学习笔记:创建一个没有线程而且杀不死的进程

本帖最后由 tangptr@126.com 于 2015-8-2 20:40 编辑

不知道大家有没有听说过没有线程的进程。刚开始我是认为这种进程是不存在的,因为是深受pjf的《进程终止的内幕》的关系。不过后来,我看到了这么一篇文章,看下来我觉得这种进程实现起来其实不难。我先把这篇文章复制粘贴过来。
《PspExitThread执行流程分析》
在TerminateProcess流程追踪、PspTerminateThreadByPointer的实现中讲到,进程的终止,最终都是通过PspTerminateThreadByPointer终止线程来实现的。而PspTerminateThreadByPointer不管是通过终止本身,还是通过插APC最终都是调用PspExitThread来完成终止过程。
那来分析下PspExitThread的执行流程。
//通过线程来获得进程对象。
if (Process != PsGetCurrentProcessByThread (Thread)) {
KeBugCheckEx (INVALID_PROCESS_ATTACH_ATTEMPT,
(ULONG_PTR)Process,
(ULONG_PTR)Thread->Tcb.ApcState.Process,
(ULONG)Thread->Tcb.ApcStateIndex,
(ULONG_PTR)Thread);
}
KeLowerIrql(PASSIVE_LEVEL);
//判断线程是否允许终止
if (Thread->ActiveExWorker) {
KeBugCheckEx (ACTIVE_EX_WORKER_THREAD_TERMINATION,
(ULONG_PTR)Thread,
0,
0,
0);
}
if (Thread->Tcb.CombinedApcDisable != 0) {
KeBugCheckEx (KERNEL_APC_PENDING_DURING_EXIT,
(ULONG_PTR)0,
(ULONG_PTR)Thread->Tcb.CombinedApcDisable,
(ULONG_PTR)0,
1);
}
ExWaitForRundownProtectionRelease (&Thread->RundownProtect);
PoRundownThread(Thread);
//通知哪些已注册的线程删除事件接受者
PERFINFO_THREAD_DELETE(Thread);
if (PspCreateThreadNotifyRoutineCount != 0) {
ULONG i;
PEX_CALLBACK_ROUTINE_BLOCK CallBack;
PCREATE_THREAD_NOTIFY_ROUTINE Rtn;
for (i=0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i++) {
CallBack = ExReferenceCallBackBlock (&PspCreateThreadNotifyRoutine);
if (CallBack != NULL) {
Rtn = (PCREATE_THREAD_NOTIFY_ROUTINE) ExGetCallBackBlockRoutine (CallBack);
Rtn (Process->UniqueProcessId,
Thread->Cid.UniqueThread,
FALSE);
ExDereferenceCallBackBlock (&PspCreateThreadNotifyRoutine,
CallBack);
}
}
}
LastThread = FALSE;
DerefThread = NULL;
PspLockProcessExclusive (Process, Thread);
//进程的活动线程计数减1
Process->ActiveThreads—;
//如果这是最后一个线程,则必须等到该进程的线程链表中所有的线程都退出才能继续往下进行。
if (Process->ActiveThreads == 0) {
PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE);
LastThread = TRUE;
if (ExitStatus == STATUS_THREAD_IS_TERMINATING) {
if (Process->ExitStatus == STATUS_PENDING) {
Process->ExitStatus = Process->LastThreadExitStatus;
}
} else {
Process->ExitStatus = ExitStatus;
}
for (Entry = Process->ThreadListHead.Flink;
Entry != &Process->ThreadListHead;
Entry = Entry->Flink) {
WaitThread = CONTAINING_RECORD (Entry, ETHREAD, ThreadListEntry);
if (WaitThread != Thread &&
!KeReadStateThread (&WaitThread->Tcb) &&
ObReferenceObjectSafe (WaitThread)) {
PspUnlockProcessExclusive (Process, Thread);
KeWaitForSingleObject (WaitThread,
Executive,
KernelMode,
FALSE,
NULL);

if (DerefThread != NULL) {
ObDereferenceObject (DerefThread);
}
DerefThread = WaitThread;
PspLockProcessExclusive (Process, Thread);
}
}
} else {
if (ExitStatus != STATUS_THREAD_IS_TERMINATING) {
Process->LastThreadExitStatus = ExitStatus;
}
}
PspUnlockProcessExclusive (Process, Thread);
if (DerefThread != NULL) {
ObDereferenceObject (DerefThread);
}
如有必要发送调试信息

if (Process->DebugPort != NULL) {
if (!IS_SYSTEM_THREAD (Thread)) {
if (LastThread) {
DbgkExitProcess (Process->ExitStatus);
} else {
DbgkExitThread (ExitStatus);
}
}
}
if (KD_DEBUGGER_ENABLED) {
if (Thread->CrossThreadFlags & PS_CROSS_THREAD_FLAGS_BREAK_ON_TERMINATION) {
PspCatchCriticalBreak (“Critical thread 0x%p (in %s) exited\n”,
Thread,
Process->ImageFileName);
}
} // End of critical thread/process exit detect
if (LastThread &&
(Process->Flags & PS_PROCESS_FLAGS_BREAK_ON_TERMINATION)) {
if (KD_DEBUGGER_ENABLED) {
PspCatchCriticalBreak (“Critical process 0x%p (%s) exited\n”,
Process,
Process->ImageFileName);
} else {
KeBugCheckEx (CRITICAL_PROCESS_DIED,
(ULONG_PTR)Process,
0,
0,
0);
}
}
ASSERT(Thread->Tcb.CombinedApcDisable == 0);
处理TerminationPort,向所有已经登记过要接受终止通知的线程发送终止消息
TerminationPort = Thread->TerminationPort;
if (TerminationPort != NULL) {
CdMsg.PortMsg.u1.s1.DataLength = sizeof(LARGE_INTEGER);
CdMsg.PortMsg.u1.s1.TotalLength = sizeof(LPC_CLIENT_DIED_MSG);
CdMsg.PortMsg.u2.s2.Type = LPC_CLIENT_DIED;
CdMsg.PortMsg.u2.s2.DataInfoOffset = 0;
do {
CdMsg.CreateTime.QuadPart = PS_GET_THREAD_CREATE_TIME (Thread);
while (1) {
Status = LpcRequestPort (TerminationPort->Port, (PPORT_MESSAGE)&CdMsg);
if (Status == STATUS_NO_MEMORY || Status == STATUS_INSUFFICIENT_RESOURCES) {
KeDelayExecutionThread (KernelMode, FALSE, &ShortTime);
continue;
}
break;
}
ObDereferenceObject (TerminationPort->Port);
NextPort = TerminationPort->Next;
ExFreePoolWithTag (TerminationPort, ‘pTsP’|PROTECTED_POOL);
TerminationPort = NextPort;
} while (TerminationPort != NULL);
} else {
if ((ExitStatus == STATUS_THREAD_IS_TERMINATING &&
(Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD)) ||
!(Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD)) {
CdMsg.PortMsg.u1.s1.DataLength = sizeof (LARGE_INTEGER);
CdMsg.PortMsg.u1.s1.TotalLength = sizeof (LPC_CLIENT_DIED_MSG);
CdMsg.PortMsg.u2.s2.Type = LPC_CLIENT_DIED;
CdMsg.PortMsg.u2.s2.DataInfoOffset = 0;
if (Process->ExceptionPort != NULL) {
//向异常端口发送终止消息
CdMsg.CreateTime.QuadPart = PS_GET_THREAD_CREATE_TIME (Thread);
while (1) {
Status = LpcRequestPort (Process->ExceptionPort, (PPORT_MESSAGE)&CdMsg);
if (Status == STATUS_NO_MEMORY || Status == STATUS_INSUFFICIENT_RESOURCES) {
KeDelayExecutionThread (KernelMode, FALSE, &ShortTime);
continue;
}
break;
}
}
}
}
通知Windows子系统当前线程退出,如果这是进程的最后一个线程,还要通知Windows子系统当前进程退出。
if (Thread->Tcb.Win32Thread) {
(PspW32ThreadCallout) (Thread, PsW32ThreadCalloutExit);
}
if (LastThread && Process->Win32Process) {
(PspW32ProcessCallout) (Process, FALSE);
}
取消I/O,取消定时器,清除任何尚未完成的注册表通知请求,释放当前线程所拥有的突变体内核对象(mutant)
IoCancelThreadIo (Thread);
ExTimerRundown ();
CmNotifyRunDown (Thread);
KeRundownThread ();
释放TEB。
Teb = Thread->Tcb.Teb;
if (Teb != NULL) {
Peb = Process->Peb;
try {
if ((Thread->CrossThreadFlags&PS_CROSS_THREAD_FLAGS_DEADTHREAD) == 0) {
SIZE_T Zero;
PVOID BaseAddress;
if (Teb->FreeStackOnTermination) {
Zero = 0;
BaseAddress = Teb->DeallocationStack;
ZwFreeVirtualMemory (NtCurrentProcess (),
&BaseAddress,
&Zero,
MEM_RELEASE);
}
#if defined(_WIN64)
if (Process->Wow64Process != NULL) {
PTEB32 Teb32;
Teb32 = WOW64_GET_TEB32_SAFE (Teb);
if (Teb32->FreeStackOnTermination) {
Zero = 0;
BaseAddress = UlongToPtr (Teb32->DeallocationStack);
ZwFreeVirtualMemory (NtCurrentProcess (),
&BaseAddress,
&Zero,
MEM_RELEASE);
}
}
#endif
}
if (Teb->DbgSsReserved != NULL) {
ObCloseHandle (Teb->DbgSsReserved, UserMode);
}
} except (EXCEPTION_EXECUTE_HANDLER) {
}
MmDeleteTeb (Process, Teb);
Thread->Tcb.Teb = NULL;
}
调用LpcExitThread以便让LPC组件来处理LPC应答消息。
LpcExitThread (Thread);
设置线程的退出时间和退出状态
Thread->ExitStatus = ExitStatus;
KeQuerySystemTime (&Thread->ExitTime);
如果是进程的最后一个线程,还要调用PspExitProcess以退出进程,并设置进程退出的相关属性,以及销毁句柄表和解除对进程的内存区对象的引用。
if (LastThread) {
Process->ExitTime = Thread->ExitTime;
PspExitProcess (TRUE, Process);
ProcessToken = PsReferencePrimaryToken (Process);
if (SeDetailedAuditingWithToken (ProcessToken)) {
SeAuditProcessExit (Process);
}
PsDereferencePrimaryTokenEx (Process, ProcessToken);
#if defined(_X86_)
if (Process->VdmObjects != NULL) {
VdmRundownDpcs (Process);
}
#endif
ObKillProcess (Process);
if (Process->SectionObject != NULL) {
ObDereferenceObject (Process->SectionObject);
Process->SectionObject = NULL;
}
if (Process->Job != NULL) {
PspExitProcessFromJob (Process->Job, Process);
}
}
强制恢复线程运行,以便处理可能有的APC,遍历用户模式APC,如果此APC有一个终止函数(RundownRountine),则执行此函数,否则直接扔掉此APC。
KeForceResumeThread (&Thread->Tcb);
KeLeaveCriticalRegionThread (&Thread->Tcb);
//
// flush user-mode APC queue
//
FirstEntry = KeFlushQueueApc (&Thread->Tcb, UserMode);
if (FirstEntry != NULL) {
Entry = FirstEntry;
do {
Apc = CONTAINING_RECORD (Entry, KAPC, ApcListEntry);
Entry = Entry->Flink;
//
// If the APC has a rundown routine then call it. Otherwise
// deallocate the APC
//
if (Apc->RundownRoutine) {
(Apc->RundownRoutine) (Apc);
} else {
ExFreePool (Apc);
}
} while (Entry != FirstEntry);
}
如果是该进程最后一个线程,则清除当前进程的地址空间。
if (LastThread) {
MmCleanProcessAddressSpace (Process);
}
将进程对象的状态设置为有信号状态,以唤醒那些可能在等待此进程对象的线程。
if (LastThread) {
KeSetProcess (&Process->Pcb, 0, FALSE);
}
最后调用KeTerminateThread,设置线程状态为有信号状态,并设置线程的调度状态为“已终止”。
处理将执行新选择的线程,以后调度算法再也不会选择这一已终止的线程。




下面是我的正文了
不知道大家有没有注意到调用PspExitProcess和ObKillProcess的那段代码,这是PspExitThread结束最后一个线程的时候退出进程用的代码,这就意味着要结束最后一个线程不让进程退出,就必须跳过PspExitProcess和ObKillProcess这两个函数。而要跳过这两个函数,最好的办法就是不执行PspExitThread。那么不执行PspExitThread该怎么结束线程呢?请看原文的倒数第二行。
最后调用KeTerminateThread,设置线程状态为有信号状态,并设置线程的调度状态为“已终止”。
可见调用KeTerminateThread是既可以实现结束线程,又可以跳过PspExitProcess/ObKillProcess的过程。
值得一提的是,KeTerminateThread在NT5系列Windows系统中导出,而在NT6系列系统中没有导出,因此本文提供的附件只支持Windows NT5。
首先看看KeTerminateThread的原型:
NTKERNELAPI VOID KeTerminateThread(IN KPRIORITY Increment);
这里的Increment参数填零就行了。
但是注意了直接调用这个函数就是自杀,所以我们要让别的线程调用这个函数。而让这个线程执行这个函数,有两种办法:
1.调用Ke(i)InsertQueueApc给目标线程插APC,在APC里执行KeTerminateThread。
2.挂钩ObOpenObjectByPointer(或者KeUsermodeCallback,KiSwapContext,KiFastCallEntry等其他调用极其频繁的函数),判断执行此函数的线程是不是目标线程,若是,执行KeTerminateThread,反之,执行原函数。
第二个方法其实思路是来自于syf大牛的《WS方法结束线程》一文。但个人认为这个方法貌似不好使,主要是什么时候恢复钩子的问题。于是我就选择第一条路。
给线程插APC已经是老技术了,只不过要先用MmIsAddressValid检查ETHREAD地址是否合法,代码如下:
NTSTATUS TerminateThread(PETHREAD Thread)
{
        NTSTATUS st = STATUS_UNSUCCESSFUL;
        PKAPC pApc = 0;
        if ( MmIsAddressValid((PVOID)Thread) == TRUE)
        {
                pApc = ExAllocatePool(NonPagedPool, sizeof(KAPC));
                //If APC is OK
                if (pApc)
                {
                        KeInitializeApc(pApc, Thread, OriginalApcEnvironment, ApcRT, 0, 0, KernelMode, 0);
                        KeInsertQueueApc(pApc,NULL,NULL,0);
                }
                st = STATUS_SUCCESS;
        }
        return st;       
}
要执行的APC例程也很简单,只不过别忘了用ExFreePool释放掉APC。
VOID ApcRT(PKAPC Apc,PKNORMAL_ROUTINE *NormalRoutine,PVOID *NormalContext,PVOID *SystemArgument1,PVOID *SystemArgument2)
{
        ExFreePool(Apc);
        KeTerminateThread(0);
}
要给进程进程内所有线程插APC,可以用PsLookupThreadByThreadId+IoThreadToProcess方式枚举线程,也可以遍历ThreadListHead。这里选择第一种方案。代码如下:
NTSTATUS TerminateProcess(PEPROCESS Process)
{
        ULONG i;
        PETHREAD txtd;
        PEPROCESS txps;
        NTSTATUS st = STATUS_UNSUCCESSFUL;
        for (i=8;i<=65536;i=i+4)
        {
                st = PsLookupThreadByThreadId(i,&txtd);
                if ( NT_SUCCESS(st) )
                {
                        txps=IoThreadToProcess(txtd);
                        if ( txps == Process )
                        {
                                TerminateThread(txtd);
                        }
                }
        }
        return STATUS_SUCCESS;
}
这样,我们就创建了一个无敌的进程,经测试,IceSword,IceLight,PsNull,XueTr(PCHunter),WTool,任务管理器,UTM4XP均杀不死我们的进程。但由于KeTerminateThread是把线程的调度链表摘掉,因此,线程调度机制的算法不会再选择这个进程内任何线程,也就意味着,这个进程死到了一半,再也活不过来了。
注:若要在NT6系列系统上使用这种方法创建无线程进程,请先查找到KeTerminateThread的地址。
方法很简单:先获得导出函数PsTerminateSystemThread的地址,查找其唯一的call指令,后面跟着的就是PspTerminateThreadByPointer的相对地址。取得地址后,查找第三个push指令,注意我指的这个push指令的机器码是0x68。后面跟着的地址是PsExitSpecialApc的绝对地址。取得地址后查找其第二个call指令,后面跟着的,就是PspExitThread。而PspExitThread的倒数第二个指令就是KeTerminateThread。
此外,不要妄想创建远程线程的方式杀进程,我测试过,无效。
p.s:本文附带的附件,其附件描述摘自Hovi.Delphic的帖子。

tangptr@126.com 发表于 2015-8-2 22:04:13

刚才拿着这篇帖子的内容核心到处装逼,结果某群的某Y冒了这么句,没搞懂啥意思。懂得解释下并测试是否可行。把结果发到楼下就行。

搞不懂的地方就是杀进程和MD5有个卵关系啊!

Tesla.Angela 发表于 2015-8-3 08:33:30

进程真可怜,半死不活。
页: [1]
查看完整版本: TP的学习笔记:创建一个没有线程而且杀不死的进程