内核里操作文件

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.内核里操作文件的更多相关文章

  1. Win64 驱动内核编程-7.内核里操作进程

    在内核里操作进程 在内核里操作进程,相信是很多对 WINDOWS 内核编程感兴趣的朋友第一个学习的知识点.但在这里,我要让大家失望了,在内核里操作进程没什么特别的,就标准方法而言,还是调用那几个和进程 ...

  2. Win64 驱动内核编程-6.内核里操作注册表

    内核里操作注册表 RING0 操作注册表和 RING3 的区别也不大,同样是"获得句柄->执行操作->关闭句柄"的模式,同样也只能使用内核 API 不能使用 WIN32 ...

  3. Win64 驱动内核编程-4.内核里操作字符串

    内核里操作字符串 字符串本质上就是一段内存,之所以和内存使用分开讲,是因为内核里的字符串太有花 样了,细数下来竟然有 4 种字符串!这四种字符串,分别是:CHAR*.WCHAR*.ANSI_STRIN ...

  4. Win64 驱动内核编程-3.内核里使用内存

    内核里使用内存 内存使用,无非就是申请.复制.设置.释放.在 C 语言里,它们对应的函数是:malloc.memcpy.memset.free:在内核编程里,他们分别对应 ExAllocatePool ...

  5. Win64 驱动内核编程-8.内核里的其他常用

    内核里的其他常用 1.遍历链表.内核里有很多数据结构,但它们并不是孤立的,内核使用双向链表把它们像糖 葫芦一样给串了起来.所以遍历双向链表能获得很多重要的内核数据.举个简单的例子,驱 动对象 Driv ...

  6. destruct析构函数里操作文件出现的问题

    这几天要给后台加一个记录操作日志的功能,可是项目已经开发完了不可能再去改以前的代码了,那有什么快捷的方法呢? 项目使用的ThinkPHP3.23 ,为了方便权限控制,后台控制器结构为:普通控制器 ex ...

  7. IO编程(2)-操作文件和目录

    操作文件和目录 如果我们要操作文件.目录,可以在命令行下面输入操作系统提供的各种命令来完成.比如dir.cp等命令. 如果要在Python程序中执行这些目录和文件的操作怎么办?其实操作系统提供的命令只 ...

  8. bash编程 将一个目录里所有文件存为一个array 并分割为三等分——利用bash array切片

    files=(a b c d e f g h i j k l m n o p)cnt="${#files[@]}"let cnt1="($cnt+2)/3"le ...

  9. WIN64内核编程-的基础知识

    WIN64内核编程基础班(作者:胡文亮)   https://www.dbgpro.com/x64driver 我们先从一份"简历"说起: 姓名:X86或80x86 性别:? 出生 ...

随机推荐

  1. [同步到 MaixPy3 文档] 使用 Python 编程入门开源硬件项目

    本文是给有一点 Python 基础但还想进一步深入的同学,有经验的开发者建议跳过. 前言 在写这篇案例系列的时候 junhuanchen 期望能够引导用户如何成为专业的开发者,不是只会调用代码就好,所 ...

  2. 从Android手机的抢红包插件说起

    前语 最近,Android手机上的手机管家更新了新版本,提供了红包闹钟功能,只要有微信红包或者QQ红包,就会自动提醒.恰逢最近又在做UI自动化的工作,使用到UI Automator框架.几行代码,就可 ...

  3. MySQL入门(5)——运算符

    MySQL入门(5)--运算符 算术运算符 MySQL支持的算数运算符包括加.减.乘.除.求余. 符号 作用 + 加法运算 - 减法运算 * 乘法运算 / 除法运算 % 求余运算 DIV 除法运算,返 ...

  4. HDU_5414 CRB and String 【字符串】

    一.题目 CRB and String 二.分析 对于这题,读懂题意非常重要. 题目的意思是在$s$的基础上,按题目中所描述的步骤,即在$s$中任意选择一个字符$c$,在这个字符后面添加一个不等于$c ...

  5. 【Azure 服务总线】详解Azure Service Bus SDK中接收消息时设置的maxConcurrentCalls,prefetchCount参数

    (Azure Service Bus服务总线的两大类消息处理方式: 队列Queue和主题Topic) 问题描述 使用Service Bus作为企业消息代理,当有大量的数据堆积再Queue或Topic中 ...

  6. 等不及要告诉你的一件事-console.log可以指定样式

    今天看`简书`文章,习惯性的打开了开发者工具,于是发现了意见有意思的事,在console面板,发现了如下的日志: ??? 这 ... 有点意思. 如果上面的图片,不能引起你的兴趣,那么你可以再看看这个 ...

  7. Java程序员都要懂得知识点:反射

    摘要:Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语 ...

  8. 【Azure Developer】Github Action部署资源(ARM模板)到Azure中国区时,遇见登录问题的解决办法

    问题描述 在参考文档"使用 GitHub Actions 部署 ARM 模板"一文中,由于是在中国区Azure上操作,所以生产的部署凭证为中国区凭证.当创建工作流时,在登录到Azu ...

  9. nginx提供网站首页的一个实例

    如果既想匹配'/'进行反向代理,同时又想通过nginx提供网站首页,可以在server中进行如下配置: user python; # 运行Nginx的用户 worker_processes auto; ...

  10. mybatis-plus的Could not set property 'updateDate' of 'class com.example.pojo.User' with value 'Fri Jul 24 10:29:39 CST 2020' Cause: java.lang.IllegalArgumentException: argument type mismatch解决方案

    按照官网在写mybatis-plus的自动填充功能一直报错,发现官网的解说不全,数据库是datetime类型,java程序又是date,类型不匹配 org.mybatis.spring.MyBatis ...