如何以编程方式分析本机 DLL 以读取其导入?
[编辑:我最初的问题如下所示,还有一大块有缺陷的代码。请参阅下面的答案以获取更正确的代码。]
此链接上的 C# 代码旨在打印本机 DLL 的导入。
我发现当我使用原始示例的目标 MSCOREE.DLL 运行示例代码时,它可以很好地打印所有导入。但是当我使用 GDI32.DLL 或 WSOCK32.DLL 等其他 dll 时,不会打印导入。这段代码缺少什么可以打印所有导入,例如 DUMPBIN.EXE 所做的?
代码中有一个非常大的问题(即 的定义THUNK_DATA
)和其他各种较小的问题,主要涉及表尾检测(使用IsBadReadPtr
而不是 NULL 检查,也没有根据需要添加基地址)。
这是一个固定版本,至少对于wsock32产生与dumpbin相同的输出:
using System;
using System.Runtime.InteropServices;
using System.Security;
namespace PETest2
{
[StructLayout(LayoutKind.Explicit)]
public unsafe struct IMAGE_IMPORT_BY_NAME
{
[FieldOffset(0)]
public ushort Hint;
[FieldOffset(2)]
public fixed char Name[1];
}
[StructLayout(LayoutKind.Explicit)]
public struct IMAGE_IMPORT_DESCRIPTOR
{
#region union
/// <summary>
/// CSharp doesnt really support unions, but they can be emulated by a field offset 0
/// </summary>
[FieldOffset(0)]
public uint Characteristics; // 0 for terminating null import descriptor
[FieldOffset(0)]
public uint OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
#endregion
[FieldOffset(4)]
public uint TimeDateStamp;
[FieldOffset(8)]
public uint ForwarderChain;
[FieldOffset(12)]
public uint Name;
[FieldOffset(16)]
public uint FirstThunk;
}
[StructLayout(LayoutKind.Explicit)]
public struct THUNK_DATA
{
[FieldOffset(0)]
public uint ForwarderString; // PBYTE
[FieldOffset(0)]
public uint Function; // PDWORD
[FieldOffset(0)]
public uint Ordinal;
[FieldOffset(0)]
public uint AddressOfData; // PIMAGE_IMPORT_BY_NAME
}
public unsafe class Interop
{
#region Public Constants
public static readonly ushort IMAGE_DIRECTORY_ENTRY_IMPORT = 1;
#endregion
#region Private Constants
#region CallingConvention CALLING_CONVENTION
/// <summary>
/// Specifies the calling convention.
/// </summary>
/// <remarks>
/// Specifies <see cref="CallingConvention.Winapi" /> for Windows to
/// indicate that the default should be used.
/// </remarks>
private const CallingConvention CALLING_CONVENTION = CallingConvention.Winapi;
#endregion CallingConvention CALLING_CONVENTION
#region IMPORT DLL FUNCTIONS
private const string KERNEL_DLL = "kernel32";
private const string DBGHELP_DLL = "Dbghelp";
#endregion
#endregion Private Constants
[DllImport(KERNEL_DLL, CallingConvention = CALLING_CONVENTION, EntryPoint = "GetModuleHandleA"), SuppressUnmanagedCodeSecurity]
public static extern void* GetModuleHandleA(/*IN*/ char* lpModuleName);
[DllImport(KERNEL_DLL, CallingConvention = CALLING_CONVENTION, EntryPoint = "GetModuleHandleW"), SuppressUnmanagedCodeSecurity]
public static extern void* GetModuleHandleW(/*IN*/ char* lpModuleName);
[DllImport(KERNEL_DLL, CallingConvention = CALLING_CONVENTION, EntryPoint = "IsBadReadPtr"), SuppressUnmanagedCodeSecurity]
public static extern bool IsBadReadPtr(void* lpBase, uint ucb);
[DllImport(DBGHELP_DLL, CallingConvention = CALLING_CONVENTION, EntryPoint = "ImageDirectoryEntryToData"), SuppressUnmanagedCodeSecurity]
public static extern void* ImageDirectoryEntryToData(void* Base, bool MappedAsImage, ushort DirectoryEntry, out uint Size);
}
static class Foo
{
// From winbase.h in the Win32 platform SDK.
//
const uint DONT_RESOLVE_DLL_REFERENCES = 0x00000001;
const uint LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010;
[DllImport("kernel32.dll"), SuppressUnmanagedCodeSecurity]
static extern uint LoadLibraryEx(string fileName, uint notUsedMustBeZero, uint flags);
public static void Main()
{
//var path = @"c:\windows\system32\mscoree.dll";
//var path = @"c:\windows\system32\gdi32.dll";
var path = @"c:\windows\system32\wsock32.dll";
var hLib = LoadLibraryEx(path, 0,
DONT_RESOLVE_DLL_REFERENCES | LOAD_IGNORE_CODE_AUTHZ_LEVEL);
TestImports(hLib, true);
}
// using mscoree.dll as an example as it doesnt export any thing
// so nothing shows up if you use your own module.
// and the only none delayload in mscoree.dll is the Kernel32.dll
private static void TestImports(uint hLib, bool mappedAsImage)
{
unsafe
{
//fixed (char* pszModule = "mscoree.dll")
{
//void* hMod = Interop.GetModuleHandleW(pszModule);
void* hMod = (void*)hLib;
uint size = 0;
uint BaseAddress = (uint)hMod;
if (hMod != null)
{
Console.WriteLine("Got handle");
IMAGE_IMPORT_DESCRIPTOR* pIID = (IMAGE_IMPORT_DESCRIPTOR*)Interop.ImageDirectoryEntryToData((void*)hMod, mappedAsImage, Interop.IMAGE_DIRECTORY_ENTRY_IMPORT, out size);
if (pIID != null)
{
Console.WriteLine("Got Image Import Descriptor");
while (pIID->OriginalFirstThunk != 0)
{
try
{
char* szName = (char*)(BaseAddress + pIID->Name);
string name = Marshal.PtrToStringAnsi((IntPtr)szName);
Console.WriteLine("pIID->Name = {0} BaseAddress - {1}", name, (uint)BaseAddress);
THUNK_DATA* pThunkOrg = (THUNK_DATA*)(BaseAddress + pIID->OriginalFirstThunk);
while (pThunkOrg->AddressOfData != 0)
{
char* szImportName;
uint Ord;
if ((pThunkOrg->Ordinal & 0x80000000) > 0)
{
Ord = pThunkOrg->Ordinal & 0xffff;
Console.WriteLine("imports ({0}).Ordinal{1} - Address: {2}", name, Ord, pThunkOrg->Function);
}
else
{
IMAGE_IMPORT_BY_NAME* pIBN = (IMAGE_IMPORT_BY_NAME*)(BaseAddress + pThunkOrg->AddressOfData);
if (!Interop.IsBadReadPtr((void*)pIBN, (uint)sizeof(IMAGE_IMPORT_BY_NAME)))
{
Ord = pIBN->Hint;
szImportName = (char*)pIBN->Name;
string sImportName = Marshal.PtrToStringAnsi((IntPtr)szImportName); // yes i know i am a lazy ass
Console.WriteLine("imports ({0}).{1}@{2} - Address: {3}", name, sImportName, Ord, pThunkOrg->Function);
}
else
{
Console.WriteLine("Bad ReadPtr Detected or EOF on Imports");
break;
}
}
pThunkOrg++;
}
}
catch (AccessViolationException e)
{
Console.WriteLine("An Access violation occured\n" +
"this seems to suggest the end of the imports section\n");
Console.WriteLine(e);
}
pIID++;
}
}
}
}
}
Console.WriteLine("Press Any Key To Continue......");
Console.ReadKey();
}
}
}
在 Jester 对原始样本的修正的基础上,这里有一个可以读取 IMPORT 和 EXPORT 的类。我一直在 32 位 DLL 上成功使用它,但是还不知道 64 位。
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
/*
Inspirations:
* http://www.bearcanyon.com/dotnet/#AssemblyParser (Mike Woodring's "Parsing PE File Headers to Determine if a DLL or EXE is an Assembly")
* http://stackoverflow.com/questions/1563134/how-do-i-read-the-pe-header-of-a-module-loaded-in-memory ("How do I read the PE header of a module loaded in memory?")
* http://stackoverflow.com/questions/2975639/resolving-rvas-for-import-and-export-tables-within-a-pe-file ("Resolving RVA's for Import and Export tables within a PE file.")
* http://www.lenholgate.com/blog/2006/04/i-love-it-when-a-plan-comes-together.html
* http://www.gamedev.net/community/forums/topic.asp?topic_id=409936
* http://stackoverflow.com/questions/4571088/how-to-programatically-read-native-dll-imports-in-c
*/
namespace PE
{
public unsafe class PortableExecutableParser
{
public delegate void DLog(string fmt, params object[] args);
private readonly DLog _fnLog;
private void Log( string fmt, params object[] args )
{
if (_fnLog != null)
_fnLog(fmt, args);
}
private readonly List<string> _exports = new List<string>();
public IEnumerable<string> Exports { get { return _exports; } }
private readonly List<Tuple<string, List<string>>> _imports = new List<Tuple<string, List<string>>>();
public IEnumerable<Tuple<string, List<string>>> Imports { get { return _imports; } }
public PortableExecutableParser( string path, DLog fnLog=null )
{
_fnLog = fnLog;
LOADED_IMAGE loadedImage;
if (MapAndLoad(path, null, out loadedImage, true, true))
{
LoadExports(loadedImage);
LoadImports(loadedImage);
}
}
private void LoadExports(LOADED_IMAGE loadedImage)
{
var hMod = (void*)loadedImage.MappedAddress;
if (hMod != null)
{
Log("Got handle");
uint size;
var pExportDir = (IMAGE_EXPORT_DIRECTORY*)ImageDirectoryEntryToData(
(void*)loadedImage.MappedAddress,
false,
IMAGE_DIRECTORY_ENTRY_EXPORT,
out size);
if (pExportDir != null)
{
Log("Got Image Export Descriptor");
var pFuncNames = (uint*)RvaToVa(loadedImage, pExportDir->AddressOfNames);
for (uint i = 0; i < pExportDir->NumberOfNames; i++)
{
uint funcNameRva = pFuncNames[i];
if (funcNameRva != 0)
{
var funcName =
(char*)RvaToVa(loadedImage, funcNameRva);
var name = Marshal.PtrToStringAnsi((IntPtr)funcName);
Log(" funcName: {0}", name);
_exports.Add(name);
}
}
}
}
}
private static IntPtr RvaToVa( LOADED_IMAGE loadedImage, uint rva )
{
return ImageRvaToVa(loadedImage.FileHeader, loadedImage.MappedAddress, rva, IntPtr.Zero);
}
private static IntPtr RvaToVa(LOADED_IMAGE loadedImage, IntPtr rva)
{
return RvaToVa(loadedImage, (uint)(rva.ToInt32()) );
}
private void LoadImports(LOADED_IMAGE loadedImage)
{
var hMod = (void*)loadedImage.MappedAddress;
if (hMod != null)
{
Console.WriteLine("Got handle");
uint size;
var pImportDir =
(IMAGE_IMPORT_DESCRIPTOR*)
ImageDirectoryEntryToData(hMod, false,
IMAGE_DIRECTORY_ENTRY_IMPORT, out size);
if (pImportDir != null)
{
Log("Got Image Import Descriptor");
while (pImportDir->OriginalFirstThunk != 0)
{
try
{
var szName = (char*) RvaToVa(loadedImage, pImportDir->Name);
string name = Marshal.PtrToStringAnsi((IntPtr) szName);
var pr = new Tuple<string, List<string>>(name, new List<string>());
_imports.Add(pr);
var pThunkOrg = (THUNK_DATA*)RvaToVa(loadedImage, pImportDir->OriginalFirstThunk);
while (pThunkOrg->AddressOfData != IntPtr.Zero)
{
uint ord;
if ((pThunkOrg->Ordinal & 0x80000000) > 0)
{
ord = pThunkOrg->Ordinal & 0xffff;
Log("imports ({0}).Ordinal{1} - Address: {2}", name, ord,
pThunkOrg->Function);
}
else
{
var pImageByName =
(IMAGE_IMPORT_BY_NAME*) RvaToVa(loadedImage, pThunkOrg->AddressOfData);
if (
!IsBadReadPtr(pImageByName, (uint) sizeof (IMAGE_IMPORT_BY_NAME)))
{
ord = pImageByName->Hint;
var szImportName = pImageByName->Name;
string sImportName = Marshal.PtrToStringAnsi((IntPtr) szImportName);
Log("imports ({0}).{1}@{2} - Address: {3}", name,
sImportName, ord, pThunkOrg->Function);
pr.Item2.Add( sImportName );
}
else
{
Log("Bad ReadPtr Detected or EOF on Imports");
break;
}
}
pThunkOrg++;
}
}
catch (AccessViolationException e)
{
Log("An Access violation occured\n" +
"this seems to suggest the end of the imports section\n");
Log(e.ToString());
}
pImportDir++;
}
}
}
}
// ReSharper disable InconsistentNaming
private const ushort IMAGE_DIRECTORY_ENTRY_IMPORT = 1;
private const ushort IMAGE_DIRECTORY_ENTRY_EXPORT = 0;
private const CallingConvention WINAPI = CallingConvention.Winapi;
private const string KERNEL_DLL = "kernel32";
private const string DBGHELP_DLL = "Dbghelp";
private const string IMAGEHLP_DLL = "ImageHlp";
// ReSharper restore InconsistentNaming
[DllImport(KERNEL_DLL, CallingConvention = WINAPI, EntryPoint = "GetModuleHandleA"), SuppressUnmanagedCodeSecurity]
public static extern void* GetModuleHandleA(/*IN*/ char* lpModuleName);
[DllImport(KERNEL_DLL, CallingConvention = WINAPI, EntryPoint = "GetModuleHandleW"), SuppressUnmanagedCodeSecurity]
public static extern void* GetModuleHandleW(/*IN*/ char* lpModuleName);
[DllImport(KERNEL_DLL, CallingConvention = WINAPI, EntryPoint = "IsBadReadPtr"), SuppressUnmanagedCodeSecurity]
public static extern bool IsBadReadPtr(void* lpBase, uint ucb);
[DllImport(DBGHELP_DLL, CallingConvention = WINAPI, EntryPoint = "ImageDirectoryEntryToData"), SuppressUnmanagedCodeSecurity]
public static extern void* ImageDirectoryEntryToData(void* pBase, bool mappedAsImage, ushort directoryEntry, out uint size);
[DllImport(DBGHELP_DLL, CallingConvention = WINAPI), SuppressUnmanagedCodeSecurity]
public static extern IntPtr ImageRvaToVa(
IntPtr pNtHeaders,
IntPtr pBase,
uint rva,
IntPtr pLastRvaSection);
[DllImport(DBGHELP_DLL, CallingConvention = CallingConvention.StdCall), SuppressUnmanagedCodeSecurity]
public static extern IntPtr ImageNtHeader(IntPtr pImageBase);
[DllImport(IMAGEHLP_DLL, CallingConvention = CallingConvention.Winapi), SuppressUnmanagedCodeSecurity]
public static extern bool MapAndLoad(string imageName, string dllPath, out LOADED_IMAGE loadedImage, bool dotDll, bool readOnly);
}
// ReSharper disable InconsistentNaming
[StructLayout(LayoutKind.Sequential)]
public struct LOADED_IMAGE
{
public IntPtr moduleName;
public IntPtr hFile;
public IntPtr MappedAddress;
public IntPtr FileHeader;
public IntPtr lastRvaSection;
public UInt32 numbOfSections;
public IntPtr firstRvaSection;
public UInt32 charachteristics;
public ushort systemImage;
public ushort dosImage;
public ushort readOnly;
public ushort version;
public IntPtr links_1; // these two comprise the LIST_ENTRY
public IntPtr links_2;
public UInt32 sizeOfImage;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct IMAGE_IMPORT_BY_NAME
{
[FieldOffset(0)]
public ushort Hint;
[FieldOffset(2)]
public fixed char Name[1];
}
[StructLayout(LayoutKind.Explicit)]
public struct IMAGE_IMPORT_DESCRIPTOR
{
#region union
/// <summary>
/// CSharp doesnt really support unions, but they can be emulated by a field offset 0
/// </summary>
[FieldOffset(0)]
public uint Characteristics; // 0 for terminating null import descriptor
[FieldOffset(0)]
public uint OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
#endregion
[FieldOffset(4)]
public uint TimeDateStamp;
[FieldOffset(8)]
public uint ForwarderChain;
[FieldOffset(12)]
public uint Name;
[FieldOffset(16)]
public uint FirstThunk;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_EXPORT_DIRECTORY
{
public UInt32 Characteristics;
public UInt32 TimeDateStamp;
public UInt16 MajorVersion;
public UInt16 MinorVersion;
public UInt32 Name;
public UInt32 Base;
public UInt32 NumberOfFunctions;
public UInt32 NumberOfNames;
public IntPtr AddressOfFunctions; // RVA from base of image
public IntPtr AddressOfNames; // RVA from base of image
public IntPtr AddressOfNameOrdinals; // RVA from base of image
}
[StructLayout(LayoutKind.Explicit)]
public struct THUNK_DATA
{
[FieldOffset(0)]
public uint ForwarderString; // PBYTE
[FieldOffset(0)]
public uint Function; // PDWORD
[FieldOffset(0)]
public uint Ordinal;
[FieldOffset(0)]
public IntPtr AddressOfData; // PIMAGE_IMPORT_BY_NAME
}
// ReSharper restore InconsistentNaming
}
To make Eric's answer work on x64 you have change the data type of the RVA addresses (AddressOfFunctions, AddressOfNames and AddressOfNameOrdinals) in IMAGE_EXPORT_DIRECTORY. Those are always 32 bit long (UInt32). IntPtr is 32 bit on x86 and 64 bit on x64.
See http://pinvoke.net/default.aspx/Structures/IMAGE_EXPORT_DIRECTORY.html
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_EXPORT_DIRECTORY
{
public UInt32 Characteristics;
public UInt32 TimeDateStamp;
public UInt16 MajorVersion;
public UInt16 MinorVersion;
public UInt32 Name;
public UInt32 Base;
public UInt32 NumberOfFunctions;
public UInt32 NumberOfNames;
public UInt32 AddressOfFunctions; // RVA from base of image
public UInt32 AddressOfNames; // RVA from base of image
public UInt32 AddressOfNameOrdinals; // RVA from base of image
}
从调试器中,您可以看到从未输入过这个 while 循环(对于 gdi32.dll 和 wsock32.dll):
while (!Interop.IsBadReadPtr((void*)pIID->OriginalFirstThunk, (uint)size))
强烈建议不要使用 IsBadReadPtr,因为您不能总是依赖它的返回值。请参阅:http: //msdn.microsoft.com/en-us/library/aa366713.aspx或http://blogs.msdn.com/b/oldnewthing/archive/2006/09/27/773741.aspx
处理指针有效性的另一种方法是使用结构化异常处理。尝试访问内存地址,处理任何访问冲突异常。
这是否是好的做法,是一个不同的讨论。
可能有用:
http://www.codeproject.com/Messages/2626152/Replacement-for-IsBadReadPtr-in-Windows-Vista.aspx
Use the PeNet Library. It can parse the PE header and is written in C#. You can use it easily by installing the NuGet package. (Disclaimer: I'm, the author of the library)
Expanding on Eric's answer (and thanks to everyone who contributed code here)... That class, when merged with the x64 fixes, appears to run correctly if the .NET host project targets x86. It reads both 32 and 64 bit executables successfully.
However, two issues occur if the host project is set to target x64.
RvaToVA crashes on some files (e.g. the Adobe reader test case mentioned below), on the line
return RvaToVa(loadedImage, (uint)(rva.ToInt32());
With an overflow. Changing this to .ToInt64 resolves the overflow, but then
It appears to produce an incomplete list of imports when tested against Acrobat Reader 10 MUI baseline (acrord32.exe version 10.0.0.396). This is a 32 bit executable.
Dumpbin reports the following 9 imports for Shell32.dll
SHGetFolderPathW,
ShellExecuteExW,
CommandLineToArgvW,
ShellExecuteW,
SHCreateDirectoryExW,
SHGetFileInfoW,
SHGetPathFromIDListW,
FindExecutableW
SHBrowseForFolderW
However the code when run in x64 mode only finds 4 of these
SHGetFolderPathW,
ShellExecuteW,
SHGetFileInfoW,
FindExecutableW
And at this point pThunk->Ordinal returns 0, causing the loop to terminate
I haven't at this stage had the chance to do some debugging to try and figure out what's going on, although it's likely to be the size of something changing in x64. Meanwhile just be aware this appears to work OK as long as the host is targeting x86 (which is good enough for my needs). If I find the underlying reason, I'll let you folks know.