Ⅰ 查R3層函數對應的內核函數的方法
3.2 獲得實際數據
這一章我們一直都在開發一個可以捕獲串口上的數據的過濾程序。現在虛擬設備已經綁定了真正的串口設備,那麼,實際上如何從虛擬設備得到串口上流過的數據呢?答案是根據「請求」。操作系統將請求發送給串口設備,請求中就含有要發送的數據,請求的回答中則含有要接收的數據。下面分析這些「請求」,以便得到實際的串口數據流。
3.2.1 請求的區分
Windows的內核開發者們確定了很多的數據結構,在前面的內容中我們逐漸地和DEVICE_OBJECT(設備對象)、FILE_OBJECT(文件對象)和DRIVER_OBJECT(驅動對象)見了面。文件對象暫時沒有什麼應用(但是在本書後面的文件系統過濾中,文件對象是極為重要的)。讀者需要了解的是:
(1)每個驅動程序只有一個驅動對象。
(2)每個驅動程序可以生成若干個設備對象,這些設備對象從屬於一個驅動對象。在一個驅動中可否生成從屬於其他驅動的驅動對象的設備對象呢?從IoCreateDevice的參數來看,這樣做是可以的,但是筆者沒有嘗試過這樣的應用。
(3)若干個設備(它們可以屬於不同的驅動)依次綁定形成一個設備棧,總是最頂端的設備先接收到請求。
請注意:IRP是上層設備之間傳遞請求的常見數據結構,但是絕對不是唯一的數據結構。傳遞請求還有很多其他的方法,不同的設備也可能使用不同的結構來傳遞請求。但在本書中,90%的情況下,請求與IRP是等價概念。
串口設備接收到的請求都是IRP,因此只要對所有IRP進行過濾,就可以得到串口上流過的所有數據。串口過濾時只需要關心有兩種請求:讀請求和寫請求。對串口而言,讀指接收數據,而寫指發出數據。串口也還有其他的請求,比如打開和關閉、設置波特率等。但是我們的目標只是獲得串口上流過的數據,而不關心打開關閉和波特率是多少這樣的問題,所以一概無視。
請求可以通過IRP的主功能號進行區分。IRP的主功能號是保存在IRP棧空間中的一個位元組,用來標識這個IRP的功能大類。相應的,還有一個次功能號來標識這個IRP的功能細分小類。
讀請求的主功能號為IRP_MJ_READ,而寫請求的主功能號為IRP_MJ_WRITE。下面的方法用於從一個IRP指針得到主功能號(這里的變數irp是一個PIRP,也就是IRP的指針)。
// 這里的irpsp稱為IRP的棧空間,IoGetCurrentIrpStackLocation獲得當前棧空間
// 棧空間是非常重要的數據結構
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
if(irpsp->MajorFunction == IRP_MJ_WRITE)
{
// 如果是寫…
}
else if(irpsp->MajorFunction == IRP_MJ_READ)
{
// 如果是讀…
}
3.2.2 請求的結局
對請求的過濾,最終的結局有3種:
(1)請求被允許通過了。過濾不做任何事情,或者簡單地獲取請求的一些信息。但是請求本身不受干擾,這樣系統行為不會有變化,皆大歡喜。
(2)請求直接被否決了。過濾禁止這個請求通過,這個請求被返回錯誤了,下層驅動程序根本收不到這個請求。這樣系統行為就變了,後果是常常看見上層應用程序彈出錯誤框提示許可權錯誤或者讀取文件失敗之類信息。
(3)過濾完成了這個請求。有時有這樣的需求,比如一個讀請求,我們想記錄讀到了什麼。如果讀請求還沒有完成,那麼如何知道到底會讀到什麼呢?只有讓這個請求先完成再去記錄。過濾完成這個請求時不一定要原封不動地完成,這個請求的參數可以被修改(比如把數據都加密一番)。
當過濾了一個請求時,就必須把這個請求按照上面3種方法之一進行處理。當然這些代碼會寫在一個處理函數中。如何使用這個處理函數將在後面的小節中提及,這里先介紹這些處理方法的代碼應該怎麼寫。
串口過濾要捕獲兩種數據:一種是發送出的數據(也就是寫請求中的數據),另一種是接收的數據(也就是讀請求中的數據)。為了簡單起見,我們只捕獲發送出的數據,這樣,只需要採取第1種處理方法即可。至於第2、3兩種處理方法,讀者會在後面的許多過濾程序中碰到。
這種處理最簡單。首先調用IoSkipCurrentIrpStackLocation跳過當前棧空間;然後調用IoCallDriver把這個請求發送給真實的設備。請注意:因為真實的設備已經被過濾設備綁定,所以首先接收到IRP的是過濾設備的對象。代碼如下(irp是過濾到的請求):
// 跳過當前棧空間
IoSkipCurrentIrpStackLocation(irp);
// 將請求發送到對應的真實設備。記得我們前面把真實設備都保存在s_nextobj
// 數組中。那麼這里i應該是多少?這取決於現在的IRP發到了哪個
// 過濾設備上。後面講解分發函數時讀者將了解到這一點
status = IoCallDriver(s_nextobj[i],irp);
3.2.3 寫請求的數據
那麼,一個寫請求(也就是串口一次發送出的數據)保存在哪裡呢?回憶前面關於IRP結構的描述(第2章的2.3.3節),裡面一共有3個地方可以描述緩沖區:一個是irp->MDLAddress,一個是irp->UserBuffer,一個是irp->AssociatedIrp.SystemBuffer。不同的IO類別,IRP的緩沖區不同。SystemBuffer是一般用於比較簡單且不追求效率情況下的解決方案:把應用層(R3層)中內存空間中的緩沖數據拷貝到內核空間。
UserBuffer則是最追求效率的解決方案。應用層的緩沖區地址直接放在UserBuffer里,在內核空間中訪問。在當前進程和發送請求進程一致的情況下,內核訪問應用層的內存空間當然是沒錯的。但是一旦內核進程已經切換,這個訪問就結束了,訪問UserBuffer當然是跳到其他進程空間去了。因為在Windows中,內核空間是所有進程共用的,而應用層空間則是各個進程隔離的。
當然一個更簡單的解決方案是把應用層的地址空間映射到內核空間,這需要在頁表中增加一個映射。當然這不需要編程者手工去修改頁表,通過構造MDL就能實現這個功能。MDL可以翻譯為「內存描述符鏈」,但是本書按業界傳統習慣一律稱之為MDL。IRP中的MDLAddress域是一個MDL的指針,從這個MDL中可以讀出一個內核空間的虛擬地址。這就彌補了UserBuffer的不足,同時比SystemBuffer的完全拷貝方法要輕量,因為這個內存實際上還是在老地方,沒有拷貝。
回到串口的問題,那麼串口寫請求到底用的是哪種方式呢?老實點說,筆者並不清楚也沒有去調查到底是哪種方式。但是如果用下面的編碼方式,無論採用哪種方式,都可以把數據正確地讀出來。
PBYTE buffer = NULL;
if(irp->MdlAddress != NULL)
buffer = (PBYTE)MmGetSystemAddressForMdlSafe(irp->MdlAddress);
else
buffer = (PBYTE)irp->UserBuffer;
if(buffer == NULL)
buffer = (PBYTE)irp->AssociatedIrp.SystemBuffer;
這其中涉及一個函數MmGetSystemAddressForMdlSafe,有興趣的讀者可以在WDK的幫助中查閱一下這個函數的含義。同時也可以深入了解一下MDL,但是對閱讀本書重要性不是很明顯。本書的後面涉及從MDL得到系統空間虛擬地址的情況下,都簡單地調用MmGetSystemAddressForMdlSafe。
此外是緩沖區有多長的問題。對一個寫操作而言,長度可以如下獲得:
ULONG length = irpsp->Parameters.Write.Length;
3.3 完整的代碼
3.3.1 完整的分發函數
下面基於前面的描述,我們再嘗試編寫一個分發函數。這個函數處理所有串口的寫請求,所有從串口輸出的數據都用DbgPrint列印出來。也就是說,讀者打開DbgView.exe,就可以看到串口的輸出數據了。這當然不如一些比較專業的串口嗅探軟體好,但是讀者可以以這個例子為基礎開發更專業的工具。
NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp)
{
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS status;
ULONG i,j;
// 首先要知道發送給了哪個設備。設備最多一共有CCP_MAX_COM_ID
// 個,是前面的代碼保存好的,都在s_fltobj中
for(i=0;i<CCP_MAX_COM_ID;i++)
{
if(s_fltobj[i] == device)
{
// 所有電源操作,全部直接放過
if(irpsp->MajorFunction == IRP_MJ_POWER)
{
// 直接發送,然後返回說已經被處理了
PoStartNextPowerIrp(irp);
IoSkipCurrentIrpStackLocation(irp);
return PoCallDriver(s_nextobj[i],irp);
}
// 此外我們只過濾寫請求。寫請求,獲得緩沖區及其長度
// 然後列印
if(irpsp->MajorFunction == IRP_MJ_WRITE)
{
// 如果是寫,先獲得長度
ULONG len = irpsp->Parameters.Write.Length;
// 然後獲得緩沖區
PUCHAR buf = NULL;
if(irp->MdlAddress != NULL)
buf =
(PUCHAR)MmGetSystemAddressForMdlSafe(
irp->MdlAddress,NormalPagePriority);
else
buf = (PUCHAR)irp->UserBuffer;
if(buf == NULL)
buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
// 列印內容
for(j=0;j<len;++j)
{
DbgPrint("comcap: Send Data: %2x\r\n",
buf[j]);
}
}
// 這些請求直接下發執行即可,我們並不禁止或者改變它
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(s_nextobj[i],irp);
}
}
// 如果根本就不在被綁定的設備中,那是有問題的,直接返回參數錯誤
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
IoCompleteRequest(irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
3.3.2 如何動態卸載
前面只說了如何綁定,但是沒說如何解除綁定。如果要把這個模塊做成可以動態卸載的模塊,則必須提供一個卸載函數。我們應該在卸載函數中完成解除綁定的功能;否則,一旦卸載一定會藍屏。
這里涉及到3個內核API:一個是IoDetachDevice,負責將綁定的設備解除綁定;另一個是IoDeleteDevice,負責把我們前面用IoCreateDevice生成的設備刪除掉以回收內存;還有一個是KeDelayExecutionThread,純粹負責延時。這三個函數的參數相對簡單,這里就不詳細介紹了,需要的讀者請查閱WDK的幫助。
卸載過濾驅動有一個關鍵的問題:我們要終止這個過濾程序,但是一些IRP可能還在這個過濾程序的處理過程中。要取消這些請求非常的麻煩,而且不一定能成功。所以解決方案是等待5秒來保證安全地卸載掉。只能確信這些請求會在5秒內完成,同時等待之前我們已經解除了綁定,所以這5秒內不會有新請求發送過來處理。這對於串口而言是沒問題的,但是並非所有的設備都如此。所以讀者在後面的章節會看到不同的處理方案。
#define DELAY_ONE_MICROSECOND (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
#define DELAY_ONE_SECOND (DELAY_ONE_MILLISECOND*1000)
void ccpUnload(PDRIVER_OBJECT drv)
{
ULONG i;
LARGE_INTEGER interval;
// 首先解除綁定
for(i=0;i<CCP_MAX_COM_ID;i++)
{
if(s_nextobj[i] != NULL)
IoDetachDevice(s_nextobj[i]);
}
// 睡眠5秒。等待所有IRP處理結束
interval.QuadPart = (5*1000 * DELAY_ONE_MILLISECOND);
KeDelayExecutionThread(KernelMode,FALSE,&interval);
// 刪除這些設備
for(i=0;i<CCP_MAX_COM_ID;i++)
{
if(s_fltobj[i] != NULL)
IoDeleteDevice(s_fltobj[i]);
}
}
3.3.3 完整的代碼
這個驅動的完整代碼比較簡單。前面已經介紹了一些函數,請把這些函數都拷貝下來集中到comcap.c文件里。再建立一個目錄,名為comcap來容納這個文件。這個文件的內容大致如下:
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
size_t i;
// 所有的分發函數都設置成一樣的
for(i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)
{
driver->MajorFunction[i] = ccpDispatch;
}
// 支持動態卸載
driver->DriverUnload = ccpUnload;
// 綁定所有的串口
ccpAttachAllComs(driver);
// 直接返回成功即可
return STATUS_SUCCESS;
}
然後編寫一個SOURCE文件,內容如下:
TARGETNAME=comcap
TARGETPATH=obj
TARGETTYPE=DRIVER
SOURCES =comcap.c
將這個文件也放在comcap目錄下。參考1.1.3節中的方法編譯,然後載入執行這個驅動。設法通過串口傳輸數據,打開DbgView.exe就能看到輸出信息了。
這個例子的代碼在隨書附帶光碟源代碼的comcap目錄下。
本章的示例代碼
本章的例子在源代碼目錄comcap下,編譯結果為comcap,可以動態載入和卸載。編譯的方法請參考本書的附錄「如何使用本書的源碼光碟」。載入後,如果有數據從串口輸出,打開DbgView.exe就會看到輸出信息了。
一般的讀者可能沒有使用串口的列印機,但是可以用如下的方法簡單地使用串口,以便讓這個程序起作用:打開「開始」菜單→「所有程序」→「附件」→「通訊」→「超級終端」,然後任意建立一個連接,如圖3-1所示。
圖3-1 打開「超級終端」用串口撥號
注意連接時使用的COM1就是第一個串口。這樣單擊「確定」按鈕之後,在上面的文本框中任意輸入字元串就會被發送到串口。此時如果載入了comapp.sys,那麼在DbgView.exe中就應該可以看到輸出信息如圖3-2所示。
圖3-2 用comcap捕獲的串口數據
練習題
1.紙上練習
(1)在這一章中,所謂的過濾是什麼意思?有什麼意義?
(2)何為內核對象?我們已經接觸到了哪幾種內核對象?
(3)何為設備對象?你能在Windows系統中指出已經存在的至少5個設備對象嗎?
(4)DO是什麼的簡稱?
(5)何為綁定?哪些內核API可以實現設備的綁定?
2.上機練習
(1)編譯comcap.c並執行,用DbgView看輸出結果。
(2)對comcap.c進行修改,使之對所有的串口輸出都禁止,然後測試。
(3)用WinObj找到並口設備的名字,並把comcap.c的代碼改為對並口的過濾。
(4)有條件的讀者請找一台並口列印機,嘗試列印一個文本文件,然後用DbgView觀察從並口過濾攔截到的數據。
Ⅱ 單片機uart通訊中的UART通訊的波特率設置問題
方式零,不屬於 UART,是 SPI 。
-----------
樓主弄混了概念。
51 單片機在串口方式0時,稱為《同步傳送方式》,並不是 UART (非同步)方式。
兩者並不兼容。
在同步傳送方式,以 P3.0 輸入、輸出數據,以 P3.1 輸出同步脈沖。
注意,數據的輸入、輸出,都是從 P3.0 走的。
在同步傳送方式,數據的傳輸速度是 1M bit/s。比 UART,要快得很多。
而且,在同步傳送方式,沒有起始位、校驗位和結束位,每一位,都是數據。
因此,在《同步傳送方式》,並沒有常說的 9600、4800...,等波特率。
同樣,在 UART 中,也就沒有 1M bit/s 這樣的說法。
--每秒可以發送10^6個數據位,這時候的波特率是1mbit/s,
--那麼在數據傳輸時波特率豈不是要設為1000,但是常用的為什麼沒有1000?
把它們混在一起討論,是樓主的基本概念不清的表現。
樓主所選的答案,也沒有搞清串口方式0的特點,只是用 UART 的理論來解釋《同步傳送》。
葫蘆攪茄子而已,呵呵
Ⅲ 串口未打開什麼意思
為確保參合農民從新農合門診統籌補償政策中受益,規范各新農合定點醫療機構和經辦機構門診補償操作程序,特製定以下操作規程。 一、參合農民因病需到門診就診的,可在縣內自主選擇新農合定點門診就診。 二、參合患者在新農合定點門診就診時,先出示《合作醫療證》,醫師核實患者身份,無誤後,再為患者開具復式處方,患者劃價繳費後,持門診發票和處方附件到新農合定點門診兌付窗口直接按規定補償門診費用(村衛生室定點門診可直接沖減)。 三、定點門診新農合兌付窗口工作人員先在新農合專網上核實患者身份,查驗是否參合,再查看患者《合作醫療證》上門診統籌補償支付情況(同時在新農合專網上查詢),若該患者年內門診統籌補償總額未滿40元,則可以結算支付該次門診費用,並將此次結算支付情況如實記錄到《合作醫療證》上 「門診補償情況」欄內的 「患者姓名、就診醫院、時間、支付金額、結算人」五個小項中,同時填寫「門診補償登記表」,在復式處方上填寫補償金額,經患者簽字確認後當場領取現金。 四、定點門診新農合兌付窗口將補償後的門診發票、復式處方作為結算材料留存,並按補償順序每人每次進行編號,同時將補償病人相關信息錄入電腦,在規定時間內帶「復式處方」連同「門診補償登記表」「匯總表」先報本鄉鎮農醫專職審核員復核,核查無誤的,再報鄉鎮農醫站審核,經鄉鎮專職審核員和鄉鎮農醫站簽署意見並蓋章後,再報縣農醫辦結算,原則上每月一次。五、定點門診每月將已匯總的門診結算材料上報縣農醫辦,先交補償監審處初審,主要審核患者是否合格、補償計算是否准確、葯品診療項目是否把握准確、審核報賬資料是否齊全、監管資料是否完整等,將審核中發現的問題如實記錄到「芷江縣新型農村合作醫療門診補償審核匯總表」上(一式兩份),多報的應如數扣除,此批次審核結束後將實報金額填寫到審核意見欄內。 六、補償監審處將初審後的門診結算資料傳微機管理處審核,主要審核補償金額與電腦數據是否相符、錄入程序及日期是否正確等。七、微機管理處將審核匯總表(一式兩份)傳財務室復核數據,開具申請撥款單。財務室將審核匯總表及申請撥款單交領導審批並簽署意見,財務室根據審核後的審定金額為定點門診撥付資金。
Ⅳ 寒江獨釣:Windows內核安全編程的圖書目錄
第1章 內核上機指導 1
Windows內核編程的動手有點麻煩,並不是僅僅安裝一個獨立的軟體(比如VC)之後就可以安然地開始編寫代碼,然後運行了。需要下載開發包、配置開發環境、准備調試工具,可能還需要一些小工具協同工作。這一步攔住了不少的初學者。本章以詳細圖文攻略,來引導讀者完成這一麻煩的步驟。
1.1 下載和使用WDK 2
1.1.1 下載安裝WDK 2
1.1.2 編寫第一個C文件 3
1.1.3 編譯一個工程 5
1.2 安裝與運行 6
1.2.1 下載一個安裝工具 6
1.2.2 運行與查看輸出信息 7
1.2.3 在虛擬機中運行 9
1.3 調試內核模塊 9
1.3.1 下載和安裝WinDbg 9
1.3.2 設置Windows XP調試執行 10
1.3.3 設置Vista調試執行 11
1.3.4 設置VMWare的管道虛擬串口 11
1.3.5 設置Windows內核符號表 13
1.3.6 實戰調試first 14
練習題 16
第2章 內核編程環境及其特殊性 17
編寫過驅動程序的讀者可能會很熟悉這一切,但是對只從事過應用程序的讀者而言,要理解內核編程環境的特殊性,就很需要一些功夫和悟性了。在應用程序中,多線程的情況已經帶來了一定理解的困難;而內核代碼呢?幾乎無時無刻不運行在多線程之下。它從哪裡開始?從哪裡結束?它在什麼進程內運行?這些問題一言難盡。
2.1 內核編程的環境 18
2.1.1 隔離的應用程序 18
2.1.2 共享的內核空間 19
2.1.3 無處不在的內核模塊 20
2.2 數據類型 21
2.2.1 基本數據類型 21
2.2.2 返回狀態 22
2.2.3 字元串 23
2.3 重要的數據結構 23
2.3.1 驅動對象 23
2.3.2 設備對象 25
2.3.3 請求 26
2.4 函數調用 28
2.4.1 查閱幫助 28
2.4.2 幫助中有的幾類函數 30
2.4.3 幫助中沒有的函數 32
2.5 Windows的驅動開發模型 32
2.6 WDK編程中的特殊點 33
2.6.1 內核編程的主要調用源 33
2.6.2 函數的多線程安全性 34
2.6.3 代碼的中斷級 36
2.6.4 WDK中出現的特殊代碼 37
練習題 38
第3章 串口的過濾 40
在安全軟體的開發中,串口驅動的應用並不常見。但是本書以串口驅動作為第一個介紹的實例。為何?僅僅是因為串口簡單。從簡單的例子入手,可以為讀者帶來稍許輕松的感受。
3.1 過濾的概念 41
3.1.1 設備綁定的內核API之一 41
3.1.2 設備綁定的內核API之二 43
3.1.3 生成過濾設備並綁定 43
3.1.4 從名字獲得設備對象 45
3.1.5 綁定所有串口 46
3.2 獲得實際數據 47
3.2.1 請求的區分 47
3.2.2 請求的結局 48
3.2.3 寫請求的數據 49
3.3 完整的代碼 50
3.3.1 完整的分發函數 50
3.3.2 如何動態卸載 52
3.3.3 完整的代碼 53
本章的示例代碼 53
練習題 54
第4章 鍵盤的過濾 56
鍵盤是很重要的輸入設備!這是因為我們用鍵盤錄入信息、用鍵盤輸入密碼,甚至用鍵盤編程,也用鍵盤著書立說。對於黑客來說,使用龐大的計算機資源去破解那些堅不可摧的加密演算法,哪如偷偷地記下用戶用鍵盤輸入的密鑰更加簡單呢?本章專注於鍵盤的保護。
4.1 技術原理 57
4.1.1 預備知識 57
4.1.2 Windows中從擊鍵到內核 58
4.1.3 鍵盤硬體原理 60
4.2 鍵盤過濾的框架 61
4.2.1 找到所有的鍵盤設備 61
4.2.2 應用設備擴展 64
4.2.3 鍵盤過濾模塊的DriverEntry 65
4.2.4 鍵盤過濾模塊的動態卸載 66
4.3 鍵盤過濾的請求處理 68
4.3.1 通常的處理 68
4.3.2 PNP的處理 69
4.3.3 讀的處理 70
4.3.4 讀完成的處理 71
4.4 從請求中列印出按鍵信息 72
4.4.1 從緩沖區中獲得KEYBOARD_INPUT_DATA 72
4.4.2 從KEYBOARD_INPUT_DATA中得到鍵 73
4.4.3 從MakeCode到實際字元 74
4.5 Hook分發函數 75
4.5.1 獲得類驅動對象 76
4.5.2 修改類驅動的分發函數指針 77
4.5.3 類驅動之下的埠驅動 78
4.5.4 埠驅動和類驅動之間的協作機制 79
4.5.5 找到關鍵的回調函數的條件 80
4.5.6 定義常數和數據結構 80
4.5.7 打開兩種鍵盤埠驅動尋找設備 81
4.5.8 搜索在KbdClass類驅動中的地址 83
4.6 Hook鍵盤中斷反過濾 86
4.6.1 中斷:IRQ和INT 86
4.6.2 如何修改IDT 87
4.6.3 替換IDT中的跳轉地址 88
4.6.4 QQ的PS/2反過濾措施 90
4.7 利用IOAPIC重定位中斷處理函數 90
4.7.1 什麼是IOAPIC 90
4.7.2 如何訪問IOAPIC 91
4.7.3 編程修改IOAPIC重定位表 92
4.7.4 插入新的中斷處理 93
4.7.5 驅動入口和卸載的實現 95
4.8 直接用埠操作鍵盤 96
4.8.1 讀取鍵盤數據和命令埠 96
4.8.2 p2cUserFilter的最終實現 97
本章的示例代碼 98
練習題 99
第5章 磁碟的虛擬 100
CPU是計算機的核心,但是它不保存信息。如果它被竊,我們可以簡單地購買一個新的。但是如果裝滿了機密信息的硬碟被竊了,那可就不是買一個新的就能彌補得了的。本章介紹硬碟內核魔術:虛擬硬碟。虛擬硬碟可以不被盜竊者利用嗎?良好的設計可以做到這一點。
5.1 虛擬的磁碟 101
5.2 一個具體的例子 101
5.3 入口函數 102
5.3.1 入口函數的定義 102
5.3.2 Ramdisk驅動的入口函數 103
5.4 EvtDriverDeviceAdd函數 104
5.4.1 EvtDriverDeviceAdd的定義 104
5.4.2 局部變數的聲明 105
5.4.3 磁碟設備的創建 105
5.4.4 如何處理發往設備的請求 107
5.4.5 用戶配置的初始化 108
5.4.6 鏈接給應用程序 110
5.4.7 小結 111
5.5 FAT12/16磁碟卷初始化 111
5.5.1 磁碟卷結構簡介 111
5.5.2 Ramdisk對磁碟的初始化 113
5.6 驅動中的請求處理 119
5.6.1 請求的處理 119
5.6.2 讀/寫請求 120
5.6.3 DeviceIoControl請求 122
5.7 Ramdisk的編譯和安裝 124
5.7.1 編譯 124
5.7.2 安裝 125
5.7.3 對安裝的深入探究 125
練習題 126
第6章 磁碟過濾 127
很多網吧的老闆、公司的IT管理部門以及讀者自己都很厭惡硬碟總是被病毒和木馬搞得一團糟。一些簡單的還原軟體可以搞定這個問題:重啟之後,對硬碟的修改都奇跡般地消失了。這是怎麼實現的呢?本章告訴您答案。
6.1 磁碟過濾驅動的概念 128
6.1.1 設備過濾和類過濾 128
6.1.2 磁碟設備和磁碟卷設備過濾驅動 128
6.1.3 注冊表和磁碟卷設備過濾驅動 129
6.2 具有還原功能的磁碟卷過濾驅動 129
6.2.1 簡介 129
6.2.2 基本思想 130
6.3 驅動分析 130
6.3.1 DriverEntry函數 130
6.3.2 AddDevice函數 132
6.3.3 PnP請求的處理 136
6.3.4 Power請求的處理 140
6.3.5 DeviceIoControl請求的處理 140
6.3.6 bitmap的作用和分析 144
6.3.7 boot驅動完成回調函數和稀疏文件 150
6.3.8 讀/寫請求的處理 152
6.3.9 示例代碼 160
6.3.10 練習題 161
第7章 文件系統的過濾與監控 162
硬碟是硬碟,而文件系統是文件系統,可是有的人總是把它們當做一回事。其實硬碟很簡單,硬碟就是一個很簡單的保存信息的盒子;而復雜的是文件系統,它很精妙地把簡單的數據組織成復雜的文件。作為信息安全的專家,我們當然不能讓文件系統脫離我們的控制之外。
7.1 文件系統的設備對象 163
7.1.1 控制設備與卷設備 163
7.1.2 生成自己的一個控制設備 165
7.2 文件系統的分發函數 166
7.2.1 普通的分發函數 166
7.2.2 文件過濾的快速IO分發函數 167
7.2.3 快速IO分發函數的一個實現 169
7.2.4 快速IO分發函數逐個簡介 170
7.3 設備的綁定前期工作 172
7.3.1 動態地選擇綁定函數 172
7.3.2 注冊文件系統變動回調 173
7.3.3 文件系統變動回調的一個實現 175
7.3.4 文件系統識別器 176
7.4 文件系統控制設備的綁定 177
7.4.1 生成文件系統控制設備的過濾設備 177
7.4.2 綁定文件系統控制設備 178
7.4.3 利用文件系統控制請求 180
7.5 文件系統卷設備的綁定 183
7.5.1 從IRP中獲得VPB指針 183
7.5.2 設置完成函數並等待IRP完成 184
7.5.3 卷掛載IRP完成後的工作 187
7.5.4 完成函數的相應實現 190
7.5.5 綁定卷的實現 191
7.6 讀/寫操作的過濾 193
7.6.1 設置一個讀處理函數 193
7.6.2 設備對象的區分處理 194
7.6.3 解析讀請求中的文件信息 195
7.6.4 讀請求的完成 198
7.7 其他操作的過濾 202
7.7.1 文件對象的生存周期 202
7.7.2 文件的打開與關閉 203
7.7.3 文件的刪除 205
7.8 路徑過濾的實現 206
7.8.1 取得文件路徑的3種情況 206
7.8.2 打開成功後獲取路徑 207
7.8.3 在其他時刻獲得文件路徑 209
7.8.4 在打開請求完成之前獲得路徑名 209
7.8.5 把短名轉換為長名 211
7.9 把sfilter編譯成靜態庫 212
7.9.1 如何方便地使用sfilter 212
7.9.2 初始化回調、卸載回調和綁定回調 213
7.9.3 綁定與回調 215
7.9.4 插入請求回調 216
7.9.5 如何利用sfilter.lib 218
本章的示例代碼 221
練習題 221
第8章 文件系統透明加密 223
如何阻止企業的機密文件被主動泄密,但是又不用關閉網路、禁止U盤等手段重重束縛大家?很多跡象表明,文件系統透明加密是最優的選擇。既然從前一章讀者已經學會了控制文件系統,那麼現在,該是我們摩拳擦掌,用它來保護我們的機密信息的時候了。
8.1 文件透明加密的應用 224
8.1.1 防止企業信息泄密 224
8.1.2 文件透明加密防止企業信息泄密 224
8.1.3 文件透明加密軟體的例子 225
8.2 區分進程 226
8.2.1 機密進程與普通進程 226
8.2.2 找到進程名字的位置 227
8.2.3 得到當前進程的名字 228
8.3 內存映射與文件緩沖 229
8.3.1 記事本的內存映射文件 229
8.3.2 Windows的文件緩沖 230
8.3.3 文件緩沖:明文還是密文的選擇 232
8.3.4 清除文件緩沖 233
8.4 加密標識 236
8.4.1 保存在文件外、文件頭還是文件尾 236
8.4.2 隱藏文件頭的大小 237
8.4.3 隱藏文件頭的設置偏移 239
8.4.4 隱藏文件頭的讀/寫偏移 240
8.5 文件加密表 241
8.5.1 何時進行加密操作 241
8.5.2 文件控制塊與文件對象 242
8.5.3 文件加密表的數據結構與初始化 243
8.5.4 文件加密表的操作:查詢 244
8.5.5 文件加密表的操作:添加 245
8.5.6 文件加密表的操作:刪除 246
8.6 文件打開處理 248
8.6.1 直接發送IRP進行查詢與設置操作 248
8.6.2 直接發送IRP進行讀/寫操作 250
8.6.3 文件的非重入打開 252
8.6.4 文件的打開預處理 255
8.7 讀寫加密/解密 260
8.7.1 在讀取時進行解密 260
8.7.2 分配與釋放MDL 261
8.7.3 寫請求加密 262
8.8 crypt_file的組裝 265
8.8.1 crypt_file的初始化 265
8.8.2 crypt_file的IRP預處理 266
8.8.3 crypt_file的IRP後處理 269
本章的示例代碼 272
練習題 272
第9章 文件系統微過濾驅動 273
從來都不原地踏步的微軟,早就准備好了下一代的文件系統過濾的框架、文檔、代碼例子。雖然本書的前兩章的範例在Windows 7上都還可以正常運行,但是如果不學習一下最新的介面,讀者一定會覺得不自在。但是讀者可以放心,在前面學習的基礎上,了解新的介面是易如反掌的。
9.1 文件系統微過濾驅動簡介 274
9.1.1 文件系統微過濾驅動的由來 274
9.1.2 Minifilter的優點與不足 275
9.2 Minifilter的編程框架 275
9.2.1 微文件系統過濾的注冊 276
9.2.2 微過濾器的數據結構 277
9.2.3 卸載回調函數 280
9.2.4 預操作回調函數 281
9.2.5 後操作回調函數 284
9.2.6 其他回調函數 285
9.3 Minifilter如何與應用程序通信 288
9.3.1 建立通信埠的方法 288
9.3.2 在用戶態通過DLL使用通信埠的範例 290
9.4 Minifilter的安裝與載入 292
9.4.1 安裝Minifilter的INF文件 293
9.4.2 啟動安裝完成的Minifilter 294
本章的示例代碼 295
練習題 295
第10章 網路傳輸層過濾 296
筆者常常使用防火牆,它們看上去真的很神奇。如果懷疑自己的機器上有見不得人的進程打開了網路埠盜走機密信息,防火牆將提醒您,雖然防火牆並不知道它是否是一個木馬。這是怎麼做到的?本章為您揭曉謎底。
10.1 TDI概要 297
10.1.1 為何選擇TDI 297
10.1.2 從socket到Windows內核 297
10.1.3 TDI過濾的代碼例子 299
10.2 TDI的過濾框架 299
10.2.1 綁定TDI的設備 299
10.2.2 唯一的分發函數 300
10.2.3 過濾框架的實現 302
10.2.4 主要過濾的請求類型 304
10.3 生成請求:獲取地址 305
10.3.1 過濾生成請求 305
10.3.2 准備解析IP地址與埠 307
10.3.3 獲取生成的IP地址和埠 308
10.3.4 連接終端的生成與相關信息的保存 310
10.4 控制請求 311
10.4.1 TDI_ASSOCIATE_ADDRESS的過濾 311
10.4.2 TDI_CONNECT的過濾 313
10.4.3 其他的次功能號 314
10.4.4 設置事件的過濾 316
10.4.5 TDI_EVENT_CONNECT類型的設置事件的過濾 318
10.4.6 直接獲取發送函數的過濾 320
10.4.7 清理請求的過濾 322
10.5 本書例子tdifw.lib的應用 323
10.5.1 tdifw庫的回調介面 323
10.5.2 tdifw庫的使用例子 325
本章的示例代碼 326
練習題 327
第11章 NDIS協議驅動 328
網路的連接只是外表而已,實際上,最終它們變成了一個個在網線上往返的網路包。高明的黑客是不會去用Socket來生成連接的。把黑暗的信息隱藏在單個的數據包里,你還可以發現它們嗎?本章介紹的NDIS協議驅動,是Windows網路抓包工具的基礎。
11.1 乙太網包和網路驅動架構 329
11.1.1 乙太網包和協議驅動 329
11.1.2 NDIS網路驅動 330
11.2 協議驅動的DriverEntry 331
11.2.1 生成控制設備 331
11.2.2 注冊協議 333
11.3 協議與網卡的綁定 335
11.3.1 協議與網卡的綁定概念 335
11.3.2 綁定回調處理的實現 335
11.3.3 協議綁定網卡的API 338
11.3.4 解決綁定競爭問題 339
11.3.5 分配接收和發送的包池與緩沖池 340
11.3.6 OID請求的發送和請求完成回調 342
11.3.7 ndisprotCreateBinding的最終實現 345
11.4 綁定的解除 351
11.4.1 解除綁定使用的API 351
11.4.2 ndisprotShutdownBinding的實現 353
11.5 在用戶態操作協議驅動 356
11.5.1 協議的收包與發包 356
11.5.2 在用戶態編程打開設備 357
11.5.3 用DeviceIoControl發送控制請求 358
11.5.4 用WriteFile發送數據包 360
11.5.5 用ReadFile發送數據包 362
11.6 在內核態完成功能的實現 363
11.6.1 請求的分發與實現 363
11.6.2 等待設備綁定完成與指定設備名 364
11.6.3 指派設備的完成 365
11.6.4 處理讀請求 368
11.6.5 處理寫請求 370
11.7 協議驅動的接收回調 374
11.7.1 和接收包有關的回調函數 374
11.7.2 ReceiveHandler的實現 376
11.7.3 TransferDataCompleteHandler的實現 380
11.7.4 ReceivePacketHandler的實現 381
11.7.5 接收數據包的入隊 383
11.7.6 接收數據包的出隊和讀請求的完成 385
本章的示例代碼 388
練習題 389
第12章 NDIS小埠驅動 390
如果厭煩了漏洞百出的乙太網,還有什麼可以充當我的網路介面嗎?當然,一切能通信的設備,皆有替代乙太網的潛質。即使您不願意修改無數通過TCP介面編程的應用程序,我們依然可以用其他通信設備來虛擬網卡。本章介紹小埠驅動來虛擬網卡的技術。
12.1 小埠驅動的應用與概述 391
12.1.1 小埠驅動的應用 391
12.1.2 小埠驅動的實例 392
12.1.3 小埠驅動的運作與編程概述 393
12.2 小埠驅動的初始化 393
12.2.1 小埠驅動的DriverEntry 393
12.2.2 小埠驅動的適配器結構 396
12.2.3 配置信息的讀取 397
12.2.4 設置小埠適配器上下文 398
12.2.5 MPInitialize的實現 399
12.2.6 MPHalt的實現 402
12.3 打開ndisprot設備 403
12.3.1 I/O目標 403
12.3.2 給IO目標發送DeviceIoControl請求 404
12.3.3 打開ndisprot介面並完成配置設備 406
12.4 使用ndisprot發送包 409
12.4.1 小埠驅動的發包介面 409
12.4.2 發送控制塊(TCB) 409
12.4.3 遍歷包組並填寫TCB 412
12.4.4 寫請求的構建與發送 415
12.5 使用ndisprot接收包 417
12.5.1 提交數據包的內核API 417
12.5.2 從接收控制塊(RCB)提交包 418
12.5.3 對ndisprot讀請求的完成函數 420
12.5.4 讀請求的發送 422
12.5.5 用於讀包的WDF工作任務 424
12.5.6 ndisedge讀工作任務的生成與入列 426
12.6 其他的特徵回調函數的實現 428
12.6.1 包的歸還 428
12.6.2 OID查詢處理的直接完成 429
12.6.3 OID設置處理 432
本章的示例代碼 433
練習題 434
第13章 NDIS中間層驅動 435
當我們不滿足於抓包和發包,而試圖控制本機上流入和流出的所有數據包的時候,NDIS中間層驅動是最終的選擇。防火牆的功能在這里得到加強:我們不再滿足於看到連接、埠、對方IP地址,而是要看到每一個數據包的原始結構。本章介紹NDIS中間層驅動。
13.1 NDIS中間層驅動概述 436
13.1.1 Windows網路架構總結 436
13.1.2 NDIS中間層驅動簡介 437
13.1.3 NDIS中間層驅動的應用 438
13.1.4 NDIS包描述符結構深究 439
13.2 中間層驅動的入口與綁定 442
13.2.1 中間層驅動的入口函數 442
13.2.2 動態綁定NIC設備 443
13.2.3 小埠初始化(MpInitialize) 445
13.3 中間層驅動發送數據包 447
13.3.1 發送數據包原理 447
13.3.2 包描述符「重利用」 448
13.3.3 包描述符「重申請」 451
13.3.4 發送數據包的非同步完成 453
13.4 中間層驅動接收數據包 455
13.4.1 接收數據包概述 455
13.4.2 用PtReceive接收數據包 456
13.4.3 用PtReceivePacket接收 461
13.4.4 對包進行過濾 463
13.5 中間層驅動程序查詢和設置 466
13.5.1 查詢請求的處理 466
13.5.2 設置請求的處理 468
13.6 NDIS句柄 470
13.6.1 不可見的結構指針 470
13.6.2 常見的NDIS句柄 471
13.6.3 NDIS句柄誤用問題 473
13.6.4 一種解決方案 475
13.7 生成普通控制設備 476
13.7.1 在中間層驅動中添加普通設備 476
13.7.2 使用傳統方法來生成控制設備 478
本章的示例代碼 483
練習題 483
附錄A 如何使用本書的源碼光碟 485