字符串處理
在驅動中一般使用的是ANSI字符串和寬字節字符串,在驅動中我們仍然可以使用C中提供的字符串操作函數,但是在DDK中不提倡這樣做,由於C函數容易導致緩沖區溢出漏洞,針對字符串的操作它提供了一組函數分別用來處理ANSI字符串和UNICODE字符串。
針對兩種字符串,首先定義了它們的結構體
typedef struct _STRING {
USHORT Length;//字符串的長度
USHORT MaximumLength;//字符緩沖的長度
PCHAR Buffer;//字符緩沖的地址
} ANSI_STRING, *PANSI_STRING;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
對於這兩個字符串的打印,可以使用%wZ打印UNICODE_STRING用%Z打印ANSI_STRING
字符串的初始化
VOID
RtlInitAnsiString(
IN OUT PANSI_STRING DestinationString,
IN PCSZ SourceString
);
VOID
RtlInitUnicodeString(
IN OUT PUNICODE_STRING DestinationString,
IN PCWSTR SourceString
);
這兩個函數只是簡單的將SourceString 的首地址賦值給Buffer成員,並初始化相關的長度,所以在使用時需要考慮緩沖的生命周期,權限,同時如果我們改變SourceString 里面存儲的字符串,那么對應的UNICODE_STRING 或者ANSI_STRING中的值也會改變,比如下面的代碼
RtlInitUnicodeString(&uTest, L"Hello World");
RtlCopyMemory(uTest.Buffer, L"Test");
由於Buffer指向的是不可修改的常量內存部分,所以后面試圖修改它的時候會造成程序崩潰。
void InitString(&pUnicodeString)
{
WCHAR szBuf[255] = L"Hello world";
RtlInitUnicodeString(pUnicodeString, szBuffer);
}
void test()
{
UNICODE_STRING uTest;
InitString(&uTest);
//后面的操作
}
我們在另外一個函數中利用局部變量來初始化這個字符串的時候由於當函數調用完成,函數中局部變量被銷毀,這個時候指向的那塊內存可能已經被其他函數所占用,而我們后面通過操作UNICODE_STRING,又要操作這段內存,這個時候一定會出現問題,所以一般如果要在多個函數中使用這個UNICODE_STRING時一般申請一段堆內存,但是在使用完成后一定要記得自己回收這段內存,否則會造成內存泄露,對此DDK專門提供了一組函數來銷毀字符串中的堆內存
VOID
RtlFreeAnsiString(
IN PANSI_STRING AnsiString
);
VOID
RtlFreeUnicodeString(
IN PUNICODE_STRING UnicodeString
);
字符串拷貝:
VOID
RtlCopyString(
IN OUT PSTRING DestinationString,
IN PSTRING SourceString OPTIONAL
);
VOID
RtlCopyUnicodeString(
IN OUT PUNICODE_STRING DestinationString,
IN PUNICODE_STRING SourceString
);
字符串比較
LONG
RtlCompareString(
IN PSTRING String1,
IN PSTRING String2,
BOOLEAN CaseInSensitive//是否忽略大小寫
);
LONG
RtlCompareUnicodeString(
IN PUNICODE_STRING String1,
IN PUNICODE_STRING String2,
IN BOOLEAN CaseInSensitive
);
字符串轉化為大寫
VOID
RtlUpperString(
IN OUT PSTRING DestinationString,
IN PSTRING SourceString
);
NTSTATUS
RtlUpcaseUnicodeString(
IN OUT PUNICODE_STRING DestinationString,
IN PCUNICODE_STRING SourceString,
IN BOOLEAN AllocateDestinationString//是否要求該函數自行為輸出參數分配內存
);
這兩個函數在調用是目標字符串和源字符串可以是同一個字符串
字符串與整形數字之間的轉化可以使用函數
NTSTATUS
RtlUnicodeStringToInteger(
IN PUNICODE_STRING String,
IN ULONG Base OPTIONAL,//需要的數的進制
OUT PULONG Value
);
NTSTATUS
RtlIntegerToUnicodeString(
IN ULONG Value,
IN ULONG Base OPTIONAL,
IN OUT PUNICODE_STRING String
);
ANSI與UNICODE字符串的相互轉化可以使用下面的函數
NTSTATUS
RtlUnicodeStringToAnsiString(
IN OUT PANSI_STRING DestinationString,
IN PUNICODE_STRING SourceString,
IN BOOLEAN AllocateDestinationString
);
NTSTATUS
RtlAnsiStringToUnicodeString(
IN OUT PUNICODE_STRING DestinationString,
IN PANSI_STRING SourceString,
IN BOOLEAN AllocateDestinationString
);
文件操作
創建或者打開一個文件
文件的創建和打開都是使用函數ZwCreateFile
NTSTATUS
ZwCreateFile(
OUT PHANDLE FileHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PLARGE_INTEGER AllocationSize OPTIONAL,
IN ULONG FileAttributes,
IN ULONG ShareAccess,
IN ULONG CreateDisposition,
IN ULONG CreateOptions,
IN PVOID EaBuffer OPTIONAL,
IN ULONG EaLength
);
- FileHandle:這個函數通過這個參數返回文件句柄
- DesiredAccess:以何種權限打開或者創建這個文件,GENERIC_READ可讀,GENERIC_WRITE可寫,GENERIC_EXECUTE可執行,GENERIC_ALL所有權限
- ObjectAttributes:這是一個文件屬性的結構體,里面包含有要打開的文件的名稱
- IoStatusBlock:接受函數操作文件的結果狀態
- AllocationSize:指定在創建愛女或者寫文件時初始大小,如果給0,則文件大小會隨着寫入數據的增加而動態的增加
- FileAttributes:指定新創建文件的屬性,一般給0或者FILE_ATTRIBUTE_NORMAL
- ShareAccess:文件的共享權限,其他線程或者進程通過這個句柄訪問文件的權限,給0表示不允許其他進程通過這個句柄訪問,FILE_SHARE_READ讀, FILE_SHARE_WRITE寫,FILE_SHARE_DELETE刪除
- CreateDisposition:指定當文件存在或者不存在時這個函數的動作。它的取值可以有下面幾個
取值 | 文件存在 | 文件不存在 |
---|---|---|
FILE_SUPERSEDE | 新建一個文件替代 | 新建文件 |
FILE_CREATE | 返回一個錯誤 | 創建文件 |
FILE_OPEN | 打開文件 | 返回一個錯誤 |
FILE_OPEN_IF | 打開文件 | 創建文件 |
FILE_OVERWRITE | 打開,並且將之前的內容覆蓋 | 返回錯誤 |
FILE_OVERWRITE_IF | 打開,並且將之前的內容覆蓋 | 創建文件 |
9. CreateOptions打開或者創建文件時的附加操作,一般給FILE_SYNCHRONOUS_IO_NONALERT
10. EaBuffer指向擴展空間的指針
11. EaLength擴展空間的大小
這個函數與應用層的CreateFile不同的時,在指定打開或者創建文件名時是使用結構OBJECT_ATTRIBUTES來指定,針對這個結構,有一個函數能夠初始化它
VOID
InitializeObjectAttributes(
OUT POBJECT_ATTRIBUTES InitializedAttributes,
IN PUNICODE_STRING ObjectName,//文件名
IN ULONG Attributes,
IN HANDLE RootDirectory,
IN PSECURITY_DESCRIPTOR SecurityDescriptor
);
Attributes:該對象的描述信息,一般給OBJ_CASE_INSENSITIVE 表示對大小寫敏感
RootDirectory :該文件的根目錄,一般給NULL
SecurityDescriptor :安全描述符,一般也是給NULL
另外這里的名稱必須使用符號鏈接名或者設備名,而不是我們熟悉的“C:\”這種形式對於C盤可以使用名稱“\??\C”或者“\Device\HarddiskVolum1”這種形式
當程序結束時需要調用ZwClose來清理文件句柄這個函數的參數比較簡單,只是簡單的傳入文件句柄即可
獲取和設置文件的相關信息
可以下面兩個函數分別獲取和設置文件的相關信息
NTSTATUS
ZwQueryInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
);
NTSTATUS
ZwSetInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
);
其中FileInformationClass是一個枚舉值,根據這個值得不同FileInformation可以被解析成不同的內容。
1. 當這個參數為FileStandardInformation時,使用結構體FILE_STANDARD_INFORMATION
typedef struct FILE_STANDARD_INFORMATION {
LARGE_INTEGER AllocationSize; //為文件分配簇所占空間的大小
LARGE_INTEGER EndOfFile;//距離文件結尾還有多少字節,當文件指針位於文件頭時,這個值就是文件本身大小
ULONG NumberOfLinks;//有多少個鏈接文件
BOOLEAN DeletePending;//是否准備刪除
BOOLEAN Directory;//是否為目錄
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;
- 當這個參數為FileBasicInformation使用結構體FILE_BASIC_INFORMATION
typedef struct FILE_BASIC_INFORMATION {
LARGE_INTEGER CreationTime; //創建時間
LARGE_INTEGER LastAccessTime;//上次訪問時間
LARGE_INTEGER LastWriteTime;//上次寫文件時間
LARGE_INTEGER ChangeTime;//上次修改時間
ULONG FileAttributes;//文件屬性
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;
其中時間參數是一個LARGE_INTEGER類型的整數,代表從1601年到現在經過多少個100ns。文件屬性參數如果為FILE_ATTRIBUTE_DIRECTORY表示這是一個目錄文件,FILE_ATTRIBUTE_NORMAL表示是一個普通文件,FILE_ATTRIBUTE_HIDDEN表示這是一個隱藏文件,FILE_ATTRIBUTE_SYSTEM表示這是一個系統文件,FILE_ATTRIBUTE_READONLY表示這是一個只讀文件
3. 當這個參數為FileNameInformation時,使用結構體FILE_NAME_INFORMATION
typedef struct _FILE_NAME_INFORMATION {
ULONG FileNameLength;//文件名長度
WCHAR FileName[1];//文件名
} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION;
- 當這個參數是FilePositionInformation時,使用結構體FILE_POSITION_INFORMATION
typedef struct FILE_POSITION_INFORMATION {
LARGE_INTEGER CurrentByteOffset;//當前文件指針的位置
} FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION;
讀寫文件
寫文件調用函數ZwCreateFile
NTSTATUS
ZwWriteFile(
IN HANDLE FileHandle,//文件句柄
IN HANDLE Event OPTIONAL,//時間對象一般給NULL
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,//一般給NULL
IN PVOID ApcContext OPTIONAL,//一般給NULL
OUT PIO_STATUS_BLOCK IoStatusBlock,//記錄寫操作的狀態用里面的Information成員記錄實際寫了多少字節
IN PVOID Buffer,//寫入文件中緩沖區的指針
IN ULONG Length,//緩沖區中數據的長度
IN PLARGE_INTEGER ByteOffset OPTIONAL,//從文件的多少地址開始寫
IN PULONG Key OPTIONAL//一般給NULL
);
讀文件使用函數ZwReadFile
NTSTATUS
ZwReadFile(
IN HANDLE FileHandle,//文件句柄
IN HANDLE Event OPTIONAL,//一般給NULL
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,//一般給NULL
IN PVOID ApcContext OPTIONAL,//一般給NULL
OUT PIO_STATUS_BLOCK IoStatusBlock, //讀取的字節數保存在結構的成員Information中
OUT PVOID Buffer,//緩沖區的指針
IN ULONG Length,//緩沖區的長度
IN PLARGE_INTEGER ByteOffset OPTIONAL,//從文件的多少位置開始讀
IN PULONG Key OPTIONAL//一般給NULL
);
注冊表操作
注冊表中有下面幾個概念:
1. 注冊表項:注冊表項類似於目錄的概念,下面可以有子項或者注冊表的鍵-值對
2. 注冊表子項:類似於子目錄的概念
3. 鍵名:通過鍵名可以尋找到相應的鍵值
4. 鍵值類別:每個鍵值在存儲的時候有不同的類型,相當於變量的類型,主要有字符串和整型
5. 鍵值:鍵名下對應存儲的數據
創建和關閉注冊表
創建注冊表使用函數ZwCreateKey
NTSTATUS
ZwCreateKey(
OUT PHANDLE KeyHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN ULONG TitleIndex,
IN PUNICODE_STRING Class OPTIONAL,
IN ULONG CreateOptions,
OUT PULONG Disposition OPTIONAL
);
- KeyHandle:輸出一個注冊表對應項的句柄,以后針對這個項操作都是以這個句柄作為標示
- DesiredAccess:訪問權限,一般都設置為KEY_ALL_ACCESS
- ObjectAttributes:用法與文件操作中的用法相同
其中應用層中注冊表項與內核中注冊表項的對應關系如下:
應用層中的子健 | 內核中的路徑 |
---|---|
HKEY_CLASSES_ROOT | 沒有對應的路徑 |
HKEY_CURRENT_USER | 沒有簡單的對應路徑,但是可以求得 |
HKEY_USERS | \Registry\User |
HKEY_LOCAL_MACHINE | \Registry\Machine |
4. TitleIndex:一般設置為0
5. Class 一般給NULL
6. CreateOptions:創建選項,一般給REG_OPTION_NON_VOLATILE
7. Disposition:返回創建的狀態,如果是REG_CREATED_NEW_KEY表示創建了一個新的注冊表項如果是REG_OPENED_EXISTING_KEY表示打開一個已有的注冊表項
8. ### 添加、修改注冊表鍵
注冊表中的鍵是類似與字典中的鍵值對,通過鍵名找到對應的值,鍵值的類型大致可以分為下面幾種
分類 | 描述 |
---|---|
REG_BINARY | 鍵值采用二進制存儲 |
REG_SZ | 鍵值用寬字符串,以\0結尾 |
REG_EXPAND_SZ | 與上面的REG_SZ相同,它是上面那個字符串的擴展字符 |
REG_MULTI_SZ | 能夠存儲多個字符串,每個都以\0隔開 |
REG_DWORD | 鍵值用4字節整型存儲(這個類型的數據在驅動中使用ULONG來替代) |
REG_QWORD | 鍵值用8字節存儲(這個用LONGLONG) |
用函數ZwSetValueKey可以添加和修改注冊表的一項內容
NTSTATUS
ZwSetValueKey(
IN HANDLE KeyHandle, //注冊表句柄
IN PUNICODE_STRING ValueName,//要修改或者新建的鍵名
IN ULONG TitleIndex OPTIONAL,//一般設置為0
IN ULONG Type,//在上面的表中選擇一個
IN PVOID Data,//鍵值
IN ULONG DataSize//鍵值數據的大小
);
當傳入的鍵值不存在則創建一個新鍵值,否則就修改原來的鍵值
查詢注冊表
查詢注冊表使用函數ZwQueryValueKey
NTSTATUS
ZwQueryValueKey(
IN HANDLE KeyHandle, //注冊表句柄
IN PUNICODE_STRING ValueName,//注冊表鍵名
IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
OUT PVOID KeyValueInformation,//接收返回信息的緩沖區
IN ULONG Length,//緩沖區的大小
OUT PULONG ResultLength//真實緩沖區的大小
);
使用這個函數時利用參數KeyValueInformationClass來指定接收數據的類型,根據這個值的不同,函數會返回不同的結構體放到一個緩沖區中。一般這個值可取:KeyValueBasicInformation 返回注冊表項的基礎信息
KeyValueFullInformation 返回注冊表的全部信息
KeyValuePartialInformation 返回注冊表的部分信息
一般情況下使用KeyValuePartialInformation查詢鍵值數據
利用這個函數來查詢時一般也是采用兩次調用的方式,第一次返回數據所需緩沖,然后分配緩沖並進行第二次調用
枚舉子項
DDK提供了兩個函數用於這個功能
NTSTATUS
ZwQueryKey(
IN HANDLE KeyHandle,//注冊表句柄
IN KEY_INFORMATION_CLASS KeyInformationClass,//保存注冊表信息的結構體的類型
OUT PVOID KeyInformation,//返回查詢到信息的緩沖
IN ULONG Length,//緩沖的大小
OUT PULONG ResultLength//真正信息的大小
);
NTSTATUS
ZwEnumerateKey(
IN HANDLE KeyHandle,//句柄
IN ULONG Index,//這個值是表示第幾個子項
IN KEY_INFORMATION_CLASS KeyInformationClass,//查詢到的信息的結構體
OUT PVOID KeyInformation,//返回信息的緩沖
IN ULONG Length,//緩沖長度
OUT PULONG ResultLength//返回信息的長度
);
其中ZwQueryKey函數用於查詢某個注冊表項中有多少個子項,在調用這個函數時傳入的KeyInformationClass的值一般給KeyFullInformation,在這個結構體中的SubKeys表示有多少個子項,而ZwEnumerateKey則是用於查詢各個子項中的具體內容,通過指定Index表示我們要查詢該項中的第幾個子項,將KeyInformationClass填入KeyBasicInformation,這樣在結構體的Name里面可以得到具體的注冊表子項的名稱
枚舉子健
枚舉子鍵的方法於上面的大致相同,首先利用ZwQueryKey查詢注冊表,然后取結構體KeyFullInformation的成員Values,根據這個值在循環中依次調用函數ZwEnumerateValueKey,結構體類填入 KeyValueBasicInformation查詢基本信息即可
刪除子項
刪除子項使用的內核函數是ZwDeleteKey
NTSTATUS
ZwDeleteKey(
IN HANDLE KeyHandle
);
這個函數只能刪除沒有子項的項目,如果有子項,則需要先刪除所有子項。
其他注冊表函數
為了簡化注冊表操作,DDK提供了另外一組以Rtl開頭的函數,把之前的Zw函數進行了封裝,下面是這些函數與它們功能的對應關系
函數名 | 描述 |
---|---|
RtlCreateRegistryKey | 創建注冊表項 |
RtlCheckRegistryKey | 查看注冊表中的某項是否存在 |
RtlWriteRegistryValue | 寫注冊表 |
RtlDeleteRegistryValue | 刪除注冊表的子鍵 |