驱动开发:取进程模块的函数地址 信息
(资料图片仅供参考)
在笔者上一篇文章《驱动开发:内核取应用层模块基地址》
中简单为大家介绍了如何通过遍历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 #include #include // 导出未导出函数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 #include #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
取消附加即可。
// 署名权// right to sign one"s name on a piece of work// PowerBy: LyShark// Email: me@lyshark.comNTSTATUS 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
函数的内存地址,如下所示;
-
驱动开发:取进程模块的函数地址 信息
博客园 2023-06-28
-
科力远:6月27日融资买入174.51万元,融资融券余额7.01亿元 当前短讯
证券之星 2023-06-28
-
快消息!成都“绿野仙踪”迎大运
成都日报 2023-06-28
-
天天速看:中金交运行业2023下半年展望:守正出奇 关注精选个股
金融界 2023-06-28
-
己土生于戌月 己土生于戌月身强-每日快播
互联网 2023-06-28
-
每日热点:故宫里,穿警服的他们比导游更懂历史
新京报 2023-06-28
-
环球焦点!北海攻略二日游_北海攻略
互联网 2023-06-28
-
6月27日基金净值:华夏中证1000ETF最新净值2.6182,涨1.35%
证券之星 2023-06-28
-
皇帝亲免的四川盐道是什么?
互联网 2023-06-28
-
qq空间关闭官方网址(qq空间关闭网址)
互联网 2023-06-27
-
驱动开发:取进程模块的函数地址 信息
博客园 2023-06-28
-
科力远:6月27日融资买入174.51万元,融资融券余额7.01亿元 当前短讯
证券之星 2023-06-28
-
快消息!成都“绿野仙踪”迎大运
成都日报 2023-06-28
-
天天速看:中金交运行业2023下半年展望:守正出奇 关注精选个股
金融界 2023-06-28
-
己土生于戌月 己土生于戌月身强-每日快播
互联网 2023-06-28
-
每日热点:故宫里,穿警服的他们比导游更懂历史
新京报 2023-06-28
-
环球焦点!北海攻略二日游_北海攻略
互联网 2023-06-28
-
6月27日基金净值:华夏中证1000ETF最新净值2.6182,涨1.35%
证券之星 2023-06-28
-
皇帝亲免的四川盐道是什么?
互联网 2023-06-28
-
qq空间关闭官方网址(qq空间关闭网址)
互联网 2023-06-27
-
万圣节那些幼儿园神级cosplay,让你知道什么才是真正的cos大佬|环球报道
生活有意义 2023-06-27
-
天天微资讯!两军对垒勇者胜什么意思_两军对垒
互联网 2023-06-27
-
环球最新:沈阳化工: 沈阳化工股份有限公司前期会计差错更正的专项报告
证券之星 2023-06-27
-
东西问丨刘和平:人工智能将如何影响翻译行业的未来?
中国新闻网 2023-06-27
-
环球速看:密战峨眉演员表角色介绍 密战峨眉演员表
城市网 2023-06-27
-
6月28号开始运势红到发紫,鸿运当头,3生肖发横财中大奖-环球关注
搜狐 2023-06-27
-
柴米夫妻,酒肉朋友,盒儿亲戚_关于柴米夫妻,酒肉朋友,盒儿亲戚概略 全球看点
互联网 2023-06-27
-
博创科技最新公告:股东拟减持不超1%公司股份_环球播报
证券之星 2023-06-27
-
突发,7人失联
广西法治日报 2023-06-27
-
继续闹!曼联球迷组织将在球衣发布会上举行抗议格雷泽活动|世界今亮点
手机网易网 2023-06-27
-
快报:哈尔滨打疫苗医院(哈尔滨宇哥:上海一医院8000元招疫苗接种志愿者)
互联网 2023-06-27
-
隐形车衣市场向好 新营销打造盾克品牌核心竞争力 每日速递
汽车时代网 2023-06-27
-
天天速看:人工智能正制造网络垃圾:低质量AI生成网站获广告支撑迅猛发展
澎湃新闻 2023-06-27
-
花旗银行董事总经理:作为银行需要给新经济业态提供负责的建议
每日经济新闻 2023-06-27
-
“走出去”+“引进来”八宝山街道守牢安全生产红线|环球热议
北京时间政务新媒体 2023-06-27
-
热门看点:五洲特纸负债率逾68%拟定增8.5亿全部补流 毛利率降至3.42%却计划净利增53%或难达成
湖北长江商报 2023-06-27
-
ST有棵树:目前亚马逊平台大额冻结资金仍未解冻 全球今日报
财联社 2023-06-27
-
豪华车品牌BBA放弃低端车型市场
中关村在线 2023-06-27
-
【速看料】数独怎么玩九宫格_数独怎么玩
互联网 2023-06-27
-
《南洋女儿情》金碧云为什么不让陆雪樵把股份转让给南兰?
搜视网 2023-06-27