Win64 驱动内核编程-5.内核里操作文件
內核里操作文件
? ? RING0?操作文件和?RING3?操作文件在流程上沒什么大的區別,也是“獲得文件句柄->讀/寫/刪/改->關閉文件句柄”的模式。當然了,只能用內核?API,不能用?WIN32API。在講解具體的代碼之前,先講解一下文件系統的流程,讓大家對整個文件系統有個大概的了解。
? ? 假設我們要讀寫一個文件,無論在?RING3?調用?ReadFile,還是在?RING0?調用?NtReadFile,它們最終會轉換為?IRP,發送到?文件系統驅動(具體哪個驅動和分區類型相關,如果是?FAT32分區,則是?FASTFAT.SYS;如果是?NTFS?分區,則是?NTFS.SYS)的?IRP_MJ_READ?分發函數里。文件系統驅動經過一定處理后,就把?IRP?傳給?磁盤類驅動(通常是?CLASSPNP.SYS,此驅動的源碼在?WDK?里有)的?IRP_MJ_READ?分發函數處理。磁盤類驅動處理完畢后,又把?IRP?傳給磁盤小端口驅動的?IRP_MJ_SCSI?分發函數處理。?磁盤小端口?驅動太多了,網上有人?用ATAPI.SYS??來指代?磁盤?小端口驅動,是極端錯誤的說法。ATAPI.SYS?是磁盤小端口驅動,但磁盤小端口驅動絕非只能是?ATAPI.SYS,常見的磁盤小端口驅動還有?LSI_SAS.SYS?等。如果安裝了芯片組驅動,磁盤小端口驅動通常會被替換成主板廠商的驅動。比安裝了英特爾?P67、HM77?的芯片組驅動后,磁盤小端口驅動就會變成?iaStroV.sys。在磁盤小端口驅動里,無論是讀還是寫,用的都是?IRP_MJ_SCSI?的分發函數。IRP??被磁盤小端口驅動處理完?之后?,?就要靠?依靠?HAL.DLL??進行口?端口?IO?,?此時數據就真的從硬盤里讀取了出來。接下來再按照相反的方向把數據返回到調用者。另外,在內核里,文件夾和文件沒啥本質的區別。比如?ZwDeleteFile既可以刪除文件,也可以刪除文件夾。接下來舉幾個例子,讓大家了解內核里讀寫、刪除、重命名和枚舉文件,以及獲取文件信息。
1.文件拷貝 BOOLEAN ZwCopyFiles ( IN PUNICODE_STRING ustrDestFile, // \??\c:\1.txt IN PUNICODE_STRING ustrSrcFile // \??\c:\0.txt ) {DbgPrint("UnicodeString:%wZ\n", ustrDestFile); DbgPrint("UnicodeString:%wZ\n", ustrSrcFile); HANDLE hSrcFile = NULL, hDestFile = NULL; PVOID buffer = NULL; ULONG length = 0; LARGE_INTEGER offset = { 0 }; IO_STATUS_BLOCK Io_Status_Block = { 0 }; OBJECT_ATTRIBUTES obj_attrib; NTSTATUS status; BOOLEAN bRet = FALSE; do { // 打開源文件 InitializeObjectAttributes(&obj_attrib, ustrSrcFile, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile(&hSrcFile, GENERIC_READ, &obj_attrib, &Io_Status_Block, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(status)) { DbgPrint("[KrnlHW64]Yuan Wen Jian 2333333333333\n"); bRet = FALSE; goto END; } // 打開目標文件 InitializeObjectAttributes(&obj_attrib, ustrDestFile, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile(&hDestFile, GENERIC_WRITE, &obj_attrib, &Io_Status_Block, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(status)) { bRet = FALSE; goto END; } // 為 buffer 分配 4KB 空間 buffer = ExAllocatePool(NonPagedPool, 1024 * 4); if (buffer == NULL) { bRet = FALSE; goto END; } // 復制文件 while (1) { length = 4 * 1024; // 讀取源文件 status = ZwReadFile(hSrcFile, NULL, NULL, NULL, &Io_Status_Block, buffer, length, &offset, NULL); if (!NT_SUCCESS(status)) { // 如果狀態為 STATUS_END_OF_FILE,說明文件已經讀取到末尾 if (status == STATUS_END_OF_FILE) { bRet = TRUE; goto END; } } // 獲得實際讀取的長度 length = (ULONG)Io_Status_Block.Information; // 寫入到目標文件 status = ZwWriteFile(hDestFile, NULL, NULL, NULL, &Io_Status_Block, buffer, length, &offset, NULL); if (!NT_SUCCESS(status)) { bRet = FALSE; goto END; } // 移動文件指針 offset.QuadPart += length; } } while (0); END: if (hSrcFile) { ZwClose(hSrcFile); } if (hDestFile) { ZwClose(hDestFile); } if (buffer != NULL) { ExFreePool(buffer); } return bRet; }VOID Test() { UNICODE_STRING UnicodeString1 = { 0 }; RtlInitUnicodeString(&UnicodeString1, L"\\??\\c:\\a.dat"); UNICODE_STRING UnicodeString2 = { 0 }; RtlInitUnicodeString(&UnicodeString2, L"\\??\\c:\\b.dat"); ZwCopyFiles(&UnicodeString1, &UnicodeString2); }2.刪除文件/文件夾 void ZwDeleteFileFolder(WCHAR *wsFileName) { NTSTATUS st; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING UniFileName; //把 WCHAR*轉化為 UNICODE_STRING RtlInitUnicodeString(&UniFileName, wsFileName); //設置包 OBJECT 對象并使用 ZwDeleteFile 刪除 InitializeObjectAttributes(&ObjectAttributes, &UniFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); st = ZwDeleteFile(&ObjectAttributes); }3.文件/文件夾重命名 /** typedef struct _FILE_RENAME_INFORMATION { BOOLEAN ReplaceIfExists; HANDLE RootDirectory; ULONG FileNameLength; WCHAR FileName[1]; } FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; */ NTSTATUS ZwRenameFile ( IN PWSTR SrcFileName, // \??\x:\xxx\...\xxx.xxx IN PWSTR DstFileName // \??\x:\xxx\...\xxx.xxx ) { #define RN_MAX_PATH 2048 #define SFLT_POOL_TAG 'fuck' HANDLE FileHandle = NULL; OBJECT_ATTRIBUTES ObjectAttributes; IO_STATUS_BLOCK IoStatus; NTSTATUS Status; PFILE_RENAME_INFORMATION RenameInfo = NULL; UNICODE_STRING ObjectName; //設置重命名的信息 RenameInfo = (PFILE_RENAME_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, sizeof(FILE_RENAME_INFORMATION) + RN_MAX_PATH * sizeof(WCHAR), SFLT_POOL_TAG); if (RenameInfo == NULL) { return STATUS_INSUFFICIENT_RESOURCES; } RtlZeroMemory(RenameInfo, sizeof(FILE_RENAME_INFORMATION) + RN_MAX_PATH * sizeof(WCHAR)); RenameInfo->FileNameLength = wcslen(DstFileName) * sizeof(WCHAR); wcscpy(RenameInfo->FileName, DstFileName); RenameInfo->ReplaceIfExists = 0; RenameInfo->RootDirectory = NULL; //設置源文件信息并獲得句柄 RtlInitUnicodeString(&ObjectName, SrcFileName); InitializeObjectAttributes(&ObjectAttributes, &ObjectName, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = ZwCreateFile(&FileHandle, SYNCHRONIZE | DELETE, &ObjectAttributes, &IoStatus, NULL, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, NULL, 0); if (!NT_SUCCESS(Status)) { ExFreePoolWithTag(RenameInfo, SFLT_POOL_TAG); return Status; } //最關鍵一步,利用 ZwSetInformationFile 來設置文件信息 Status = ZwSetInformationFile(FileHandle, &IoStatus, RenameInfo, sizeof(FILE_RENAME_INFORMATION) + RN_MAX_PATH * sizeof(WCHAR), FileRenameInformation); if (!NT_SUCCESS(Status)) { ExFreePoolWithTag(RenameInfo, SFLT_POOL_TAG); ZwClose(FileHandle); return Status; } ZwClose(FileHandle); return Status; }4.獲取文件大小 //這里傳入的是文件句柄不是文件名,大家嘗試把這里改成傳入文件名 ULONG64 GetFileSize(HANDLE hfile) { IO_STATUS_BLOCK iostatus = { 0 }; NTSTATUS ntStatus = 0; FILE_STANDARD_INFORMATION fsi = { 0 }; ntStatus = ZwQueryInformationFile(hfile, &iostatus, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation); if (!NT_SUCCESS(ntStatus)) return 0; return fsi.EndOfFile.QuadPart; }5.枚舉文件(RING3 的 FindFirstFile 和 FindNextFile 內部就是用 ZwQueryDirectoryFile 實現的,為了方便大家以后抄代碼,我就把 ZwQueryDirectoryFile 封裝成了 RING0 版的 FindFirstFile 和FindNextFile): NTKERNELAPI NTSTATUS ZwQueryDirectoryFile //最關鍵的 API ( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass, BOOLEAN ReturnSingleEntry, PUNICODE_STRING FileName, BOOLEAN RestartScan ); //幾個常量 #define INVALID_HANDLE_VALUE (HANDLE)-1 #define MAX_PATH2 4096 #define kmalloc(_s) ExAllocatePoolWithTag(NonPagedPool, _s, 'SYSQ') #define kfree(_p) ExFreePool(_p) /* //枚舉文件用到的結構體 typedef struct _FILE_BOTH_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; CCHAR ShortNameLength; WCHAR ShortName[12]; WCHAR FileName[1]; } FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION; */ //山寨版 MyFindFirstFile HANDLE MyFindFirstFile(LPSTR lpDirectory, PFILE_BOTH_DIR_INFORMATION pDir, ULONG uLength) { char strFolder[MAX_PATH2] = { 0 }; STRING astrFolder; UNICODE_STRING ustrFolder; OBJECT_ATTRIBUTES oa; IO_STATUS_BLOCK ioStatus; NTSTATUS ntStatus; HANDLE hFind = INVALID_HANDLE_VALUE; memset(strFolder, 0, MAX_PATH2); strcpy(strFolder, "\\??\\"); strcat(strFolder, lpDirectory); RtlInitString(&astrFolder, strFolder); if (RtlAnsiStringToUnicodeString(&ustrFolder, &astrFolder, TRUE) == 0) { InitializeObjectAttributes(&oa, &ustrFolder, OBJ_CASE_INSENSITIVE, NULL, NULL); ntStatus = IoCreateFile( &hFind, FILE_LIST_DIRECTORY | SYNCHRONIZE | FILE_ANY_ACCESS, &oa, &ioStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, //FILE_OPEN FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT, NULL, 0, CreateFileTypeNone, NULL, IO_NO_PARAMETER_CHECKING); RtlFreeUnicodeString(&ustrFolder); if (ntStatus == 0 && hFind != INVALID_HANDLE_VALUE) { ntStatus = ZwQueryDirectoryFile( hFind, // File Handle NULL, // Event NULL, // Apc routine NULL, // Apc context &ioStatus, // IoStatusBlock pDir, // FileInformation uLength, // Length FileBothDirectoryInformation, // FileInformationClass TRUE, // ReturnSingleEntry NULL, // FileName FALSE //RestartScan ); if (ntStatus != 0) { ZwClose(hFind); hFind = INVALID_HANDLE_VALUE; } } } return hFind; } //山寨版 MyFindNextFile BOOLEAN MyFindNextFile(HANDLE hFind, PFILE_BOTH_DIR_INFORMATION pDir, ULONG uLength) { IO_STATUS_BLOCK ioStatus; NTSTATUS ntStatus; ntStatus = ZwQueryDirectoryFile( hFind, // File Handle NULL, // Event NULL, // Apc routine NULL, // Apc context &ioStatus, // IoStatusBlock pDir, // FileInformation uLength, // Length FileBothDirectoryInformation, // FileInformationClass FALSE, // ReturnSingleEntry NULL, // FileName FALSE //RestartScan ); if (ntStatus == 0) return TRUE; else return FALSE; } //枚舉文件夾內容的函數,輸入路徑,返回目錄下的文件和文件夾數目 ULONG SearchDirectory(LPSTR lpPath) { ULONG muFileCount = 0; HANDLE hFind = INVALID_HANDLE_VALUE; PFILE_BOTH_DIR_INFORMATION pDir; char *strBuffer = NULL, *lpTmp = NULL; char strFileName[255 * 2]; ULONG uLength = MAX_PATH2 * 2 + sizeof(FILE_BOTH_DIR_INFORMATION); strBuffer = (PCHAR)kmalloc(uLength); pDir = (PFILE_BOTH_DIR_INFORMATION)strBuffer; hFind = MyFindFirstFile(lpPath, pDir, uLength); if (hFind != INVALID_HANDLE_VALUE) { kfree(strBuffer); uLength = (MAX_PATH2 * 2 + sizeof(FILE_BOTH_DIR_INFORMATION)) * 0x2000; strBuffer = (PCHAR)kmalloc(uLength); pDir = (PFILE_BOTH_DIR_INFORMATION)strBuffer; if (MyFindNextFile(hFind, pDir, uLength)) { while (TRUE) { memset(strFileName, 0, 255 * 2); memcpy(strFileName, pDir->FileName, pDir->FileNameLength); if (strcmp(strFileName, "..") != 0 && strcmp(strFileName, ".") != 0) { if (pDir->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { DbgPrint("[目錄]%S\n", strFileName); } else { DbgPrint("[文件]%S\n", strFileName); } muFileCount++; } if (pDir->NextEntryOffset == 0) break; pDir = (PFILE_BOTH_DIR_INFORMATION)((char *)pDir + pDir->NextEntryOffset); } kfree(strBuffer); } ZwClose(hFind); } return muFileCount; }6.創建文件夾(其實用 IoCreateFile 也能實現 ZwCreateFile 的功能,ZwCreateFile 不過是 IoCreateFile 的 stub 而已。下面利用 IoCreateFile 創建文件夾)void ZwCreateFolder(char *FolderPath) { NTSTATUS st; HANDLE FileHandle; OBJECT_ATTRIBUTES ObjectAttributes; IO_STATUS_BLOCK IoStatusBlock; UNICODE_STRING UniFileName; WCHAR wsFileName[2048] = { 0 }; CharToWchar(FolderPath, wsFileName); RtlInitUnicodeString(&UniFileName, wsFileName); InitializeObjectAttributes(&ObjectAttributes, &UniFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); st = IoCreateFile(&FileHandle, GENERIC_READ, &ObjectAttributes, &IoStatusBlock, 0, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, FILE_DIRECTORY_FILE, NULL, 0, 0, NULL, IO_NO_PARAMETER_CHECKING); if (NT_SUCCESS(st)) ZwClose(FileHandle); }最后總結一下幾個常見的、和文件相關的?Zw?函數的功能:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
總結
以上是生活随笔為你收集整理的Win64 驱动内核编程-5.内核里操作文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Win64 驱动内核编程-4.内核里操作
- 下一篇: Win64 驱动内核编程-6.内核里操作