首页 > 其他分享 >驱动开发:内核LoadLibrary实现DLL注入

驱动开发:内核LoadLibrary实现DLL注入

时间:2023-06-13 09:33:23浏览次数:42  
标签:DbgPrint return 函数 DLL 内核 LoadLibrary NULL EProcess ptr

远程线程注入是最常用的一种注入技术,在应用层注入是通过CreateRemoteThread这个函数实现的,该函数通过创建线程并调用 LoadLibrary 动态载入指定的DLL来实现注入,而在内核层同样存在一个类似的内核函数RtlCreateUserThread,但需要注意的是此函数未被公开,RtlCreateUserThread其实是对NtCreateThreadEx的包装,但最终会调用ZwCreateThread来实现注入,RtlCreateUserThreadCreateRemoteThread的底层实现。

基于LoadLibrary实现的注入原理可以具体分为如下几步;

  • 1.调用AllocMemory,在对端应用层开辟空间,函数封装来源于《内核远程堆分配与销毁》章节;
  • 2.调用MDLWriteMemory,将DLL路径字符串写出到对端内存,函数封装来源于《内核MDL读写进程内存》章节;
  • 3.调用GetUserModuleAddress,获取到kernel32.dll模块基址,函数封装来源于《内核远程线程实现DLL注入》章节;
  • 4.调用GetModuleExportAddress,获取到LoadLibraryW函数的内存地址,函数封装来源于《内核远程线程实现DLL注入》章节;
  • 5.最后调用本章封装函数MyCreateRemoteThread,将应用层DLL动态转载到进程内,实现DLL注入;

总结起来就是首先在目标进程申请一块空间,空间里面写入要注入的DLL的路径字符串或者是一段ShellCode,找到该内存中LoadLibrary的基址并传入到RtlCreateUserThread中,此时进程自动加载我们指定路径下的DLL文件。

注入依赖于RtlCreateUserThread这个未到处内核函数,该内核函数中最需要关心的参数是ProcessHandle用于接收进程句柄,StartAddress接收一个函数地址,StartParameter用于对函数传递参数,具体的函数原型如下所示;

typedef DWORD(WINAPI* pRtlCreateUserThread)(
    IN HANDLE                    ProcessHandle,          // 进程句柄
    IN PSECURITY_DESCRIPTOR      SecurityDescriptor,
    IN BOOL                      CreateSuspended,
    IN ULONG                     StackZeroBits,
    IN OUT PULONG                StackReserved,
    IN OUT PULONG                StackCommit,
    IN LPVOID                    StartAddress,          // 执行函数地址 LoadLibraryW
    IN LPVOID                    StartParameter,        // 参数传递
    OUT HANDLE                   ThreadHandle,          // 线程句柄
    OUT LPVOID                   ClientID
    );

由于我们加载DLL使用的是LoadLibraryW函数,此函数在运行时只需要一个参数,我们可以将DLL的路径传递进去,并调用LoadLibraryW以此来将特定模块拉起,该函数的定义规范如下所示;

HMODULE LoadLibraryW(
  [in] LPCWSTR lpLibFileName
);

根据上一篇文章中针对注入头文件lyshark.h的封装,本章将继续使用这个头文件中的函数,首先我们实现这样一个功能,将一段准备好的UCHAR字符串动态的写出到应用层进程内存,并以宽字节模式写出在对端内存中,这段代码可以写为如下样子;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#include "lyshark.h"

// 驱动卸载例程
VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint("Uninstall Driver \n");
}

// 驱动入口地址
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("Hello LyShark \n");

	DWORD process_id = 7112;
	DWORD create_size = 1024;
	DWORD64 ref_address = 0;

	// 分配内存堆 《内核远程堆分配与销毁》 核心代码
	NTSTATUS Status = AllocMemory(process_id, create_size, &ref_address);

	DbgPrint("对端进程: %d \n", process_id);
	DbgPrint("分配长度: %d \n", create_size);
	DbgPrint("[*] 分配内核堆基址: %p \n", ref_address);

	UCHAR DllPath[256] = "C:\\hook.dll";
	UCHAR Item[256] = { 0 };

	// 将字节转为双字
	for (int x = 0, y = 0; x < strlen(DllPath) * 2; x += 2, y++)
	{
		Item[x] = DllPath[y];
	}

	// 写出内存 《内核MDL读写进程内存》 核心代码
	ReadMemoryStruct ptr;

	ptr.pid = process_id;
	ptr.address = ref_address;
	ptr.size = strlen(DllPath) * 2;

	// 需要写入的数据
	ptr.data = ExAllocatePool(PagedPool, ptr.size);

	// 循环设置
	for (int i = 0; i < ptr.size; i++)
	{
		ptr.data[i] = Item[i];
	}

	// 写内存
	MDLWriteMemory(&ptr);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

