文件操作 相对于Windows用户模式下的文件操作,稍微复杂了一些,但也并不难。
创建文件 ZwCreateFile
这个函数就在驱动层进行文件创建的一个函数,函数的原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 NTSTATUS ZwCreateFile ( _Out_ PHANDLE FileHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_opt_ PLARGE_INTEGER AllocationSize, _In_ ULONG FileAttributes, _In_ ULONG ShareAccess, _In_ ULONG CreateDisposition, _In_ ULONG CreateOptions, _In_opt_ PVOID EaBuffer, _In_ ULONG EaLength ) ;
这几个参数里面比较重要的就是第三个参数,这个参数其实是一个结构体,这个结构体里面是通过InitializeObjectAttributes
这个函数生成的,这个函数比较关键的就是第二个参数,我们需要填写的是一个Unicode字符串,是我们生成文件的路径,这个路径需要注意的是,我们需要用符号连接名的方式进行存储,例如:"\\?\\C:\\1.log"
,这里两个\是取消转义。
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 void CreateFileTest (UNICODE_STRING * FileName) { OBJECT_ATTRIBUTES objectAttributes; IO_STATUS_BLOCK iostatus; HANDLE hfile; UNICODE_STRING logFileUniocdeString; logFileUniocdeString.Buffer = (PWSTR)ExAllocatePool(PagedPool, 1024 ); logFileUniocdeString.MaximumLength = 1024 ; WCHAR* wideString = L"\\??\\C:\\" ; logFileUniocdeString.Length = wcslen(wideString) * 2 ; RtlCopyMemory(logFileUniocdeString.Buffer, wideString, logFileUniocdeString.Length); RtlAppendUnicodeStringToString(&logFileUniocdeString, FileName); InitializeObjectAttributes(&objectAttributes, &logFileUniocdeString, OBJ_CASE_INSENSITIVE, NULL , NULL ); NTSTATUS ntStatus = ZwCreateFile(&hfile, GENERIC_WRITE, &objectAttributes, &iostatus, NULL , FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL , 0 ); if (NT_SUCCESS(ntStatus)) { KdPrint(("Create File Successfully\n" )); } else { KdPrint(("Create File unsuccessfully\n" )); } ZwClose(hfile); ExFreePool(logFileUniocdeString.Buffer); }
还有一个iostatus,这个里面有个information,这个在后面是比较有用的。
修改查询文件属性 主要使用的是zwSetInformationFile
和zwQueryInformationFile
这两个函数的参数基本相同,唯一需要注意的是我第三个参数,查询是out的,设置是in的,这个查询和设置的参数是一个PVOID,这个是取决于我们使用的结构体,有文件信息,基本信息,文件名信息,文件指针信息,这些就不记录了,网上很多。
读写文件 这个相对和用户模式下的没啥太大的区别,基本上操作类似可以说。 函数的原型:
1 2 3 4 5 6 7 8 9 10 11 NTSTATUS ZwWriteFile ( _In_ HANDLE FileHandle, _In_opt_ HANDLE Event, _In_opt_ PIO_APC_ROUTINE ApcRoutine, _In_opt_ PVOID ApcContext, _Out_ PIO_STATUS_BLOCK IoStatusBlock, _In_ PVOID Buffer, _In_ ULONG Length, _In_opt_ PLARGE_INTEGER ByteOffset, _In_opt_ PULONG Key ) ;
第一个参数是文件的句柄就是我们之前打开的,二三四参数一般为NULL,第五个参数就是iostatus,这个里面的information代表我们实际操作的自己数目,第六个参数是我们的缓冲区,第七个参数是我们写入数据的大小,第八个参数是我们开始的写入的偏移量,最后一个参数一般为NULL。 可以附加也可以覆盖。
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 void CreateFileTest (UNICODE_STRING * FileName) { OBJECT_ATTRIBUTES objectAttributes; IO_STATUS_BLOCK iostatus; HANDLE hfile; UNICODE_STRING logFileUniocdeString; logFileUniocdeString.Buffer = (PWSTR)ExAllocatePool(PagedPool, 1024 ); logFileUniocdeString.MaximumLength = 1024 ; WCHAR* wideString = L"\\??\\C:\\" ; logFileUniocdeString.Length = wcslen(wideString) * 2 ; RtlCopyMemory(logFileUniocdeString.Buffer, wideString, logFileUniocdeString.Length); RtlAppendUnicodeStringToString(&logFileUniocdeString, FileName); InitializeObjectAttributes(&objectAttributes, &logFileUniocdeString, OBJ_CASE_INSENSITIVE, NULL , NULL ); NTSTATUS ntStatus = ZwCreateFile(&hfile, GENERIC_WRITE, &objectAttributes, &iostatus, NULL , FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL , 0 ); if (NT_SUCCESS(ntStatus)) { KdPrint(("Create File Successfully\n" )); } else { KdPrint(("Create File unsuccessfully\n" )); } #define BUFFER_SIZE 1024 PUCHAR pBuffer = (PUCHAR)ExAllocatePool(PagedPool, BUFFER_SIZE); RtlFillMemory(pBuffer, BUFFER_SIZE, 0xAA ); KdPrint(("将写入文件%d个字节\n" , BUFFER_SIZE)); ZwWriteFile(hfile, NULL , NULL , NULL , &iostatus, pBuffer, BUFFER_SIZE, NULL , NULL ); KdPrint(("已经写入文件%d个字节\n" , iostatus.Information)); RtlFillMemory(pBuffer, BUFFER_SIZE, 0xBB ); KdPrint(("追加数据开始\n" )); LARGE_INTEGER number; number.QuadPart = 1024 i64; ZwWriteFile(hfile, NULL , NULL , NULL , &iostatus, pBuffer, BUFFER_SIZE, &number, NULL ); KdPrint(("追加数据:%d\n" , iostatus.Information)); ZwClose(hfile); ExFreePool(pBuffer); ExFreePool(logFileUniocdeString.Buffer); }
注意我们在CreateFile中使用的是FILE_OPEN_IF,可以覆盖也可以生成。 其他的话呢就是一个文件追加数据。 读取文件的话呢,需要注意的是我们需要知道文件的大小,这个文件大小是通过查询文件属性获得的,然后也是通过缓冲区进行查询,这里不记录了,也比较简单,也就是把写的一些参数改为了读。
注册表操作 其实相对的注册表操作十分类似于文件的操作,就连参数中的传递过去的结构体也都是一样的。 打开注册表:
1 2 3 4 5 6 7 8 9 10 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 );
ObjectAttributes和之前的文件操作是一样的,Disposition这个代表是否是已经存在了的注册表项。
打开注册表的话呢,注意的就是打开的权限,一般要ALL。
1 2 3 4 5 6 NTSTATUS ZwOpenKey( OUT PHANDLE KeyHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes );
设置注册表项:
1 2 3 4 5 6 7 8 9 NTSTATUS ZwSetValueKey( IN HANDLE KeyHandle, IN PUNICODE_STRING ValueName, IN ULONG TitleIndex OPTIONAL, IN ULONG Type, IN PVOID Data, IN ULONG DataSize );
需要注意的是,数据和数据大小是要根据键值类型进行决定的。
查询注册表项:
1 2 3 4 5 6 7 8 9 NTSTATUS ZwQueryValueKey( IN HANDLE KeyHandle, IN PUNICODE_STRING ValueName, IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, OUT PVOID KeyValueInformation, IN ULONG Length, OUT PULONG ResultLength );
这个函数要注意是我们一般需要用到两次,首先第一次我们不知道数据的长度,所以我们的Length参数只能传输为0,那个ResultLength就是我们的真是要查询的长度,并且我们还需要指定查询的信息类型,这个很像是查询文件的属性,这个值可以是KeyValueBasicInformation ,KeyValueFullInformation,KeyValuePartialInformation,我们就要为KeyValueInformation
这个参数申请一块内存,是我们查询类型大小的内存块,然后第二次调用的时候我们就需要将查询数据的大小设置成刚才查询出来的大小了。
枚举子健和枚举子项很是相似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 );
第一个函数得到的KeyInformation里有子项的个数,然后用第二个函数再获得子项的名称,这两个函数和查询注册表键值是类似的,都是要执行两次,枚举子键只是把ZwEnumerateKey
换成了ZwEnumerateValueKey
,这两个函数调用是类似的。
删除的话呢就只有一个参数,就是打开的句柄,但是需要注意的是,我们删除的话呢项目是不允许有子健的。
在DDK中为了简便操作,提供了Rtl开头的函数(感觉应该也是宏),操作起来比较简单,而且还有很多的功能。