Windows PsSetLoadImageNotifyRoutine的0day漏洞

在研讨windows内核过程当中,咱们存眷了一个很感兴趣的内容,便是PsSetLoadImageNotifyRoutine,像他名字异样便是供给模块加载关照的。
工作是如许的,内核中为加载的PE文件注册了一个回调关照以后,可以或许会收到一个不法的模块名字。
在对这个成绩停止发掘以后,看起来是一个偶尔的成绩实在是由于windows内核自己的代码差错惹起的。
这个缺点存在于从Windows 2000到最新的Windows 10宣布版本的一切版本中。
长处:模块加载关照
假如你是个开辟驱动的安全厂商,你必要晓得体系甚么时刻加载了模块。经由过程Hook来实现,可以或许….然则可以或许会有许多安全和实现的缺点。
微软是这么先容windows2000的PsSetLoadImageNotifyRoutine的。这个机制会在一个PE文件被加载到虚构内存中(不论是内核态照样用户态)关照内核中注册过回调的驱动,。
深刻面前:
上面这几种情况会挪用到会地哦啊关照例程:
加载驱动
启动新过程(过程可执行文件/体系DLL:ntdll.dll(对付Wow64过程会有两种分歧的文件))
静态加载PE镜像-导入表,LoadLibrary,LoadLibraryEx,NtMapViewOfSection

图1:在ntoskrnl.exe中一切对PsSetLoadImageNotifyRoutine的挪用
在挪用已注册的关照回调时,内核供给一些参数来准确标志加载的PE镜像。参数可以或许看上面的回调函数原型界说:
1
2
3
4
5
VOID (*PLOAD_IMAGE_NOTIFY_ROUTINE)(
_In_opt_ PUNICODE_STRING FullImageName, // The image name
_In_ HANDLE ProcessId, // A handle to the process the PE has been loaded to
_In_ PIMAGE_INFO ImageInfo // Information describing the loaded image (base address, size, kernel/user-mode image, etc)
);
独一的前途:
现实上,这是WDK文档化的独一用于监督PE加载到内存的的办法。
别的一种微软保举的办法,是应用文件体系mini-filter回调(IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION)。NtCreateSection为了可以或许辨别section object能否是一个加载的可执行镜像的一部分,会反省能否存在SEC_IMAGE标志。但是,文件体系mini-filter不会接管这个标志,是以不克不及辨别section object能否是加载PE镜像创立的。
缺点:差错的模块参数
独一标志加载的PE文件的参数是FullImageName。
但是,在前面描写的一切场景中,内核应用的别的一种格局的FullImageName。
第一看的时刻,咱们留意到获得过程可执行文件全门路和体系DLL的情况变量(没有卷名)时,其余静态加载的用户态PE供给的门路也没有卷名。
更让人担心的是不仅是门路没有了卷名,有时刻门路完备是畸形的,可以或许指向了一个分歧的或许不存在的文件。
RTFM
和一切研讨职员/开辟职员异样,咱们做的第一件事便是去看文档,包管对这个器械懂得准确了。
依据MSDN的描写,FullImageName表现文件在磁盘的门路,用来标志可执行文件。没有提到可以或许存在不合法或许不存在的门路。
文档中进步了门路可以或许是空:在过程创立时代假如操纵体系无奈获得镜像的完备门路,这个参数可以或许为空。也便是说,假如这个参数不是空的,那末内核就会觉得这是准确的参数而接管。
甚于拼写差错的文档
细心浏览文档是,咱们留意到另外一件事,MSDN中表现的函数原型是差错的。参数Create依据描写完备像是跟这个机制没有关系,在WDK中的函数原型完备没有这个参数。很讥讽的便是,应用MSDN供给的原型会招致栈溢出瓦解。
面纱的上面
nt!PsCallImageNotifyRoutines会挪用已注册回调函数的。它仅仅是将它挪用者传来的UNICODE_STRING指针作为FullImageName参数传给回调函数。在nt!MiMapViewOfImageSection映照一个image的section时,UNICODE_STRING是section表现的FILE_OBJECT的FileName字段。

图2 传给回调的FullImageName现实是FILE_OBJECT的FileName字段
FILE_OBJECT经由过程SECTION->SEGMENT->CONTROL_AREA来获得。这些都是外部未文档的内核布局体。内存治理器在映照文件到内存中的时刻创立了这些布局,只需文件曾经映照了,都邑在外部应用这些布局。

图3 挪用nt!PsCallImageNotifyRoutines以前nt!MiMapViewOfImageSection获得FILE_OBJECT
每个映照的镜像只有一个SEGMENT。意味着异样一个镜像在同一个过程当中或许跨过程间同时存在的多个section会应用同一个SEGMENT和CONTROL_AREA。这就说明了为何FullImagename在同一个PE文件同时加载到分歧过程可以或许作为PE文件的标志了。

图4 文件映照外部布局(简化版)
继承RTFM
为了弄明确FileName是若何设置和治理的,咱们回到文档中,发明MSDN制止应用它。由于这个值只在初始化过程的IRP_MJ_CREATE哀求时是有用的,在文件体系开端处置IRP_MJ_CREATE哀求时不考虑是有用的值,然则在文件体系处置完IPR_MJ_CREATE以后FILE_OBJECT确切在应用它。
很显著,NTFS驱动领有这个UNICODE_STRING(FILE_OBJECT.FileName)的一切权
应用内核调试器调试中,咱们发明ntfs!NtfsUpdateCcbsForLcbMove 是卖力重命名的一个操纵。在看这个函数时咱们揣摸出在IRP_MJ_CREATE哀求中文件体系驱动只是创立了一个FILE_OBJECT.FileName的浅拷贝,而后零丁保护它。这也就意味着只是拷贝了buffer的地点,而没有拷贝内容。


图5 ntfs!NtfsUpdateCcbsForLcbMove更新文件名字值
寻根究底
假如新门路长度没有跨越MaximumLength,同享的buffer内容会被笼罩,FILE_OBJECT.FileName的Legnth字段不会更新,内核可以或许拿到这个值给回调函数,假如新门路长度跨越了MaximunLength,会分派一块新内存,而后回调函数就会拿到过去的值。
只管咱们曾经找到了这个bug的缘故原由,然则照样有些事没有弄清楚。好比为何在image一切句柄(SECTION和FILE_OBJECT中的)封闭以后咱们仍然可以或许看到这些畸形的门路。假如文件一切的句柄真的关了,下次这个PE镜像会被关上加载到一个新的FILE_OBJECT中,会创立没有援用的新的门路。
但是,FullImageName仍然只想这个老的UNICODE_STRING。这表现句柄计数为0了FILE_OBJECT并无封闭,意味着援用计数确定是高于0的。咱们也经由过程调试器确认了这个工作。
末了
内核中援用计数泄漏根本是不可以或许了,咱们只有把怀疑指向了:缓存治理器。这可以或许是一种缓存行动,文件体系驱动保护文件名,但缺惹起可以或许拿到不法的文件名的重大差错。
影响
此时,咱们确切曾经弄清楚了惹起这个成绩的缘故原由,然则咱们怀疑的是这个bug为何还存在?有无甚么好的解决方案?
下咱们下一篇文章中,咱们会尽力找到这些成绩的谜底。
留意
咱们大部分阐发都在Windows 7 SP1 X86中,体系打了全补钉。这些发明在Windows XP Sp3,Windows 7 SP1 X64, Windows 10 (Redstone)X86/X64(全补钉)体系版本中也验证了。