日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

64位内核开发第二讲.内核编程注意事项,以及UNICODE_STRING

發布時間:2023/12/18 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 64位内核开发第二讲.内核编程注意事项,以及UNICODE_STRING 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • 一丶驅動是如何運行的
    • 1.服務注冊驅動
  • 二丶Ring3跟Ring0通訊的幾種方式
    • 1.IOCTRL_CODE 控制代碼的幾種IO
    • 2.非控制 緩沖區的三種方式.
  • 三丶Ring3跟Ring0開發區別
    • 1.什么是Ring3 什么是Ring0
    • 四丶IRQL中斷級別
    • 五丶驅動函數的分類
    • 六丶編寫內核中的注意事項

一丶驅動是如何運行的

1.服務注冊驅動

我們編寫驅動.一定要知道驅動是如何運行的
首先在我們安裝一個驅動的時候,會創建一個服務.(注冊表)


HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SrvNmae
最后一個是是你驅動的名字.
如下:

里面有一個StartType 它是按照GroupOrder順序來啟動的

StartType值越小.代表越早啟動.總共是0 - 4

數值啟動時機說明
0系統核心裝載器裝載系統在啟動的時候優先加載.
1IO子系統裝載稍微晚一些加載.加載完核心驅動才加載我們的
2自動啟動在登錄界面出現的時候開始加載.電腦好驅動還沒加載也會登錄到桌面系統中.
3手工啟動如果設置為3.重啟之后不會再加載,你需要自己重新加載一次
4禁止啟動代表我們驅動不會加載.比如設置start值小于4才可以

其中設置Start的值是在我們3環加載驅動的時候設置的 調用 CreateService安裝驅動的時候,傳遞的參數值.其中有個參數就是Start.如下:

SC_HANDLE CreateServiceA(SC_HANDLE hSCManager,LPCSTR lpServiceName,LPCSTR lpDisplayName,DWORD dwDesiredAccess,DWORD dwServiceType,DWORD dwStartType, 這個值設置DWORD dwErrorControl,LPCSTR lpBinaryPathName,LPCSTR lpLoadOrderGroup, GroupOrder注意這個值LPDWORD lpdwTagId,LPCSTR lpDependencies,LPCSTR lpServiceStartName,LPCSTR lpPassword );

關于如何使用代碼加載我們的驅動.前邊也有說過.請參考前面資料.

https://www.cnblogs.com/iBinary/p/8280912.html

GroupOrder值
這個值是在注冊表
HEEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GroupOrderList

這個值是越靠前的驅動.啟動越早.
如下:

怎么結合的.
如果Start 值位0那么就看 GroupOrder 那個在上邊就加載誰.
Start值為0. 另一個為1 則值為0的先啟動.

2.對象管理器生成驅動對象

上面說了我們的服務會放在注冊表中. 但是我們的寫的驅動是怎么加載或執行的那.

對象管理器生成驅動對象 (DriverObject)并且把它傳遞給DriverEntry(). 執行 DriverEntry()入口函數.

3.創建控制設備對象
4.創見控制設備符號鏈接(Ring可以操作的)
5.綁定過濾驅動
如果我們有過濾驅動.則會創建過濾設備對象.并且進行綁定.
6.注冊分發函數
7.完成初始化動作.
8.應用程序通過符號鏈接打開設備對象.并且通過IRP發送讀寫請求.

二丶Ring3跟Ring0通訊的幾種方式

1.IOCTRL_CODE 控制代碼的幾種IO

1.METHOD_BUFFERED 通訊方式

METHOD_BUFFERED
在我們內核中 Ring3可以傳遞控制碼給內核層.其中需要指明我們的讀寫方式
如下:

#define IOCTRL_BASE 0x800 #define MYIOCTRL_CODE(i)\CTL_CODE(FILE_DEVICE_UNKNOWN,IOCTRL_BASE+i,METHOD_BUFFERED,FILE_ANY_ACCESS) 4個參數: 參數1: 驅動的類型 參數2: 驅動的控制碼. 參數3: 以哪種緩沖方式進行通訊 參數4: 權限

