看流星社区

 找回密码
 注册账号
查看: 1872|回复: 0

拦截进程创建(不会卡死桌面)

[复制链接]

该用户从未签到

发表于 2017-6-1 17:21:00 | 显示全部楼层 |阅读模式
标 题: 【原创】拦截进程创建(不会卡死桌面)
作 者: bycon
时 间: 2011-01-28,16:44:32
链 接: http://bbs.pediy.com/showthread.php?t=128733

菜鸟作品,大牛请无视。如有错误或纰漏,望指出

之前看过很多关于进程创建的拦截,都是勾在NtCreateProcess或者NtCreateSection上,拦截到的往往都是Explorer进程中的线程,此时如果将线程卡在内核中,就会导致桌面卡死。这不是我想要的。

百度GOOGLE了好久,没找到类似的解决办法,逆又逆不出来,就只有自己翻书动脑子了(不知道各大安全卫士是怎么实现的)。

既然不能卡死Explorer的线程,那咱就卡别人去,卡谁?我想你已经知道了——被创建者的主线程



相关知识:

每一个线程都是从内核空间的KiThreadStartup开始运行的,线程在被创建的时候就指定了它在系统空间的上下文以及返回用户空间的自陷框架。在KiThreadStartup中,线程将运行级别降低至APC_LEVEL后调用PspUserThreadStartup(如果是系统线程,这里调用的是PspSystemThreadStartup),PspUserThreadStartup将用户空间ntdll.dll中的函数LdrInitializeThunk作为APC函数挂入APC队列。当线程返回用户空间时,就会检测到APC函数的存在,并先加以执行,直到APC队列中不再有请求时才算正式回到用户空间。
回到用户空间的什么地方呢?这要看线程被创建的时候设定的自陷框架了。如果是主线程,则回到BaseProcessStartup,非主线程则是BaseThreadStartup。至于用户空间给定的线程入口(如主线程的OEP),则存放在寄存器EAX中,作为参数调用BaseProcessStartup或者BaseThreadStartup,再由这二者之一将其放在一个SEH保护域中加以调用。

实现:
如果上面看晕了,没关系,只需知道每个线程都是在KiThreadStartup中开始运行的,如果是用户线程,则会调用PspUserThreadStartup。注意此时的IRQL为APC_LEVEL。
而我选择的挂钩地点就是PspUserThreadStartup。E9跳转需要5个字节,我选择在PspUserThreadStartup+2的位置JMP,即

Code:

kd>uPspUserThreadStartup
nt!PspUserThreadStartup:
805c70506a20push20h
805c70526870ae4d80pushoffsetnt!ObWatchHandles+0x61c(804dae70)
805c7057e8c41ef7ffcallnt!_SEH_prolog(80538f20)
805c705c64a124010000moveax,dwordptrfs:[00000124h]
805c70628bf0movesi,eax
805c70648975e0movdwordptr[ebp-20h],esi
805c70678b7e44movedi,dwordptr[esi+44h]
805c706a897ddcmovdwordptr[ebp-24h],edi
[/code]



相关函数:

程序中的g_uStartSign,是用来判断线程是否是进程的主线程的。它记录的是线程在用户空间的开始地址,如果是主线程,它就是BaseProcessStartup,非主线程就是BaseThreadStartup。ETHREAD结构中有这个值。g_uStartSign的赋值是通过创建一个线程(肯定不是主线程)调用DeviceIoControl,然后通过ETHREAD活得,这个值是BaseThreadStartup的地址。BaseProcessStartup和BaseThreadStartup是kernel32.dll中的函数,kernel32.dll在每个进程中映射的位置都相同,因此这两个函数的地址也在每个进程中也都相同。


Code:

kd>dt_ETHREAD
ntdll!_ETHREAD
...
+0x224StartAddresstr32Void
...
[/code]


注意:BaseProcessStartup或BaseThreadStartup是创建者进程在用户空间通过BasepInitializeContext设置的,如果以R0下安全,R3下不安全来算,这么判断是否主线程是不严谨的。这是学习的时候的一个测试,后面没改回来。

我测试过,如果把主线程的自陷框架中的StartAddress从BaseProcessStartup改成BaseThreadStartup,进程一样能运行起来,而把非主线程改成BaseProcessStartup,这个线程却运行不起来了。。

这里还有一个问题。尽管主线程拦住了,但是该进程中的EXE文件映像和ntdll.dll都已经映射完毕,如果这时候创建一个远程线程,并将线程入口指定为OEP,程序一样能运行起来。因此,我们要拦截的不止是主线程(第一个线程),而是该进程所有的线程,但是不可能每个线程都要给用户选择一遍。。

