探索黑客技术攻防,实战研究与安全创新

导航菜单

探秘父进程

父进程似乎是一个很简单的概念:如果进程a创建了进程b,那么进程a就是进程b的父进程,反之进程b就是进程a的子进程。最近在研究UAC的时候,发现事情并非这样简单,进程在创建新进程时,可以指定另一个进程作为被创建进程的父进程。

我们先来看看《深入解析Windows操作系统》中关于UAC提权的描述:

WhenauseragreestoanelevationbyeitherenteringadministratorcredentialsorclickingContinue,AIScallsCreateProcessAsUsertolaunchtheprocesswiththeappropriateadministrativeidentity.

AlthoughAISistechnicallytheparentoftheelevatedprocess,AISusesnewsupportinthe

CreateProcessAsUserAPIthatsetstheprocess’sparentprocessIDtothatoftheprocessthatoriginallylaunchedit.That’swhyelevatedprocessesdon’tappearaschildrenoftheAISservice-hostingprocessintoolssuchasProcessExplorerthatshowprocesstrees.

这段话明确表明,当用户允许一次UAC提权时,AIS服务(AppInfoService)调用CreateProcessAsUser创建进程并赋予恰当的管理员权限。从理论上讲,AIS服务(所在的进程)是提权后的进程的父进程,但当我们用ProcessExplorer等工具检查进程树时,会发现提权的进程的父进程是创建它的进程。这是因为AIS利用了CreateProcessAsUserAPI中的“新功能”,将提权进程的父进程设置成为创建该进程的进程。如果我们能够利用这种功能,就可以将想要启动的进程的父进程设为任意可信进程,进而躲过查杀;对于检测父进程是否为explorer.exe的反调试技术,也可以轻易破解。不过书中没有就这种“新功能”做详细介绍,只有查阅MSDN。

根据MSDN的介绍,如果CreateProcessAsUser的dwCreationFlags参数被设置为EXTENDED_STARTUPINFO_PRESENT,则表示存在扩展启动信息。此时lpStartupInfo参数需要填入STARTUPINFOEX结构。该结构非常简单,由STARTUPINFO结构和PROC_THREAD_ATTRIBUTE_LIST指针组成。

typedefstruct_STARTUPINFOEX{

STARTUPINFO

StartupInfo;

PPROC_THREAD_ATTRIBUTE_LIST

lpAttributeList;

}STARTUPINFOEX,*LPSTARTUPINFOEX;

PROC_THREAD_ATTRIBUTE_LIST

是未公开的结构,需要通过InitializeProcThreadAttributeList函数初始化,并通过UpdateProcThreadAttribute函数添加和设置属性。我们只需添加PROC_THREAD_ATTRIBUTE_PARENT_PROCESS属性,并提供一个(有足够权限的)进程句柄,即可设置被创建进程的父进程。部分代码如下:


DWORDpid=0;
GetProcessIdByName(L"explorer.exe",&pid))
HANDLEhandle=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
cout<<"PID:"<<pid<<endl<<"Handle:"<<handle<<endl;
STARTUPINFOEXAsi;
ZeroMemory(&si,sizeof(si));
si.StartupInfo.cb=sizeof(si);
SIZE_Tlpsize=0;
InitializeProcThreadAttributeList(NULL,1,0,&lpsize);
char*temp=newchar[lpsize];
LPPROC_THREAD_ATTRIBUTE_LIST
AttributeList=(LPPROC_THREAD_ATTRIBUTE_LIST)temp;
InitializeProcThreadAttributeList(AttributeList,1,0,&lpsize);
if(!UpdateProcThreadAttribute(AttributeList,0,PROC_THREAD_ATTRIB
UTE_PARENT_PROCESS,&handle,sizeof(HANDLE),NULL,NULL)){
cout<<"Failtoupdateattributes"<<endl;
}
si.lpAttributeList=AttributeList;
PROCESS_INFORMATIONpi;
ZeroMemory(&pi,sizeof(pi));
#ifdefADMIN
HANDLEToken;
OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&Token);
if(CreateProcessAsUserA(Token,0,"regedit.exe",0,0,0,EXTENDED_STA
RTUPINFO_PRESENT,0,0,(LPSTARTUPINFOA)&si,&pi))
#else
if(CreateProcessAsUserA(NULL,0,"calc.exe",0,0,0,EXTENDED_STARTUP
INFO_PRESENT,0,0,(LPSTARTUPINFOA)&si,&pi))
#endif
{
cout<<"Processstarted"<<endl;
}else{
cout<<"Errorcode:"<<GetLastError()<<endl;
}
DeleteProcThreadAttributeList(AttributeList);
deletetemp;


以上代码会用explorer.exe作为父进程启动calc.exe。利用ProcessExplorer可以验证这一点,如图1和图2为伪造父进程与直接启动calc.exe的进程树对比。如果需要创建有管理员权限的进程(如regedit),只需以管理员权限运行本程序,并将CreateProcessAsUser的第一个参数设为当前进程的令牌句柄(对应于上述代码中#defineADMIN的情况)。

C6.png

C7.png

如果我们想要调试的程序会检测父进程,则只需用上述方法启动它,并将其父进程设为允许的进程即可。当然,启动时需要设置CREATE_SUSPEND并在调试器挂载后用ResumeThread恢复其执行。

综上,进程的父进程不一定是进程的创建者,所以杀软并不能简单的根据父进程(进程树)决定进程是否可信。实验发现,上述方法伪造父进程不能绕过360的检测,它是怎么做到的呢?查阅MSDN发现,通过PsSetCreateProcessNotifyRoutine设置的监控回调会接受一个指向PS_CREATE_NOTIFY_INFO结构的指针。通常我们认为ParentProcessId成员为父进程的PID;其实该结构有一个成员为CreatingThreadId,其中的CreatingThreadId->UniqueProcess为进程的创建者(即CreateProcess*的调用者),这样便可以确定谁是真正的“父进程”了。