驱动开发:PE导出函数与RVA转换
在笔者上篇文章《驱动开发:内核扫描SSDT挂钩状态》中简单介绍了如何扫描被挂钩的SSDT函数,并简单介绍了如何解析导出表,本章将继续延申PE导出表的解析,实现一系列灵活的解析如通过传入函数名解析出函数的RVA偏移,ID索引,Index下标等参数,并将其封装为可直接使用的函数,以在后期需要时可以被直接引用,同样为了节约篇幅本章中的LoadKernelFile()内存映射函数如需要使用请去前一篇文章中自行摘取。
首先实现GetRvaFromModuleName()函数,当用户传入参数后自动将函数名解析为对应的RVA偏移或Index下标索引值,该函数接收三个参数传递,分别是wzFileName模块名,FunctionName所在模块内的函数名,Flag标志参数,函数输出ULONG64类型的数据。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
// 从指定模块中得到特定函数的RVA或相对序号相对偏移
ULONG64 GetRvaFromModuleName(WCHAR *wzFileName, UCHAR *FunctionName, INT Flag)
{
// 加载内核模块
PVOID BaseAddress = LoadKernelFile(wzFileName);
// 取出导出表
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSectionHeader;
ULONGLONG FileOffset;
PIMAGE_EXPORT_DIRECTORY pExportDirectory;
// DLL内存数据转成DOS头结构
pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
// 取出PE头结构
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);
// 判断PE头导出表是否为空
if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
{
return 0;
}
// 取出导出表偏移
FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
// 取出节头结构
pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
// 遍历节结构进行地址运算
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 导出表地址
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数地址
PULONG AddressOfFunctions;
FileOffset = pExportDirectory->AddressOfFunctions;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数名字
PUSHORT AddressOfNameOrdinals;
FileOffset = pExportDirectory->AddressOfNameOrdinals;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数序号
PULONG AddressOfNames;
FileOffset = pExportDirectory->AddressOfNames;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 分析导出表
ULONG uOffset;
LPSTR FunName;
ULONG uAddressOfNames;
ULONG TargetOff = 0;
for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
{
uAddressOfNames = *AddressOfNames;
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);
// 如果找到则返回RVA
if (!_stricmp((const char *)FunctionName, FunName))
{
// 等于1则返回RVA
if (Flag == 1)
{
TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
// DbgPrint("索引 [ %p ] 函数名 [ %s ] 相对RVA [ %p ] \n", *AddressOfNameOrdinals, FunName, TargetOff);
return TargetOff;
}
// 返回索引
else if (Flag == 0)
{
return *AddressOfNameOrdinals;
}
}
}
// 结束后释放内存
ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
return 0;
}
调用该函数很容易,传入模块路径以及该模块内的函数名,解析出RVA地址或Index下标。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
// 函数分别传入 [模块路径,函数名,标志=1] 返回该导出函数的RVA
ULONG64 get_rva = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 1);
DbgPrint("NtReadFile RVA = %p \n", get_rva);
// 函数分别传入 [模块路径,函数名,标志=0] 返回该导出函数的ID下标
ULONG64 get_id = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 0);
DbgPrint("NtReadFile ID = %d \n", get_id);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
编译并运行程序,分别获取到ntoskrnl.exe模块内NtReadFile函数的RVA,Index索引,调用效果如下;

第二个函数GetModuleNameFromRVA()则实现传入RVA或者函数Index序号,解析出函数名,具体实现方法与如上函数基本一致,仅仅只是在过滤时做了调整。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
// 根据传入的函数RVA或Index下标,获取该函数的函数名
PCHAR GetModuleNameFromRVA(WCHAR *wzFileName, ULONG64 uRVA, INT Flag)
{
// 加载内核模块
PVOID BaseAddress = LoadKernelFile(wzFileName);
// 取出导出表
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSectionHeader;
ULONGLONG FileOffset;
PIMAGE_EXPORT_DIRECTORY pExportDirectory;
// DLL内存数据转成DOS头结构
pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
// 取出PE头结构
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);
// 判断PE头导出表是否为空
if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
{
return 0;
}
// 取出导出表偏移
FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
// 取出节头结构
pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
// 遍历节结构进行地址运算
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 导出表地址
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数地址
PULONG AddressOfFunctions;
FileOffset = pExportDirectory->AddressOfFunctions;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数名字
PUSHORT AddressOfNameOrdinals;
FileOffset = pExportDirectory->AddressOfNameOrdinals;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);
// 取出导出表函数序号
PULONG AddressOfNames;
FileOffset = pExportDirectory->AddressOfNames;
// 遍历节结构进行地址运算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
// 分析导出表
ULONG uOffset;
LPSTR FunName;
ULONG uAddressOfNames;
ULONG TargetOff = 0;
for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
{
uAddressOfNames = *AddressOfNames;
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);
TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
// 等于1则通过RVA返回函数名
if (Flag == 1)
{
if (uRVA == TargetOff)
{
return FunName;
}
}
// 返回索引
else if (Flag == 0)
{
if (uRVA == *AddressOfNameOrdinals)
{
return FunName;
}
}
}
// 结束后释放内存
ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
return "None";
}
调用GetModuleNameFromRVA()并传入相应的RVA偏移或Index下标。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
PCHAR function_name;
// 传入函数RVA得到函数名
function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 0x5e5220, 1);
DbgPrint("根据RVA得到函数名 = %s \n", function_name);
// 传入函数下标得到函数名
function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 1472, 0);
DbgPrint("根据Index得到函数名 = %s \n", function_name);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
编译并运行程序,调用后分别获取到RVA=0x5e5220或Index=1472的函数名;