我的处理方法是维护一张进程PID表,如果有新创建的进程等待用户选择的时候,将其PID添加到表中。之后如果有线程创建,则遍历这张PID表,如果线程所属的进程正在等待用户选择,则让该线程在一个通知事件对象g_eventNotify上等待,否则放行。如果用户允许该进程运行,则在PID表中将该进程PID删除,再通知等待在事件对象g_eventNotify上等待的所有线程,这些线程判断它们所属的进程是已经允许运行还是在等待判断,从而放其运行或者继续等待。注意同步的问题。

具体程序请看附件。我加了注释。



Code:


/************************************************************************/
/*获取信息,并根据用户的选择结束进程或者允许运行*/
/*arg[0]和arg[1]分别为PspUserThreadStartup的两个参数*/
/*arg[0]=StartRoutine,arg[1]=StartContext*/
/************************************************************************/
voidDoSomethingIWant(PULONGarg)
{
ULONGdwEProcess=(ULONG)PsGetCurrentProcess();
ANSI_STRINGansiCurrentProcessName,ansiPerentProcessName;
ULONGuRet=0;
ULONGPid,PerentPid;
KSPIN_LOCKSpinLock;
KIRQLkirql;
PLIST_ENTRYhead,curr;
ULONGn;

if(g_uStartSign==0)//没准备好?
{
return;
}
Pid=(ULONG)PsGetCurrentProcessId();
PerentPid=*(PULONG)(dwEProcess+OFFSET_PERENT_PID_EPROCESS);//通过偏移从EPROCESS中获取父进程ID,偏移为硬编码
KeInitializeSpinLock(&SpinLock);
if(g_uStartSign!=arg[1])//是否为进程的第一个线程
{
//添加PID到PidTable中
KeAcquireSpinLock(&SpinLock,&kirql);
SetPidTable(Pid,1);
KeReleaseSpinLock(&SpinLock,kirql);
//取当前进程名
GetProcessPathName(dwEProcess,&ansiCurrentProcessName);
//取父进程名
GetProcessPathNameByPID(PerentPid,&ansiPerentProcessName);
//打印信息~~
DbgPrint("[ProcessMon]ProcessCreating,ProcessName=%s,PID=%d,PerentName=%sPID=%d\r\n",
ansiCurrentProcessName.Buffer,Pid,ansiPerentProcessName.Buffer,PerentPid);
//让用户选择
uRet=GetUserChoose(&ansiPerentProcessName,&ansiCurrentProcessName,PerentPid,Pid);

//从PidTable中删除PID
KeAcquireSpinLock(&SpinLock,&kirql);
SetPidTable(Pid,0);
KeReleaseSpinLock(&SpinLock,kirql);
//释放内存了.
if(ansiCurrentProcessName.Length)RtlFreeAnsiString(&ansiCurrentProcessName);
if(ansiPerentProcessName.Length)RtlFreeAnsiString(&ansiPerentProcessName);
//根据用户的选择允许运行或者结束进程
if(uRet)
ZwTerminateProcess(NtCurrentProcess(),0);
KeSetEvent(&g_eventNotify,0,0);//通知那些等待的线程---------------------
KeClearEvent(&g_eventNotify);//置为非信号----------------------------|
}//|
else//|
{//|
//不是第一个线程|
while(IsPidInTable(Pid))//如果线程所属进程在等待用户判断,则让其等待|
{//|
KeWaitForSingleObject(&amp;g_eventNotify,Executive,KernelMode,0,0);//<-----
}
//通过EPROCESS->ThreadListHead遍历线程,ThreadListHead的偏移量为硬编码
for(n=0,curr=head=(PLIST_ENTRY)(dwEProcess+OFFSET_THREAD_LIST_HEAD_EPROCESS);
curr->Blink!=head;
curr=curr->Blink,n++){}//计算当前进程的线程数目
DbgPrint("[ProcessMon]Pid:%.4d,Threadcount=%d\r\n",Pid,n);
}

return;
}


/************************************************************************/
/*自己假冒的函数保存现场后CALLDoSomethingIWant*/
/************************************************************************/
__declspec(naked)voidFakePspUserThreadStartup()
{
__asm{
pushfd
pushad
movebx,esp
addebx,44
pushebx
callDoSomethingIWant
popad
popfd
jmpg_Orig
}

}

