驱动开发:取进程模块的函数地址

这篇具有很好参考价值的文章主要介绍了驱动开发:取进程模块的函数地址。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在笔者上一篇文章《驱动开发:内核取应用层模块基地址》中简单为大家介绍了如何通过遍历PLIST_ENTRY32链表的方式获取到32位应用程序中特定模块的基地址,由于是入门系列所以并没有封装实现太过于通用的获取函数,本章将继续延申这个话题,并依次实现通用版GetUserModuleBaseAddress()取远程进程中指定模块的基址和GetModuleExportAddress()取远程进程中特定模块中的函数地址,此类功能也是各类安全工具中常用的代码片段。

首先封装一个lyshark.h头文件,此类头文件中的定义都是微软官方定义好的规范,如果您想获取该结构的详细说明文档请参阅微软官方,此处不做过多的介绍。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>

// 导出未导出函数
NTKERNELAPI PPEB NTAPI PsGetProcessPeb(IN PEPROCESS Process);
NTKERNELAPI PVOID NTAPI PsGetProcessWow64Process(IN PEPROCESS Process);

typedef struct _PEB32
{
  UCHAR InheritedAddressSpace;
  UCHAR ReadImageFileExecOptions;
  UCHAR BeingDebugged;
  UCHAR BitField;
  ULONG Mutant;
  ULONG ImageBaseAddress;
  ULONG Ldr;
  ULONG ProcessParameters;
  ULONG SubSystemData;
  ULONG ProcessHeap;
  ULONG FastPebLock;
  ULONG AtlThunkSListPtr;
  ULONG IFEOKey;
  ULONG CrossProcessFlags;
  ULONG UserSharedInfoPtr;
  ULONG SystemReserved;
  ULONG AtlThunkSListPtr32;
  ULONG ApiSetMap;
} PEB32, *PPEB32;

