逆向基础-Windows驱动开发(一)

2023-05-16

Windows内核开发

第一个驱动程序

环境配置:
安装WDK:

WDK版本与SDK保持一致

然后记得把Spectre Mitigation给Disabled掉,就不用去下载漏洞补丁了。

然后在内核层,警告是会被视为错误的,我们也直接屏蔽掉。

.sys都是作为Windows系统服务存在的,我们需要注册它成为一个服务,这里借助一个工具:A1SysTest v0.3.0.1。

使用DebugView来查看DbgPrint输出的信息。首先要做一些配置:
1.打开 DebugView
2.Capture 菜单
3.打勾 Capture Kernel 和 Enable Verbose Kernel Output 这两个菜单项
4.完成。。。

#include <ntddk.h>

/*
	驱动程序和三环程序的区别:
		-1.三环程序运行在低2G【每个进程独立的】,驱动程序运行在高2G【进程间共享的】
		-2.三环程序每次运行都是一个独立的程序,驱动程序都运行在一个大进程里面【三环加载dll之类的 丢进去了】
*/


/*
	@function 驱动程序的停止函数DriverUnload
	@param pDriver 当前的驱动对象
*/
void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload!\n");
}

/*
	@function 驱动程序的主函数DriverEntry
	@param pDriver 当前的驱动对象
	@param pRegPath 当前的驱动对应的注册表路径
	@return NTSTATUS 其实是一个LONG类型
*/
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver,PUNICODE_STRING pRegPath) {
	DbgBreakPoint(); // 这个其实就是一个int 3指令,在反调试的时候有提到 可以使用它对驱动程序进行调试
	pDriver->DriverUnload = DriverUnload; // 指定驱动停止函数
	DbgPrint("Hello world!\n");
	return STATUS_SUCCESS;
}

PDRIVER_OBJECT:
  -PVOID DriverStart; 模块基址,相当于PE文件的GetModuleHandle
  -ULONG DriverSize; 内存对齐以后的大小
  -UNICODE_STRING DriverName; 驱动名字

PUNICODE_STRING

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

/*
	typedef struct _UNICODE_STRING {
		USHORT Length;      字符串所占空间的大小,不包含字符串结尾 【也就是实际大小】
		USHORT MaximumLength; Buffer的大小【也就是创建的缓冲区大小】
		#ifdef MIDL_PASS
			[size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;  缓冲区大小
		#else // MIDL_PASS
			_Field_size_bytes_part_opt_(MaximumLength, Length) PWCH   Buffer;
		#endif // MIDL_PASS
	} UNICODE_STRING;
*/
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	WCHAR wstr1[100] = L"StarHook"; // 准备一个Unicode类型的数组
	// 创建PUNICODE_STRING变量
	UNICODE_STRING ustr1 = {0};
	ustr1.Buffer = wstr1; // 指向一个缓冲区
	ustr1.Length = wcslen(wstr1) * sizeof(WCHAR); // 多少个字符 * Unicode中每一个字符的大小
	ustr1.MaximumLength = 100; // 缓存区的大小

	DbgPrint("%ws",ustr1.Buffer); // 输出缓存区内容

	// 定义一个UNICODE_STRING类型的变量,第一个参数 变量名,第二个参数 Buffer缓存区里面的内容
	DECLARE_CONST_UNICODE_STRING(ustr2,L"DECLARE_CONST_UNICODE_STRING");
	DbgPrint("%ws", ustr2.Buffer); // 输出缓存区内容

	// 定义一个UNICODE_STRING类型的变量, 第一个参数Buffer缓存区里面的内容
	UNICODE_STRING ustr3 = RTL_CONSTANT_STRING(L"RTL_CONSTANT_STRING");
	DbgPrint("%ws", ustr3.Buffer); // 输出缓存区内容

	UNICODE_STRING ustr4;
	// 定义一个UNICODE_STRING类型的变量,第一个参数是UNICODE_STRING的类型的地址,第二个是Buffer的内容
	RtlInitUnicodeString(&ustr4,L"RtlInitUnicodeString");
	DbgPrint("%ws", ustr4.Buffer); // 输出缓存区内容

	// UNICODE 和 ANSI的转换
	ANSI_STRING astr;
	RtlInitAnsiString(&astr, "AnsiString");
	UNICODE_STRING ustr;
	// 第一个参数UNICODE_STRING地址即接收,第二个参数ANSI_STRING 需要转换的
	// 第三个参数是否要分配一个新的缓冲区来存放转换之后的字符串
	NTSTATUS ret = RtlAnsiStringToUnicodeString(&ustr,&astr,TRUE);
	if (ret == STATUS_SUCCESS) {
		// 如果转换成功
		DbgPrint("%ws", ustr.Buffer); // 输出缓存区内容
		RtlFreeUnicodeString(&ustr); // 释放第三个参数申请出来的内存空间
	}

	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

内存分配

应用层的内存分配:
malloc,new都是在堆上分配内存

堆和虚拟内存的关系?堆内存是基于虚拟内存的更小粒度的分割,这个分割由堆管理器来管理。
根据开发者的需要堆管理器会申请一页或多页虚拟内存,然后对这个虚拟内存进行更小粒度的内存分割和管理,以满足开发者对内存的需求。

