NTLDR是Windows NT系列内核的Osloader,当开机以后BIOS载入MBR,MBR载入DBR,然后DBR载入NTLDR,并将执行权转交给NTLDR,最后再来启动内核。理论上讲NTLDR是最接近内核的,而且对NTLDR做HOOK,可以避免针对各种外设的编码工作,提高通用性。BOOTKIT具有以下几个特点:
1.在Ring3下可完成Hook(改写NTLDR);
2.注入内核的代码没有内存大小限制,也无需自己读入代码;
3.BOOTDRIVER驱动初始化时加载(依情况而定,也可hook内核其它地方);
4.理论上可以Hook各种版本ntldr;
5.理论上可以引导各个版本NT内核和内存相关boot.ini参数。
本文适用范围仅限于Win2000 sp4、WinXp sp2、Win2003 sp1,暂时没有对PAE内核,x64和内存相关BOOT.INI参数提供支持。
一、NTLDR的HOOK:
要做NTLDR的BOOTKIT,我们首先面临的问题就是如何对NTLDR做HOOK?要对NTLDR做HOOK,那就首先要对NTLDR的文件结构有所理解才行。NTLDR是由两部分构成,一部分是被称作Su Module(Startup)的16位汇编代码,另一部分则是名为Osloader的PE文件。Su module位于NTLDR的头部,Osloader紧随其后。DBR将NTLDR加载到物理地址2000:0000开始的地方,然后跳转到这个地址,将控制权交给NTLDR进行引导,而这个地址也就是Su的入口。Su的主要功能是为Osloader准备内存环境。在Windows 2003中为了避免Su对NTDLR完整性检测,本文将会用没有校验的Su替换了它。
Osloader,它的作用比较复杂,说的简单点就是为内核准备执行环境,然Osloader会根据boot.ini的设置将Windows内核、hal.dll和其它Boot Driver加载进内存。有关NTLDR更详细的结构和功能,请读者参看有关资料。
了解了NTLDR的大致结构和功能。接着我们来看一下我们HOOK NTLDR所存在的问题?首先就是该在哪下手,理论上你可以在任何一个地方下手,但是接下来你该跳转到哪里呢?执行HOOK CODE之后,才能让CPU有个地方可以去。我选择将CODE放在Osloader里,没错,Su会按SECTION的地址重定位Osloader,这样我们就可以很轻松地定位我们CODE的地址。我的做法就是直接用工具在Osloader里新建了一个节,将所有HOCK代码都放在了里面。因为我们的根本目的是要HOOK内核,所以执行代码的时候,内核要已经被加载进了内存。那我们怎么知道在哪内核已经被加载进了内存呢?通常,在将控制权交给内核之前,Osloader会调用函数BlSetupForNt,这个函数的会最后做些设置的收尾工作,包括剪裁页表。在BlSetupForNt函数里面存在如下特征码:
| mov eax, cr3 mov cr3, eax (机器码:“0F 20 D8 0F 22 D8” ) |
这段代码是用来刷新TLB的,这段代码在内核加载前后被频繁调用的。为了顺利HOOK内核,所以我在CODE里加入保护性代码,通过这段代码几乎可以在任何完整指令处进行HOOK。你可以下很多HOOK,不过得有一个是在内核加载之后执行的。利用这段特征串可以比较方面的找到HOOK点。
实际过程中“0F 20 D8 0F 22 D8” 串会集中出现在文件头的部分和文件尾的部分,建议只HOOK文件尾的串。这里使用"call RVA"共5个字节的指令来实现跳转。
小提示:Osloader位置的确定可以直接搜索特征值"MZ", "PE"。
二、内核的加载与定位
当控制权顺着hook代码,我们必须确定内核是否加载。这里我们首先想到的办法就是硬编码内核一个地址,然后测试这个地址是否有效,因为同一个NTLDR总会将内核加载在同一个地址。但是这样的通用性不高,ring3下面的工作会增加。为了使测试具有较高通用性,我使用了暴力搜索的方法,从可能的加载地址:VA0x80400000~0x81000000。为了不致引起page fault,首先必须从PDE搜索起,再确定PTE是否有效,然后测试MZ、PE标志,最后ImageSize的大小要大于0x150000(也就内核这么大了),这是为了避免其它PE文件影响结果,像Osloader。
三、对内核做HOOK
这个时候整个内核就在我们眼前了,但是我们还不能直接动手,因为我们现在代码所在的内存会在内核初始化的时候被清洗掉,所以我们还要HOOK一次EntryPoint。这是为了获取内核入口函数KiSystemStartup的参数LoaderBlock。
这是一个类型名为LOADER_PARAMETER_BLOCK的指针。它的结构如下:
| typedef struct _LOADER_PARAMETER_BLOCK { LIST_ENTRY LoadOrderListHead; LIST_ENTRY MemoryDescriptorListHead; LIST_ENTRY BootDriverListHead; ULONG KernelStack; ULONG Prcb; ULONG Process; ULONG Thread; ULONG RegistryLength; PVOID RegistryBase; PCONFIGURATION_COMPONENT_DATA ConfigurationRoot; PCHAR ArcBootDeviceName; PCHAR ArcHalDeviceName; PCHAR NtBootPathName; PCHAR NtHalPathName; PCHAR LoadOptions; PNLS_DATA_BLOCK NlsData; PARC_DISK_INFORMATION ArcDiskInformation; PVOID OemFontFile; struct _SETUP_LOADER_BLOCK *SetupLoaderBlock; ULONG Spare1; union { I386_LOADER_BLOCK I386; MIPS_LOADER_BLOCK Mips; ALPHA_LOADER_BLOCK Alpha; PPC_LOADER_BLOCK Ppc; } u; } LOADER_PARAMETER_BLOCK, *PLOADER_PARAMETER_BLOCK; |
在这里我们能找到很多我们想得到的数据,以便我们能成功hook内核。

