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;}// 搜索模块 InLoadOrderModuleListfor (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位PEBPPEB 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函数的内存地址,如下所示;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/170011.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Debian 11.3 ARM64 安装中文语言包

文章目录 Debian 介绍1、执行命令2、语言选择3、修改设置 Debian 介绍 Debian是一种自由开源的操作系统&#xff0c;被广泛用于服务器、个人计算机和嵌入式设备。它是由全球志愿者组成的开发团队开发和维护的&#xff0c;以稳定性、安全性和自由性而闻名。 以下是一些关于Deb…

分块矩阵知识点整理:

1.分块方法&#xff1a;横竖线不能拐弯&#xff0c;思想为将矩阵分块看作向量计算 2.标准型 不一定是方的 特殊性&#xff1a;经过分块后会出现单位矩阵和0矩阵 3.分块矩阵的运算: 1.加减乘的运算与向量运算相同 4.分块矩阵求转置&#xff1a; 1.将子块看作普通元素求转置 2…

2017年4月10日 Go生态洞察:开发者体验工作组介绍

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

计算机应用基础_错题集_PPT演示文稿_操作题_计算机多媒体技术操作题_文字处理操作题---网络教育统考工作笔记007

PPT演示文稿操作题 提示:PPT部分操作题 将第2~第4张幻灯片背景效果设为渐变预置的“雨后初晴”效果(2)设置幻灯片放映方式

HTTP/2:多路复用、服务器推送和首部压缩的革命

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【小沐学写作】免费在线AI辅助写作汇总

文章目录 1、简介2、文涌Effidit&#xff08;腾讯&#xff09;2.1 工具简介2.2 工具功能2.3 工具体验 3、PPT小助手&#xff08;officeplus&#xff09;3.1 工具简介3.2 使用费用3.3 工具体验 4、DeepL Write&#xff08;仅英文&#xff09;4.1 工具简介4.2 工具体验 5、天工AI…

Linux学习记录

Linux 文章目录 LinuxLinux发行版Debian 分支Red Hat 分支Arch Linux 分支 服务器基础操作lscat和less设置权限删除文件和目录搜索文件 cmake使用文件 Linux发行版 一个典型的 Linux 发行版除了 Linux 内核以外&#xff0c;通常还会包括一系列 GNU 工具和库、一些附带的软件、…

单片机AT89C51直流电机控制电路PWM设计

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;直流电机 获取论文报告源码源程序原理图 此文将介绍一种直流电机&#xff0c;详细阐述了用单片机输出口所给占空比的不同实现电机的调速的设计方法&#xff1b;着重讨论L298用于电机驱动时特有的优势。直流电机调速具有…

docker devicemapper: Error running DeleteDevice dm_task_run failed

docker 删除容器&#xff0c;遇到&#xff1a; devicemapper: Error running DeleteDevice dm_task_run failed 异常 [hadoophadoop02 ~]$ sudo docker rm 5ede1280f0bf Error response from daemon: container 5ede1280f0bf791e91d40038b15decd42e8923546ae578abd96e08114c76…

Python基础语法之判断语句

1.布尔类型和比较运算符 布尔类型&#xff1a;数字类型的一种。 比较运算符&#xff1a; > < > < ! 2.if语句基本格式 if 要判断的条件&#xff1a; 条件成立&#xff0c;即做~ 例子&#xff1a; 注意&#xff1a;格式上冒号和缩进 3.if else组合…

2023年【道路运输企业安全生产管理人员】最新解析及道路运输企业安全生产管理人员复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 道路运输企业安全生产管理人员最新解析是安全生产模拟考试一点通总题库中生成的一套道路运输企业安全生产管理人员复审考试&#xff0c;安全生产模拟考试一点通上道路运输企业安全生产管理人员作业手机同步练习。2023…

matlab三维地形图

matlab三维地形图 %%%%—————Code to draw 3D bathymetry—————————— %-------Created by bobo,10/10/2021-------------------- clear;clc;close all; ncdisp E:\data\etopo\scs_etopo.nc filenmE:\data\etopo\scs_etopo.nc; londouble(ncread(filenm,lon)); lat…

DBeaver连接Oracle时报错:Undefined Error

连接信息检查了很多遍&#xff0c;应该是没问题的&#xff0c;而且驱动也正常下载了&#xff0c;但是就是连不上。 找了好久&#xff0c;终于找到一个可用的方式了&#xff0c;记录一下。 在安装目录修改dbeave.ini文件&#xff0c;最后一行添加 -Duser.nameTest。重启就可以…

如何使用Python在3dMax控制网格对象?

我们以一个在3dMax中使用Python脚本在网格对象对象上创建水波变形作为例子。 首先&#xff0c;在3dmax创建两个对象&#xff0c;一个“box”对象&#xff0c;将长宽方向的分段设置的多一些&#xff08;目的是为了后面的水波变形&#xff09;&#xff0c;一个“点”帮助对象&am…

Yakit工具篇:WebFuzzer模块之热加载技术

简介 官方定义&#xff1a; 什么是热加载&#xff1f; 广义上来说&#xff0c;热加载是一种允许在不停止或重启应用程序的情况下&#xff0c;动态加载或更新特定组件或模块的功能。这种技术常用于开发过程中&#xff0c;提高开发效率和用户体验。 在Yakit 的Web Fuzzer中&…

Docker 部署 Nacos(单机),利用 MySQL 数据库存储配置信息

前面的话 默认你已经懂 Docker、docker-compose Nacos版本&#xff1a;v2.2.3 MySQL 版本&#xff1a;8.2.0 一、下载 打开 Nacos 官网 官网地址&#xff1a;官网 点击手册 左侧 Nacos Docker 克隆项目到本地 # 克隆项目&#xff0c;如果提示连接不到 github 请自行解决 …

【Flutter】设置顶部状态栏的显示、隐藏、半透明灰色显示

【Flutter】设置顶部状态栏的显示、隐藏、半透明灰色显示 设置方法&#xff1a; // 这种模式不现实状态栏 SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); // 这种模式显示状态栏 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); // 修…

运行软件报错找不到vcruntime140_1.dll无法继续执行代码如何解决?-常见问题

关于vcruntime140_1.dll丢失的6个解决方法。在我们使用电脑的过程中&#xff0c;有时候会遇到一些错误提示&#xff0c;其中之一就是“vcruntime140_1.dll丢失”。那么&#xff0c;究竟什么是vcruntime140_1.dll文件呢&#xff1f;又是什么原因导致了它的丢失&#xff1f;接下来…

基于WSL2+Docker+VScode搭建机器学习(深度学习)开发环境

基于WSL2DockerVScode搭建机器学习(深度学习)开发环境 内容概述&#xff1a;由于最近配发了新的工作电脑但不想装双系统&#xff0c;因此通过本博文来记录基于Windows子系统WSLDocker搭建机器学习与深度学习开发环境的流程步骤&#xff0c;同时记录该过程中所遇到的相关问题及解…

交流充电桩与直流充电桩的区别

1、背景 直流充电桩的学名是非车载充电机&#xff0c;是相对于交流充电桩而言的。交流充电桩是采用传导方式为具备车载充电机的电动汽车提供交流电能的专用装置。 2、交流充电桩和直流充电桩 1.1、交流充电桩 交流充电桩包括单相和三相交流充电桩。 图一是交流充电桩原理框…