image
內(nèi)核的基本操作厂庇,多線程和數(shù)據(jù)結(jié)構(gòu)
內(nèi)核的基本操作
UNICODE_STRING
為什么字符串很重要
- 大型工程中10%~20%的代碼都與字符串操作有關(guān)
- 面試里面也有很多字符串相關(guān)問題
- 字符串涉及到指針操作
- 字符串是編程第二個重要關(guān)口
字符串分兩類:0結(jié)尾和非零結(jié)尾
char *
在c語言中使用繁堡,以‘\0'結(jié)尾
BSTR
在win32編程中使用,前4個字節(jié)表示字節(jié)長伯诬,后面以'\0'結(jié)尾,可以理解為是char*
和UNICODE_STRING
的綜合
UNICODE STRING
內(nèi)核函數(shù)多以這種字符串作為輸入?yún)?shù)巫财,不是以'\0'結(jié)尾
定義:
/// unicode編碼字符
typedef struct UNICODE_STRING{
USHORT Length; ///< 字節(jié)數(shù)盗似,不是字符數(shù),而是數(shù)據(jù)的字節(jié)數(shù),字節(jié)數(shù) = 字符數(shù) *sizeof(WCHAR)
USHORT MaximumLength; ///< 字節(jié)數(shù),告訴系統(tǒng)函數(shù)最多有多少內(nèi)存可用
PWSTR Buffer; ///< 只是一個指針平项,一旦定義之后并沒有內(nèi)存赫舒,需要正確初始化之后,讓Buffer含有真正的數(shù)據(jù)闽瓢,才擁有內(nèi)存.**非零結(jié)尾接癌,中間也可能含有0**,PWSTR等價于WCHAR
}UNICODE_STRING,*PUNICODE_STRING;
/// 多字節(jié)編碼字符
typedef struct _STRING{
USHORT Length; ///< 其他參數(shù)同上
USHORT MaximumLength; ///< 其他參數(shù)同上
PCHAR Buffer; ///< 其他參數(shù)同上
}ANSESTRING,*PANSI_STRING;
/// Buffer不是以零結(jié)尾,中間也可能含有零扣讼,wcscpy/wcscmp等操作其中的Buffer不可靠缺猛,
///(如果中間UNICODE_STRING.Buffer或者_STRING.Buffer中間也可能含有零,則會提前截斷椭符,如果中間不含有零荔燎,則會溢出)
對比:
typedef struct XXX{
USHORT Leng;
...
WCHAR Buffer[MAX_PATH]; ///< 字符數(shù)組,一旦定義就擁有內(nèi)存
}XXX,*PXXX
初始化方式:
用字符串常量對其初始化
UNICODE_STRING uStr={0}销钝;///< 直接定義有咨,Buffer沒有內(nèi)存(Buffer = NULL),必須正確初始化,讓Buffer含有真正的數(shù)據(jù)蒸健。
/// 在棧上的局部變量未初始化摔吏,調(diào)試的時候內(nèi)存顯示的是"燙燙燙"
/// 在堆上的局部變量未初始化,調(diào)試的時候內(nèi)存顯示的是"屯屯屯"
/// L"Hello,world!"字符串常量存放在靜態(tài)區(qū)的.rdata
WCHAR *szHello = L"Hello,world!";
/// buffer是淺拷貝(只把常量字符串的地址拷貝到buffer中)纵装,buffer直接指向L"Hello,world!"
/// Length:wcslen(szHello)*sizeof(WCHAR)征讲;
/// MaximumLength:(wcslen(szHello)+1)*sizeof(WCHAR);
RtllnitUnicodeString(&ustrTest,szHello);
上面兩句等價于:DECLARE_CONST_UNICODE_STRING(ustrTest,L"Hello,world!);
/// buffer直接指向靜態(tài)常量L"Hello,world!"橡娄,常量區(qū)內(nèi)存不可被修改诗箍,下面的操作必然會出錯。
RtlCopyUnicodeString(&ustrTest,&uStr2);
RtlAppendUnicodeToString(&ustrTest,Str1);
RtlAppendUnicodeStringToString(&ustrTest挽唉,&uStr2);
RtlAnsiStringToUnicodeString(&ustrTest,&aStr1,FALSE);
用棧上的buffer或靜態(tài)區(qū)的內(nèi)存對其初始化
/// 定義并初始化一個UNICODE_STRING字符串
UNICODE_STRING ustrTest=(0};
/// 如果把SZHello定義在函數(shù)內(nèi)部就是在棧上
/// 如果把SZHello定義在全局變量就是在靜態(tài)區(qū).bss
WCHAR SZHello[512]=L"Hello,world!"
/// 用棧上的buffer或靜態(tài)區(qū)的內(nèi)存對其初始化滤祖,
ustrTest.Buffer = szHello;
ustrTest.Length = wcslen("Lhello,world")*sizeof(WCHAR);
ustrTest.MaximumLength*sizeof(szHello);
用從堆上分配一個內(nèi)存對其初始化
/// 定義并初始化一個UNICODE_STRING字符串
UNICODE_STRING ustrTest = {0};
/// 設(shè)置有效長度
ULONG ulLength =wcslen(L"Hello world")*sizeof(WCHAR);
/// @param MAX_PATH*sizeof(WCHAR)表示待分配的內(nèi)存的大小(是字節(jié)數(shù)瓶籽,在驅(qū)動中UNICODE_STRING一般用來表示設(shè)備對象名匠童,符號鏈接,文件路徑塑顺,所以**字符數(shù)**不會超過MAX_PATH)MAX_PATH宏是260(個)汤求,表示在windows系統(tǒng)中一個文件的路徑最大個**字符數(shù)**俏险。(除去一個盤符,一個':',一個'\',一個'\0',所以實際上文件路徑剩下可修改的是256)
/// 為Buffer分配一個堆上的內(nèi)存
ustrTest.Buffer = ExAllocatePooMWithTag(PagedPool,MAX_PATH*sizeof(WCHAR),'POCU');
/// 如果分配失敗扬绪,則return
if (ustrTest.Buffer ==NULL)
{
return竖独;
}
/// 分配成,則對Buffer的指向的堆上內(nèi)存初始化為0
RtlZeroMemory(ustrTest.Buffer,MAX PATH*sizeof(WCHAR))挤牛;
/// 把L"Hello,world"拷貝到Buffer指向的內(nèi)存中去莹痢,是深拷貝
memcpy(ustrTest.Buffer,L"Hello,world",ulLength);
/// 設(shè)置有效長度
ustrTest.Length =ulLength;
/// 設(shè)置最大長度
ustrTest.MaximumLength MAX_PATH*sizeof(WCHAR);
DbgPrint("%wZ\n",&ustrTest);
/// 堆上分配的內(nèi)存需要手動把它釋放掉
ExFreePool(ustrTest.Buffer);
/// 如果下面還需要使用ustrTest墓赴,一定要把它設(shè)為NEULL
/// 如果執(zhí)行完ExFreePool(ustrTest.Buffer);函數(shù)返回了竞膳,就不需要設(shè)置為NULL
/// ustrTest.Buffer = NULL;
問題代碼:如果文件超過130個字符,就會出問題诫硕,文件路徑不全顶猜。
UNICODE STRING uPath={0};
uPath.Buffer=ExAllocatePooMWithTag(
PagedPool,
MAX_PATH,
'MLFC'
);
uPath.Length=wcslen(L"c:\doc\1.txt")*sizeof(WCHAR);
uPath.MaximumLength=MAX_PATH;
常用對字符串處理的API
初始化
/// 是淺拷貝(只把字符串str1的地址拷貝到uStr1.buffer中),buffer直接指向字符串str1
RtlInitUnicodeString(&uStr1,str1);
淺拷貝只是拷貝的一個地址痘括。在用堆上的內(nèi)存對UNICODE_STRING初始化時长窄,應(yīng)該使用memcpy(ustrTest.Buffer,L"Hello,world",ulLength);``ustrTest.Length =ulLength;``ustrTest.MaximumLength MAX_PATH*sizeof(WCHAR);
來賦值纲菌,要注意不要使用RtllnitunicodeString(ustrTest,L"Hello,world")
來賦值(會有風險)
/// eg:UNICODE_STRING初始化不當導(dǎo)致的藍屏
UNICODE_STRING ustrTest = {0};
ustrTest.Buffer = ExAllocatePoolWithTag(PagedPool,
(wcslen(L"Hello,world"))*sizeof(WCHAR),'POCU');
if (ustrTest.Buffer == NULL)
{
return;
}
RtlZeroMemory(ustrTest.Buffer,
(wcslen(L"Hello,world"))*sizeof(WCHAR));
/// Buffer原來執(zhí)行的堆上的內(nèi)存挠日,執(zhí)行完之后,Buffer指向了靜態(tài)區(qū)的常量字符串
RtllnitunicodeString(ustrTest,L"Hello,world");
DbgPrint("%wZIn"&ustrTest);
/// 這里釋放Buffer所指向的內(nèi)存翰舌,不是堆上內(nèi)存嚣潜,內(nèi)存泄漏了,同時釋放的是靜態(tài)區(qū)內(nèi)存椅贱,系統(tǒng)自我保護藍屏了
ExFreePool(ustrTest.Buffer);
拷貝
/// 深拷貝(copy值懂算,不是地址),很明顯名字中有copy字樣
RtlCopyUnicodeString(&uStr1,&uStr2);
拼接
/// @param str2 是簡單以'\0'結(jié)尾的C語言中的字符串
RtlAppendUnicodeToString(&uStr1,str2);
/// @param ustr2 本身就是UNICODE_STRING的字符串
RtlAppendUnicodeStringToString(&uStr1,&uStr2);
/// eg:
/// 用棧上的buffer或靜態(tài)區(qū)的內(nèi)存對其初始化
UNICODE_STRING uStr1 = {0};
WCHAR buff[100] = "Hello";
uStr1.Length = 10;
uStr1.Buffer = buff;
/// str1是簡單以'\0'結(jié)尾的C語言中的字符串
WCHARstr *str1 = L"world";
/// 用str1對uStr2初始化
UNICODE_STRING uStr2 = {0};
uStr2.Length=10:
uStr2.Buffer=str1;
///如果直接拼接一個簡單以'\0'結(jié)尾的C語言中的字符串則使用下面這個函數(shù)
RtlAppendUnicodeToString(&uStr1,str1);
///如果直接拼接一個UNICODE_STRING字符串則使用下面這個和函數(shù)
RtlAppendUnicodeStringToString(&uStr1,&uStr2);
拆分
比較
/// @param TRUE/FALSE 是否忽略大小寫
/// @return 0表示相等庇麦,負數(shù)表示uStr1 < uStr1,正數(shù)則表示uStr1 > uStr1
RtlcompareUnicodeString(&uStr1,&uStr1
TRUE/FALSE);
編碼轉(zhuǎn)換
/// 把多字節(jié)字符串轉(zhuǎn)換成寬字節(jié)字符串
/// @param TRUE/FALSE 轉(zhuǎn)換的過程中內(nèi)存的大小會發(fā)生變化计技,涉及內(nèi)存分配和計算,TRUE表示交給系統(tǒng)去計算內(nèi)存的大小和分配內(nèi)存山橄,F(xiàn)ALSE則表示程序員自己去計算和分配內(nèi)存
RtlAnsiStringToUnicodeString(&uStr1,&aStr1,
TRUE/FALSE);
/// 如果前面設(shè)置為TRUE垮媒,系統(tǒng)幫忙計算和分配內(nèi)存,用完之后一定要釋放掉航棱,否則會造成內(nèi)存泄漏
/// 實際上很少會遇到字符串編碼的轉(zhuǎn)換睡雇,因為內(nèi)核中用的都是UNICODE_STRING
/// DbgPrint在打印unicode中文的話,在debug view里面是看不到的饮醇,這種情況下就需要把unicode中文轉(zhuǎn)化成多字節(jié)編碼才能看到
RtlFreeUnicodeString(&uStr1);
安全函數(shù)
/// unicode編碼字符
typedef struct UNICODE_STRING{
USHORT Length; ///< 字節(jié)數(shù)它抱,不是字符數(shù),而是有效數(shù)據(jù)的字節(jié)數(shù)
USHORT MaximumLength; ///< 字節(jié)數(shù),告訴系統(tǒng)函數(shù)最多有多少內(nèi)存可用
PWSTR Buffer; ///< 只是一個指針朴艰,一旦定義之后并沒有內(nèi)存观蓄,需要正確初始化之后混移,讓Buffer含有真正的數(shù)據(jù),才擁有內(nèi)存.**非零結(jié)尾蜘腌,中間也可能含有0**,PWSTR等價于WCHAR
}UNICODE_STRING,*PUNICODE_STRING;
在UNICODE_STRNG的類型定義中可以發(fā)現(xiàn),是存在UNICODE_STRING能存儲大字符長度是655(USHORT取值范圍是0-(2^16-1)
),能表示65535/2 = 32,767
個字符沫屡,而在上述的對字符串操作的函數(shù)中饵隙,都沒有溢出檢測的撮珠,存溢出風險
//安全函數(shù),溢出檢測
#include <ntstrsafe.h>
/// str1金矛,&uStr2超過了32767芯急,或者uStr1+uStr2超過了32767,函數(shù)就會返回失敗
RtlUnicodeStringInit(&uStr1,str1);
RtlUnicodeStringCopy(&uStr1,&uStr2);
RtlUnicodeStringCat(&uStr1,&uStr2);
/// 不能操作32767個字符
#define NTSTRSAFE UNICODE_STRING_MAX_CCH(Oxffff / sizeof((wchar t))
自己實現(xiàn)所有c語言中與字符串相關(guān)的庫函數(shù)驶俊,再與微軟的對比
初始化
拷貝
拼接
拆分
比較
文件
文件的表示
- 應(yīng)用層:
"c\\doc\\hi.txt"
- 內(nèi)核:
L"\\??\\c:\\hi.txt"
-->"\\device\\harddiskvolume3\\hi.txt
-
"\\device\\harddiskvolume3\\hi.txt
表示在設(shè)備對象上有hi.txt文件娶耍,設(shè)備對象名"\\device\\harddiskvolume3\\hi.txt
是由內(nèi)核驅(qū)動創(chuàng)建的,然后在根據(jù)設(shè)備對象名創(chuàng)建符號鏈接饼酿。"\\device\\harddiskvolume3\\hi.txt
-
\\??\\c:
代表卷設(shè)備對象的符號鏈接榕酒,也稱為盤符。
-
-
設(shè)備對象的表示
- 應(yīng)用層:
-設(shè)備名:L"\\\\.\\xxxDrv"
其中xxxDrv
代表符號鏈接名故俐,把設(shè)備對象當作一個特殊的文件打開想鹰,打開得到一個句柄。 - 內(nèi)核層:
- 設(shè)備名:`"\device\xxxDrv"``
- 符號鏈接名:
"dosdevices\\xxxDrv"
等價于\\??\\xxxDrv"
文件操作
應(yīng)用層
打開文件獲得handle -> 基于handle讀寫刪除查詢 -> 關(guān)閉
創(chuàng)建文件/文件夾
讀/寫
拷貝
移動
刪除
屬性訪問與設(shè)置
內(nèi)核層
每個API都有對應(yīng)的Irp药版,但復(fù)制辑舷、粘貼、移動沒有對應(yīng)的Irp槽片,因為這三個動作本質(zhì)是讀和寫
ZwCreateFile
-
ZwCreateFile
打開文件 - 接口說明
/**
* @brief DriverEntry NtCreateFile 例程創(chuàng)建一個新文件或打開一個現(xiàn)有文件何缓。
* @param[out] FileHandle 指向接收文件句柄的 HANDLE 變量的指針。
* @param[in] DesiredAccess 指定一個ACCESS_MASK值还栓,該值確定對對象的請求訪問權(quán)限碌廓。
* @param[in] ObjectAttributes 文件路徑
* @param[out] IoStatusBlock 操作的結(jié)果,指向IO_STATUS_BLOCK結(jié)構(gòu)的指針剩盒,該結(jié)構(gòu)接收最終完成狀態(tài)和有關(guān)所請求操作的其他信息
* @param[in] AllocationSize 指向LARGE_INTEGER的指針氓皱,該LARGE_INTEGER包含創(chuàng)建或覆蓋的文件的初始分配大小(以字節(jié)為單位)勃刨。
* 如果"分配大小"為 NULL波材,則未指定分配大小。如果未創(chuàng)建或覆蓋任何文件身隐,則忽略分配大小廷区。
* @param[in] FileAttributes 指定一個或多個FILE_ATTRIBUTE_XXX 標志,這些標志表示在創(chuàng)建或覆蓋文件時要設(shè)置的文件屬性贾铝。
* 調(diào)用方通常指定FILE_ATTRIBUTE_NORMAL隙轻,從而設(shè)置默認屬性埠帕。
* @param[in] ShareAccess 創(chuàng)建/打開這個文件的共享訪問的類型,指定為零或以下標志的任意組合玖绿。共享讀|共享寫|共享刪除敛瓷,如果設(shè)為0,則進程以獨占的方式打開這個文件斑匪,
* 其他進程沒辦法再打開它呐籽,也就沒辦法刪除它,但還是有其他辦法強刪的
* @param[in] CreateDisposition 指定在文件存在或不存在時要執(zhí)行的操作蚀瘸。FILE_OPEN_IF狡蝶,存在則打開這個文件屋确,不存在則創(chuàng)建這個文件
* 360在處理文件創(chuàng)建攔截的時候舀患,曾經(jīng)在FILE_OPEN_IF出現(xiàn)過漏洞,流氓軟件生成仿造系統(tǒng)軟件的圖標誘導(dǎo)
* 用戶點擊攻晒,給網(wǎng)站導(dǎo)流獲取收益寂嘉。因為當時360考慮到大部分文件都是以FILE_OPEN奏瞬、FILE_OPEN_IF方式打開的,
* 如果監(jiān)控會拖慢系統(tǒng)性能泉孩,后來還是把這個標志加入到監(jiān)控中來了
* @param[in] CreateOptions 指定驅(qū)動程序創(chuàng)建或打開文件時要應(yīng)用的選項硼端。同步操作的標志、普通文件標志棵譬、文件夾標志
* @param[in] EaBuffer 對于設(shè)備和中間驅(qū)動程序显蝌,此參數(shù)必須是 NULL 指針。
* @param[in] EaLength 對于設(shè)備和中間驅(qū)動程序订咸,此參數(shù)必須為零曼尊。
* @return ntStatus NtCreateFile 在成功時返回 STATUS_SUCCESS,或在失敗時返回相應(yīng)的 NTSTATUS 錯誤代碼脏嚷。在后一種情況下骆撇,
* 調(diào)用方可以通過檢查 IoStatusBlock 參數(shù)來確定失敗的原因。
* @see https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntcreatefile
* @author cisco(微信公眾號:堅毅猿)
* @date 2022-02-24 22:09
*/
ZwCreateFile(
_Out_ PHANDLE FileHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_opt_ PLARGE_INTEGER AllocationSize,
_In_ ULONG FileAttributes,
_In_ ULONG ShareAccess,
_In_ ULONG CreateDisposition,
_In_ ULONG CreateOptions,
_In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
_In_ ULONG EaLength
);
ZwWriteFile
-
ZwWriteFile
寫文件父叙,是相對于應(yīng)用層來說神郊,數(shù)據(jù)流向:r3->R0
ZwReadFile
-
ZwReadFile
讀文件,是相對于應(yīng)用層來說趾唱,數(shù)據(jù)流向:r0->R3
ZwQuerylnformationFile
-
ZwQuerylnformationFile
查詢文件信息
ZwQueryFullAttributesFile
-
ZwQueryFullAttributesFile
查詢文件完整信息
ZwSetinformationFile
-
ZwSetinformationFile
設(shè)置文件信息 ->irp_mj_set_information
- 在這里還有兩個重要的Irp(重命名和刪除涌乳,Major)
ZwClose
-
ZwClose
關(guān)閉文件
ZwQueryDirectoryFile
-
ZwQueryDirectoryFile
遍歷文件夾-
./
當前目錄 -
..
父目錄
-