C# 遍历DLL导出函数

2023-06-08,,

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方便通过

Marshal进行转换最后只是进行一个资源释放的操作好了基本就是这个样子剩下的还需要大家自己去理解多说无益

    using System;
    using System.Runtime.InteropServices;
    // #include "stdafx.h"
    // #include <ImageHlp.h>
    // #include <Windows.h>
    // #pragma comment(lib, "ImageHlp.lib")
    static partial class Win32Native
    {
    [DllImport("dbghelp", SetLastError = true)] // PIMAGE_SECTION_HEADER LastRvaSection
    public static extern IntPtr ImageRvaToVa(IntPtr NtHeaders, IntPtr Base, uint Rva, int LastRvaSection);
    [DllImport("kernel32", SetLastError = true)]
    public static extern IntPtr _lopen(string lpPathName, int iReadWrite);
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr CreateFileMapping(IntPtr hFile, int lpFileMappingAttributes, int flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, int dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, int dwNumberOfBytesToMap);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern int UnmapViewOfFile(IntPtr hMapFile);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern int _lclose(IntPtr hFile);
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern int CloseHandle(IntPtr hObject);
    }
    static partial class Win32Native
    {
    public const int NULL = 0;
    public const int OF_SHARE_COMPAT = 0;
    public const int PAGE_READONLY = 2;
    public const int FILE_MAP_READ = 4;
    public const int IMAGE_DIRECTORY_ENTRY_EXPORT = 0;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct IMAGE_DOS_HEADER
    {
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public char[] e_magic;       // Magic number
    public ushort e_cblp;    // Bytes on last page of file
    public ushort e_cp;      // Pages in file
    public ushort e_crlc;    // Relocations
    public ushort e_cparhdr;     // Size of header in paragraphs
    public ushort e_minalloc;    // Minimum extra paragraphs needed
    public ushort e_maxalloc;    // Maximum extra paragraphs needed
    public ushort e_ss;      // Initial (relative) SS value
    public ushort e_sp;      // Initial SP value
    public ushort e_csum;    // Checksum
    public ushort e_ip;      // Initial IP value
    public ushort e_cs;      // Initial (relative) CS value
    public ushort e_lfarlc;      // File address of relocation table
    public ushort e_ovno;    // Overlay number
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public ushort[] e_res1;    // Reserved words
    public ushort e_oemid;       // OEM identifier (for e_oeminfo)
    public ushort e_oeminfo;     // OEM information; e_oemid specific
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
    public ushort[] e_res2;    // Reserved words
    public int e_lfanew;      // File address of new exe header
    private string _e_magic
    {
    get { return new string(e_magic); }
    }
    public bool isValid
    {
    get { return _e_magic == "MZ"; }
    }
    }
    [StructLayout(LayoutKind.Explicit)]
    public struct IMAGE_OPTIONAL_HEADERS
    {
    [FieldOffset(0)]
    public MagicType Magic;
    [FieldOffset(2)]
    public byte MajorLinkerVersion;
    [FieldOffset(3)]
    public byte MinorLinkerVersion;
    [FieldOffset(4)]
    public uint SizeOfCode;
    [FieldOffset(8)]
    public uint SizeOfInitializedData;
    [FieldOffset(12)]
    public uint SizeOfUninitializedData;
    [FieldOffset(16)]
    public uint AddressOfEntryPoint;
    [FieldOffset(20)]
    public uint BaseOfCode;
    // PE32 contains this additional field
    [FieldOffset(24)]
    public uint BaseOfData;
    [FieldOffset(28)]
    public uint ImageBase;
    [FieldOffset(32)]
    public uint SectionAlignment;
    [FieldOffset(36)]
    public uint FileAlignment;
    [FieldOffset(40)]
    public ushort MajorOperatingSystemVersion;
    [FieldOffset(42)]
    public ushort MinorOperatingSystemVersion;
    [FieldOffset(44)]
    public ushort MajorImageVersion;
    [FieldOffset(46)]
    public ushort MinorImageVersion;
    [FieldOffset(48)]
    public ushort MajorSubsystemVersion;
    [FieldOffset(50)]
    public ushort MinorSubsystemVersion;
    [FieldOffset(52)]
    public uint Win32VersionValue;
    [FieldOffset(56)]
    public uint SizeOfImage;
    [FieldOffset(60)]
    public uint SizeOfHeaders;
    [FieldOffset(64)]
    public uint CheckSum;
    [FieldOffset(68)]
    public SubSystemType Subsystem;
    [FieldOffset(70)]
    public DllCharacteristicsType DllCharacteristics;
    [FieldOffset(72)]
    public uint SizeOfStackReserve;
    [FieldOffset(76)]
    public uint SizeOfStackCommit;
    [FieldOffset(80)]
    public uint SizeOfHeapReserve;
    [FieldOffset(84)]
    public uint SizeOfHeapCommit;
    [FieldOffset(88)]
    public uint LoaderFlags;
    [FieldOffset(92)]
    public uint NumberOfRvaAndSizes;
    [FieldOffset(96)]
    public IMAGE_DATA_DIRECTORY ExportTable;
    [FieldOffset(104)]
    public IMAGE_DATA_DIRECTORY ImportTable;
    [FieldOffset(112)]
    public IMAGE_DATA_DIRECTORY ResourceTable;
    [FieldOffset(120)]
    public IMAGE_DATA_DIRECTORY ExceptionTable;
    [FieldOffset(128)]
    public IMAGE_DATA_DIRECTORY CertificateTable;
    [FieldOffset(136)]
    public IMAGE_DATA_DIRECTORY BaseRelocationTable;
    [FieldOffset(144)]
    public IMAGE_DATA_DIRECTORY Debug;
    [FieldOffset(152)]
    public IMAGE_DATA_DIRECTORY Architecture;
    [FieldOffset(160)]
    public IMAGE_DATA_DIRECTORY GlobalPtr;
    [FieldOffset(168)]
    public IMAGE_DATA_DIRECTORY TLSTable;
    [FieldOffset(176)]
    public IMAGE_DATA_DIRECTORY LoadConfigTable;
    [FieldOffset(184)]
    public IMAGE_DATA_DIRECTORY BoundImport;
    [FieldOffset(192)]
    public IMAGE_DATA_DIRECTORY IAT;
    [FieldOffset(200)]
    public IMAGE_DATA_DIRECTORY DelayImportDescriptor;
    [FieldOffset(208)]
    public IMAGE_DATA_DIRECTORY CLRRuntimeHeader;
    [FieldOffset(216)]
    public IMAGE_DATA_DIRECTORY Reserved;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct IMAGE_FILE_HEADER
    {
    public ushort Machine;
    public ushort NumberOfSections;
    public uint TimeDateStamp;
    public uint PointerToSymbolTable;
    public uint NumberOfSymbols;
    public ushort SizeOfOptionalHeader;
    public ushort Characteristics;
    }
    public enum MachineType : ushort
    {
    Native = 0,
    I386 = 0x014c,
    Itanium = 0x0200,
    x64 = 0x8664
    }
    public enum MagicType : ushort
    {
    IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b,
    IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
    }
    public enum SubSystemType : ushort
    {
    IMAGE_SUBSYSTEM_UNKNOWN = 0,
    IMAGE_SUBSYSTEM_NATIVE = 1,
    IMAGE_SUBSYSTEM_WINDOWS_GUI = 2,
    IMAGE_SUBSYSTEM_WINDOWS_CUI = 3,
    IMAGE_SUBSYSTEM_POSIX_CUI = 7,
    IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9,
    IMAGE_SUBSYSTEM_EFI_APPLICATION = 10,
    IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11,
    IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12,
    IMAGE_SUBSYSTEM_EFI_ROM = 13,
    IMAGE_SUBSYSTEM_XBOX = 14
    }
    public enum DllCharacteristicsType : ushort
    {
    RES_0 = 0x0001,
    RES_1 = 0x0002,
    RES_2 = 0x0004,
    RES_3 = 0x0008,
    IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040,
    IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY = 0x0080,
    IMAGE_DLL_CHARACTERISTICS_NX_COMPAT = 0x0100,
    IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200,
    IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400,
    IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800,
    RES_4 = 0x1000,
    IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000,
    IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct IMAGE_DATA_DIRECTORY
    {
    public uint VirtualAddress;
    public uint Size;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct IMAGE_EXPORT_DIRECTORY
    {
    public uint Characteristics;
    public uint TimeDateStamp;
    public ushort MajorVersion;
    public ushort MinorVersion;
    public uint Name;
    public uint Base;
    public uint NumberOfFunctions;
    public uint NumberOfNames;
    public uint AddressOfFunctions;     // RVA from base of image
    public uint AddressOfNames;     // RVA from base of image
    public uint AddressOfNameOrdinals;  // RVA from base of image
    }
    [StructLayout(LayoutKind.Explicit)]
    public struct IMAGE_NT_HEADERS
    {
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public char[] Signature;
    [FieldOffset(4)]
    public IMAGE_FILE_HEADER FileHeader;
    [FieldOffset(24)]
    public IMAGE_OPTIONAL_HEADERS OptionalHeader;
    private string _Signature
    {
    get { return new string(Signature); }
    }
    public bool isValid
    {
    get { return _Signature == "PE\0\0" && (OptionalHeader.Magic == MagicType.IMAGE_NT_OPTIONAL_HDR32_MAGIC || OptionalHeader.Magic == MagicType.IMAGE_NT_OPTIONAL_HDR64_MAGIC); }
    }
    }

C# 遍历DLL导出函数的相关教程结束。

《C# 遍历DLL导出函数.doc》

下载本文的Word格式文档,以方便收藏与打印。