运行如上方所示的代码,将会在目标进程7112中开辟一段内存空间,并写出C:\hook.dll字符串,运行效果图如下所示;

此处你可以通过x64dbg附加到应用层进程内,并观察内存0000000002200000会看到如下字符串已被写出,双字类型则是每一个字符空一格,效果图如下所示;

继续实现所需要的子功能,实现动态获取Kernel32.dll模块里面LiadLibraryW这个导出函数的内存地址,这段代码相信你可以很容易的写出来,根据上节课的知识点我们可以二次封装一个GetProcessAddress来实现对特定模块基址的获取功能,如下是完整代码案例;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#include "lyshark.h"

// 实现取模块基址
PVOID GetProcessAddress(HANDLE ProcessID, PWCHAR DllName, PCCHAR FunctionName)
{
	PEPROCESS EProcess = NULL;
	NTSTATUS Status = STATUS_SUCCESS;
	KAPC_STATE ApcState;
	PVOID RefAddress = 0;

	// 根据PID得到进程EProcess结构
	Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
	if (Status != STATUS_SUCCESS)
	{
		return Status;
	}

	// 判断目标进程是32位还是64位
	BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;

	// 验证地址是否可读
	if (!MmIsAddressValid(EProcess))
	{
		return NULL;
	}

	// 将当前线程连接到目标进程的地址空间(附加进程)
	KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);

	__try
	{
		UNICODE_STRING DllUnicodeString = { 0 };
		PVOID BaseAddress = NULL;

		// 得到进程内模块基地址
		RtlInitUnicodeString(&DllUnicodeString, DllName);

		BaseAddress = GetUserModuleAddress(EProcess, &DllUnicodeString, IsWow64);

		if (!BaseAddress)
		{
			return NULL;
		}

		DbgPrint("[*] 模块基址: %p \n", BaseAddress);

		// 得到该函数地址
		RefAddress = GetModuleExportAddress(BaseAddress, FunctionName, EProcess);
		DbgPrint("[*] 函数地址: %p \n", RefAddress);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return NULL;
	}

	// 取消附加
	KeUnstackDetachProcess(&ApcState);
	return RefAddress;
}

VOID Unload(PDRIVER_OBJECT pDriverObj)
{
	DbgPrint("[-] 驱动卸载 \n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
	DbgPrint("Hello LyShark.com \n");

	// 取模块基址
	PVOID pLoadLibraryW = GetProcessAddress(5200, L"kernel32.dll", "LoadLibraryW");

	DbgPrint("[*] 所在内存地址 = %p \n", pLoadLibraryW);

	DriverObject->DriverUnload = Unload;
	return STATUS_SUCCESS;
}

编译并运行如上驱动代码,将自动获取PID=5200进程中Kernel32.dll模块内的LoadLibraryW的内存地址,输出效果图如下所示;

实现注入的最后一步就是调用自定义函数MyCreateRemoteThread该函数实现原理是调用RtlCreateUserThread开线程执行,这段代码的最终实现如下所示;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]

#include "lyshark.h"

// 定义函数指针
typedef PVOID(NTAPI* PfnRtlCreateUserThread)
(
	IN HANDLE ProcessHandle,
	IN PSECURITY_DESCRIPTOR SecurityDescriptor,
	IN BOOLEAN CreateSuspended,
	IN ULONG StackZeroBits,
	IN OUT size_t StackReserved,
	IN OUT size_t StackCommit,
	IN PVOID StartAddress,
	IN PVOID StartParameter,
	OUT PHANDLE ThreadHandle,
	OUT PCLIENT_ID ClientID
);