驱动开发:PE导出函数与RVA转换的更多相关文章
- Windows PE导出表编程3(暴力覆盖导出函数)
今天要尝试的导出表相关编程内容是:覆盖函数地址部分的指令代码. 这种覆盖技术,是将AddressOfFunctions指向的地址空间指令字节码实施覆盖,这种技术又繁衍出两种: 暴力覆盖,即将所有的代码 ...
- 驱动开发:内核特征码扫描PE代码段
在笔者上一篇文章<驱动开发:内核特征码搜索函数封装>中为了定位特征的方便我们封装实现了一个可以传入数组实现的SearchSpecialCode定位函数,该定位函数其实还不能算的上简单,本章 ...
- 驱动开发:内核层InlineHook挂钩函数
在上一章<驱动开发:内核LDE64引擎计算汇编长度>中,LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的InlineHook函 ...
- C# 遍历DLL导出函数
C#如何去遍历一个由C++或E语言编写的本地DLL导出函数呢 不过在这里我建议对PE一无所知的人 你或许应先补补这方面的知识,我不知道为什么PE方面的 应用在C#中怎么这么少,我查阅过相关 C#的知识 ...
- Linux 驱动开发
linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...
- C#调用c++类的导出函数
C# 需要调用C++东西,但是有不想做成COM,就只好先导出类中的函数处理. 不能直接调用,需单独导出函数 参考:http://blog.csdn.net/cartzhang/article/deta ...
- 驱动开发:内核枚举DpcTimer定时器
在笔者上一篇文章<驱动开发:内核枚举IoTimer定时器>中我们通过IoInitializeTimer这个API函数为跳板,向下扫描特征码获取到了IopTimerQueueHead也就是I ...
- Linux驱动开发概述
原文出处:http://www.cnblogs.com/jacklu/p/4722563.html Linux设备分类 设备的驱动程序也要像裸机程序那样进行一些硬件操作,不同的是驱动程序需要" ...
- 【转】基于V4L2的视频驱动开发
编写基于V4L2视频驱动主要涉及到以下几个知识点:1> 摄像头方面的知识 要了解选用的摄像头的特性,包括访问控制方法.各种参数的配置方法.信号输出类型等.2> Camera解码器.控制器 ...
- DLL的导出函数重定向机制
曾经,调试时跟进HeapAlloc,结果发现直接进入到ntdll的RtlAllocateHeap中,感到很有趣,就使用Dependency Walker查看kernel32.dll的导出函数,结果发现 ...
随机推荐
- 了解 BASH
管理整个计算机硬件的其实是操作系统的核心(kernel),这个核心是需要被保护的.所以使用者就只能通过shell来跟核心沟通,以让核心达到想要的工作.那么系统中有多少 shell?为什么要使用 bas ...
- Markdown/Latex常用数学公式语法
0. 写在前面:MarkDown快捷键总结 名称 语法 快捷键 标题 用#号表示,#一级标题,##表示二级标题,依次类推 Ctrl+1.2.3.4 字体加粗 左右用**包裹起来 Ctrl+B 斜体字 ...
- ChatGPT推荐最常用的自动化测试、性能、安全测试工具!
ChatGPT是一种当前被广泛关注的人工智能技术,它具备生成自然语言的能力,能够完成一些简单的文本生成.对话交互等任务.ChatGPT 算法的出现,打破了以前自然语言处理的瓶颈,使得机器具备了更加贴合 ...
- Java——多线程(代码)
例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式 * 1.卖票过程中出现重票.错票 --->出现了线程的安全问题 * 2.问题出现的原因:当某个线程操作车票的 ...
- 京东LBS推荐算法实践
作者:京东零售 郑书剑 1.推荐LBS业务介绍 1.1 业务场景 现有的同城购业务围绕京东即时零售能力搭建了到店.到家两种业务场景.同城业务与现有业务进行互补,利用高频,时效性快的特点,可以有效提升主 ...
- golang常用库包:log日志记录-uber的Go日志库zap使用详解
Go 日志记录库:uber-go 的日志操作库 zap 使用 一.简介 zap 是 uber 开源的一个高性能,结构化,分级记录的日志记录包. go1.20.2 zap v1.24.0 zap的特性 ...
- super 与 this 关键字
super与this用法相似: 1.普通的直接引用 2.形参与成员名字重名,用 this 来指代类本身,super指代父类 public class Students extends Person { ...
- 【Java SE】网络编程
1. 网络编程概述 网络编程的目的:直接或者间接地通过网络协议与其他计算机实现数据交换,进行通讯. 网络编程两个主要的问题: ①如何精准地定位网络上的一台或多台主机,并定位主机上的特定应用 ②找到主机 ...
- golang 必会之 pprof 监控系列(5) —— cpu 占用率 统计原理
golang pprof 监控系列(5) -- cpu 占用率 统计原理 大家好,我是蓝胖子. 经过前面的几节对pprof的介绍,对pprof统计的原理算是掌握了七八十了,我们对memory,bloc ...
- Django相关配置信息
Django相关配置信息 1.配置数据库mysql 1.1 setting.py中配置信息 DATABASES = { 'default': { 'ENGINE': 'django.db.backen ...