内核里操作文件

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. Tomcat8弱口令+后台getshell

    漏洞原因 用户权限在conf/tomcat-users.xml文件中配置: <?xml version="1.0" encoding="UTF-8"?&g ...

  2. react第三方库

    作者:慕课网链接:https://www.zhihu.com/question/59073695/answer/1071631250来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  3. linux安装mysql8.0

    linux 上安装mysql8.0 mysql版本8.0.16 MySQL Community 操作系统centos7 准备工作: mysql8.0 rpm文件 安装步骤: 1. 下载mysql的re ...

  4. 让你的浏览器变成Siri一样的语音助手

    最近业余时间浏览技术文章的时候,看到了一篇关于语音朗读的文章:Use JavaScript to Make Your Browser Speak(用Javascript让你的浏览器说话),文章中提到可 ...

  5. 从设计模式角度看OkHttp源码

    前言 说到源码,很多朋友都觉得复杂,难理解. 但是,如果是一个结构清晰且完全解耦的优质源码库呢? OkHttp就是这样一个存在,对于这个原生网络框架,想必大家也看过很多很多相关的源码解析了. 它的源码 ...

  6. SQL注入与参数化查询

    SQL注入的本质 SQL注入的实质就是通过SQL拼接字符串追加命令,导致SQL的语义发生了变化.为什么发生了改变呢? 因为没有重用以前的执行计划,而是对注入后的SQL语句重新编译,然后重新执行了语法解 ...

  7. RPC 框架设计

    RPC 框架设计 初识 RPC 服务化有什么好处? 防止代码拷贝 防止底层复杂性的扩散 防止公共库的耦合 保证 SQL 的质量,能够解除数据库的耦合 什么是 RPC RPC:Remote Proced ...

  8. java例题 判断一个数能被几个9整除

    有点懵,被几个9整除,我理解的是n=n/9能整除几次,代码如下: 1 /*45 [程序 45 被 9 整除] 2 题目:判断一个数能被几个 9 整除 3 */ 4 5 /*分析 6 * 1.用whil ...

  9. shell的配置文件

    1. bash shell 的配置文件 bash shell的配置文件很多,可以分成下面类别 1.1 按生效范围划分两类 全局配置:针对所有用户皆有效 /etc/profile /etc/profil ...

  10. css盒模型以及如何计算盒子的宽度

    css盒模型以及如何计算盒子的宽度 盒模型 每个存在于可访问性树中的元素都会被浏览器绘制成一个盒子[1]. 每个盒子都可以看成由4部分组成,它们分别是 - 元素外边距(margin).元素边框(bor ...