Windows内核 内核API的使用 关于内存检查的API 1 2 3 MmIsAddressValid ();ProbeForRead (); ProbeForWrite ();
关于字符串的API 以 Rtl
(Runtime Library)开头的函数命名源自 Windows 操作系统的运行时库(Runtime Library),这些库提供了一组基本函数,用于支持操作系统和驱动程序的开发。Rtl
函数主要用于以下几个方面:
字符串操作 :包括 Unicode 和 ANSI 字符串的初始化、复制、比较、转换等操作。
内存管理 :提供内存分配、释放、复制等基本功能。
其他基础操作 :如列表操作、异常处理等。
1 2 3 4 5 6 7 8 9 10 RtlInitUnicodeString ();RtlCopyUnicodeString ();RtlAppendUnicodeToString ();RtlAppendUnicodeStringToString ();RtlCompareUnicodeString ();RtlAnsiTringToUnicodeString ();RtlFreeUnicodeString ();RtlStringCbCopyW (); RtlStringCcbLengthW ();RtlZeroMemory ();
观察这个代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void String () { UNICODE_STRING unicode_str; RtlInitUnicodeString (&unicode_str, L"Hello" ); unicode_str.Length = (USHORT)(wcslen (L"Hello" ) * sizeof (WCHAR)); unicode_str.MaximumLength = sizeof (L"Hello" ); NTSTATUS status = RtlAppendUnicodeToString (&unicode_str, L" world" ); if (!NT_SUCCESS (status)) KdPrint (("First Append Function Fail\n" )); else KdPrint (("First Append Function Success!\n" )); }
为什么这个代码会报错???
因为UNICODE_STRING存的字符串放在一个常量里面,也就是把L“Hello”这个字符串放到UNICODE_STRING这个结构体里面,这是只读的,不能更改的
所以我们要改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void String () { UNICODE_STRING unicode_str2; WCHAR Buffer2[100 ] = L"Hello" ; RtlInitUnicodeString (&unicode_str2, Buffer2); unicode_str2.Length = (USHORT)(wcslen (Buffer2) * sizeof (WCHAR)); unicode_str2.MaximumLength = sizeof (Buffer2); status = RtlAppendUnicodeToString (&unicode_str2, L" World" ); if (!NT_SUCCESS (status)) KdPrint (("Second Append Function Fail\n" )); else KdPrint (("Second Append Function Success!\n" )); }
注意注意:UNICODE_STRING是没有\x00的概念的,因为在这个结构体里面,是给了Length的,一切以这个为标准
执行层API(ExXxx) 1 2 3 4 ExAllocatePool ExAllocatePoolWithTag ExFreePoolWithTag ExFreePool
1 2 3 4 PVOID ExAllocatePool ( POOL_TYPE PoolType, SIZE_T NumberOfBytes ) ;
参数 :
PoolType
:指定要分配的内存池的类型。常见的值包括:
NonPagedPool
:非分页内存池,内存始终驻留在物理内存中。
PagedPool
:分页内存池,内存可以换出到磁盘。
NonPagedPoolMustSucceed
:非分页内存池,分配失败时会引发系统崩溃。
NumberOfBytes
:要分配的内存字节数。
1 2 3 4 5 PVOID ExAllocatePoolWithTag ( POOL_TYPE PoolType, SIZE_T NumberOfBytes, ULONG Tag ) ;
Tag
:用于标识分配的内存块的四字符标识符。这个标签可以帮助开发者跟踪和调试内存使用。
1 2 3 4 5 6 7 8 PVOID pMemory2 = ExAllocatePoolWithTag (NonPagedPool, 1024 , 'Tag1' ); if (pMemory2 != NULL ) { ExFreePoolWithTag (pMemory2, 'Tag1' ); }
Windbg用命令
可以查看内核所有使用的内存池,此时Tag就有用了。如果没有Tag就找不到申请的内存,也不知道有没有释放,也不好定位
文件的表示 在 Windows 驱动程序开发中, 应用层和内核层在访问同一个文件时,路径的表示方式在应用层和内核层有所不同。了解这些不同的表示方法对正确处理文件路径至关重要。
应用层:c:\1.txt
内核层:\\??\\c:\\1.txt \\sysroot\\1.txt
设备路径格式(??\c:\1.txt) :
在内核中,文件路径通常以 \\??\\
前缀开头。这种表示方法被称为设备路径格式,用于表示绝对路径。
NT 路径格式(\Device\HarddiskVolumeX\Path 或 \SystemRoot\Path) :
另一种常见的内核路径格式是 NT 路径格式,通常用于表示系统路径。
ZwQuerySymbolicLinkObject
是一个内核模式函数,用来查询符号链接绑定哪个设备路径( 通常是 NT 路径格式 )
1 2 3 4 5 NTSTATUS ZwQuerySymbolicLinkObject ( HANDLE LinkHandle, PUNICODE_STRING LinkTarget, PULONG ReturnedLength ) ;
参数说明
LinkHandle
:一个指向符号链接对象的句柄。这个句柄必须是通过适当的访问权限打开的。
LinkTarget
:指向一个 UNICODE_STRING
结构,该结构接收符号链接对象所指向的目标路径。
ReturnedLength
:可选参数,指向一个变量,该变量接收目标路径的实际长度(以字节为单位)。
QueryDosDevice是一个三环的函数,用来查询三环设备名对应在内核的路径:
例如我们查询c盘的路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <windows.h> #include <iostream> using namespace std;#ifdef _UNICODE #define _tcout std::wcout #else #define _tcout std::cout #endif int main () { TCHAR Buf[100 ]; QueryDosDevice (L"C:" , Buf, sizeof (Buf)); _tcout << Buf << endl; return 0 ; }
这样就可以在三环知道三环设备在内核对应的设备路径
用WinObj查出来也确实C盘就是对应这个设备
ZwQueryDirectoryFile
是一个 Windows 内核模式 API,用于查询指定目录中的文件信息。通过这个函数,驱动程序可以枚举目录中的文件和子目录,并检索它们的属性信息。
头文件ntifs要在ntddk前面
举例:找到\Device\HarddiskVolume1这个设备存的一个叫1.txt的文件,代码要咋写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #include "header.h" #define BUFFER_SIZE 256 typedef struct _DEVICE_EXTENSION { CHAR Buffer[BUFFER_SIZE]; ULONG BufferLength; } DEVICE_EXTENSION, * PDEVICE_EXTENSION; TCHAR g_GlobalBuffer[BUFFER_SIZE] = { 0 }; NTSTATUS FindFileInDirectory (HANDLE directoryHandle, UNICODE_STRING targetFileName) { NTSTATUS status; IO_STATUS_BLOCK ioStatusBlock; PVOID buffer; ULONG bufferLength = 1024 ; BOOLEAN restartScan = TRUE; buffer = ExAllocatePoolWithTag (PagedPool, bufferLength, 12138 ); if (!buffer) { return STATUS_INSUFFICIENT_RESOURCES; } while (TRUE) { status = ZwQueryDirectoryFile ( directoryHandle, NULL , NULL , NULL , &ioStatusBlock, buffer, bufferLength, FileDirectoryInformation, TRUE, NULL , restartScan ); if (status == STATUS_NO_MORE_FILES) { break ; } if (!NT_SUCCESS (status)) { ExFreePool (buffer); return status; } PFILE_DIRECTORY_INFORMATION fileInfo = (PFILE_DIRECTORY_INFORMATION)buffer; do { UNICODE_STRING fileName; fileName.Buffer = fileInfo->FileName; fileName.Length = (USHORT)fileInfo->FileNameLength; fileName.MaximumLength = (USHORT)fileInfo->FileNameLength; if (RtlCompareUnicodeString (&fileName, &targetFileName, TRUE) == 0 ) { KdPrint (("Found file: %s\n" , fileName.Buffer)); ExFreePool (buffer); return STATUS_SUCCESS; } if (fileInfo->NextEntryOffset == 0 ) { break ; } fileInfo = (PFILE_DIRECTORY_INFORMATION)((PUCHAR)fileInfo + fileInfo->NextEntryOffset); } while (TRUE); restartScan = FALSE; } ExFreePool (buffer); return STATUS_OBJECT_NAME_NOT_FOUND; } NTSTATUS DriverEntry (PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER (RegistryPath); KdBreakPoint (); OBJECT_ATTRIBUTES objAttr; UNICODE_STRING volumeName; UNICODE_STRING targetFileName; HANDLE directoryHandle; IO_STATUS_BLOCK ioStatusBlock; DriverObject->DriverUnload = DriverUnload; KdPrint (("Create!\n" )); RtlInitUnicodeString (&volumeName, L"\\??\\C:\\" ); RtlInitUnicodeString (&targetFileName, L"1.txt" ); InitializeObjectAttributes (&objAttr, &volumeName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL , NULL ); NTSTATUS status = ZwOpenFile ( &directoryHandle, FILE_LIST_DIRECTORY | SYNCHRONIZE, &objAttr, &ioStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT ); if (NT_SUCCESS (status)) { status = FindFileInDirectory (directoryHandle, targetFileName); ZwClose (directoryHandle); if (NT_SUCCESS (status)) { DbgPrint ("File '1.txt' found on the volume.\n" ); } else { DbgPrint ("File '1.txt' not found on the volume.\n" ); } } else { DbgPrint ("Failed to open directory: %x\n" , status); } return STATUS_SUCCESS; } VOID DriverUnload (PDRIVER_OBJECT DriverObject) { DriverObject; KdPrint (("Unload\n" )); }
数据结构API 在内核编程中,不能使用标准模板库(STL)
内存管理 :
内核的内存分配要求更高 :内核中需要严格控制内存的分配和释放,任何内存泄漏都会导致系统崩溃。而STL中的容器在进行动态内存分配时并不能保证其内存分配策略完全符合内核的要求。
分页问题 :内核中的某些代码运行在高IRQL(如 DISPATCH_LEVEL
)时不能访问分页内存,而STL的实现可能会在这些地方使用分页内存。
实时性和确定性 :
性能问题 :STL的一些操作(如动态内存分配、迭代器操作等)在用户模式下可能是可以接受的,但在内核模式下这些操作可能会导致不可预测的延迟。
中断处理 :在内核中运行的代码有时需要在中断处理程序中运行,这要求这些代码必须非常高效且不能阻塞,而STL的很多操作并不满足这些要求。
异常处理 :
内核代码通常不支持C++异常 :STL广泛使用异常来处理错误情况,但在内核中使用异常处理机制是不可行的,因为它可能会导致更多的问题。
内核提供了两个主要的数据结构API:链表和AVL树。
链表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <ntddk.h> typedef struct _MY_DATA { LIST_ENTRY ListEntry; int Data; } MY_DATA, *PMY_DATA; VOID UseLinkedList () { LIST_ENTRY ListHead; PMY_DATA pData; PLIST_ENTRY pListEntry; InitializeListHead (&ListHead); pData = (PMY_DATA)ExAllocatePool (NonPagedPool, sizeof (MY_DATA)); if (pData) { pData->Data = 1 ; InsertTailList (&ListHead, &pData->ListEntry); } pData = (PMY_DATA)ExAllocatePool (NonPagedPool, sizeof (MY_DATA)); if (pData) { pData->Data = 2 ; InsertTailList (&ListHead, &pData->ListEntry); } for (pListEntry = ListHead.Flink; pListEntry != &ListHead; pListEntry = pListEntry->Flink) { pData = CONTAINING_RECORD (pListEntry, MY_DATA, ListEntry); DbgPrint ("Data: %d\n" , pData->Data); } while (!IsListEmpty (&ListHead)) { pListEntry = RemoveHeadList (&ListHead); pData = CONTAINING_RECORD (pListEntry, MY_DATA, ListEntry); ExFreePool (pData); }C++ }
AVL树
内核中的AVL树使用的是 RTL_AVL_TABLE
结构。下面是一个简单的示例,展示如何使用AVL树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #include <ntddk.h> #include <ntifs.h> typedef struct _MY_DATA { int Key; } MY_DATA, *PMY_DATA; RTL_GENERIC_COMPARE_RESULTS NTAPI MyCompareRoutine ( _In_ struct _RTL_AVL_TABLE *Table, _In_ PVOID FirstStruct, _In_ PVOID SecondStruct ) { PMY_DATA first = (PMY_DATA)FirstStruct; PMY_DATA second = (PMY_DATA)SecondStruct; if (first->Key < second->Key) return GenericLessThan; if (first->Key > second->Key) return GenericGreaterThan; return GenericEqual; } PVOID NTAPI MyAllocateRoutine ( _In_ struct _RTL_AVL_TABLE *Table, _In_ CLONG ByteSize ) { UNREFERENCED_PARAMETER (Table); return ExAllocatePoolWithTag (NonPagedPool, ByteSize, 'mytg' ); } VOID NTAPI MyFreeRoutine ( _In_ struct _RTL_AVL_TABLE *Table, _In_ PVOID Buffer ) { UNREFERENCED_PARAMETER (Table); ExFreePool (Buffer); } VOID UseAvlTree () { RTL_AVL_TABLE AvlTable; MY_DATA data1 = { 1 }; MY_DATA data2 = { 2 }; PMY_DATA pData; BOOLEAN newElement; RtlInitializeGenericTableAvl ( &AvlTable, MyCompareRoutine, MyAllocateRoutine, MyFreeRoutine, NULL ); RtlInsertElementGenericTableAvl (&AvlTable, &data1, sizeof (MY_DATA), &newElement); RtlInsertElementGenericTableAvl (&AvlTable, &data2, sizeof (MY_DATA), &newElement); pData = (PMY_DATA)RtlLookupElementGenericTableAvl (&AvlTable, &data1); if (pData) { DbgPrint ("Found element with key: %d\n" , pData->Key); } RtlDeleteElementGenericTableAvl (&AvlTable, &data1); }