其中我們這里說的就是參數3.指定什么方式進行通訊.

ME_THOD_BUFFERED 則我們的數據都會會封裝在IRP頭部的SystemBuffer中.

pIrp->AssociatedIrp.SystemBuffer;

2.METHOD_IN_DIRECT 只讀緩沖 METHOD_OUT_DIRECT 只寫緩沖

如果我們的CTRL_CODE指定的是這兩種的其中一種.看如下解釋

METHOD_IN_DIRECT
只讀緩沖的方式.則我們的緩沖區還是會封裝到IRP頭部的SystemBuffer緩沖區.

IN pIrp->AssociatedIrp.SystemBuffer;

如上.我們 ring3 輸入的數據都會放在這個SystemBuffer中.

METHOD_OUT_DIRECT
只寫緩存
如果是這種方式.則我們的數據也會封裝到IRP頭部.但是會設置的 IRP
頭部MdlAddress中.我們輸出的數據都要放在MDL中.

如下:

OUT PVOID pOutBuffer = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);

MDL是 3環虛擬地址,映射到內核中的物理地址. 我們不能直接使用.
比如通過MmGetSystemAddressForMdlSafe這個函數.將映射的物理地址. 轉換為內核中的 "虛擬地址" 可以這樣理解. 然后對這個內存進入輸出即可.我們Ring3則可以接受到數據.

區別:
如果是只讀權限打開設備的時候.METHOD_IN_DIRECT則會成功.METHOD_OUT_DIRECT則會失敗.
如果是讀寫的方式.則都會成功.

3.METHOD_NEITHER 其它方式

使用其它方式.則我們Ring3發送過來的數據 會在IRP堆棧中
我們獲取Ring3的數據

PIRPSTACK_LOCATIO pIrpStack; pIrpStack = IoGetCurrentIrpStackLocation(pIrp); pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer

則我們的輸出的數據要放在 IRP頭部的UserBuffer中傳遞給三環

pIrp->UserBuffer;

2.非控制 緩沖區的三種方式.

我們上面說了.控制派遣函數可以傳遞不同的緩沖區方式.進而在內核中.
進行不同的 取緩沖區 寫緩沖區的操作.那么如果不是控制.則會有3種方式.

1.緩沖IO DO_BUFFERED_IO

當我們創建完畢設備對象的時候.可以給設備對象的 Flags字段設置一個緩沖區的方式..分別有三種.

如我們設置 緩沖區讀取.

pDevice->Flags |= DO_BUFFERED_IO;

如果是緩沖區方式.則 我們Ring3發送的數據就會封裝到
IRP頭部的SystemBuffer中.
也就是說說我們只需要取出 IRP頭部的SystemBuffer就可以了.

緩沖IO的意思就是 3環 發送數據到0環. 0環開辟一個空間.用來接收.
這種方式很安全.但是效率差.如果一次發送很多字節.不建議使用這種方式.
因為你進入了內核.那么內核空間就是共享了.如果你在創建這種很多字節的緩沖區.那么就讓原本已經很緊張的內核空間負擔更重.而且如果內存來不及釋放.則會永久占據.除非你重啟電腦.

2.DO_DIRECT_IO 直接IO

直接IO的方式就是 將ring3數據所在的虛擬地址,映射到內核空間中.
內核進行讀取.這種方式效率快.一般內核廠家都是這種.

聽到映射.大家應該知道數據怎么傳遞了.

如果使用這種方式.那么數據 會在IRP頭部的MdlAddress中.

我們取出來就是用 "API"進行獲取即可.
這里的API指的是使用內核API.不是ring3的.注意

PVOID pBuffer = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);

3.其它IO方式.

如果是其它IO方式.
我們輸入的數據則會在 IRP堆棧中的DeviceContrl字段中Type3InputBuffer中

pIrpStack->DeviceControl.Type3InputBuffer中.

輸出的數據則會在 IRP的頭部中的UserBuffer中

PIrp->UserBuffer

使用這種方式很危險.這種方式是內核直接讀取ring3虛擬地址數據.
必須保證ring3進程跟內核進程處于同一運行狀態中.
對此我們對其內存必須進行檢查.
有兩個API函數

