()将把STATUS_ACCESS_DENIED写入EAX,恢复ESP并返回-从调用者的观点来看,这就象对NtCreateSection()的调用失败一样-以错误状态STATUS_ACCESS_DENIED返回。
check()函数是怎样做出决定的?一旦它收到一个指向服务参数的指针参数,它就可以检查这些参数。首先,它检查标志和属性-如果有一部分没有被要求作为一个可执行映像映射,或如果要求的页面保护不允许执行,那么我们可以确定NtCreateSection()调用与进程创建毫无关系。在这种情况下,check()直接返回TRUE。否则,它将检查该潜在文件的扩展-毕竟,SEC_IMAGE属性和允许执行的页面保护可能被要求来映射某个DLL文件。如果该潜在文件不是一个.exe文件,那么,check()将返回TRUE。否则,它给用户模式代码一个作出决定的机会。因此,它仅把文件名和路径写到交换缓冲区,并且对它循环
查询,直到它得到响应为止。
在打开我们的驱动程序前,我们的应用
程序创建一个运行下面函数的线程:
void thread() { DWORD a,x; char msgbuff[512]; while(1) { memmove(&a,&outputbuff[0],4); //如果什么也没有,Sleep() 10毫秒并再检查 if(!a){Sleep(10);continue;} //看起来象我们的权限被询问。 //如果被怀疑的文件已经存在于空白列表中, // 则给出一个积极的响应。 char*name=(char*)&outputbuff; for(x=0;x<stringcount;x++) { if(!stricmp(name,strings[x])){a=1;goto skip;} } //要求用户允许运行该程序 strcpy(msgbuff, "Do you want to run "); strcat(msgbuff,&outputbuff); //如果用户的答复是积极的,那么把这个程序添加到空白列表中 if(IDYES==MessageBox(0, msgbuff,"WARNING",MB_YESNO|MB_ICONQUESTION|0x00200000L)) {a=1; strings[stringcount]=_strdup(name);stringcount++;} else a=0; // 把响应写入缓冲区中,而由驱动程序之后取回它 skip:memmove(&outputbuff,&a,4); //告诉驱动程序继续 a=0; memmove(&outputbuff[0],&a,4); } } |
这段代码是显然的-我们的线程每10毫秒
查询交换缓冲区。如果它发现我们的驱动程序已经把它的请求寄到了该缓冲区中,它就检查被允许在本机上运行的程序列表中的文件的文件名和路径。如果发现匹配,它直接给出一个OK响应。否则,它显示一个消息窗口,询问用户是否允许有问题的程序执行。如果响应是积极的,我们就把有问题的程序添加到允许在本机上运行的软件列表中。最后,我们把用户响应写入缓冲区,也就是说,把它传递到我们的驱动程序。因此,该用户就能完全控制它的PC上的进程的创建-只要我们的程序运行,在没有用户所给予权限的情况下,绝对没有办法来启动该PC上的任何进程。
正如你所见,我们让内核方式代码等待用户反应。这是否是一种聪明的举措呢?为了回答这个问题,你必须问你自己你是否正在堵住任何关键的系统资源-一切都依赖于具体的情况。在我们的情况下,一切发生在IRQLPASSIVE_LEVEL级上,并没有包含对IRPs的处理,并且必须等待用户响应的线程并不十分重要。因此,在我们的情况下,一切工作正常。然而,本例仅为演示之目的而编写。为了实际地使用它,以一个自动启动的服务的方式来重写我们的应用程序是很重要的。在这种情况下,我建议我们解除LocalSystem帐户,并且,在NtCreateSection()被用LocalSystem帐户特权在一个线程的上下文中调用的情况下,可以继续实际的服务实现而不施行任何检查-不管怎么说,LocalSystem帐户仅运行那些在
注册表中指定的可执行程序。因此,这样的一种解除不会是与我们的安全相妥协的。
四、 结论 最后,我必须指出,钩住本机API很明显是现已存在的最强有力的编程技