俺的小站:
http://blog.shajincheng.com/post/68.html
NtTerminateProcess这个函数已经被大家分析透了N多遍了
在高版本的windows中.该函数被修改为了一个封装函数
通过调用PsTerminateProcess-PspTerminateProcess来终止进程
昨天偶然想起大家都获取PspTerminateProcess或者插APC的方式来结束进程
那么我们是不是可以用别的来结束进程?
然后我就瞄上了PspTerminateThreadByPointer这个函数(虽然说也被说烂了)
该函数没有导出.也木有文档化..也就是说我们不能直接调用..
那么就想办法获取到地址
果断盯上了PsTerminateSystemThread这个导出函数(搜索到的)
该函数地址可以通过
UNICODE_STRINGFunName;
RtlInitUnicodeString(&FunName,L"PsTerminateSystemThread");
ULONGFunAddress = 0;
FunAdd =(ULONG) MmGetSystemRoutineAddress(&FunName);
来获取
该函数内部就是调用的PsTerminateSystemThread
很简单的定位到调用点:
push eax
call PspTerminateThreadByPointer
特征码为:{0x50,0xe8}
//push eax,call
其实如果长一点的函数.这么定位特征码很容易出问题..但是很庆幸这个函数内部只这么调用了我们想要的函数
PsTerminateSystemThread函数长39字节.
那么我们只需要
UCHAR FeatureCode[2]={0x50,0xe8};
FeatureAddress = SearchFeatureCodeForAddress(FunAdd,FeatureCode,2,0x27);
RtlCopyMemory(&FunAddress,(PULONG)(FeatureAddress + 2),4);
RetAddress = FunAddress + ( FeatureAddress + 6 - 5 );
FeatureCode为特征码
SearchFeatureCodeForAddress是我写的一个扫描特征码的函数,将返回扫描到特征码的首地址(从123465中扫描46返回4而不是3或5)
也就是会返回push eax命令所在的地址(push eax为0x50)
那么我们向后两个字节就是call的偏移地址了
然后通过 目的地址 = 偏移 +( EIP - 代码长度 ) 公式计算出真实的地址
PS:因为FeatureAddress是push eax的地址.所以需要+6(包括push eax的一个字节和call的5个字节)来计算得到真正call时候的eip
然后我们得到RetAddress也就是PspTerminateThreadByPointer的地址了
不过总觉得不对劲..因为PspTerminateThreadByPointer是把线程干掉了..那么PEPROCESS啥的谁来销毁?
翻翻wrk看看
NtTerminateProcess的实现是在
WRK-v1.2\base\ntos\ps\psdelete.c@273
首先找到和我们实现方法差不多的地方
for (Thread = PsGetNextProcessThread (Process, NULL);Thread != NULL;Thread = PsGetNextProcessThread (Process, Thread))
{
st = STATUS_SUCCESS;
if (Thread != Self)
{
PspTerminateThreadByPointer (Thread, ExitStatus, FALSE);
}
}
其中的Self是通过
1
Self = PsGetCurrentThread();
获取的当前线程的ETHREAD指针
也就是说在这里先不杀掉自己的线程..
注意到后面有个判断:
先补下前面的赋值操作
CurrentProcess = PsGetCurrentProcessByThread (Self);
if (ARGUMENT_PRESENT (ProcessHandle))
{
ProcessHandleSpecified = TRUE;
}
else
{
ProcessHandleSpecified = FALSE;
ProcessHandle = NtCurrentProcess();
}
然后贴上WRK的代码
if (ProcessHandleSpecified) {
PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE);
}
if (Process == CurrentProcess)
{
if (ProcessHandleSpecified)
{
ObDereferenceObject (Process);
//
// Never Returns
//
PspTerminateThreadByPointer (Self, ExitStatus, TRUE);
}
} else if (ExitStatus == DBG_TERMINATE_PROCESS)
{
DbgkClearProcessDebugObject (Process, NULL);
}
if (st == STATUS_NOTHING_TO_TERMINATE || (Process->DebugPort != NULL && ProcessHandleSpecified))
{
ObClearProcessHandleTable (Process);
st = STATUS_SUCCESS;
}
这时候有问题了
ARGUMENT_PRESENT (ProcessHandle)会判断句柄是否为Null
如果为null那么就变成自杀.并且会将ProcessHandleSpecified设置为false
这样的话执行完毕时候不会杀掉调用线程.也不会清理句柄表了,同时也不会将进程设置为delete
然后去请教MJ大大,MJ大大的回复大意是:
在exitprocess时候会向NtTerminateProcess传0,而传0的作用就是清理除了自己之外的所有线程,然后通过CsrClientCallServer通知csrss
最后再用
NtTerminateProcess(NtCurrentProcess(), ExitStatus)
来杀掉自己
然后转回头来看看之前的情况.果然有问题.因为我们没有去ObClearProcessHandleTable
虽然不知道不加会有什么问题.但是还是加上吧
另外.根据
PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE)
还得要加下标记
然后还有个问题
就是NtTerminateProcess中会进行rundown锁的操作
if (!ExAcquireRundownProtection (&Process->RundownProtect)) {
ObDereferenceObject (Process);
return STATUS_PROCESS_IS_TERMINATING;
}
所以我们也应该获取该锁防止出问题(为了稳定)
完事之后再记得释放这个锁就好了:
ExReleaseRundownProtection (&Process->RundownProtect);
突然又想到个问题
内核对象是引用为0时才进行销毁.那么这里又如何保证可以销毁该eprocess对象的呢?
莫非是
PS_SET_BITS (&Process->Flags, PS_PROCESS_FLAGS_PROCESS_DELETE)
吗?那这样有点说不通啊
然后再次去求教MJ大神
答复是:
"根Flag没关系,就是在进程销毁,对象引用计数为0时系统销毁的"
"根Delete没有任何关系,delete主要用于告诉一些组件这个进程正在销毁,一些线程创建、调试等就不要工作了
进程退出与否和句柄、引用计数以及对象是否销毁没关系,进程线程都被销毁了就没有代码执行能力,从进程链中删除了也看不到了。
实际销毁对象结构本身是在句柄和引用计数都清0了完成多大,如果继续持有进程对象引用或句柄,进程对象不会销毁,但也只是留一个空壳 没有实际能力"
我想我大概明白了.然后去搜索了下wrk
BOOLEAN
DbgkpSuspendProcess (
VOID
)
/*++
Routine Description:
This function causes all threads in the calling process except for
the calling thread to suspend.
Arguments:
None.
Return Value:
None.
--*/
{
PAGED_CODE();
//
// Freeze the execution of all threads in the current process, but the
// calling thread. If we are in the process of being deleted don't do this.
//
if ((PsGetCurrentProcess()->Flags&PS_PROCESS_FLAGS_PROCESS_DELETE) == 0) {
KeFreezeAllThreads();
return TRUE;
}
return FALSE;
}
发现果然很多操作都会验证Flags&PS_PROCESS_FLAGS_PROCESS_DELETE
如果为flase(0)则说明没有这个标记.然后才会继续执行操作
这样一来.我们如果想杀掉一个进程.流程很明显了
1.获取rundown锁,然后设置PS_PROCESS_FLAGS_PROCESS_DELETE
2.插APC把线程全干掉(PspTerminateThreadByPointer本质上也是插APC)
3.释放rundown锁
4.把自己的引用计数平衡掉,这样一来该进程就剩下个空壳了
5.等着他的引用计数归0就自然销毁了
PS:大神不愧是大神
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)