当前位置:首页 > Windows程序 > 正文

C# 遍历DLL导出函数

2021-05-24 Windows程序

C#如何去遍历一个由C++或E语言编写的本地DLL导出函数呢 不过在这里我建议对PE一无所知的人 

你或许应先补补这方面的知识,我不知道为什么PE方面的 应用在C#中怎么这么少,我查阅过相关

C#的知识大概只见一个人写过关于PE的应用 还只是从PE信息中判断执行文件是X86还是X64方式

编译,难道C#程序员真的很差 真的只能会点Asp.Net / MVC?想想看雪论坛那些玩inline-asm / 

inline-hook的牛牛 真是感到有很大差距 不过不论什么语言 在我看来其实都差不多 重点在于人是否

有心。虽然我不敢保证C#可以嵌入动态汇编(auto-asm)但是我可以保证C#可以做inline-hook虽然会

的人比较少,不过也还好,至少C#程序员不会是一群渣渣。不过我在写下述代码时,可是累得紧 写

结构体部分有些麻烦 而且C#与C++有些不同 当然也可以动态偏移地址搞定不过那个有些麻烦了,你

想推敲地址可不是那么好玩的事情,可能你自己推敲半天结果发现你推敲错了,那种方法用在结构体

层次较少的情况下的确可以提升逼格 反正别人看不懂就好嘛? 呵呵。下面的代码需要在X86的环境下

使用主要在于该代码中使用的PE信息全是32位的结构体而非64位的PE信息结构体 所以需要X86环境

不过不论是X86还是X64方法都是相等的,只是两者的结构体与对称不太一样而已。

技术分享


PE格式,是微软Win32环境可移植执行文件如(exe / sys / dll / vxd / vdm)等都是标准的文件格式

PE格式衍生于VAX / VMS上的COFF文件格式,Portable是指对于不同的Windows版本和不同的

CPU类型上PE文件的格式是一样的,或许CPU不一指令与二进制编码不一,但是文件中各种东

西的布局是一至的。

PE文件中第一个字节是MS-DOS信息头即IMAGE_DOS_HEADER与IMAGE_NT_HEADER中包

含许多PE装载器用到。