/************************************************************************/
/*挂钩PspUserThreadStartup*/
/*挂钩的地址是PspUserThreadStartup+2*/
/************************************************************************/
voidHookPspUserThreadStartup()
{
PUCHARpHookAddr=(PUCHAR)FindPspUserThreadStartupAddress();
UCHARJMPCode[5]={0xe9,0,0,0,0};
UCHARJMPBackCode[5]={0xe9,0,0,0,0};
g_Orig=ExAllocatePool(NonPagedPool,10);
if(!g_Orig)
{
DbgPrint("[ProcessMon]FailedwithAllocatePool\r\n");
return;
}

if(!pHookAddr)return;
pHookAddr+=2;//挂钩的地址是PspUserThreadStartup+2

*((PULONG)(JMPCode+1))=(ULONG)FakePspUserThreadStartup-((ULONG)pHookAddr+5);
*((PULONG)(JMPBackCode+1))=(ULONG)pHookAddr+5-((ULONG)g_Orig+10);
memcpy(
g_Orig,
(PVOID)pHookAddr,
5);
memcpy(
(PVOID)((ULONG)g_Orig+5),
(PVOID)JMPBackCode,
5);
__asm{
cli
moveax,cr0
andeax,not10000h
movcr0,eax
}

memcpy((PVOID)pHookAddr,JMPCode,5);

__asm{
moveax,cr0
oreax,10000h
movcr0,eax
sti
}

}
[/code]


进程和父进程信息的获取主要来源于EPROCESS结构。

PspUserThreadStartup不是一个导出函数,需要对其定位。这里用的是搜索内存。先从SSDT中找到NtCreateThread,再从NtCreateThread中找到PspCreateThread,最后再通过PspCreateThread找PspUserThreadStartup
函数如下:


Code:

/************************************************************************/
/*取PspUserThreadStartup函数的地址*/
/************************************************************************/
PVOIDFindPspUserThreadStartupAddress()
{
ULONGAddrNtCreateThread=0;
ULONGAddrPspCreateThread=0;
ULONGAddrPspUserThreadStartup=0;
ULONGi;
UCHARcode1[29]="\x52\x52\xff\x75\x24\x8d\x45\xc8\x50\xff\x75\x1c\xff\x75\x18\x52\xff\x75\x14\xff\x75\x10\xff\x75\x0c\xff\x75\x08\xe8";
UCHARcode2[15]="\xff\x75\xe4\xff\x75\x20\xff\xb6\x24\x02\x00\x00\x6a\x00\x68";
//通过索引从SSDT中获得NtCreateThread的地址,索引为硬编码
AddrNtCreateThread=KeServiceDescriptorTable.ServiceTableBase[INDEX_OF_NTCREATETHREAD];
DbgPrint("[ProcessMon]AddrNtCreateThread=0x%.8X\r\n",AddrNtCreateThread);
//先从NtCreateThread中活得PspCreateThread的地址,
for(i=0;
i<0x1000;
i++){
if(memcmp((PVOID)(AddrNtCreateThread+i),code1,29)==0){
AddrPspCreateThread=AddrNtCreateThread+i+29;
AddrPspCreateThread=AddrPspCreateThread+4+*(PULONG)AddrPspCreateThread;
DbgPrint("[ProcessMon]AddrPspCreateThread=0x%.8X\r\n",AddrPspCreateThread);
break;
}
}

//没找到
if(AddrPspCreateThread==0){
returnNULL;
}
//再从PspCreateThread中活得PspUserThreadStartup的地址,
for(i=0;
i<0x1000;
i++){

if(memcmp((PVOID)(AddrPspCreateThread+i),code2,15)==0){
AddrPspUserThreadStartup=*(PULONG)(AddrPspCreateThread+i+15);
DbgPrint("[ProcessMon]AddrPspUserThreadStartup=0x%.8X\r\n",AddrPspUserThreadStartup);
break;
}
}
return(PVOID)AddrPspUserThreadStartup;
}
[/code]


注意:
(1)此程序以测试为目的,用到了硬编码,不利于移植。此程序在XPsp3下测试通过,没在别的平台上测试过。主要用到的硬编码:(见sys\ProcessMon.c)

Code:
//硬编码
#defineINDEX_OF_NTCREATETHREAD0x35//NtCreateProcess在SSDT中的偏移
#defineOFFSET_PERENT_PID_EPROCESS0x14c//父进程PID在EPROCESS中的偏移
#defineOFFSET_THREAD_LIST_HEAD_EPROCESS0x190//ThreadListHead在EPROCESS中的偏移
#defineOFFSET_START_ADDRESS_ETHREAD0x224//StartAddress在ETHREAD中的偏移
[/code]


(2)这里对于线程是不是主线程的判断是不严谨的。BaseProcessStartup或BaseThreadStartup是创建者进程在用户空间通过BasepInitializeContext设置的,如果以R0下安全,R3下不安全来算,这么判断是否主线程是不严谨的。这是学习的时候的一个测试,后面没改回来。


此外,大四了,快要去找工作了,今后未必有时间慢慢看书,慢慢些帖子了。特此感谢看雪,感谢那些在论坛上奉献的大小牛们。我们的成长离不开你们的帮助。*转载请注明来自看雪论坛@PEdiy.com
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-3-19 13:07

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表