-
-
[翻译]符号链接第二部分:获得目标名
-
发表于: 2020-4-3 14:45 7161
-
原文: http://windows-internals.com/symhooks-part-two/
翻译:看雪翻译小组----OsWalker
校对:看雪翻译小组----fyb波
上一篇博客中,我们最终实现了一个能够运行的回调函数,但是没有在回调中得到要访问的路径信息。当然,我们可以在栈中得到路径,它一定位于栈中的某个位置,但我们相信一定有更优雅的办法。我们也不想在Unwind Opcodes中写一本书和如果有效地恢复栈参数。
因此,我们决定换一条路,找到一个方法强制执行我们自己的解析过程,在解析过程中获得原路径,并在其中决定是否重定向调用者的路径。脑海中有两个可选方法:
用ObCreateObjectTypeEx创建一个新对象类型,实现我们自己的ParseRoutine,将符号链接重定向到用我们的新类型创建的对象,这样就可以使用原始目标设备对象名为参数让我们的ParseRoutine返回STATUS_REPARSE。
使用IoCreateDevice创建一个新设备对象,实现我们自己的IRP_MJ_CREATE处理函数,让处理函数使用I/O管理器现成的重解析功能(称作transmogrification),我们可以给要创建的文件对象返回STATUS_REPARSE和一个新名字,这会将请求重定向到原始目标设备对象。
然而,创建新对象类型没有文档,如果进行了错误行为将被PatchGuard监控,最重要的,没有合适的API来取消所进行的操作。Yep,不存在删除对象类型的方法,我们的驱动将无法卸载。
因此,我们决定让符号链接回调函数重定向符号链接到一个我们创建的设备对象,而不是返回原始字符串。然后,当我们设备对象的IRP_MJ_CREATE处理函数被调用,I/O管理器已经创建好了一个文件对象,我们可以通过IRP得到它,获得它的名字和该文件对象及其调用者有关的其它信息。
因此,我们要先创建设备对象\Device\HarddiskVolume0。接下来,我们使用Part1中的方法获得符号链接,将其LinkTarget域修改为我们的回调函数。然后还有做一个修改,将我们的新设备传入做SymlinkContext而不是原始的链接对象。
当有人访问该回调函数时,会到达我们的回调函数并收到一个指向我们设备对象(\Device\HarddiskVolume0)的路径,而不是指向\Device\HarddiskVolume<N>的路径,在样例中N为C分区。
接下来,打开该路径时,I/O管理器将用后续路径创建一个文件对象,如\Windows\Notepad.exe,然后调用我们的驱动对象的IRP_MJ_CREATE处理函数,在处理函数中我们可以在FILE_OBJECT结构中获得文件名,并用一个新的完整路径替换它,这个完整路径包含原始设备对象路径(\Device\HarddiskVolumeC)和后续路径(\Windows\Notepad.exe)。
替换FILE_OBJECT名很有技巧,I/O管理器分配的原始路径有一个特定的pool标签,我们释放它并分配一个我们自己的路径会被各种测试工具(如Driver Verifier)看做是内存泄露,除非我们模仿原始标签。
要处理这个问题,微软实现了一个API:IoReplaceFileObjectName。它不仅使用正确的内核内部pool标签,还进行了一些优化,比如文件名字符串缓存始终以偏移56/120/248字节分配(如果名称很长,则使用准确大小)。这在很多情况下避免执行释放、重分配操作,所以新名字可以直接重写就名字。
创建新名字的最终代码如下所示:
替换文件对象名后任然有个问题,不能返回STATUS_SUCCESS,这会使我们的设备对象变成该文件的拥有者,而不是原始分区的设备对象。之后的所有I/O的IRP都会经过我们的驱动,使我们要为每个操作实现转发。
我们能够获得正确\Device\HarddiskVolume<N>的设备对象并将所有IRP转给它,但是随后的所有请求仍然会提交给我们的设备。这不仅使我们易被发现,而且使我们的驱动变成了一个文件系统过滤驱动。我们只想获得创建请求并将其传给正确的设备,之后的请求都不想处理。
要实现这个,我们需要利用I/O管理器的转换逻辑,这分为两步:
返回STATUS_REPARSE以表示需要进行重解析。触发IopParseDevice检查文件对象的新名字字符串,再次对这个新名字进行名字解析功能,释放旧对象并且结束之前的解析工作。这个代码很复杂,在ReactOS中可以找到一个类似的。
设置IRP的Information域为IO_REPARSE,这是进行的重解析操作的类型。这使用一个特殊的重解析标签和微软文档的匹配结构(如REPARSE_DATA_BUFFER)指示常规的真正硬链接或符号链接。但是,IO_REPARSE只是一个保留值表明一个简单的名字替换,而不是一个真正的重解析点。
整体功能机制如下:
你们可能会发现这个方法在没有符号链接回调时也能正常工作,只需将符号链接的LinkTarget替换为我们的设备的路径,就能获得所有的请求,实际上只需要修改路径的最后一个数字。但我们认为,这样做很容易被发现,只需检查符号链接对象就能发现被修改的路径和结构。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)