5.7 Windows驱动开发:取进程模块函数地址
在笔者上一篇文章《内核取应用层模块基地址》中简单为大家介绍了如何通过遍历PLIST_ENTRY32链表的方式获取到32位应用程序中特定模块的基地址,由于是入门系列所以并没有封装实现太过于通用的获取函数,本章将继续延申这个话题,并依次实现通用版GetUserModuleBaseAddress()取远程进程中指定模块的基址和GetModuleExportAddress()取远程进程中特定模块中的函数地址,此类功能也是各类安全工具中常用的代码片段。
首先封装一个lyshark.h头文件,此类头文件中的定义都是微软官方定义好的规范,如果您想获取该结构的详细说明文档请参阅微软官方,此处不做过多的介绍。
#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;
5.7.1 取进程中模块基址
GetUserModuleBaseAddress 取进程中模块基址,该功能在《内核取应用层模块基地址》中详细介绍过原理,这段代码核心原理如下所示,此处最需要注意的是如果是32位进程则我们需要得到PPEB32 Peb32结构体,该结构体通常可以直接使用PsGetProcessWow64Process()这个内核函数获取到,而如果是64位进程则需要将寻找PEB的函数替换为PsGetProcessPeb(),其他的枚举细节与上一篇文章中的方法一致。
#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取消附加即可。
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 \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的模块基址,输出效果如下;

5.7.2 取模块内函数基址
GetModuleExportAddress 实现获取特定模块中特定函数的基地址,通常我们通过GetUserModuleBaseAddress()可得到进程内特定模块的基址,然后则可继续通过GetModuleExportAddress()获取到该模块内特定导出函数的内存地址,至于获取导出表中特定函数的地址则可通过如下方式循环遍历导出表函数获取。
// 获取特定模块下的导出函数地址
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函数的内存地址,如下所示;

5.7 Windows驱动开发:取进程模块函数地址的更多相关文章
- C++第三十三篇 -- 研究一下Windows驱动开发(一)内部构造介绍
因为工作原因,需要做一些与网卡有关的测试,其中涉及到了驱动这一块的知识,虽然程序可以运行,但是不搞清楚,心里总是不安,觉得没理解清楚.因此想看一下驱动开发.查了很多资料,看到有人推荐Windows驱动 ...
- Windows驱动开发(中间层)
Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...
- [Windows驱动开发](一)序言
笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...
- windows 驱动开发入门——驱动中的数据结构
最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书--<独钓寒江 windows安全编程> 和 <windows驱动 ...
- Windows驱动开发-IRP的完成例程
<Windows驱动开发技术详解 >331页, 在将IRP发送给底层驱动或其他驱动之前,可以对IRP设置一个完成例程,一旦底层驱动将IRP完成后,IRP完成例程立刻被处罚,通过设置完成例程 ...
- windows驱动开发推荐书籍
[作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...
- Windows 驱动开发 - 5
上篇<Windows 驱动开发 - 4>我们已经完毕了硬件准备. 可是我们还没有详细的数据操作,比如接收读写操作. 在WDF中进行此类操作前须要进行设备的IO控制,已保持数据的完整性. 我 ...
- Windows驱动——读书笔记《Windows驱动开发技术详解》
=================================版权声明================================= 版权声明:原创文章 谢绝转载 请通过右侧公告中的“联系邮 ...
- Windows 驱动开发 - 7
在<Windows 驱动开发 - 5>我们所说的读写操作在本篇实现. 在WDF中实现此功能主要为:EvtIoRead和EvtIoWrite. 首先,在EvtDeviceAdd设置以上两个回 ...
- Windows 驱动开发 - 8
最后的一点开发工作:跟踪驱动. 一.驱动跟踪 1. 包括TMH头文件 #include "step5.tmh" 2. 初始化跟踪 在DriverEntry中初始化. WPP_INI ...
随机推荐
- Vue2--入门学习
看了慕课网的教学视频,觉得挺不错的,在此做个随堂记录,有兴趣的可以去看视频 vue版本:2.5 文档链接:https://v2.cn.vuejs.org/v2/guide/installation.h ...
- 【网络爬虫学习】Python 爬虫初步
本系列基于 C语言中文网的 Python爬虫教程(从入门到精通)来进行学习的, 部分转载的文章内容仅作学习使用! 前言 网络爬虫又称网络蜘蛛.网络机器人,它是一种按照一定的规则自动浏览.检索网页信息的 ...
- POJ 1456 Supermarket【贪心 + 并查集】
http://poj.org/problem?id=1456 题意:给你 N 件不同的商品,每件商品最多可以买一次.每件物品对应两个值 pi di pi 表示物品的价值,di 表示可以买的最迟时间(也 ...
- 活动回顾|阿里云 Serverless 技术实战与创新上海站回放&PPT下载
5月27日"阿里云 Serverless 技术实战与创新"上海站圆满落幕.活动现场邀请了来自阿里云 一线技术专家,分享当前 Serverless 趋势和落地实践过程中的挑战和机遇: ...
- Element 动态表头渲染表格
element 中的table表头动态渲染 https://blog.csdn.net/heixiuheixiu666/article/details/104705024/ Element 动态表头渲 ...
- C语言基础之理论概述
C语言介绍 C语言是一种高级程序设计语言,由贝尔实验室的Dennis Ritchie在1972年开发.C语言是结构化编程语言,支持变量.数据类型.运算符.表达式.流程控制语句和函数等基本程序设计元素. ...
- C++ std::initializer_list 实现原理勘误
今天正在看侯捷<C++ 新标准 C++11-14>的视频,里面讲到 std::initializer_list 的实现原理,并且把源码贴出来. /// initializer_list t ...
- P5707 【深基2.例12】上学迟到
1.题目介绍 2.题解 这里只有两个稍微注意的点 2.1 s % v != 0(向上取整) 这里的话,若是结果不为整数,我们必须向上取整,必须保证空余时间永远大于所需时间! 2.2 ceil向上取整函 ...
- 【linux】虚拟机 ubuntu 使用 sudo apt-get install 安装软件出现 “Unable to locate package xxx ”解决方法
使用 sudo apt-get install 安装软件出现如下错误 上述错误表示找不到软件源,可更改软件源服务器解决 还有工具链 arm-none-eabi-gcc 实际安装的是 sudo apt ...
- ZHS16GBK字符集下面Oracle数据库varchar与nvarchar的验证
ZHS16GBK字符集下面Oracle数据库varchar与nvarchar的验证 背景 周末分析了 SQLServer mysql等数据库 想着继续分析一下oracle数据库 这边oracle使用的 ...