在笔者上篇文章《驱动开发:内核扫描SSDT挂钩状态》中简单介绍了如何扫描被挂钩的SSDT函数,并简单介绍了如何解析导出表,本章将继续延申PE导出表的解析,实现一系列灵活的解析如通过传入函数名解析出函数的RVA偏移,ID索引,Index下标等参数,并将其封装为可直接使用的函数,以在后期需要时可以被直接引用,同样为了节约篇幅本章中的LoadKernelFile()内存映射函数如需要使用请去前一篇文章中自行摘取。

首先实现GetRvaFromModuleName()函数,当用户传入参数后自动将函数名解析为对应的RVA偏移或Index下标索引值,该函数接收三个参数传递,分别是wzFileName模块名,FunctionName所在模块内的函数名,Flag标志参数,函数输出ULONG64类型的数据。

  1. // 署名权
  2. // right to sign one's name on a piece of work
  3. // PowerBy: LyShark
  4. // Email: me@lyshark.com
  5. // 从指定模块中得到特定函数的RVA或相对序号相对偏移
  6. ULONG64 GetRvaFromModuleName(WCHAR *wzFileName, UCHAR *FunctionName, INT Flag)
  7. {
  8. // 加载内核模块
  9. PVOID BaseAddress = LoadKernelFile(wzFileName);
  10. // 取出导出表
  11. PIMAGE_DOS_HEADER pDosHeader;
  12. PIMAGE_NT_HEADERS pNtHeaders;
  13. PIMAGE_SECTION_HEADER pSectionHeader;
  14. ULONGLONG FileOffset;
  15. PIMAGE_EXPORT_DIRECTORY pExportDirectory;
  16. // DLL内存数据转成DOS头结构
  17. pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
  18. // 取出PE头结构
  19. pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);
  20. // 判断PE头导出表是否为空
  21. if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
  22. {
  23. return 0;
  24. }
  25. // 取出导出表偏移
  26. FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
  27. // 取出节头结构
  28. pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
  29. PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
  30. // 遍历节结构进行地址运算
  31. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  32. {
  33. if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  34. {
  35. FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  36. }
  37. }
  38. // 导出表地址
  39. pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);
  40. // 取出导出表函数地址
  41. PULONG AddressOfFunctions;
  42. FileOffset = pExportDirectory->AddressOfFunctions;
  43. // 遍历节结构进行地址运算
  44. pSectionHeader = pOldSectionHeader;
  45. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  46. {
  47. if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  48. {
  49. FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  50. }
  51. }
  52. AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
  53. // 取出导出表函数名字
  54. PUSHORT AddressOfNameOrdinals;
  55. FileOffset = pExportDirectory->AddressOfNameOrdinals;
  56. // 遍历节结构进行地址运算
  57. pSectionHeader = pOldSectionHeader;
  58. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  59. {
  60. if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  61. {
  62. FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  63. }
  64. }
  65. AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);
  66. // 取出导出表函数序号
  67. PULONG AddressOfNames;
  68. FileOffset = pExportDirectory->AddressOfNames;
  69. // 遍历节结构进行地址运算
  70. pSectionHeader = pOldSectionHeader;
  71. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  72. {
  73. if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  74. {
  75. FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  76. }
  77. }
  78. AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
  79. // 分析导出表
  80. ULONG uOffset;
  81. LPSTR FunName;
  82. ULONG uAddressOfNames;
  83. ULONG TargetOff = 0;
  84. for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
  85. {
  86. uAddressOfNames = *AddressOfNames;
  87. pSectionHeader = pOldSectionHeader;
  88. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  89. {
  90. if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  91. {
  92. uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  93. }
  94. }
  95. FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);
  96. // 如果找到则返回RVA
  97. if (!_stricmp((const char *)FunctionName, FunName))
  98. {
  99. // 等于1则返回RVA
  100. if (Flag == 1)
  101. {
  102. TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
  103. // DbgPrint("索引 [ %p ] 函数名 [ %s ] 相对RVA [ %p ] \n", *AddressOfNameOrdinals, FunName, TargetOff);
  104. return TargetOff;
  105. }
  106. // 返回索引
  107. else if (Flag == 0)
  108. {
  109. return *AddressOfNameOrdinals;
  110. }
  111. }
  112. }
  113. // 结束后释放内存
  114. ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
  115. return 0;
  116. }

调用该函数很容易,传入模块路径以及该模块内的函数名,解析出RVA地址或Index下标。

  1. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  2. {
  3. // 函数分别传入 [模块路径,函数名,标志=1] 返回该导出函数的RVA
  4. ULONG64 get_rva = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 1);
  5. DbgPrint("NtReadFile RVA = %p \n", get_rva);
  6. // 函数分别传入 [模块路径,函数名,标志=0] 返回该导出函数的ID下标
  7. ULONG64 get_id = GetRvaFromModuleName(L"\\SystemRoot\\system32\\ntoskrnl.exe", "NtReadFile", 0);
  8. DbgPrint("NtReadFile ID = %d \n", get_id);
  9. Driver->DriverUnload = UnDriver;
  10. return STATUS_SUCCESS;
  11. }

