首页
社区
课程
招聘
[原创]因为不想直接插APC所以使用PspTerminateThreadByPointer去结束进程引出的疑问的解决过程
发表于: 2014-5-21 13:35 10634

[原创]因为不想直接插APC所以使用PspTerminateThreadByPointer去结束进程引出的疑问的解决过程

2014-5-21 13:35
10634
俺的小站: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期)

收藏
免费 0
支持
分享
最新回复 (15)
雪    币: 272
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2

MJ大大的回复大意是:
在exitprocess时候会向NtTerminateProcess传0,而传0的作用就是清理除了自己之外的所有线程,然后通过CsrClientCallServer通知csrss
最后再用
NtTerminateProcess(NtCurrentProcess(), ExitStatus)
来杀掉自己


牛逼,他这都知道,MJ大神名不虚传啊,这句话无意中解决了一个困扰许久的 某些时候调用exitprocess会出错的问题!
2014-5-21 14:55
0
雪    币: 8188
活跃值: (2847)
能力值: ( LV9,RANK:180 )
在线值:
发帖
回帖
粉丝
3
直接 |一下PS_PROCESS_FLAGS_PROCESS_DELETE会怎么样,线程留着。
2014-5-21 14:59
0
雪    币: 220
活跃值: (117)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
那么创建新线程时候会失败.似乎dup句柄啥的也会出问题吧
然后只要杀他 就会返回正在被杀..
2014-5-21 15:10
0
雪    币: 6552
活跃值: (4346)
能力值: ( LV10,RANK:163 )
在线值:
发帖
回帖
粉丝
5
堆栈能回溯到函数调用的地方,解决一下这个吧-.-
2014-5-21 18:27
0
雪    币: 11
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
似乎自己插apc比用PspTerminateThreadByPointer更稳定……
2014-5-21 18:37
0
雪    币: 27
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
这样一来.我们如果想杀掉一个进程.流程很明显了
1.获取rundown锁,然后设置PS_PROCESS_FLAGS_PROCESS_DELETE
2.插APC把线程全干掉(PspTerminateThreadByPointer本质上也是插APC)
3.释放rundown锁
4.把自己的引用计数平衡掉,这样一来该进程就剩下个空壳了
5.等着他的引用计数归0就自然销毁了

学习一下
2014-5-22 19:16
0
雪    币: 239
活跃值: (133)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
8
请教MJ大大是亮点,楼主数字的?
2014-5-22 20:20
0
雪    币: 544
活跃值: (264)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
9
为毛都喜欢问别人,IDA一下kernel32,一目了然的事啊。
2014-5-23 22:00
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
10
非常赞~

int __stdcall _ExitProcess(UINT ExitCode)
{
  int result; // eax@1
  char v2; // [sp+10h] [bp-DCh]@2
  UINT v3; // [sp+38h] [bp-B4h]@2
  CPPEH_RECORD ms_exc; // [sp+D4h] [bp-18h]@2

  result = __security_cookie;
  if ( !BaseRunningInServerProcess )
  {
    RtlAcquirePebLock();
    ms_exc.disabled = 0;
    NtTerminateProcess(0, ExitCode);
    LdrShutdownProcess();
    v3 = ExitCode;
    CsrClientCallServer(&v2, 0, 65539, 4);
    NtTerminateProcess(-1, ExitCode);
    ms_exc.disabled = -1;
    result = RtlReleasePebLock();
  }
  return result;
}
2014-5-23 22:56
0
雪    币: 220
活跃值: (117)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
不问你能一下子就知道是因为exitprocess用到的?
我看了好几个函数了 反正是没想到是这里用的
2014-5-24 10:13
0
雪    币: 544
活跃值: (264)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
12
#include <Windows.h>
#pragma comment(linker,"/ENTRY:main")
void main()
{
    __asm{int 3}
}

按F10、F11,看看流程怎么流的。
2014-5-24 12:37
0
雪    币: 1
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
最TM鄙视LS这种人 张嘴闭嘴就是IDA 调试器
请问你一出生就会用IDA吗 小菜遇到问题 第一时间想到的就是问大牛
如果不让问 不如把看雪给关了吧 人人看看IDA调试器就能解决问题了
2014-5-24 12:45
0
雪    币: 544
活跃值: (264)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
14
每个人都是从小菜成长起来的,遇到问题时,第一时间应该想到的是“我有什么方法来解决这个问题”,像楼主的这个问题,只要自己写个程序,加个int 3,跟踪下进程的退出流程,然后加上IDA就很清楚了。

“小菜遇到问题 第一时间想到的就是问大牛”,大牛不在怎么办?大牛不理你怎么办?

不说了。
2014-5-24 13:06
0
雪    币: 220
活跃值: (117)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
谢谢啦。
这办法不错。
自己写个程序在ret之前断下来。然后紧跟流程看看

不过说真的。我一开始也没有想到是进程退出用到的(还以为是某种神奇用法)。如果想到了直接拽进IDA或者看下WRK就搞明白了。
2014-5-25 09:02
0
雪    币: 244
活跃值: (174)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
mark  进来学习下哈
2017-6-14 10:27
0
游客
登录 | 注册 方可回帖
返回
//