驱动开发:内核封装WSK网络通信接口

驱动,开发,内核,封装,wsk,网络,通信接口 · 浏览次数 : 610

小编点评

**CreateSocket 创建套接字** ```C++ PWSK_SOCKET TcpSocket = CreateSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, WSK_FLAG_CONNECTION_SOCKET); ``` **绑定套接字** ```C++ LocalAddress.sin_family = AF_INET; LocalAddress.sin_addr.s_addr = INADDR_ANY; status = Bind(TcpSocket, (PSOCKADDR)&LocalAddress); ``` **接收数据到服务端** ```C++ CHAR* read_buffer = (CHAR*)ExAllocatePoolWithTag(NonPagedPool, 2048, "read"); memset(read_buffer, 0, 1024); Receive(TcpSocket, read_buffer, 2048, 0); ``` **发送确认包** ```C++ Send(TcpSocket, "ok", 2, 0); ``` **释放内存** ```C++ ExFreePool(read_buffer); ``` **最终结果** ``` [内核B] => %s ``` **说明** * `WSK_FLAG_CONNECTION_SOCKET`表示建立连接套接字 * `AF_INET`表示AFINET套接字 * `SOCK_STREAM`表示套接字类型 * `IPPROTO_TCP`表示TCP套接字协议

正文

本章LyShark将带大家学习如何在内核中使用标准的Socket套接字通信接口,我们都知道Windows应用层下可直接调用WinSocket来实现网络通信,但在内核模式下应用层API接口无法使用,内核模式下有一套专有的WSK通信接口,我们对WSK进行封装,让其与应用层调用规范保持一致,并实现内核与内核直接通过Socket通信的案例。

当然在早期如果需要实现网络通信一般都会采用TDI框架,但在新版本Windows10系统上虽然依然可以使用TDI接口,但是LyShark并不推荐使用,因为微软已经对接口搁置了,为了使WSK通信更加易用,我们需要封装内核层中的通信API,新建LySocket.hpp头文件,该文件中封装了WSK通信API接口,其封装格式与应用层接口保持了高度一致,当需要在内核中使用Socket通信时可直接引入本文件。

我们需要使用WDM驱动程序,并配置以下参数。

  • 配置属性 -> 连接器 -> 输入-> 附加依赖 -> $(DDK_LIB_PATH)\Netio.lib
  • 配置属性 -> C/C++ -> 常规 -> 设置 警告等级2级 (警告视为错误关闭)

配置好以后,我们就开始吧,先来看看服务端如何实现!

对于服务端来说,驱动通信必须保证服务端开启多线程来处理异步请求,不然驱动加载后系统会处于等待状态,而一旦等待则系统将会卡死,那么对于服务端DriverEntry入口说我们不能让其等待,必须使用PsCreateSystemThread来启用系统线程,该函数属于WDM的一部分,官方定义如下;

NTSTATUS PsCreateSystemThread(
  [out]           PHANDLE            ThreadHandle,
  [in]            ULONG              DesiredAccess,
  [in, optional]  POBJECT_ATTRIBUTES ObjectAttributes,
  [in, optional]  HANDLE             ProcessHandle,
  [out, optional] PCLIENT_ID         ClientId,
  [in]            PKSTART_ROUTINE    StartRoutine,
  [in, optional]  PVOID              StartContext
);

我们使用PsCreateSystemThread函数开辟线程TcpListenWorker在线程内部执行如下流程启动驱动服务端,由于我们自己封装实现了标准接口组,所以使用起来几乎与应用层无任何差异了。

  • CreateSocket 创建套接字
  • Bind 绑定套接字
  • Accept 等待接收请求
  • Receive 用于接收返回值
  • Send 用于发送返回值
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "LySocket.hpp"

PETHREAD m_EThread = NULL;

// 线程函数
// PowerBy: LySHark
VOID TcpListenWorker(PVOID Context)
{
	WSK_SOCKET* paccept_socket = NULL;
	SOCKADDR_IN LocalAddress = { 0 };
	SOCKADDR_IN RemoteAddress = { 0 };
	NTSTATUS status = STATUS_UNSUCCESSFUL;

	// 创建套接字
	PWSK_SOCKET TcpSocket = CreateSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, WSK_FLAG_LISTEN_SOCKET);
	if (TcpSocket == NULL)
	{
		return;
	}

	// 设置绑定地址
	LocalAddress.sin_family = AF_INET;
	LocalAddress.sin_addr.s_addr = INADDR_ANY;
	LocalAddress.sin_port = HTON_SHORT(8888);

	status = Bind(TcpSocket, (PSOCKADDR)&LocalAddress);
	if (!NT_SUCCESS(status))
	{
		return;
	}

	// 循环接收
	while (1)
	{
		CHAR* read_buffer = (CHAR*)ExAllocatePoolWithTag(NonPagedPool, 2048, "read");
		paccept_socket = Accept(TcpSocket, (PSOCKADDR)&LocalAddress, (PSOCKADDR)&RemoteAddress);
		if (paccept_socket == NULL)
		{
			continue;
		}

		// 接收数据
		memset(read_buffer, 0, 2048);
		int read_len = Receive(paccept_socket, read_buffer, 2048, 0);
		if (read_len != 0)
		{
			DbgPrint("[内核A] => %s \n", read_buffer);

			// 发送数据
			char send_buffer[2048] = "Hi, lyshark.com B";
			Send(paccept_socket, send_buffer, strlen(send_buffer), 0);

			// 接收确认包
			memset(read_buffer, 0, 2048);
			Receive(paccept_socket, read_buffer, 2, 0);
		}

		// 清理堆
		if (read_buffer != NULL)
		{
			ExFreePool(read_buffer);
		}

		// 关闭当前套接字
		if (paccept_socket)
		{
			CloseSocket(paccept_socket);
		}
	}

	if (TcpSocket)
	{
		CloseSocket(TcpSocket);
	}
	PsTerminateSystemThread(STATUS_SUCCESS);
	return;
}