编译并运行程序,分别获取到ntoskrnl.exe模块内NtReadFile函数的RVA,Index索引,调用效果如下;

第二个函数GetModuleNameFromRVA()则实现传入RVA或者函数Index序号,解析出函数名,具体实现方法与如上函数基本一致,仅仅只是在过滤时做了调整。

  1. // 署名权
  2. // right to sign one's name on a piece of work
  3. // PowerBy: LyShark
  4. // Email: me@lyshark.com
  5. // 根据传入的函数RVA或Index下标,获取该函数的函数名
  6. PCHAR GetModuleNameFromRVA(WCHAR *wzFileName, ULONG64 uRVA, INT Flag)
  7. {
  8. // 加载内核模块
  9. PVOID BaseAddress = LoadKernelFile(wzFileName);
  10. // 取出导出表
  11. PIMAGE_DOS_HEADER pDosHeader;
  12. PIMAGE_NT_HEADERS pNtHeaders;
  13. PIMAGE_SECTION_HEADER pSectionHeader;
  14. ULONGLONG FileOffset;
  15. PIMAGE_EXPORT_DIRECTORY pExportDirectory;
  16. // DLL内存数据转成DOS头结构
  17. pDosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
  18. // 取出PE头结构
  19. pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)BaseAddress + pDosHeader->e_lfanew);
  20. // 判断PE头导出表是否为空
  21. if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
  22. {
  23. return 0;
  24. }
  25. // 取出导出表偏移
  26. FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
  27. // 取出节头结构
  28. pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
  29. PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
  30. // 遍历节结构进行地址运算
  31. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  32. {
  33. if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  34. {
  35. FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  36. }
  37. }
  38. // 导出表地址
  39. pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)BaseAddress + FileOffset);
  40. // 取出导出表函数地址
  41. PULONG AddressOfFunctions;
  42. FileOffset = pExportDirectory->AddressOfFunctions;
  43. // 遍历节结构进行地址运算
  44. pSectionHeader = pOldSectionHeader;
  45. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  46. {
  47. if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  48. {
  49. FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  50. }
  51. }
  52. AddressOfFunctions = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
  53. // 取出导出表函数名字
  54. PUSHORT AddressOfNameOrdinals;
  55. FileOffset = pExportDirectory->AddressOfNameOrdinals;
  56. // 遍历节结构进行地址运算
  57. pSectionHeader = pOldSectionHeader;
  58. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  59. {
  60. if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  61. {
  62. FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  63. }
  64. }
  65. AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)BaseAddress + FileOffset);
  66. // 取出导出表函数序号
  67. PULONG AddressOfNames;
  68. FileOffset = pExportDirectory->AddressOfNames;
  69. // 遍历节结构进行地址运算
  70. pSectionHeader = pOldSectionHeader;
  71. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  72. {
  73. if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  74. {
  75. FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  76. }
  77. }
  78. AddressOfNames = (PULONG)((ULONGLONG)BaseAddress + FileOffset);
  79. // 分析导出表
  80. ULONG uOffset;
  81. LPSTR FunName;
  82. ULONG uAddressOfNames;
  83. ULONG TargetOff = 0;
  84. for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
  85. {
  86. uAddressOfNames = *AddressOfNames;
  87. pSectionHeader = pOldSectionHeader;
  88. for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
  89. {
  90. if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
  91. {
  92. uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
  93. }
  94. }
  95. FunName = (LPSTR)((ULONGLONG)BaseAddress + uOffset);
  96. TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
  97. // 等于1则通过RVA返回函数名
  98. if (Flag == 1)
  99. {
  100. if (uRVA == TargetOff)
  101. {
  102. return FunName;
  103. }
  104. }
  105. // 返回索引
  106. else if (Flag == 0)
  107. {
  108. if (uRVA == *AddressOfNameOrdinals)
  109. {
  110. return FunName;
  111. }
  112. }
  113. }
  114. // 结束后释放内存
  115. ExFreePoolWithTag(BaseAddress, (ULONG)"LyShark");
  116. return "None";
  117. }

调用GetModuleNameFromRVA()并传入相应的RVA偏移或Index下标。

  1. NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
  2. {
  3. DbgPrint("hello lyshark.com \n");
  4. PCHAR function_name;
  5. // 传入函数RVA得到函数名
  6. function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 0x5e5220, 1);
  7. DbgPrint("根据RVA得到函数名 = %s \n", function_name);
  8. // 传入函数下标得到函数名
  9. function_name = GetModuleNameFromRVA(L"\\SystemRoot\\system32\\ntoskrnl.exe", 1472, 0);
  10. DbgPrint("根据Index得到函数名 = %s \n", function_name);
  11. Driver->DriverUnload = UnDriver;
  12. return STATUS_SUCCESS;
  13. }

编译并运行程序,调用后分别获取到RVA=0x5e5220Index=1472的函数名;