内核层内存分配:
内核层中不叫堆,称为池,我们可以从池中申请内存,形式与应用层相似。
ExAllocatePool();

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	/*
		// 重点就是这两个
		typedef enum _POOL_TYPE {
            NonPagedPool, 非分页内存  【效率高,存代码的话】
            PagedPool, 分页内存		  【可以用来存数据,特别要是不经常使用】
        } POOL_TYPE;
		其实就是我们之前提到的页交换。
		当内存满了以后,会把一部分不用的应用程序的数据放到硬盘里面。
		而这里的非分页内存,就是不会被放到硬盘里面。
		分页内存,就是会被放到硬盘里面。
	*/
	//PWCHAR pwStr = ExAllocatePool(NonPagedPool,0x100); // 第二个参数是大小
	 释放内存
	//ExFreePool(pwStr);
	
	// 具有标签的内存
	PWCHAR pwStr2 = ExAllocatePoolWithTag(NonPagedPool, 0x100,'abcr'); // 第三个参数是标签
	if (pwStr2) {
		RtlZeroMemory(pwStr2, 0x100); // 初始化内存
		// 给内存赋值
		RtlCopyMemory(pwStr2, L"StarHook", wcslen(L"StarHook") * sizeof(WCHAR));
		// 创建一个UNICODE_STRING 进行初始化 Buffer指向申请出来的内存
		UNICODE_STRING ustr = {0};
		RtlInitUnicodeString(&ustr,pwStr2); // 指向我们申请的那块内存Buffer


		// 可以直接打印UNICODE_STRING 使用wZ
		DbgPrint("%wZ", &ustr);

		// 释放内存
		ExFreePoolWithTag(pwStr2, 'abcr');
	}
	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	// 旁氏列表内存分配
	// 适用于高频率从系统内存池中申请固定大小内存
	// 1.初始化一个旁氏列表对象
	// 要求一定是要非页交换的,即NonPagedPool
	PNPAGED_LOOKASIDE_LIST pgList = ExAllocatePool(NonPagedPool,sizeof(NPAGED_LOOKASIDE_LIST));
	if (pgList) {
		// 1.旁氏列表结构体 2.分配的函数 NULL就好 3.释放的函数 NULL就好
		// 4.标志 0就好       5.大小      6.TAG      7.保留 必须为0
		ExInitializeNPagedLookasideList(pgList, NULL, NULL, 0, 0x100, '123', 0);
		
		// 2.通过旁氏列表对象申请内存 后面每次申请都是通过旁氏列表对象 所以申请的大小都一样
		PWCHAR wcstr = ExAllocateFromNPagedLookasideList(pgList);
		if (wcstr) {
			RtlZeroMemory(wcstr, 0x100);
			RtlCopyMemory(wcstr,L"StarHook",wcslen(L"StarHook") * sizeof(WCHAR));
			UNICODE_STRING ustr = {0};
			RtlInitUnicodeString(&ustr, wcstr);
			DbgPrint("%wZ", ustr);

			// 旁氏列表对象,要释放的条目  
			ExFreeToNPagedLookasideList(pgList,wcstr);
		}
		// 释放旁氏列表对象
		ExDeleteNPagedLookasideList(pgList);
	}


	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	// 1.创建一个旁氏列表对象 【可以页交换的】
	PPAGED_LOOKASIDE_LIST pgList = ExAllocatePool(PagedPool, sizeof(PAGED_LOOKASIDE_LIST));
	if (pgList) {
		// 2.初始化旁氏列表对象
		ExInitializePagedLookasideList(pgList, NULL, NULL, 0, 0x100, 'star', 0);
		// 3.就可以通过旁氏列表对象 创建固定大小的内存能存了
		PWCHAR pwstr = ExAllocateFromPagedLookasideList(pgList);
		if (pwstr) {
			RtlZeroMemory(pwstr, 0x100);
			RtlCopyMemory(pwstr, L"StarHook", wcslen(L"StarHook") * sizeof(WCHAR));

			UNICODE_STRING ustr = {0};
			RtlInitUnicodeString(&ustr, pwstr);
			DbgPrint("%wZ", ustr);
			// 4.释放申请出来的内存
			ExFreeToPagedLookasideList(pgList, pwstr);
		}
		// 5.销毁旁氏列表对象
		ExDeletePagedLookasideList(pgList);
	}

	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

链表LIST_ENTRY

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

typedef struct _TEST {
	ULONG m_dataA;
	ULONG m_dataB;
	LIST_ENTRY m_listEntry;
	ULONG m_dataC;
}TEST,*PTEST;

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	/*
		typedef struct _LIST_ENTRY {
		   struct _LIST_ENTRY *Flink; 下一个
		   struct _LIST_ENTRY *Blink; 上一个
		} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

		这就像一辆货车,和数据脱离开来了,我不管你给我放什么数据,我就是一辆车。
		你把数据放上来就好了,你要访问数据通过偏移的形式去访问就行。
	*/
	PLIST_ENTRY pHeader = ExAllocatePool(NonPagedPool, sizeof(LIST_ENTRY)); // 创建一个非页交换节点
	TEST test1 = { 0 };
	TEST test2 = { 0 };
	TEST test3 = { 0 };
	TEST test4 = { 0 };
	test1.m_dataA = 0x123;
	test2.m_dataB = 0x456;
	test3.m_dataA = 0x789;
	test4.m_dataA = 0x321;
	if (pHeader) {
		RtlZeroMemory(pHeader, sizeof(LIST_ENTRY)); // 初始化内存
		// 初始化头结点 实际上就是让 下一个指针和上一个指针都指向自身
		// pHeader->Blink = pHeader->Flink = pHeader;
		InitializeListHead(pHeader);
		// 插入到头部
		InsertHeadList(pHeader,&test1.m_listEntry);
		InsertHeadList(pHeader,&test2.m_listEntry);
		InsertHeadList(pHeader,&test3.m_listEntry);
		InsertHeadList(pHeader,&test4.m_listEntry);
		// 因为每次都是插入到头部 所以现在顺序是 头结点->test4->test3->test2->test1
		PLIST_ENTRY pListEntry = pHeader->Flink;
		while (pListEntry != pHeader) { // 双向链表,最后一个节点的next = 头结点 当还没有遍历到头结点
			// 微软帮我们封装了偏移,直接通过这个可以定位到 结构体的首地址
			// 当前链表指针 结构体类型 listEntnry在结构体中的名字
			PTEST pTest = CONTAINING_RECORD(pListEntry, TEST, m_listEntry);
			DbgPrint("m_dataA = %x,m_dataB = %x,m_dataC = %x\n", 
				pTest->m_dataA, pTest->m_dataB, pTest->m_dataC);
			// 移动到下一个节点
			pListEntry = pListEntry->Flink;
		}
		//  RemoveHeadList 移除头结点
		//  RemoveTailist 移除尾节点
		//  RemoveEntryList 移除指定节点
		// 释放内存
		ExFreePool(pHeader);
	}
	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