ProbeForRead(); 檢查內存是否可讀 ProbeForWrite(); 檢查內存是否可寫.

三丶Ring3跟Ring0開發區別

1.什么是Ring3 什么是Ring0

CPU提供了4層. 而微軟只用了2層.
分別是Ring0到Rign3.
而微軟只用Ring0. 因為這個設計所以我們寫的驅動才能跟操作系統的權限一樣.這樣做也是因為不過與依賴Cpu的設計.否則以后CPU架構一改.操作系統就廢了.

X64系統
在X64系統中.CPU廠商因為操作系統沒有使用. 所以CPU直接把
RING1 RING2給去掉了.
所以在X64下,只剩下ring0 跟 ring3了.

虛擬技術 (VT技術)

虛擬技術有三種方式 0 1 3
內核運行在0層. 虛擬機運行在1層 應用程序運行在3層.

VT模式: 虛擬機運行在 -1級別. 根模式. -1就是在0環旁邊加了一個新的模式. -1級別就是權限很大的.比內核權限更大.

2.RING3 與 Ring0開發的區別

在內核中

printf scanf fopen fclose fwrite fread malloc free

都不可以使用.

但是與內存相關與字符串相關的可以使用

strcmp strcpy wcslen memset

但是不建議使用.在內核中有專門的操作函數.
這種函數是Rtl開頭.
如:

RtlStringcbCatA /W 字符串拼接 RtlStringcbCopy();字符串拷貝 RtlStringcbLength();求長度 RtlStringcbPrnt(); 字符串打印

如果是UNICODE_STRING則如下

RtlStringcbCopyUnicodeString(); RtlUnicodeStringCat();

在內核中我們的字符串數據結構有得新的數據類型

UNICODE_STRINGANSI_STRING
其實就是一個結構體.
如下:

typedef struct _STRING {USHORT Length;USHORT MaximumLength;PCHAR Buffer; } ANSI_STRING, *PANSI_STRING; 記錄了長度.最大長度.以及一個緩沖區. UNICODE_STRING也一樣.

對此針對這個結構體有了新的初始化 函數