驱动开发:PE导出函数与RVA转换的更多相关文章

  1. Windows PE导出表编程3(暴力覆盖导出函数)

    今天要尝试的导出表相关编程内容是:覆盖函数地址部分的指令代码. 这种覆盖技术,是将AddressOfFunctions指向的地址空间指令字节码实施覆盖,这种技术又繁衍出两种: 暴力覆盖,即将所有的代码 ...

  2. 驱动开发:内核特征码扫描PE代码段

    在笔者上一篇文章<驱动开发:内核特征码搜索函数封装>中为了定位特征的方便我们封装实现了一个可以传入数组实现的SearchSpecialCode定位函数,该定位函数其实还不能算的上简单,本章 ...

  3. 驱动开发:内核层InlineHook挂钩函数

    在上一章<驱动开发:内核LDE64引擎计算汇编长度>中,LyShark教大家如何通过LDE64引擎实现计算反汇编指令长度,本章将在此基础之上实现内联函数挂钩,内核中的InlineHook函 ...

  4. C# 遍历DLL导出函数

    C#如何去遍历一个由C++或E语言编写的本地DLL导出函数呢 不过在这里我建议对PE一无所知的人 你或许应先补补这方面的知识,我不知道为什么PE方面的 应用在C#中怎么这么少,我查阅过相关 C#的知识 ...

  5. Linux 驱动开发

    linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...

  6. C#调用c++类的导出函数

    C# 需要调用C++东西,但是有不想做成COM,就只好先导出类中的函数处理. 不能直接调用,需单独导出函数 参考:http://blog.csdn.net/cartzhang/article/deta ...

  7. 驱动开发:内核枚举DpcTimer定时器

    在笔者上一篇文章<驱动开发:内核枚举IoTimer定时器>中我们通过IoInitializeTimer这个API函数为跳板,向下扫描特征码获取到了IopTimerQueueHead也就是I ...

  8. Linux驱动开发概述

    原文出处:http://www.cnblogs.com/jacklu/p/4722563.html Linux设备分类 设备的驱动程序也要像裸机程序那样进行一些硬件操作,不同的是驱动程序需要" ...

  9. 【转】基于V4L2的视频驱动开发

    编写基于V4L2视频驱动主要涉及到以下几个知识点:1> 摄像头方面的知识 要了解选用的摄像头的特性,包括访问控制方法.各种参数的配置方法.信号输出类型等.2> Camera解码器.控制器 ...

  10. DLL的导出函数重定向机制

    曾经,调试时跟进HeapAlloc,结果发现直接进入到ntdll的RtlAllocateHeap中,感到很有趣,就使用Dependency Walker查看kernel32.dll的导出函数,结果发现 ...

随机推荐

  1. java多线程基础小白指南--线程的状态

    线程的状态比较混乱,网上的资料也是五花八门,这时候就要参考 注意截图中的最后一句话,很多人把jvm中线程状态与实际上的os线程状态搞混了,所以才会有很多乱七八糟的状态出现. 注意Runnable其实含 ...

  2. STM32 HAL库学习 (3) 中断!

        中断在单片机开发中有着重中之重的地位.    中断即打断,实至CPU再执行当前程序时,由于系统出现了某种需要处理的紧急情况,CPU暂停正在执行的程序,转而去执行另一段特殊程序来处理的出现的紧急 ...

  3. ACM-NEFUOJ-P209湖南修路

    思路 prim的最小生成树,套上肝就完事了 代码 #include<iostream> #include<cstdio> #include<string.h> #d ...

  4. SimpleAdmin手摸手教学之:项目架构设计2.0

    一.说明 在SimpleAdmin1.0版本中,我将整体项目结构分为三大块,分别为架构核心.业务模块和应用服务.随着1.0版本的封版,回去再看我之前的项目架构,也暴露了一些问题,比如在1.0版本中,S ...

  5. Mybatis 框架下 SQL 注入攻击的方式

    前言 SQL注入漏洞作为WEB安全的最常见的漏洞之一,在java中随着预编译与各种ORM框架的使用,注入问题也越来越少. 新手代码审计者往往对Java Web应用的多个框架组合而心生畏惧,不知如何下手 ...

  6. 部署:keepalived-1.3.5+MHA部署mysql集群

    MHA: MHA工作原理总结为以下几条: 从宕机崩溃的master保存二进制日志事件(binlog events): 识别含有最新更新的slave: 应用差异的中继日志(relay log)到其他sl ...

  7. The first week match's conclusion

    自我声讨(不是 这周比赛有难也有易,但是我都是写得很少,摸鱼实在太严重,当然技术不到位也是一个方面,主要还是自己的问题.不再讨论 这周比赛学到.用到的的语法如下 快读 int read() { int ...

  8. 2023高效的mysql 随机语句 200万数据为例 用了 0.0030秒

    是的,如果数据表中有200万条记录,使用 ORDER BY RAND() 这种方式来随机选择记录会非常慢,因为 MySQL 需要对整个表进行排序,然后再返回指定数量的记录.这个过程需要消耗大量的时间和 ...

  9. [备份]Open-CMSIS-Pack Flash Programing

    Flash Programming Flash Programming Algorithms are a piece of software to erase or download applicat ...

  10. Sentinel实战

    一.Sentinel简介 Sentinel是阿里开源的面向服务流量治理的框架,官方原文是Sentinel 是面向分布式.多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由.流量控制.流 ...