[STAThread] unsafe static void Main() { IntPtr hFileBase = Win32Native._lopen(@"C:/Windows/System32/ATL.dll", Win32Native.OF_SHARE_COMPAT); IntPtr hFileMapping = Win32Native.CreateFileMapping(hFileBase, Win32Native.NULL, Win32Native.PAGE_READONLY, 0, 0, null); IntPtr psDos32pe = Win32Native.MapViewOfFile(hFileMapping, Win32Native.FILE_MAP_READ, 0, 0, Win32Native.NULL); // e_lfanew 248 IMAGE_DOS_HEADER sDos32pe = (IMAGE_DOS_HEADER)Marshal.PtrToStructure(psDos32pe, typeof(IMAGE_DOS_HEADER)); IntPtr psNt32pe = (IntPtr)(sDos32pe.e_lfanew + (long)psDos32pe); IMAGE_NT_HEADERS sNt32pe = (IMAGE_NT_HEADERS)Marshal.PtrToStructure(psNt32pe, typeof(IMAGE_NT_HEADERS)); // 63 63 72 75 6E 2E 63 6F 6D IntPtr psExportDirectory = Win32Native.ImageRvaToVa(psNt32pe, psDos32pe, sNt32pe.OptionalHeader.ExportTable.VirtualAddress, Win32Native.NULL); IMAGE_EXPORT_DIRECTORY sExportDirectory = (IMAGE_EXPORT_DIRECTORY)Marshal.PtrToStructure(psExportDirectory, typeof(IMAGE_EXPORT_DIRECTORY)); IntPtr ppExportOfNames = Win32Native.ImageRvaToVa(psNt32pe, psDos32pe, sExportDirectory.AddressOfNames, Win32Native.NULL); for (uint i = 0, nNoOfExports = sExportDirectory.NumberOfNames; i < nNoOfExports; i++) { IntPtr pstrExportOfName = Win32Native.ImageRvaToVa(psNt32pe, psDos32pe, (uint)Marshal.ReadInt32(ppExportOfNames, (int)(i * 4)), Win32Native.NULL); Console.WriteLine(Marshal.PtrToStringAnsi(pstrExportOfName)); } Win32Native.UnmapViewOfFile(psDos32pe); Win32Native.CloseHandle(hFileMapping); Win32Native._lclose(hFileBase); Console.ReadKey(false); }

包含 入口点 Entry Point 

文件偏移地址 File Offset

虚拟地址 Virtual Address(VA)

地址 Image Base

相对虚拟地址 Relative Virual Address(RVA)

公式:RVA (相对虚拟地址) = VA(虚拟地址) - Image Base (基地址)

文件偏移地址和虚拟地址转换

在X86系统中,每个内存页的大小是4KB

文件偏移地址 File Offset = RVA(相对虚拟地址) - ΔK

文件偏移地址 File Offset = VA(虚拟地址) - Image Base (基地址) - ΔK

详细解释内容请参考百度百科,反正你想真正理解还需要自己去研究PE文件

IMAGE_NT_HEADERS在MS-DOS信息头后面它是标准的Win32执行文件信息头,其中包含了

导入的函数表,导出函数表,资源信息表、CLR运行时头,IAT、TLS表、包括调试信息 等等

我们现在要做的就是获取在DLL中导出的函数名,而DLL是属于标准Win32执行文件中的一种

那么我们则必须要获取到IMAGE_NT_HEADERS结构,实际上需要定位NT结构是很简单的,

因为在规定中NT信息头在DOS信息头后面,即IMAGE_DOS_HEADER.e_lfanew +  IMAGE_DOS_HEADER

所以你会看到我在代码中有这样一句话IntPtr psNt32pe = (IntPtr)(sDos32pe.e_lfanew + (long)psDos32pe);

IMAGE_OPTIONAL_HEADER可选映像头是一个可选结构,但是IMAGE_FILE_HEADER结构不满足PE文件

需求定义的属性,因此这些属性在OPTIONAL结构中定义,因此FILE+OPTIONAL两个结构联合起来 才是一

个完整的PE文件结构,在其中包含了很多重要的信息字段 如 AddressOfEntryPoint、DataDirectory、Subsystem

不过提到DataDirectory我想说一下,在C#中不好定义所以在代码中该字段换了另一种方式定义,DataDirectory

默认是有16个IMAGE_DATA_DIRECTORY的尺寸,所以在代码中你可以看到有很多该类型的定义。它们则是表

示DataDirectory中信息IMAGE_DIRECTORY_ENTRY_EXPORT导出表 我们现在只需要获取它的信息,在这里

我们需要用到ImageRvaToVa(相对虚拟地址到虚拟地址)有人是这样理解的, 物理地址到虚拟地址 不过原来我在

理解时这个地方也是小小纠结了一番,不过后来则释然了。ImageRvaToVa(NT_H, DOS_H, RVA, RvaSection);

IMAGE_DATA_DIRECTORY中包含两个字段,一个VirtualAddress(RVA)另一个为Size(尺寸)获取到结构体中的

RVA但是这个地址我们不管怎么转换都没法使用,对的因为提供给我的地址根本没法用 那么我们则需要把RVA

转换为VA利用上面提到函数,,只有默默的感谢微软一番 呵呵,当转换后会得到IMAGE_EXPORT_DIRECTORY

在这里我需要提示一下大家,不是每个DataDirectory包含的RVA对应的结构都是EXPORT每个都有自己独立的

解释结构,不要搞混了 不然肯定会飞高的。

我们需要IMAGE_EXPORT_DIRECTORY中NumberOfNames(函数名总数)与AddressOfNames(函数名地址)

两个字段中的内容,不过AddressOfNames中包含的是相对虚拟地址RVA,所以我们需要做一次转换,会返回有

char**的指针前提你提供的数据有效否则返回NULL,由于C#中你懂的char占两个字节,即char=wchar_t那么

我们查看指针中的数据肯定会有问题DLL导出函数名全部是Ascii编码,所以为了方便在C#专用干脆IntPtr方便通过

温馨提示: 本文由Jm博客推荐,转载请保留链接: https://www.jmwww.net/file/70219.html