// 关闭套接字
VOID UnDriver(PDRIVER_OBJECT driver)
{
	WSKCleanup();
	KeWaitForSingleObject(m_EThread, Executive, KernelMode, FALSE, NULL);
	if (m_EThread != NULL)
	{
		ObDereferenceObject(m_EThread);
	}
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("hello lyshark.com \n");

	// 初始化
	WSKStartup();

	HANDLE hThread = NULL;
	NTSTATUS status = STATUS_UNSUCCESSFUL;

	// 创建系统线程
	status = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, TcpListenWorker, NULL);
	if (!NT_SUCCESS(status))
	{
		return status;
	}

	// 获取线程EProcess结构
	status = ObReferenceObjectByHandle(hThread, THREAD_ALL_ACCESS, NULL, KernelMode, (PVOID*)&m_EThread, NULL);
	if (NT_SUCCESS(status) == FALSE)
	{
		return status;
	}

	ZwClose(hThread);
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

对于客户端来说,只需要创建套接字并连接到指定地址即可,这个过程大体上可以总结为如下;

  • CreateSocket 创建套接字
  • Bind 绑定套接字
  • Connect 链接服务端驱动
  • Send 发送数据到服务端
  • Receive 接收数据到服务端
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include "LySocket.hpp"

VOID UnDriver(PDRIVER_OBJECT driver)
{
	// 卸载并关闭Socket库
	WSKCleanup();
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("hello lyshark.com \n");
	// 初始化
	WSKStartup();

	NTSTATUS      status = STATUS_SUCCESS;
	SOCKADDR_IN   LocalAddress = { 0, };
	SOCKADDR_IN   RemoteAddress = { 0, };

	// 创建套接字
	PWSK_SOCKET TcpSocket = CreateSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, WSK_FLAG_CONNECTION_SOCKET);
	if (TcpSocket == NULL)
	{
		Driver->DriverUnload = UnDriver;
		return STATUS_SUCCESS;
	}

	LocalAddress.sin_family = AF_INET;
	LocalAddress.sin_addr.s_addr = INADDR_ANY;
	status = Bind(TcpSocket, (PSOCKADDR)&LocalAddress);

	// 绑定失败则关闭驱动
	if (!NT_SUCCESS(status))
	{
		CloseSocket(TcpSocket);

		Driver->DriverUnload = UnDriver;
		return STATUS_SUCCESS;
	}

	// 初始化服务端地址与端口信息
	ULONG address[4] = { 127, 0, 0, 1 };

	RemoteAddress.sin_family = AF_INET;
	RemoteAddress.sin_addr.s_addr = change_uint(address[0], address[1], address[2], address[3]);
	RemoteAddress.sin_port = HTON_SHORT(8888);

	status = Connect(TcpSocket, (PSOCKADDR)&RemoteAddress);

	// 连接服务端,如果失败则关闭驱动
	if (!NT_SUCCESS(status))
	{
		CloseSocket(TcpSocket);
		Driver->DriverUnload = UnDriver;
		return STATUS_SUCCESS;
	}

	// 发送数据
	char send_buffer[2048] = "hello lyshark.com A";
	Send(TcpSocket, send_buffer, strlen(send_buffer), 0);

	// 接收数据
	CHAR* read_buffer = (CHAR*)ExAllocatePoolWithTag(NonPagedPool, 2048, "read");

	memset(read_buffer, 0, 1024);
	Receive(TcpSocket, read_buffer, 2048, 0);
	DbgPrint("[内核B] => %s \n", read_buffer);

	// 发送确认包
	Send(TcpSocket, "ok", 2, 0);

	// 释放内存
	ExFreePool(read_buffer);
	CloseSocket(TcpSocket);
	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

编译两个驱动程序,首先运行server.sys驱动,运行后该驱动会在后台等待客户端连接,接着运行client.sys屏幕上可输出如下提示,说明通信已经建立了。

与驱动开发:内核封装WSK网络通信接口相似的内容:

驱动开发:内核封装WSK网络通信接口

