一種基於NTLDR的BOOTKIT 原理及實現

2021-12-29 21:08:15 字數 4308 閱讀 7473

author: inghu

email: [email protected]

site:  

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 u;

} loader_parameter_block, *ploader_parameter_block;

沒錯,在這裡我們能找到很多我們想得到的資料,以便我們能成功hook核心。

五.在記憶體裡搭個狗窩

當cpu執行到這裡的時候,我們已經來到了核心入口點——寶庫就在眼前。寶庫雖好,可是卻沒有我們住的地方啊,所以我們要在寶庫旁邊建個 窩,這樣才能讓我們長期在寶庫裡xx。為了搭建我們的狗窩,我們有兩樣工作要做,一是,設法找到空閒的物理頁用於對映;二是,找乙個虛擬位址空間對映我們 找到的物理頁。對於第一步,我想你已經猜到了,嗯,就是loaderblock中的memorydescriptorlisthead。它指向的實際資料 結構如下:

typedef struct _memory_allocation_descriptor memory_allocation_descriptor, *pmemory_allocation_descriptor;

其中 memorytype為

typedef enum _type_of_memory {

loaderexceptionblock = memoryexceptionblock,            // 0

loadersystemblock = memorysystemblock,                  // 1

loaderfree = memoryfree,                                // 2

loaderbad = memorybad,                                  // 3

一種基於有序序列mapjoin的方法

在解決資料傾斜問題時,我們經常會採用一種方式 mapjoin,按照hive的實現,mapjoin是將其中一張表在map的過程中載入到記憶體中,但是如果在join的表中,最小的表的資料量也不小的情況下。我們該怎麼辦呢?其中一種解決的方式是 將兩張表需要實現排序 直接用hadoop解決 如下,兩張表都是...

一種基於mget的資料持久辦法

專案中需要一部分資料常駐於redis中,但是要求資料冷卻下來之後自動掉出記憶體,所以使用了如下的方法 材料php,mget,sql 的 in 首先方法體開頭迴圈傳入的陣列引數list,拼接成redis中的key,存放於新陣列userlist中,然後呼叫mget userlist 獲得結果settin...

一種基於富文案顯示設計

概述 一般複雜互動的系統會存在著大量的文案顯示,這樣可以告訴使用者的互動行為,主要用來作提示和操作區分,但是由於不同的點使用的文案根據具體的位置,呼叫方產生不同的文案,導致資訊的顯示往往會出現各個不同的顯示方式。概念 使用場景 1.頁面和 的互動 2.介面和呼叫方的互動 文字型別 1.純文字 2.富...