// 实现取模块基址
PVOID GetProcessAddress(HANDLE ProcessID, PWCHAR DllName, PCCHAR FunctionName)
{
	PEPROCESS EProcess = NULL;
	NTSTATUS Status = STATUS_SUCCESS;
	KAPC_STATE ApcState;
	PVOID RefAddress = 0;

	// 根据PID得到进程EProcess结构
	Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
	if (Status != STATUS_SUCCESS)
	{
		return Status;
	}

	// 判断目标进程是32位还是64位
	BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;

	// 验证地址是否可读
	if (!MmIsAddressValid(EProcess))
	{
		return NULL;
	}

	// 将当前线程连接到目标进程的地址空间(附加进程)
	KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);

	__try
	{
		UNICODE_STRING DllUnicodeString = { 0 };
		PVOID BaseAddress = NULL;

		// 得到进程内模块基地址
		RtlInitUnicodeString(&DllUnicodeString, DllName);

		BaseAddress = GetUserModuleAddress(EProcess, &DllUnicodeString, IsWow64);

		if (!BaseAddress)
		{
			return NULL;
		}

		DbgPrint("[*] 模块基址: %p \n", BaseAddress);

		// 得到该函数地址
		RefAddress = GetModuleExportAddress(BaseAddress, FunctionName, EProcess);
		DbgPrint("[*] 函数地址: %p \n", RefAddress);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return NULL;
	}

	// 取消附加
	KeUnstackDetachProcess(&ApcState);
	return RefAddress;
}

// 远程线程注入函数
BOOLEAN MyCreateRemoteThread(ULONG pid, PVOID pRing3Address, PVOID PParam)
{
	NTSTATUS status = STATUS_UNSUCCESSFUL;
	PEPROCESS pEProcess = NULL;
	KAPC_STATE ApcState = { 0 };

	PfnRtlCreateUserThread RtlCreateUserThread = NULL;
	HANDLE hThread = 0;

	__try
	{
		// 获取RtlCreateUserThread函数的内存地址
		UNICODE_STRING ustrRtlCreateUserThread;
		RtlInitUnicodeString(&ustrRtlCreateUserThread, L"RtlCreateUserThread");
		RtlCreateUserThread = (PfnRtlCreateUserThread)MmGetSystemRoutineAddress(&ustrRtlCreateUserThread);
		if (RtlCreateUserThread == NULL)
		{
			return FALSE;
		}

		// 根据进程PID获取进程EProcess结构
		status = PsLookupProcessByProcessId((HANDLE)pid, &pEProcess);
		if (!NT_SUCCESS(status))
		{
			return FALSE;
		}

		// 附加到目标进程内
		KeStackAttachProcess(pEProcess, &ApcState);

		// 验证进程是否可读写
		if (!MmIsAddressValid(pRing3Address))
		{
			return FALSE;
		}

		// 启动注入线程
		status = RtlCreateUserThread(ZwCurrentProcess(),
			NULL,
			FALSE,
			0,
			0,
			0,
			pRing3Address,
			PParam,
			&hThread,
			NULL);
		if (!NT_SUCCESS(status))
		{
			return FALSE;
		}

		return TRUE;
	}

	__finally
	{
		// 释放对象
		if (pEProcess != NULL)
		{
			ObDereferenceObject(pEProcess);
			pEProcess = NULL;
		}

		// 取消附加进程
		KeUnstackDetachProcess(&ApcState);
	}

	return FALSE;
}