ANSI_STRING string; RtlInitAnsiString(string,"HelloWorld"");UNICODE則是 RtlInitUnicodeString(string,L"Hello");

3.返回值的判斷

在內核中使用函數.則會返回一個狀態值.
如:

ntStatuse =IoCreateDevice();

需要使用宏判斷

if (!NT_SUCESS(ntStatus))xxxx

常見的幾個狀態值

|狀態|含義|
|---|---||
|STATUS_SUCCESS|代表此次調用成功|
|STATUS_UNSUCCESSFUL|代表失敗|
|STATUS_ACCESS_DENIED|代表訪問拒絕|
|STATUS_INSUFFICIENT_RESOURCES|資源不足|
這些狀態嗎都在 <ntstatus.h>中定義的.

4.內存的使用與申請

在內核中申請內存跟ring3不同.
內核中申請內存使用

PVOID ExAllocatePoolWithTag(IN POOL_TYPE PoolType, 申請內存的類型.內存是分頁還是不可以IN SIZE_T NumberOfBytes, 申請的字節IN ULONG Tag 4個字節的內存標識,隨便寫.);

其中參數1需要你指定類型.
分頁內存.就是內存可以交換到磁盤使用.
非分頁內存.就是內存不能交換.就是固定的.不能變.但是非分頁內存很寶貴.只有100-200MB.給我們操作系統使用.所以建議使用分頁內存.

四丶IRQL中斷級別

這一講我很多博客也說過了.就是說我們調用的 內核函數都有級別一說.

如下表:
了解:

編碼級別說明
PASSIVE_LEVE無中斷常規線程執行
APC_LEVEL軟中斷異步過程調用執行
DISPATC_LEVEL軟中斷線程調度延時過程調用執行
DIRQL硬中斷設備中斷請求級處理程序執行
PROFILE_LEVEL硬中斷配置文件定時器
CLOCK2_LEVEL硬中斷時鐘
SYNCH_LEVEL硬中斷同步級
IPI_LEVE硬中斷處理器之間的中斷級
POWER_LEVEL硬中斷電源故障級別

除了硬中斷是最高的. 我們只看軟中斷.
PASSIVER_LEVE APC_LEVEL DISPATCH_LEVEL 級別. 分別是 0 1 2
在軟中斷中Dispath級別最高.

如我們編寫內核的時候給的派遣函數.以及入口點函數.
可以如下圖:

調用源函數級別
DriverEntry,DriverUnLoad0Passive級別
各種分發函數Passiver級別
完成函數Dispatch級別
各種NDIS回調函數Dispatch級別

在使用函數的時候.應該查詢WDK文檔.看看級別.

五丶驅動函數的分類

在驅動中有一些函數前綴都是固定的

如:
Ex開頭的.

函數函數說明
ExAllocatePoolWithTag分配內存
ExAcquireFastMutex獲取快速互斥鎖
ExGetPreviousMode獲取前一個請求者的運行模式. 判斷來自Ring0還是Ring3,攔截ring3.過濾ring0

Io開頭的 屬于Io管理器的

函數函數說明
IoCreateDevice創建設備對象
IoCreateSymbolicLink創建符號鏈接
IoGetCurrentIrpStackLocation獲取Irp堆棧
IoAttachDeviceToDeviceStack設備綁定,自己生成的設備綁定到別人的設備上,做過濾用的.
IoAllocateIrp自己分配一個IRP.
IoSetCompletionRoutine為IRP設置完成例程的.

Ke開頭的

函數函數說明
KeWaitForSingleObject等待事件發生.做同步用的.
KeSetEvent設置事件信號
KeInitializeEvent初始化一個事件對象.

Mm開頭的. 與Memory相關的.

函數函數說明
MmGetSystemRoutineAddress內核中獲取函數的內存地址. 跟ring3 GetProcAddress相似.一個ring3一個ring0
MmIsAddressValid判斷函數地址是否無效.

Ob開頭 與內核對象相關的.

函數函數說明
ObReferenceObjectByHandle把一個內核對象的Handle轉化成內核它的內核對象. 句柄不能夸進程.所以轉換為內核對象.
ObQueryNameString查詢名字跟路徑.可能會死鎖

Ps開頭的. 與進程相關的.

函數函數說明
PsGetCurrentProcess獲取當前進程的EPROCESS未導出的結構
PsGetCurrentProcessId獲取當前進程Pid
PsCreateSystemThead在內核中創建線程的
PsLookUpProcessByProcessId進程進程PID獲取這個PID的EPROCESS結構

Rtl開頭的 一組重寫的函數.可以操作內存跟字符串

函數函數說明
RtlZeroMemory對一塊內存清空位0.跟memset一樣.
RtlInitUnicodeString初始化UNICODE字符串

Zw開頭. 系統服務的. 文件系統.注冊表.

函數函數說明
ZwOpenKey打開注冊表鍵
ZwCreateFile創建文件或打開一個文件
ZwOpenProcess打開一個進程
ZwQuerySystemInformation遍歷進程的一些信息

Flt開頭的. 文件過濾相關的一組函數 (minfilter)

Ndis 防火墻中用的一些函數

六丶編寫內核中的注意事項

1 不要使用 MmIsAddressValid函數.這個函數對于校驗內存沒有意義

這個函數只能判斷一個字節地址的有效性

if (MmisAddressValid(Buffer)) {memcmp(BUffer,BufferTwo,Length); }

它只判斷地址字節的第一個地址.只要你的地址在這個分頁.那么可以.
但是就怕分頁.后面分頁不對就會出錯.

他還會對 Page Out不能準確的判斷. 所以攻擊者可以利用你的判斷.來繞過你的保護.

2.保證我們的代碼在 tye _except中完成.否則藍屏.
編寫驅動代碼一定要注意不要產生異常.否則就會藍屏.
如:

try {ProbeFroRead(Buffer,len,alig);if (memcpy(Buffer,buffer2,len){};//這行出錯就會在except. } _except(EXECUTE_HANDLER_EXCEPTION) {//如果出錯就會在這. }

3.注意長度為0的緩存. 以及為NULL的緩存指針與緩存對其

緩存長度為0
ProbeForread跟Write. 如果我們Buffer長度穿的為0.這兩個函數是不工作的.很容易被別人攻擊.所以要小心len為0的情況.
如下漏洞代碼:

try {ProbeForRead(Buffer1,len,sizeof(char));if (strcmp(Buffer1,Buffer2,len){}}_except(EXECUTE_HANDLER_EXCEPTION) {xxx }

上面的代碼會產生問題.因為當ProbeForRead的時候.長度傳遞為0
則這個函數不工作.但是我們的strcmp至少會訪問一個字節.這樣就造成了崩潰藍屏. 繞過你的保護.所以最好使用Rtl之類的函數操作.

緩存指針如空

不要使用下面的代碼

if (userBuffer == NULL){xxx};

windows操作系統運行用戶態申請的一個地址為0的內存. 攻擊者可以以它來繞過檢查過保護.

在我們以前講調用們的時候也說過. ring3可以使用0內存.
在Windows8以后內存不能申請為NULL.

緩存對齊

ProbeForRead(Address,length,Alignm);

在函數的第三個參數是對其. 默認是按照1對齊.如果使用 Sizeof(ULONG) 也會出問題.導致過保護.

4.注意不正確的內核調用引發的問題

如函數:

ObReferenceObjectByHandle();

如果使用這個函數.不指定類型.任然可以獲得對應的對象地址.但是如果你直接訪問這個對象.就會引發漏洞.

如:

HookZwSetInformationFile(); ObReferenceObjectByHandle(FileHandle,Access,NULL); //ObReferenceObjectByHandle(FileHandle,Access,&Fileobject);if (wscnicmp(fileObject->FileName){}

如上,參數如果傳遞為NULL. 攻擊者可以傳入非文件類型的句柄.如果你沒有校驗.就會導致悲劇.所以使用必須給指定對象類型.會影響第一個參數.
第一個參數攻擊者可以傳入任何Handle.
這就是拒絕服務攻擊.一句話你就藍屏.

不正確的Zw函數使用
使用Zw函數的時候.不能將用戶態的內存給它. 因為Zw函數不會進行校驗.
就算你進行了校驗.傳遞這樣的內存給系統也可以引發崩潰. 比如內存也在調用的時候突然無效. 就算你進行異常駁貨.也可以造成內存泄漏.對象泄漏.甚至權限提升等問題.

不要下發內核對象給內核
我們Ring3的內核對象.不要通過 DeviceControl 進行傳遞.
如果這樣寫.很可能讓攻擊者可以做到任意地址寫入.提升權限.

5.給驅動提供的功能性接口必須小心

如果對注冊表 文件 內核內存.進程線程等操作的功能性接口.一定要非常小心才可以.禁止一切受信進程的調用. 不然你暴露接口就會被利用.

6.數據傳輸盡量使用 BUFFERED_IO 緩存的方式.

我們內核中的最好使用緩沖IO.也就是說使用SystemBuffer.如果不使用BUFFERED_IO而使用UserBuffer一定注意使用 Pro等檢查函數.

7.發布的驅動必須通過內核校驗

微軟提供的驅動校驗工具: verifier
在CMD命令中輸入即可.打開后界面如下:

使用的時候

創建自定義設置(供程序開發人員使用) -> 從一個完整列表選擇單個設置
->出來很多檢查. -> 從一個列表中選擇驅動程序 ->有的話你選擇.沒有的話你自動選擇一個.選中之后則會重啟.自動進行檢測. 如果出錯.就會藍屏.

隊友掛鉤內核函數的驅動. 還可以使用 BSOD HOOK 一類的Fuzz工具
來進行檢查.

轉載于:https://www.cnblogs.com/iBinary/p/10990667.html

總結

以上是生活随笔為你收集整理的64位内核开发第二讲.内核编程注意事项,以及UNICODE_STRING的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。