运行一个EXE程序如果出现无法运行的情况,比如受限制或者程序被替换掉,一般有两种情况,第一种是外壳限制。双击一个程序通常调用Shell开头的函数来执行,比如ShellExecute,这个函数并非真正执行了EXE,而是会做出许多选择,例如是否被外壳限制(注册表RestrictRun项),检查文件的关联等。第二种是执行限制。当EXE文件关联被改变的情况下,Windows的任务管理器还是可以运行的,因为它用的是直接执行方式CreateProcess,所以它不会受到关联的影响。然而CreateProcess并非最底层,它也会被拦截,CreateProcess之后是CreateProcessInternal,后者会做各种检查,比如以下三种方式:
1.映像劫持(IFEO)
这种技术如今已经过时了,在这里简单说一下。通过IFEO可以对目标程序进行Hook,原理是利用了Windows的调试机制。在执行一个程序时,系统会检查注册表中Image File Execution Options项,若存在同名程序,则放弃执行原程序,而用Debugger后面的程序用来调试原来的程序,这里实际上运行了调试器,原程序只是一个参数而已。系统不会检查是否循环调用,出现这种情况则会卡死直到超出路径最大长度进而出错。这种技术可以被病毒利用达到偷梁换柱的目的,也可以用来限制某个程序运行。关于IFEO有个错误的说法,说是以lpApplicationName方式运行EXE就不会被调试,事实证明这种方式无效,只要有debugger就会被调试,除非指定DEBUG_PROCESS或者DEBUG_ONLY_THIS_PROCESS。
2.软件限制策略
系统组策略中有一项叫软件限制策略,用于限制一个程序运行,可以根据路径限制,也可以根据文件MD5来限制。比如禁止一个路径规则?:\*就会限制运行所有程序。
3.Manifest文件
这是一个程序集文件,有些软件会带有一个这样的文件,文件命名方式是在原EXE文件名后加上个.Manifest后缀。大多数软件是没有这个文件的,如果在软件目录里建了一个这样的空文件或文件夹,那么软件运行时因为读取出错就会终止运行。这种方式作用同IFEO差不多,都可以根据文件名来限制其运行,只是不能替换文件。
那么如何摆脱它们的限制呢?其实也很简单,通用调试CreateProcess会发现,中间会对上面的三者分别进程检查。检查的顺序是,软件限制策略->映像劫持->Manifest文件。首先是软件限制策略,执行到这一步系统会调用四个函数:SaferIdentifyLevel、SaferComputeTokenFromLevel、SaferCloseLevel和SaferRecordEventLogEntry。这4个函数位于advapi32.dll库中,其中SaferComputeTokenFromLevel比较关键,它的返回值决定了是去还是留,若返回0则表示校验失败,说明存在限制,若返回1代表校验成功,程序会往下执行。我们直接内部Hook掉SaferComputeTokenFromLevel返回1,便可无视软件限制策略。接下来再看映像劫持,程序之所以会被劫持是因为注册表中存在同名程序,所以系统势必会查询“Image File Execution Options”项,我们直接Hook掉ntdll.dll的NoOpenKey这个存根函数,一旦发现在查询IFEO就返回,程序就会跳过IFEO的检查了。其实这种方法比较烦琐一些,还有个更为简单的方法,ntdll.dll中有一个LdrQueryImageFileExecutionOptions函数,它负责管理IFEO,直接干掉它让它返回0即可。当然,如果还是感觉麻烦干脆如开头所说的直接在创建进程时加个DEBUG_PROCESS标志,运行后再结束调试即可。再来看最后一个,这个比较特别,因为我没有找到哪个函数在检查Manifest,找不到也无所谓,因为不管是什么函数在检查它,实质是要判断是否存在Manifest这个文件,系统一般会采用打开文件的方法来判断,也就是NtOpenFile,现在我们将NtOpenFile回调一下,只要监控到manifest则返回一个STATUS_OBJECT_NAME_NOT_FOUND,意思是没有找到对象名称,这样也就绕过了Manifest机制。剩余的就是系统的任务了,比如NtCreateSection