本章`LyShark`将带大家学习如何在内核中使用标准的`Socket`套接字通信接口,我们都知道`Windows`应用层下可直接调用`WinSocket`来实现网络通信,但在内核模式下应用层API接口无法使用,内核模式下有一套专有的`WSK`通信接口,我们对WSK进行封装,让其与应用层调用规范保持一致,并实现内核与内核直接通过`Socket`通信的案例。

驱动开发:内核封装TDI网络通信接口

在上一篇文章`《驱动开发:内核封装WSK网络通信接口》`中,`LyShark`已经带大家看过了如何通过WSK接口实现套接字通信,但WSK实现的通信是内核与内核模块之间的,而如果需要内核与应用层之间通信则使用TDK会更好一些因为它更接近应用层,本章将使用TDK实现,TDI全称传输驱动接口,其主要负责连接`Socket`和协议驱动,用于实现访问传输层的功能,该接口比`NDIS`更接近于应用层,在早期W

驱动开发:内核封装WFP防火墙入门

WFP框架是微软推出来替代TDIHOOK传输层驱动接口网络通信的方案,其默认被设计为分层结构,该框架分别提供了用户态与内核态相同的AIP函数,在两种模式下均可以开发防火墙产品,以下代码我实现了一个简单的驱动过滤防火墙。WFP 框架分为两大层次模块,用户态基础过滤引擎`BFE (BaseFilteringEngine)` ,以及内核态过滤引擎 `KMFE (KMFilteringEngine)`,基

驱动开发:内核枚举LoadImage映像回调

在笔者之前的文章`《驱动开发:内核特征码搜索函数封装》`中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核`LoadImage`映像回调,在Win64环境下我们可以设置一个`LoadImage`映像加载通告回调,当有新驱动或者DLL被加载时,回调函数就会被调用从而执行我们自己的回调例程,映像回调也存储在数组里,枚举时从数组中读取值之后,需要进行位运算解密得到地址。

驱动开发:内核实现SSDT挂钩与摘钩

在前面的文章`《驱动开发:内核解析PE结构导出表》`中我们封装了两个函数`KernelMapFile()`函数可用来读取内核文件,`GetAddressFromFunction()`函数可用来在导出表中寻找指定函数的导出地址,本章将以此为基础实现对特定`SSDT`函数的`Hook`挂钩操作,与`《驱动开发:内核层InlineHook挂钩函数》`所使用的挂钩技术基本一致,不同点是前者使用了`CR3`

驱动开发:内核解析PE结构导出表

在笔者的上一篇文章`《驱动开发:内核特征码扫描PE代码段》`中`LyShark`带大家通过封装好的`LySharkToolsUtilKernelBase`函数实现了动态获取内核模块基址,并通过`ntimage.h`头文件中提供的系列函数解析了指定内核模块的`PE节表`参数,本章将继续延申这个话题,实现对PE文件导出表的解析任务,导出表无法动态获取,解析导出表则必须读入内核模块到内存才可继续解析,所

驱动开发:内核读写内存浮点数

如前所述,在前几章内容中笔者简单介绍了`内存读写`的基本实现方式,这其中包括了`CR3切换`读写,`MDL映射`读写,`内存拷贝`读写,本章将在如前所述的读写函数进一步封装,并以此来实现驱动读写内存浮点数的目的。内存`浮点数`的读写依赖于`读写内存字节`的实现,因为浮点数本质上也可以看作是一个字节集,对于`单精度浮点数`来说这个字节集列表是4字节,而对于`双精度浮点数`,此列表长度则为8字节。

驱动开发:内核解析PE结构节表

在笔者上一篇文章`《驱动开发:内核解析PE结构导出表》`介绍了如何解析内存导出表结构,本章将继续延申实现解析PE结构的PE头,PE节表等数据,总体而言内核中解析PE结构与应用层没什么不同,在上一篇文章中`LyShark`封装实现了`KernelMapFile()`内存映射函数,在之后的章节中这个函数会被多次用到,为了减少代码冗余,后期文章只列出重要部分,读者可以自行去前面的文章中寻找特定的片段。

驱动开发:内核注册表增删改查

注册表是Windows中的一个重要的数据库,用于存储系统和应用程序的设置信息,注册表是一个巨大的树形结构,无论在应用层还是内核层操作注册表都有独立的API函数可以使用,而在内核中读写注册表则需要使用内核装用API函数,如下将依次介绍并封装一些案例,实现对注册表的创建,删除,更新,查询等操作。

驱动开发:内核PE结构VA与FOA转换

本章将继续探索内核中解析PE文件的相关内容,PE文件中FOA与VA,RVA之间的转换也是很重要的,所谓的FOA是文件中的地址,VA则是内存装入后的虚拟地址,RVA是内存基址与当前地址的相对偏移,本章还是需要用到`《驱动开发:内核解析PE结构导出表》`中所封装的`KernelMapFile()`映射函数,在映射后对其PE格式进行相应的解析,并实现转换函数。