typedef struct _PEB_LDR_DATA
{
  ULONG Length;
  UCHAR Initialized;
  PVOID SsHandle;
  LIST_ENTRY InLoadOrderModuleList;
  LIST_ENTRY InMemoryOrderModuleList;
  LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

typedef struct _PEB
{
  UCHAR InheritedAddressSpace;
  UCHAR ReadImageFileExecOptions;
  UCHAR BeingDebugged;
  UCHAR BitField;
  PVOID Mutant;
  PVOID ImageBaseAddress;
  PPEB_LDR_DATA Ldr;
  PVOID ProcessParameters;
  PVOID SubSystemData;
  PVOID ProcessHeap;
  PVOID FastPebLock;
  PVOID AtlThunkSListPtr;
  PVOID IFEOKey;
  PVOID CrossProcessFlags;
  PVOID KernelCallbackTable;
  ULONG SystemReserved;
  ULONG AtlThunkSListPtr32;
  PVOID ApiSetMap;
} PEB, *PPEB;

typedef struct _PEB_LDR_DATA32
{
  ULONG Length;
  UCHAR Initialized;
  ULONG SsHandle;
  LIST_ENTRY32 InLoadOrderModuleList;
  LIST_ENTRY32 InMemoryOrderModuleList;
  LIST_ENTRY32 InInitializationOrderModuleList;
} PEB_LDR_DATA32, *PPEB_LDR_DATA32;

typedef struct _LDR_DATA_TABLE_ENTRY32
{
  LIST_ENTRY32 InLoadOrderLinks;
  LIST_ENTRY32 InMemoryOrderLinks;
  LIST_ENTRY32 InInitializationOrderLinks;
  ULONG DllBase;
  ULONG EntryPoint;
  ULONG SizeOfImage;
  UNICODE_STRING32 FullDllName;
  UNICODE_STRING32 BaseDllName;
  ULONG Flags;
  USHORT LoadCount;
  USHORT TlsIndex;
  LIST_ENTRY32 HashLinks;
  ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY32, *PLDR_DATA_TABLE_ENTRY32;

typedef struct _LDR_DATA_TABLE_ENTRY
{
  LIST_ENTRY InLoadOrderLinks;
  LIST_ENTRY InMemoryOrderLinks;
  LIST_ENTRY InInitializationOrderLinks;
  PVOID DllBase;
  PVOID EntryPoint;
  ULONG SizeOfImage;
  UNICODE_STRING FullDllName;
  UNICODE_STRING BaseDllName;
  ULONG Flags;
  USHORT LoadCount;
  USHORT TlsIndex;
  LIST_ENTRY HashLinks;
  ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

GetUserModuleBaseAddress(): 实现取进程中模块基址,该功能在《驱动开发:内核取应用层模块基地址》中详细介绍过原理,这段代码核心原理如下所示,此处最需要注意的是如果是32位进程则我们需要得到PPEB32 Peb32结构体,该结构体通常可以直接使用PsGetProcessWow64Process()这个内核函数获取到,而如果是64位进程则需要将寻找PEB的函数替换为PsGetProcessPeb(),其他的枚举细节与上一篇文章中的方法一致。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

#include <ntifs.h>
#include <windef.h>
#include "lyshark.h"

// 获取特定进程内特定模块的基址
PVOID GetUserModuleBaseAddress(IN PEPROCESS EProcess, IN PUNICODE_STRING ModuleName, IN BOOLEAN IsWow64)
{
	if (EProcess == NULL)
		return NULL;
	__try
	{
		// 设置延迟时间为250毫秒
		LARGE_INTEGER Time = { 0 };
		Time.QuadPart = -250ll * 10 * 1000;

		// 如果是32位则执行如下代码
		if (IsWow64)
		{
			// 得到PEB进程信息
			PPEB32 Peb32 = (PPEB32)PsGetProcessWow64Process(EProcess);
			if (Peb32 == NULL)
			{
				return NULL;
			}

			// 延迟加载等待时间
			for (INT i = 0; !Peb32->Ldr && i < 10; i++)
			{
				KeDelayExecutionThread(KernelMode, TRUE, &Time);
			}

			// 没有PEB加载超时
			if (!Peb32->Ldr)
			{
				return NULL;
			}

			// 搜索模块 InLoadOrderModuleList
			for (PLIST_ENTRY32 ListEntry = (PLIST_ENTRY32)((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList.Flink; ListEntry != &((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList; ListEntry = (PLIST_ENTRY32)ListEntry->Flink)
			{
				UNICODE_STRING UnicodeString;
				PLDR_DATA_TABLE_ENTRY32 LdrDataTableEntry32 = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks);
				RtlUnicodeStringInit(&UnicodeString, (PWCH)LdrDataTableEntry32->BaseDllName.Buffer);

				// 找到了返回模块基址
				if (RtlCompareUnicodeString(&UnicodeString, ModuleName, TRUE) == 0)
				{
					return (PVOID)LdrDataTableEntry32->DllBase;
				}
			}
		}
		// 如果是64位则执行如下代码
		else
		{
			// 同理,先找64位PEB
			PPEB Peb = PsGetProcessPeb(EProcess);
			if (!Peb)
			{
				return NULL;
			}

			// 延迟加载
			for (INT i = 0; !Peb->Ldr && i < 10; i++)
			{
				KeDelayExecutionThread(KernelMode, TRUE, &Time);
			}

			// 找不到PEB直接返回
			if (!Peb->Ldr)
			{
				return NULL;
			}

			// 遍历链表
			for (PLIST_ENTRY ListEntry = Peb->Ldr->InLoadOrderModuleList.Flink; ListEntry != &Peb->Ldr->InLoadOrderModuleList; ListEntry = ListEntry->Flink)
			{
				// 将特定链表转换为PLDR_DATA_TABLE_ENTRY格式
				PLDR_DATA_TABLE_ENTRY LdrDataTableEntry = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);

				// 找到了则返回地址
				if (RtlCompareUnicodeString(&LdrDataTableEntry->BaseDllName, ModuleName, TRUE) == 0)
				{
					return LdrDataTableEntry->DllBase;
				}
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		return NULL;
	}
	return NULL;
}

那么该函数该如何调用传递参数呢,如下代码是DriverEntry入口处的调用方法,首先要想得到特定进程的特定模块地址则第一步就是需要PsLookupProcessByProcessId找到模块的EProcess结构,接着通过PsGetProcessWow64Process得到当前被操作进程是32位还是64位,通过调用KeStackAttachProcess附加到进程内存中,然后调用GetUserModuleBaseAddress并传入需要获取模块的名字得到数据后返回给NtdllAddress变量,最后调用KeUnstackDetachProcess取消附加即可。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	HANDLE ProcessID = (HANDLE)7924;

	PEPROCESS EProcess = NULL;
	NTSTATUS Status = STATUS_SUCCESS;
	KAPC_STATE ApcState;

	DbgPrint("Hello LyShark.com \n");

	// 根据PID得到进程EProcess结构
	Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
	if (Status != STATUS_SUCCESS)
	{
		DbgPrint("获取EProcessID失败 \n");
		return Status;
	}

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

	// 验证地址是否可读
	if (!MmIsAddressValid(EProcess))
	{
		DbgPrint("地址不可读 \n");
		Driver->DriverUnload = UnDriver;
		return STATUS_SUCCESS;
	}

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

	__try
	{
		UNICODE_STRING NtdllUnicodeString = { 0 };
		PVOID NtdllAddress = NULL;

		// 得到进程内ntdll.dll模块基地址
		RtlInitUnicodeString(&NtdllUnicodeString, L"Ntdll.dll");
		NtdllAddress = GetUserModuleBaseAddress(EProcess, &NtdllUnicodeString, IsWow64);
		if (!NtdllAddress)
		{
			DbgPrint("没有找到基址 \n");
			Driver->DriverUnload = UnDriver;
			return STATUS_SUCCESS;
		}

		DbgPrint("[*] 模块ntdll.dll基址: %p \n", NtdllAddress);
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
	}

	// 取消附加
	KeUnstackDetachProcess(&ApcState);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

替换DriverEntry入口函数处的ProcessID并替换为当前需要获取的应用层进程PID,运行驱动程序即可得到该进程内Ntdll.dll的模块基址,输出效果如下;

驱动开发:取进程模块的函数地址

GetModuleExportAddress(): 实现获取特定模块中特定函数的基地址,通常我们通过GetUserModuleBaseAddress()可得到进程内特定模块的基址,然后则可继续通过GetModuleExportAddress()获取到该模块内特定导出函数的内存地址,至于获取导出表中特定函数的地址则可通过如下方式循环遍历导出表函数获取。

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com

// 获取特定模块下的导出函数地址
PVOID GetModuleExportAddress(IN PVOID ModuleBase, IN PCCHAR FunctionName, IN PEPROCESS EProcess)
{
	PIMAGE_DOS_HEADER ImageDosHeader = (PIMAGE_DOS_HEADER)ModuleBase;
	PIMAGE_NT_HEADERS32 ImageNtHeaders32 = NULL;
	PIMAGE_NT_HEADERS64 ImageNtHeaders64 = NULL;
	PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;
	ULONG ExportDirectorySize = 0;
	ULONG_PTR FunctionAddress = 0;

	// 为空则返回
	if (ModuleBase == NULL)
	{
		return NULL;
	}

	// 是不是PE文件
	if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
	{
		return NULL;
	}

	// 获取NT头
	ImageNtHeaders32 = (PIMAGE_NT_HEADERS32)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);
	ImageNtHeaders64 = (PIMAGE_NT_HEADERS64)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);

	// 是64位则执行
	if (ImageNtHeaders64->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
	{
		ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
		ExportDirectorySize = ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
	}
	// 是32位则执行
	else
	{
		ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
		ExportDirectorySize = ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
	}

	// 得到导出表地址偏移和名字
	PUSHORT pAddressOfOrds = (PUSHORT)(ImageExportDirectory->AddressOfNameOrdinals + (ULONG_PTR)ModuleBase);
	PULONG  pAddressOfNames = (PULONG)(ImageExportDirectory->AddressOfNames + (ULONG_PTR)ModuleBase);
	PULONG  pAddressOfFuncs = (PULONG)(ImageExportDirectory->AddressOfFunctions + (ULONG_PTR)ModuleBase);

	// 循环搜索导出表
	for (ULONG i = 0; i < ImageExportDirectory->NumberOfFunctions; ++i)
	{
		USHORT OrdIndex = 0xFFFF;
		PCHAR  pName = NULL;

		// 搜索导出表下标索引
		if ((ULONG_PTR)FunctionName <= 0xFFFF)
		{
			OrdIndex = (USHORT)i;
		}
		// 搜索导出表名字
		else if ((ULONG_PTR)FunctionName > 0xFFFF && i < ImageExportDirectory->NumberOfNames)
		{
			pName = (PCHAR)(pAddressOfNames[i] + (ULONG_PTR)ModuleBase);
			OrdIndex = pAddressOfOrds[i];
		}
		else
		{
			return NULL;
		}

		// 找到设置返回值并跳出
		if (((ULONG_PTR)FunctionName <= 0xFFFF && (USHORT)((ULONG_PTR)FunctionName) == OrdIndex + ImageExportDirectory->Base) || ((ULONG_PTR)FunctionName > 0xFFFF && strcmp(pName, FunctionName) == 0))
		{
			FunctionAddress = pAddressOfFuncs[OrdIndex] + (ULONG_PTR)ModuleBase;
			break;
		}
	}
	return (PVOID)FunctionAddress;
}

如何调用此方法,首先将ProcessID设置为需要读取的进程PID,然后将上图中所输出的0x00007FF9553C0000赋值给BaseAddress接着调用GetModuleExportAddress()并传入BaseAddress模块基址,需要读取的LdrLoadDll函数名,以及当前进程的EProcess结构。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	HANDLE ProcessID = (HANDLE)4144;
	PEPROCESS EProcess = NULL;
	NTSTATUS Status = STATUS_SUCCESS;

	// 根据PID得到进程EProcess结构
	Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
	if (Status != STATUS_SUCCESS)
	{
		DbgPrint("获取EProcessID失败 \n");
		return Status;
	}

	PVOID BaseAddress = (PVOID)0x00007FF9553C0000;
	PVOID RefAddress = 0;

	// 传入Ntdll.dll基址 + 函数名 得到该函数地址
	RefAddress = GetModuleExportAddress(BaseAddress, "LdrLoadDll", EProcess);
	DbgPrint("[*] 函数地址: %p \n", RefAddress);

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

运行这段程序,即可输出如下信息,此时也就得到了x64.exe进程内ntdll.dll模块里面的LdrLoadDll函数的内存地址,如下所示;

驱动开发:取进程模块的函数地址文章来源地址https://www.toymoban.com/news/detail-503918.html

到了这里,关于驱动开发:取进程模块的函数地址的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 驱动开发:内核实现进程汇编与反汇编

    在笔者上一篇文章 《驱动开发:内核MDL读写进程内存》 简单介绍了如何通过MDL映射的方式实现进程读写操作,本章将通过如上案例实现远程进程反汇编功能,此类功能也是ARK工具中最常见的功能之一,通常此类功能的实现分为两部分,内核部分只负责读写字节集,应用层部

    2024年02月06日
    浏览(41)
  • C++ Qt开发:QProcess进程管理模块

    Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用 QProcess 组件实现针对进程的控制管理等。 当你在使用Qt进行跨平台应

    2024年03月22日
    浏览(34)
  • 一篇读懂 Android 开发中模块化、组件化、插件化和热修复

    网上关于 “Android 开发\\\" 的文章很多,我本人学习 Android 开发的过程也借鉴了网上先辈们的文章;但大多数文章都从底层的细枝末节开始讲述,由下而上给人一种这门技术“博大精深”望而生畏的感觉;而我写这篇文章的初衷就是由上而下,希望别人在阅读的过程中能够觉得

    2023年04月08日
    浏览(40)
  • C++64位游戏软件安全汇编与反汇编反调试 x64驱动开发进程保护进程隐藏驱动读写过保护 视频教程

    ├─课程1 x32dbgx64dbg驱动调试器反反调试器驱动调试环境搭载 │ 1.为什么要搭载驱动调试环境.mp4 │ 2.驱动调试环境搭载1.mp4 │ 3.三种过PG的方法.mp4 │ 4.驱动调试环境搭载2.mp4 │ 5.驱动调试与驱动进程保护对抗.mp4 │ ├─课程2 C++64位游戏软件安全汇编与反汇编反调试2021年新

    2024年02月02日
    浏览(40)
  • Linux驱动开发——内核模块

    目录 内核模块的由来 第一个内核模块程序  内核模块工具  将多个源文件编译生成一个内核模块  内核模块参数 内核模块依赖 关于内核模块的进一步讨论  习题 最近一直在玩那些其它的技术,眼看快暑假了,我决定夯实一下我的驱动方面的技能,迎接我的实习,找了一本

    2024年02月04日
    浏览(83)
  • 驱动开发 day2 (led模块化驱动操控)

    head.h fun.c mycdev.c Makefile

    2024年02月15日
    浏览(51)
  • 2、Linux驱动开发:模块_引用符号

    🍅点击这里查看所有博文   随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有

    2024年02月15日
    浏览(43)
  • 1、Linux驱动开发:模块_加载卸载

    🍅点击这里查看所有博文   随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有

    2024年02月16日
    浏览(40)
  • 3、Linux驱动开发:模块_传递参数

    🍅点击这里查看所有博文   随着自己工作的进行,接触到的技术栈也越来越多。给我一个很直观的感受就是,某一项技术/经验在刚开始接触的时候都记得很清楚。往往过了几个月都会忘记的差不多了,只有经常会用到的东西才有可能真正记下来。存在很多在特殊情况下有

    2024年02月15日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包