VOID Unload(PDRIVER_OBJECT pDriverObj)
{
	DbgPrint("[-] 驱动卸载 \n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
	DbgPrint("Hello LyShark.com \n");

	ULONG process_id = 5200;
	DWORD create_size = 1024;
	DWORD64 ref_address = 0;

	// -------------------------------------------------------
	// 取模块基址
	// -------------------------------------------------------

	PVOID pLoadLibraryW = GetProcessAddress(process_id, L"kernel32.dll", "LoadLibraryW");
	DbgPrint("[*] 所在内存地址 = %p \n", pLoadLibraryW);

	// -------------------------------------------------------
	// 应用层开堆
	// -------------------------------------------------------

	NTSTATUS Status = AllocMemory(process_id, create_size, &ref_address);

	DbgPrint("对端进程: %d \n", process_id);
	DbgPrint("分配长度: %d \n", create_size);
	DbgPrint("分配的内核堆基址: %p \n", ref_address);

	// 设置注入路径,转换为多字节
	UCHAR DllPath[256] = "C:\\lyshark_hook.dll";
	UCHAR Item[256] = { 0 };

	for (int x = 0, y = 0; x < strlen(DllPath) * 2; x += 2, y++)
	{
		Item[x] = DllPath[y];
	}

	// -------------------------------------------------------
	// 写出数据到内存
	// -------------------------------------------------------

	ReadMemoryStruct ptr;

	ptr.pid = process_id;
	ptr.address = ref_address;
	ptr.size = strlen(DllPath) * 2;

	// 需要写入的数据
	ptr.data = ExAllocatePool(PagedPool, ptr.size);

	// 循环设置
	for (int i = 0; i < ptr.size; i++)
	{
		ptr.data[i] = Item[i];
	}

	// 写内存
	MDLWriteMemory(&ptr);

	// -------------------------------------------------------
	// 执行开线程函数
	// -------------------------------------------------------

	// 执行线程注入
	// 参数1:PID
	// 参数2:LoadLibraryW内存地址
	// 参数3:当前DLL路径
	BOOLEAN flag = MyCreateRemoteThread(process_id, pLoadLibraryW, ref_address);
	if (flag == TRUE)
	{
		DbgPrint("[*] 已完成进程 %d 注入文件 %s \n", process_id, DllPath);
	}

	DriverObject->DriverUnload = Unload;
	return STATUS_SUCCESS;
}

编译这段驱动程序,并将其放入虚拟机中,在C盘下面放置好一个名为lyshark_hook.dll文件,运行驱动程序将自动插入DLL到Win32Project进程内,输出效果图如下所示;

回到应用层进程,则可看到如下图所示的注入成功提示信息;

标签:DbgPrint,return,函数,DLL,内核,LoadLibrary,NULL,EProcess,ptr
From: https://www.cnblogs.com/LyShark/p/17170818.html

相关文章

  • centos7 内核 kernel-devel-3.10.0-957.el7.x86_64
    centos7内核kernel-devel-3.10.0-957.el7.x86_64https://people.centos.org/arrfab/shim/results/kernel/20181108233701/3.10.0-957.el7.x86_64/......
  • 进程在用户态和内核态的区别[独家解析]
     先看基础常识:基础内核在创建进程的时候,会为进程创建相应的堆栈。   每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。 当进程在用户空间运行时,CPU寄存器里面的内容是用户堆栈地址,使用用户栈  当进程在内核空间时,CPU寄存器里面的内容是内核栈......
  • 内核在Linux启动中的中流砥柱
        内核时操作系统中最基本的部分,它在系统启动时被加载到内存里面,然后留在内存中管理系统的资源、调度进程等。 内核可以管理进程、用户、内存等主要部分。在编译过程中静态链接到主机的内核模块中。有一种可加载的内核模块,在编译时没有链接到主机,但是在系统启动时或启动......
  • 字符设备驱动-9.内核定时器
    1引入定时器前面的gpio按键驱动引入了中断,当按键按下会记录按键信息,理想状况是按下一次按键记录一组数据,但实际上按下机械振动导致电平反复跳动最后才稳定,按下一次gpioirq会触发多次,这个被叫做“抖动”,那么可以利用定时器进行“去抖”。到linux_5.10版本structtimer_list......
  • 驱动开发:内核遍历文件或目录
    在笔者前一篇文章《驱动开发:内核文件读写系列函数》简单的介绍了内核中如何对文件进行基本的读写操作,本章我们将实现内核下遍历文件或目录这一功能,该功能的实现需要依赖于ZwQueryDirectoryFile这个内核API函数来实现,该函数可返回给定文件句柄指定的目录中文件的各种信息,此类信息会......
  • 使用.net4引用Delph写的动态链接库DLL,you经验的大佬看一下
    vs2017、net4、无法引用?是Delph动态链接库的问题吗?也把dll放bin同目录底下啦这个是用vs打开的dll是机器代码请有经验的大佬指点一手......
  • RISCV Ubuntu Linux内核更换
    交叉编译内核#!/bin/bash#LINUXSRC=/root/linuxexportLINUXSRC=/keystone/linuxexportOUTPUT=/keystone/build/linux.build#exportCONFIG=/keystone/conf/linux64-defconfigexportCONFIG=/keystone/build/config-5.19.0-1012-genericexportCROSS_COMPILE=riscv64-u......
  • linux 内核版本
    截止支持时间CentOSversion  Releasedate  Fullupdates[40][41]  Maintenanceupdates[40][41]3  2004-03-19  2006-07-20  2010-10-31[42]4  2005-03-09  2009-03-31  2012-02-29[42][43]5  2007-04-12  2014-01-31  2017-03-3......
  • 【Linux内核及驱动编程】Linux信号机制分析
           Linux信号机制分析 Sailor_forever  【摘要】本文分析了Linux内核对于信号的实现机制和应用层的相关处理。首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理。接着分析了内核对于信号的处理流程包括信号的触发/注册/执行及注销等。最后......
  • spdlog使用头文件或dll的宏设置
    宏作用SPDLOG_HEADER_ONLYspdlog只使用头文件FMT_HEADER_ONLYfmtlib只用用头文件SPDLOG_COMPILED_LIBspdlog使用dllSPDLOG_SHARED_LIBspdlog使用dll,实际可以不设置,单独设置SPDLOG_COMPILED_LIB就可以SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACESPDLOG_TRAC......