二叉查找树

RTL_GENERIC_TABLE
是一个二叉树结构
伸展树(Splay Tree)是一种二叉查找树,这种结构常用在文件系统。
每次查找之后对树进行重构,把被查找的条目搬移到离根节点近的位置。伸展树是一种自调节形式的二叉查找树。
他会沿着某个节点到根节点之间的路径,通过一系列的旋转把这个节点移动到根节点附近。

【自己感觉其实有点像哈夫曼树,每次查找就是一次权重的更新吧,然后把权重大的放在离根节点近的位置】

第一个节点:申请内存->插入
后面的节点:比较看这个元素是否存在->不存在申请内存->插入

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

typedef struct _Test {
	ULONG a;
	ULONG b;
}TEST,*PTEST;

// 比较函数
RTL_GENERIC_COMPARE_RESULTS NTAPI RtlCmp(
	struct _RTL_GENERIC_TABLE* Table,
	PVOID FirstStruct,
	PVOID SecondStruct
) {
	DbgPrint("RtlCmp");

	PTEST first = FirstStruct;
	PTEST second = SecondStruct;
	if (first->a == second->a) {
		// 如果a的值相同 我们就认为相同
		return GenericEqual;
	}else if (first->a > second->a) {
		return GenericGreaterThan;
	}
	return GenericLessThan;
}
// 申请内存函数
PVOID NTAPI RtlAlloc(
	struct _RTL_GENERIC_TABLE* Table,
	CLONG ByteSize
) {
	DbgPrint("RtlAlloc");
	return ExAllocatePool(NonPagedPool, ByteSize);
}

