首页 > 代码库 > 关于父进程和子进程的关系(UAC 绕过思路)

关于父进程和子进程的关系(UAC 绕过思路)

      表面上看,在windows中。如果是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* 函数的调用者),用这种办法判断父进程才是真正的父进程。
这里参考文献是杂志《黑客防线》,我也不想学习了知识装起来,所以学习始终是学无止境!