假设是a进程创建了b进程,那么a进程就是b进程的父进程。反之,假设是b创建了a,那么b进程就是a的父进程,这是在windows出现以来一直是程序员们都证实的,可是在在win Vista后面有了一个新安全消息机制。UAC(user account control),这里科普下UAC的功能,事实上UAC就是大家常见的安装软件或者启动程序的时候的出现的全屏变暗的一个提示框,这里顺便提醒下大家不要把它的提醒级别减少。这里大家不要蓄意把他的提示级别较低。这样会带来非常大的安全隐患。由于正常的UAC级别下,会检測程序是否有数字签名(可识别程序),以及他的数字签名是否合法。这对于一部分低端的木马具有提醒作用(注意这里说的是能够提示一般的 灰鸽子等变种,高端的木马会绕过这里,具体思路见后面),好了这里再回头说进程关系,这里先说一句关键的话:进程在创建进程时。他的父进程能够被指定。这个是在《深入解析Windows操作系统》(第六版)中有具体的说明,里面的意思是这样解释UAC提权的,当用户同意一次UAC提权时。AIS服务(AppInfo Service)调用的CreateProcessAsUser() 函数创建进程而且赋予恰当的管理员权限,在理论上说AIS服务(所在的进程)是提权后进程的父进程。当我们用进程树查看工具(顺便推荐几款用过的Process moniter。IceSworld,Process Explorer等) 查看时,会发现提权的进程的父进程是创建它的进程,这是由于AIS利用了CreateProcessAsUser() API中的一个新的功能,这里的新功能就是将提权进程的父进程设置成创建该进程的进程,假设我们利用一下该API,我们就能够将自己的进程的的父进程设置为随意进程(要提权绕过UAC的鸽子注意了),假设把木马进程的父进程设置为 杀软 的ID或者csrss.exe ,notepad.exe 等可信进程,那么对于根据父进程可疑(进程链)来查杀的杀软就轻易绕过了,这里顺便提示下还有一个绕过反调试的小技巧,假设你发现一个该死的小程序检查父进程是不是explorer.exe来推断是否是合法环境。那你会咋办?这里通常是逆向一些小游戏的时候常见滴,好吧,不卖关子了。根据上面的介绍,我调试的时候把他的父进程从 ollydbg直接改成他要求的explorer.exe 就Ok了。
有木有? 呵呵。这里事实上是高兴的太早。由于道高一尺。魔高一丈,要想真正的搞清楚原理,还是继续往下看吧,这个新的功能须要哪里查?这里微软的东东首推MSDN,以下去看下喽:
在MSDN中介绍的,假设是CreateProcessAsUser 的dwCreationFlags 的參数被设置为EXTENDED_STARTUPINFO_PRESENT, 这就是有扩展启动信息的结构体, 这里的IpStartupInfo參数须要填好STARTUPEX 结构,这个结构由STARTUOINFO结构和PROC_THREAD_ATTRIBUTE_LIST 指针构成:
typedef struct _STARTUPINFOEX { STARTUPINFO StartupInfo; PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList; } STARTUPINFOEX, *LPSTARTUPINFOEX;
这里有个没有公开的PROC_THREAD_ATTRIBUTE_LIST 结构,这个结构须要通过InitializeProcThreadAttributeList() 函数来进行初始化,通过UpdateProcThreadAttribute()函数加入设置属性。我们仅仅须要加入PROC_THREAD_ATTRIBUTE_PROCESS 属性。而且提供一个(有足够权限的)进程句柄。就能能设置这个被创建进程的父进程,这里也仿照黑防上贴下部分代码:
DWORD pid = 0; /* 依据进程名获取随意进程Id */ GetProcessIdByName(L"explorer.exe",&pid); /* 已所有权限打开explorer.exe 进程 */ HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid); cout << "PID:" << pid << endl << "Handle:" << handle << endl; /* 创建启动信息结构体 */ STARTUPOINFOEXA si; /* 初始化结构体 */ ZeroMemory(&si,sizeof(si)); /* 设置结构体成员 */ si.StartupInfo.cb = sizeof(si); SIZE_T lpsize = 0; /* 用微软规定的特定的函数初始化结构体 */ InitializeProcThreadAttributeList(NULL,1,0,&lpsize); char * temp = new char[lpsize]; /* 转换指针到正确类型 */ LPPROC_THREAD_ATTRIBUTE_LIST AttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)temp; /* 真正为结构体初始化属性參数 */ InitializeProcThreadAttributeList(AttributeList,1,0,&lpsize); /* 用已构造的属性结构体更新属性表 */ if (!UpdateProcThreadAttributeList,0,PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &handle,sizeof(HANDLE),NULL,NULL) { cout << "Fail to update attributes" << endl; } /* 移交指针,这里已更换了父进程的属性表是 explorer.exe */ si.lpAttributeList = AttributeList; PROCESS_INFORMATION pi; ZeroMemory(&pi, sizeof(pi)); # ifdef ADMIN HANDLE Token; /* 这里的token须要改动,假设在启动如注冊表等时,而且要右键管理员形式启动(这个过程能够程序实现,你懂的!!
!) */ OpenProcessAsUserA(Token, 0 , "regedit.exe", 0, 0, 0, EXTENDED_STARTUPINFO_PRESENT,0, 0, (LPSTARTUPINFOA)&si, &pi); # else if (CreateProcessAsUserA(NULL,0,"calc.exe",0, 0, 0, EXTENDED_STARTUPINFO_PRESENT,0, 0, (LPSTARTUPINFOA),&si, &pi)) # endif { cout << "Process started" << endl; } else { cout << "Error code:" << GetLastError() << endl; } /* 处理后事 */ DeleteProcThreadAttributeList(AttributeList); delete temp;
以上就是伪造explorer.exe为calc.exe的父进程。 假设你调试的程序检測父进程,直接用以上的办法启动它,当然父进程就是他检測同意的父进程喽, 这里启动时要注意的是设置CREATE_SUSPEND 就是创建挂起,然后在创建后使用ResumeThread恢复就能够顺利调试了。
所以说进程的父进程不一定是进程的创建者,所以那一群依据父进程来看进程是否可信的杀软就呵呵了。 可是这里说下 360 这个绕只是,原因是啥哪? 记得我开篇时说过道高一尺,魔高一丈吗?事实上在MSDN中还有个函数PsSetCreateProcessNotifyRoutine(), 这个函数就是设置监控回调函数,而且接受一个指向PS_CREARTE_NOTIFY_INFO的结构的指针, 通常我们所觉得的ParentProcesId 成员为父进程的PID。可是这个结构有一个成员为CreateThreadId,当中的CreatingThreadId->UniqueProcess 为真正进程的创建者(也就是CreateProcess* 函数的调用者)。用这样的办法推断父进程才是真正的父进程。
这里參考文献是杂志《黑客防线》。我也不想学习了知识装起来。所以学习始终是学无止境!