// 释放内存函数
VOID NTAPI RtlFree(
	struct _RTL_GENERIC_TABLE* Table,
	__drv_freesMem(Mem) _Post_invalid_ PVOID Buffer
) {
	DbgPrint("RtlFree");
	ExFreePool(Buffer);
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	TEST test1 = {1,2};
	TEST test2 = {2,2};
	TEST test3 = {3,2};
	TEST test4 ={4,2};
	BOOLEAN isNew = FALSE;
	PRTL_GENERIC_TABLE pGenericRootTree = ExAllocatePool(NonPagedPool, sizeof(RTL_GENERIC_TABLE));
	if (pGenericRootTree) {
		RtlZeroMemory(pGenericRootTree, sizeof(RTL_GENERIC_TABLE)); // 初始化内存
		// 初始化二叉查找树
		// 1.二叉查找树结构体指针 2.比较函数【一样就不插入】 3.申请内存函数  4.释放内存函数 5.自定义上下文,不用就NULL
		RtlInitializeGenericTable(pGenericRootTree, RtlCmp, RtlAlloc, RtlFree,NULL);
		// 2.插入元素 根节点 要插入元素 结构体大小 返回值这个元素是否已经存在【不存在返回TRUE】
		RtlInsertElementGenericTable(pGenericRootTree,&test1,sizeof(TEST),&isNew);
		RtlInsertElementGenericTable(pGenericRootTree,&test2,sizeof(TEST),&isNew);
		RtlInsertElementGenericTable(pGenericRootTree,&test3,sizeof(TEST),&isNew);
		RtlInsertElementGenericTable(pGenericRootTree,&test4,sizeof(TEST),&isNew);

		// 获取节点数量
		ULONG treeElements = RtlNumberGenericTableElements(pGenericRootTree);
		DbgPrint("当前树的节点数量:%d", treeElements);

		// 遍历树 
		// 获取第几个序号的节点
		for (ULONG index = 0; index < treeElements; index++) {
			PTEST pElement = RtlGetElementGenericTable(pGenericRootTree,index);
			DbgPrint("当前节点的a值:%d", pElement->a);
		}

		// 遍历2
		ULONG RestartKey = 0; // 从头开始 设置为0
		PTEST pElement = NULL;
		for (pElement = RtlEnumerateGenericTableWithoutSplaying(pGenericRootTree, &RestartKey);
			pElement != NULL;
			pElement = RtlEnumerateGenericTableWithoutSplaying(pGenericRootTree, &RestartKey)) {
			DbgPrint("当前节点的b值:%d", pElement->b);
		}

		// 查询节点
		pElement = RtlLookupElementGenericTable(pGenericRootTree,&test2);
		if (pElement != NULL) { // 其实都是根据我们都比较函数来查找的
			DbgPrint("查询到节点,a值:%d", pElement->a);
		}else {
			DbgPrint("查询不到节点");
		}

		// RtlDeleteElementGenericTable 删除节点
		// RtlIsGenericTableEmpty 当前二叉查找树是否为空
		
		// 释放内存 
		ExFreePool(pGenericRootTree);
	}

	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

AVL树:平衡二叉查找树
普通的二叉查找树没有平衡的限制,很可能在某些特殊的情况下退化成一条类似链表的树。这些搜索效率就极为低下。

文件操作

句柄:
像我们之前在三环CreateFile之类的时候会返回一个句柄

内核对象存放在内核空间。 APU HANDLE
就像我们保护模式中

线性地址->物理地址
句柄->内核对象

也就是说可以理解为句柄映射到一个内核对象。
A:HANDLE hEvent = CreateEvent() hEvent = 0x1234
B:
A进程的hEvent的句柄值0x1234在B进程是用不了的。
每一个进程都有自己的一个句柄表。
所以每个进程在使用句柄的时候都会找到自己当前这个进程对应的句柄表,然后再找到对应的内核对象。

同理,我们0环也有自己定句柄表,不过我们前面也是了,三环里面每一个进程都是独立的。
而0环,是在一个大进程里面的。它只有一个句柄表,所有的进程共享。

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	// 打开/创建一个文件
	/*
		NTSYSAPI NTSTATUS ZwCreateFile(
		  [out]          PHANDLE            FileHandle,   输出参数 文件句柄
		  [in]           ACCESS_MASK        DesiredAccess, 文件操作权限
		  [in]           POBJECT_ATTRIBUTES ObjectAttributes, 设置句柄的相关属性
		  [out]          PIO_STATUS_BLOCK   IoStatusBlock, 调用状态 输出参数
		  [in, optional] PLARGE_INTEGER     AllocationSize, 文件初始分配的大小 填0就好
		  [in]           ULONG              FileAttributes, 文件属性
		  [in]           ULONG              ShareAccess, 文件共享权限
		  [in]           ULONG              CreateDisposition, 创建环境【新建开始打开还是】
		  [in]           ULONG              CreateOptions, 创建选项【打开的是文件还是目录】
		  [in, optional] PVOID              EaBuffer,  固定NULL
		  [in]           ULONG              EaLength   固定0
		);
		typedef struct _OBJECT_ATTRIBUTES {
			ULONG Length;   相当于dwSize指向结构体的大小
			HANDLE RootDirectory;      文件路径句柄
			PUNICODE_STRING ObjectName;  相对于RootDirectory的相对路径.Root填NULL,这里填绝对路径就好
			ULONG Attributes; 句柄属性
			PVOID SecurityDescriptor; 安全描述符NULL      
			PVOID SecurityQualityOfService;  NULL
		} OBJECT_ATTRIBUTES;
	*/
	HANDLE hFileHandle = 0;
	OBJECT_ATTRIBUTES oa = {0};
	oa.Length = sizeof(OBJECT_ATTRIBUTES);
	oa.RootDirectory = NULL;
	UNICODE_STRING objPath = RTL_CONSTANT_STRING(L"\\??\\c:\\1.txt");
	oa.ObjectName = &objPath;
	oa.Attributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE; // 路径不区分大小写 | 内核权限句柄
	oa.SecurityDescriptor = NULL;
	oa.SecurityQualityOfService = NULL;

	IO_STATUS_BLOCK isb = {0};

	NTSTATUS ret = ZwCreateFile(&hFileHandle, GENERIC_ALL, &oa, &isb, 0,
		FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,FILE_OPEN_IF, 
		FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, // 打开文件 | 同步
		NULL,0);
	if (ret == STATUS_SUCCESS) {
		// 如果打开/创建文件成功  就是读写文件了 接着
		DbgPrint("打开/创建文件成功");
		// 文件句柄 异步操作 异步操作 异步操作 输出参数 缓冲区 缓冲区大小 文件偏移 KEY给NULL就好
		IO_STATUS_BLOCK isbRead = { 0 };
		LARGE_INTEGER offset = {0};
		PVOID fileBuff = ExAllocatePool(NonPagedPool, 0x1000);
		if (fileBuff) {
			RtlZeroMemory(fileBuff, 0x1000);
			ret = ZwReadFile(hFileHandle, NULL, NULL, NULL, &isbRead, fileBuff, 0x1000, &offset, NULL);
			if (ret == STATUS_SUCCESS) {
				DbgPrint("读取文件内容成功");
				DbgBreakPoint();
			}else {
				DbgPrint("读取文件内容失败");
			}
		}
		// 关闭文件
		ZwClose(hFileHandle);
		ExFreePool(fileBuff); // 释放内存
	}else {
		DbgPrint("打开/创建文件失败");
	}

	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

文件操作2

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	// 打开/创建一个文件
	/*
		NTSYSAPI NTSTATUS ZwCreateFile(
		  [out]          PHANDLE            FileHandle,   输出参数 文件句柄
		  [in]           ACCESS_MASK        DesiredAccess, 文件操作权限
		  [in]           POBJECT_ATTRIBUTES ObjectAttributes, 设置句柄的相关属性
		  [out]          PIO_STATUS_BLOCK   IoStatusBlock, 调用状态 输出参数
		  [in, optional] PLARGE_INTEGER     AllocationSize, 文件初始分配的大小 填0就好
		  [in]           ULONG              FileAttributes, 文件属性
		  [in]           ULONG              ShareAccess, 文件共享权限
		  [in]           ULONG              CreateDisposition, 创建环境【新建开始打开还是】
		  [in]           ULONG              CreateOptions, 创建选项【打开的是文件还是目录】
		  [in, optional] PVOID              EaBuffer,  固定NULL
		  [in]           ULONG              EaLength   固定0
		);
		typedef struct _OBJECT_ATTRIBUTES {
			ULONG Length;   相当于dwSize指向结构体的大小
			HANDLE RootDirectory;      文件路径句柄
			PUNICODE_STRING ObjectName;  相对于RootDirectory的相对路径.Root填NULL,这里填绝对路径就好
			ULONG Attributes; 句柄属性
			PVOID SecurityDescriptor; 安全描述符NULL      
			PVOID SecurityQualityOfService;  NULL
		} OBJECT_ATTRIBUTES;
	*/
	HANDLE hFile = 0;
	OBJECT_ATTRIBUTES oa = {0};
	oa.Length = sizeof(OBJECT_ATTRIBUTES);
	oa.RootDirectory = NULL;
	UNICODE_STRING objPath = RTL_CONSTANT_STRING(L"\\??\\c:\\1.txt");
	oa.ObjectName = &objPath;
	oa.Attributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE; // 不区分大小写 和内核权限句柄
	oa.SecurityDescriptor = NULL;
	oa.SecurityQualityOfService = NULL;

	IO_STATUS_BLOCK pibFile = {0};
	NTSTATUS ret = ZwCreateFile(&hFile,GENERIC_ALL,&oa,&pibFile,NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,
		FILE_OVERWRITE_IF, // 不存在创建,存在覆盖
		FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, // 打开的是文件而是同步
		NULL,0);
	if (NT_SUCCESS(ret)) {
		// 操作文件 写入
		IO_STATUS_BLOCK pibWrite = {0};
		LARGE_INTEGER li = {0};
		ret = ZwWriteFile(hFile, NULL, NULL, NULL, &pibWrite,
			L"Hello,StarHook",wcslen(L"Hello,StarHook") * sizeof(WCHAR), &li, NULL);
		if (NT_SUCCESS(ret)) {
			DbgPrint("写入成功");
		}else {
			DbgPrint("写入失败");
		}
		// 关闭句柄
		ZwClose(hFile);
	}else {
		DbgPrint("打开文件失败");
	}
	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

获取文件大小:
IO_STATUS_BLOCK pibQuery = { 0 };
FILE_STANDARD_INFORMATION fsInfo = { 0 };
		// 文件句柄 输出参数 缓存区 缓冲区大小 要查询什么信息【文件标准信息】
ret = ZwQueryInformationFile(hFile, &pibQuery,&fsInfo,sizeof(FILE_STANDARD_INFORMATION),FileStandardInformation);
if (NT_SUCCESS(ret)) {
		DbgPrint("文件大小:%lld", fsInfo.EndOfFile.QuadPart);
}  

文件操作3

那么我们读写文件可以一次性读取完毕,然后一次性写入

我们也可以通过IO_STATUS_BLOCK获取实际读取到大小,如果读取成功,每次我们就写入实际读取到的大小,循环操作

拷贝文件。

#include <ntddk.h>

/*
	@function 创建/打开文件
	@param [int,out] phFile 文件句柄
	@param [in] objPath 文件路径
	@param [in] isbFile 状态
	@return 是否创建成功
*/
NTSTATUS MyZwCreateFile(PHANDLE phFile,PUNICODE_STRING objPath, PIO_STATUS_BLOCK isbFile) {
	OBJECT_ATTRIBUTES oa = {0};
	oa.Length = sizeof(OBJECT_ATTRIBUTES);
	oa.Attributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE; // 忽略大小写 和 内核句柄
	oa.RootDirectory = NULL;
	oa.ObjectName = objPath;
	oa.SecurityDescriptor = NULL;
	oa.SecurityQualityOfService = NULL;
	return ZwCreateFile(phFile, GENERIC_ALL, &oa, isbFile, NULL,FILE_ATTRIBUTE_NORMAL,
		NULL, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);
}

/*
	@function 文件拷贝
	@return 是否拷贝成功
*/
NTSTATUS MyZwCopyFile(PUNICODE_STRING objPath1, PUNICODE_STRING objPath2) {
	NTSTATUS status;
	HANDLE hRead, hWrite;
	IO_STATUS_BLOCK isbFile = {0};
	status = MyZwCreateFile(&hRead,  objPath1, &isbFile);
	if (!NT_SUCCESS(status)) {
		return status;
	}
	status = MyZwCreateFile(&hWrite, objPath2, &isbFile);
	if (!NT_SUCCESS(status)) {
		return status;
	}
	ULONG buffLen = 0x1000;
	PVOID buffer = ExAllocatePool(NonPagedPool,buffLen);
	if (buffer == NULL) {
		return -1;
	}
	RtlZeroMemory(buffer, buffLen);
	LARGE_INTEGER li = {0};
	// 读写文件
	while (TRUE) {
		status = ZwReadFile(hRead, NULL, NULL, NULL, &isbFile, buffer,buffLen, &li,NULL);
		if (!NT_SUCCESS(status)) { // 文件读取到结尾了也会出错
			if (status == STATUS_END_OF_FILE) {
				// 正常读取完毕文件
				DbgPrint("文件拷贝完毕");
				status = STATUS_SUCCESS;
			}
			break;
		}
		// 拷贝  isbFile.Information 真实读取了多少
		status = ZwWriteFile(hWrite, NULL, NULL, NULL, &isbFile, buffer, isbFile.Information, &li, NULL);
		if (!NT_SUCCESS(status)) {
			break;
		}
		// 移动文件指针偏移位置
		li.QuadPart += isbFile.Information;
	}
	if(buffer) ExFreePool(buffer);
	if(hRead) ZwClose(hRead);
	if(hWrite) ZwClose(hWrite);
	return status;
}

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	// 拷贝
	UNICODE_STRING readPath = RTL_CONSTANT_STRING(L"\\??\\c:\\1.txt");
	UNICODE_STRING writePath = RTL_CONSTANT_STRING(L"\\??\\c:\\1_new.txt");
	MyZwCopyFile(&readPath, &writePath);

	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

注册表操作

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	
	// 创建或打开注册表
	HANDLE hReg = 0;
	OBJECT_ATTRIBUTES oa = {0};
	oa.Length = sizeof(OBJECT_ATTRIBUTES);
	oa.Attributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
	oa.SecurityDescriptor = NULL;
	oa.SecurityQualityOfService = NULL;
	oa.RootDirectory = NULL;
	// HKEY_LOCAL_MACHINE\SOFTWARE\Star
	UNICODE_STRING objPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SOFTWARE\\Star");
	oa.ObjectName = &objPath;
	// 1.句柄 2.操作权限 3.OBJECT_ATTRIBUTES指针类型 4.0 5.0 6.创建标志 7.新建还是打开KEY【输出参数】
	/*
	6.创建标志
		REG_OPTION_VOLATILE	重新启动系统时不会保留密钥。
		REG_OPTION_NON_VOLATILE	重新启动系统时会保留密钥。
	7.新建还是打开KEY 
		REG_CREATED_NEW_KEY	创建了一个新密钥。
		REG_OPENED_EXISTING_KEY 已打开现有密钥。
	*/
	ULONG Disposition = 0;
	NTSTATUS ret = ZwCreateKey(&hReg,KEY_ALL_ACCESS,&oa,0,0,REG_OPTION_NON_VOLATILE,&Disposition);
	if (NT_SUCCESS(ret)) {
		// 那么就成功打开了注册表  HKEY_LOCAL_MACHINE\SOFTWARE\Star 这个目录
		if (Disposition == REG_CREATED_NEW_KEY) {
			DbgPrint("创建了新的Key=>\\SOFTWARE\\Star");
		}else if(Disposition == REG_OPENED_EXISTING_KEY){
			DbgPrint("打开已经存在Key=>\\SOFTWARE\\Star");
		}
		// 关闭句柄
		ZwClose(hReg);
	}else {
		DbgPrint("KEY => \\SOFTWARE\\Star 创建失败");
	}
	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

#include <ntddk.h>

void DriverUnload(PDRIVER_OBJECT pDriver) {
	DbgPrint("DriverUnload");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath) {
	
	// 打开注册表
	HANDLE hReg = 0;
	OBJECT_ATTRIBUTES oa = {0};
	oa.Length = sizeof(OBJECT_ATTRIBUTES);
	oa.RootDirectory = NULL;
	UNICODE_STRING objPath = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SOFTWARE\\Star");
	oa.ObjectName = &objPath;
	oa.Attributes = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
	oa.SecurityDescriptor = NULL;
	oa.SecurityQualityOfService = NULL;
	// ULONG Disposition = 0;
	// NTSTATUS ret = ZwCreateKey(&hReg,KEY_ALL_ACCESS,&oa,0,0, REG_OPTION_NON_VOLATILE,&Disposition);
	// 打开Key 要求一定存在,ZwCreateKey存在打开,不存在创建
	NTSTATUS ret = ZwOpenKey(&hReg, GENERIC_ALL, &oa);
	if (NT_SUCCESS(ret)) {
		DbgPrint("打开key=>\\SOFTWARE\\Star");
		// 操作内容
		// 句柄 key的名称 固定0 类型 Value【Key对应的数据】 Value数据的大小
		UNICODE_STRING key = RTL_CONSTANT_STRING(L"name");
		// REG_SZ 字符串类型 字符串长度要计算上 \0的长度
		ret = ZwSetValueKey(hReg, &key, 0, REG_SZ,L"StarHook",(wcslen(L"StarHook") + 1)* sizeof(WCHAR));
		if (NT_SUCCESS(ret)) {
			DbgPrint("写入注册表成功");
			// 查询数据
			// 句柄 要查询的key 要查询那一列的信息 结果缓冲区 缓冲区的长度 实际上需要多大
			PVOID dataBuff = ExAllocatePool(NonPagedPool,0x100);
			RtlZeroMemory(dataBuff,0x100);
			ULONG resultLong = 0;
			ret = ZwQueryValueKey(hReg, &key, KeyValuePartialInformation,
				dataBuff,0x100,&resultLong);
			if (NT_SUCCESS(ret)) { // 如果查询成功
				PKEY_VALUE_PARTIAL_INFORMATION pi = dataBuff;
				if(pi->Type == REG_SZ)
					DbgPrint("%ls", pi->Data);
			}
			ExFreePool(dataBuff);
		}
		// 关闭句柄
		ZwClose(hReg);
	}else{
		DbgPrint("打开失败");
	}

	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

逆向基础-Windows驱动开发(一) 的相关文章

  • 提取证书中主题属性的所有值

    我目前正在使用CertGetNameString http msdn microsoft com en us library windows desktop aa376086 28v vs 85 29 aspx提取每个主题属性的值 如下所示
  • 如何使用 PowerShell 扩展 ZIP 存档(UTF-8 文件名)

    我的 zip 存档有一个文件 P re No l txt 该 zip 可以通过 Windows 文件资源管理器 7 Zip 或我尝试过的任何其他工具很好地扩展 但我不知道如何从 PowerShell 中做到这一点 显然我已经尝试过展开 存档
  • Rails Windows Vagrant 响应时间非常慢

    我在跑 Vagrant 1 7 1 Rails 4 1 4 Thin 1 6 1 Windows 7 每个静态文件的发送时间都超过一秒 在我的 PC 上加载一个页面可能需要大约 20 秒 而在同事的 Linux 机器上则只需瞬间 有一些帖子
  • VB - 以隐式方式链接 DLL

    我正在开发 VB6 图形界面 并且需要隐式链接到 DLL 这样做的动机来自于我上一个问题 https stackoverflow com questions 5194573 有问题的 DLL 使用静态 TLS declspec thread
  • 为什么 fopen 无法打开已存在的文件?

    我在 Windows XP 上使用 Visual Studio 6 是的 我知道它很旧 构建 维护 C DLL 我遇到了 fopen 无法打开现有文件的问题 它总是返回 NULL 我试过了 通过将 errno 和 doserrno 设置为零
  • 无法加载 JNI 共享库 (JDK)

    当我尝试打开时Eclipse http www eclipse org 弹出对话框指出 无法加载 JNI 共享库 C JDK bin client jvm dll 此后 Eclipse 强制关闭 我想提出以下几点 我检查了这条路径上是否存在
  • 如何使用Python在Django for Windows中激活虚拟环境?

    我被告知要在 Django for Windows 中激活虚拟环境 我应该尝试 environment path Scripts activate 但是当我输入该命令时 cmd 返回此错误 该系统找不到指定的路径 我通过输入以下命令创建了虚
  • 仅在单个端口 8080 上转发到本地主机(Windows)可能吗? [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我知道如何在 Windows 上使用 xampp 设置本地网络服务器 我在主机文件 c windows system32 drivers etc ho
  • 在高 dpi Windows 平台上自动重新缩放应用程序?

    我正在编写一个需要在高 dpi Windows 192dpi 而不是 96dpi 上运行的 Qt 应用程序 不幸的是 Qt 框架尚不支持高 dpi 至少在 Windows 上 因此我的应用程序及其所有元素看起来只有应有尺寸的一半 有没有办法
  • Windows 上的 ffmpeg-android ndk

    我正在尝试编译 bash 文件 带有 android ndk 的 ffmpeg 我收到如下错误 arm linux androideabi gcc 无法创建可执行文件 C 编译器测试失败 Makefile 2 config mak 没有这样
  • 如何在Windows服务器上将node.js文件作为后台进程运行?

    我正在创建一个 node js 项目并将其上传到我的 Windows 服务器 以为移动应用程序提供 API 服务 当我打开命令提示符并键入 node app js 它运行正常 但是当我关闭命令提示符时 我的 Node js 服务器停止运行
  • Windows 上的 boot2docker 缺少 apt-get / 包管理器

    我在 boot2docker 中 我不确定我完全理解它 但它似乎是我正在进行的项目所需要的 我已经通过 ssh 登录了 并且尝试对我们的项目进行 make 但是我得到了 make not found 然后我尝试了一个 apt get ins
  • 什么是 SO_SNDBUF 和 SO_RCVBUF

    你能解释一下到底是什么吗SO SNDBUF and SO RCVBUF选项 好的 出于某种原因 操作系统缓冲传出 传入数据 但我想澄清这个主题 他们的角色 通 常 是什么 它们是每个套接字的缓冲区吗 传输层的缓冲区 例如 TCP 缓冲区 和
  • 在 Windows 上通过 ctypes 将文件描述符传递给 C 库函数

    我试图通过 ctypes 将文件描述符传递给在 fd 上执行写入的 C 函数 在linux上它可以工作 在 Windows 上则不然 我不明白为什么 我没有 Windows 开发人员的经验 C func signature void fun
  • 批处理脚本 - IF EXIST 复制到 %localappdata% 错误

    我似乎被批处理脚本困住了 需要一些帮助 基本上我需要检查文件是否存在于文件夹中 localappdata 如果确实如此 则覆盖该文件 如果没有放置在不同的位置 那么目前它的内容如下 IF EXIST localappdata foldern
  • 如何在拥有句柄时检查给定进程是否正在运行

    我在用ShellExecuteEx启动应用程序 成功开始阅读后TShellExecuteInfo hProcess获取已启动进程的句柄 我想定期检查我的应用程序启动的进程是否仍在运行 两个或多个同名进程可以同时运行 我想确保我的应用程序正在
  • 已达到网络 BIOS 命令限制

    我的 ASP Net 应用程序从另一台 Windows 服务器上的共享文件夹获取文件 当请求增加时 我收到以下错误 The network BIOS command limit has been reached 我已按照以下步骤操作微软 K
  • 如何为 Windows toast 注册协议?

    如何注册 Windows toast 协议 样本中来自https blogs msdn microsoft com tiles and toasts 2015 07 02 adaptive and interactive toast not
  • 当用户尝试打开新实例时返回到已打开的应用程序

    这是我有一段时间想不通的问题 防止第二个实例很简单并且有很多方法 但是恢复已经运行的进程则不然 我想 最小化 撤消最小化并将正在运行的实例置于最前面 在其他窗口后面 将应用程序置于最前面 我使用的语言是 VB NET 和 C 我发现这段代码
  • MSysGit 与 Windows 版 Git

    我无法确定MSysGit 和 Windows 版 Git 之间的区别 http msysgit github com 它们有何不同 为什么我会选择其中之一而不是另一个 它们不是同一个东西吗 On http msysgit github co

随机推荐

  • Golang + Qt5 桌面开发终极解决方案

    Golang 43 Qt5 桌面开发终极解决方案 首先要安装Qt和Golang 一 安装前准备 1 下载Go1 4版本的压缩包版本 xff0c 解压至C盘User目录下 2 安装MinGW 并配置相关环境变量 参考链接 xff1a MinG
  • Oracle snapper ASH监控工具

    Oracle snapper ASH监控工具 snapper工具是由国外技术人员 xff0c 将基于Oracle ash技术原理用来监控数据库会话的负载情况 比较适合小范围时间监控 xff0c 可以生成多个快照 xff0c 例如1小时内 x
  • Matlab之数据筛选

    Matlab功能强大 xff0c 这里介绍一些数据筛选方法 xff0c 至少让其达到Excel的数据筛选程度 一 从多维数组中取某些行或列组合为新数组 示例如下 xff1a 取某些列组成新数组 newdata span class toke
  • kurento-room的搭建教程,绝对可行

    目前网上参考的kurento room的搭建教程 xff0c 比如https blog csdn net u010602143 article details 106670864 已经跑不起了 我估计原来也跑不起 原因很简单 xff0c k
  • Python 爬取携程所有机票

    打开携程网 xff0c 查询机票 xff0c 如广州到成都 这时网址为 xff1a http flights ctrip com booking CAN CTU day 1 html DDate1 61 2018 06 15 其中 xff0
  • Rust Web框架warp使用

    目录 简介快速开始Request和Response从path和body中获取参数从query中获取参数 设置状态码 静态文件 目录websocket重定向tls 简介 warp是一个超级便捷 可组合 速度极快的异步Web框架 目前最新版本为
  • CCNP路由实验之四 动态路由协议之EIGRP

    CCNP 路由实验之四 动态路由协议之 EIGRP 动态路由协议可以自动的发现远程网络 xff0c 只要网络拓扑结构发生了变化 xff0c 路由器就会相互交换路由信息 xff0c 不仅能够自动获知新增加的网络 xff0c 还可以在当前网络连
  • C++中typedef用法说明

    typedef声明提供了一种将标识符声明为类型别名的方法 xff0c 用于替换复杂的类型名 解释 在声明中使用typedef说明符时 xff0c 会指定这个声明是typedef声明 xff0c 而不是变量或函数声明 通常 xff0c typ
  • Ubuntu 服务配置(sysv-rc-conf)

    版权声明 xff1a 本文为博主原创文章 xff0c 未经博主允许不得转载 sudo apt get install sysv rc conf sudo sysv rc conf 运行级别说明 xff1a S表示开机后就会运行的服务0表示关
  • 安装vnc的各种悲剧解决

    系统 环境 VM 43 RHEL5 1 root 64 localhost vnc uname r 2 6 18 53 el5xen 本地XP系统安装 VNCVIEW去控制VM中的RHEL5 1 下面在LINUX上安装VNCSERVER 1
  • iOS基础 UITabBarController

    使用 创建子控制器继承自UITabBarController xff0c 在viewDidLoad阶段 xff0c 把各个分页上的控制器给创建好 xff0c 用UITabBarController的方法addChildControoler相
  • 插入内核模块失败提示"Invalid module format"

    产品需要编译自己的定制内核 43 内核模块 xff0c 下载内核源码定制修改后rpmbuild方式 点击打开链接 编译升级内核 xff0c 如下方式编译内核模块 make C kernel source SUBDIRS 61 96 pwd
  • microsoft visual c++ build tools

    因为visual studio的安装包太大 xff0c 所以在不需要开发的情况下 xff0c 可以选择使用microsoft visual c 43 43 build tools安装c 43 43 编译器 xff0c 这个工具会小很多 安装
  • C++ 应用程序 内存结构 --- BSS段,数据段,代码段,堆内存和栈

    转自 xff1a http hi baidu com C6 BF D6 D0 B5 C4 C5 AE CE D7 blog item 5043d08e741075f3503d922c html ld 时把所有的目标文件的代码段组合成一个代码
  • 4.1 简单题 - B 恭喜你

    当别人告诉你自己考了 x 分的时候 xff0c 你要回答说 xff1a 恭喜你考了 x 分 xff01 比如小明告诉你他考了90分 xff0c 你就用汉语拼音打出来 gong xi ni kao le 90 fen 输入格式 xff1a 输
  • <script>在页面代码上没有显示

    记录一下 导入js文件 xff0c 自己路径都没有问题 xff0c 为什么在浏览器查看页面代码没有自己写的那行js导入文件的代码呢 xff0c 原来 xff0c 是之前看着不舒服 xff0c 点了exclude xff0c exclude是
  • 利用Rust构建一个REST API服务

    利用Rust构建一个REST API服务 关注公众号 xff1a 香菜粉丝 了解更多精彩内容 Rust 是一个拥有很多忠实粉丝的编程语言 xff0c 还是很难找到一些用它构建的项目 xff0c 而且掌握起来甚至有点难度 想要开始学习一门编程
  • 安装cmake3.22

    升级cmake版本 脚本 span class token assign left variable file name span span class token operator 61 span cmake 3 22 0 yum era
  • stdout stderr 重定向到文件

    1 stdout stderr 重定向 1 stdout stderr 重定向 1 1 dup dup2 重定向到已打开文件 或 新文件1 2 freopen 重定向到新文件1 3 命令行重定向1 4 参考资料 1 1 dup dup2 重
  • 逆向基础-Windows驱动开发(一)

    Windows内核开发 第一个驱动程序 环境配置 xff1a 安装WDK xff1a WDK版本与SDK保持一致 然后记得把Spectre Mitigation给Disabled掉 xff0c 就不用去下载漏洞补丁了 然后在内核层 xff0