如何以编程方式分析本机DLL以读取其导入内容?
[编辑:我的原始问题看起来如下,以及大量有缺陷的代码。请参阅下面的答案以获取更正确的代码。]
位于this link的C#代码用于打印本机DLL的导入。
我发现当我使用原始示例的目标MSCOREE.DLL运行示例代码时,它会打印所有导入。但是当我使用其他dll如GDI32.DLL或WSOCK32.DLL时,导入不会被打印出来。这段代码中缺少哪些内容可以打印所有导入,例如DUMPBIN.EXE?
答案 0 :(得分:4)
代码中存在一个非常大的问题(即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();
}
}
}
答案 1 :(得分:2)
在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
}
答案 2 :(得分:2)
要使Eric's answer在x64上运行,您已在IMAGE_EXPORT_DIRECTORY中更改RVA地址(AddressOfFunctions,AddressOfNames和AddressOfNameOrdinals)的数据类型。那些总是32位长(UInt32)。 IntPtr在x86上为32位,在x64上为64位。
请参阅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
}
答案 3 :(得分:1)
从调试器中你可以看到永远不会输入这个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
答案 4 :(得分:1)
使用PeNet Library。它可以解析PE头并用C#编写。您可以通过安装NuGet包轻松使用它。 (免责声明:我是图书馆的作者)
答案 5 :(得分:0)
扩展Eric's answer(感谢所有在此处贡献代码的人)...如果.NET主机项目以x86为目标,那么当与x64修复程序合并时,该类似乎可以正常运行。它成功读取了32位和64位可执行文件。
但是,如果将主机项目设置为目标x64,则会出现两个问题。
RvaToVA在某些文件(例如下面提到的Adobe阅读器测试用例)上崩溃了
返回RvaToVa(loadedImage,(uint)(rva.ToInt32());
溢出。将此更改为.ToInt64可解决溢出问题,但
在针对Acrobat Reader 10 MUI基线(acrord32.exe版本10.0.0.396)进行测试时,似乎会生成不完整的导入列表。这是一个32位可执行文件。
Dumpbin报告Shell32.dll的以下9个导入
SHGetFolderPathW,
ShellExecuteExW,
CommandLineToArgvW,
ShellExecuteW,
SHCreateDirectoryExW,
SHGetFileInfoW,
SHGetPathFromIDListW,
FindExecutableW
SHBrowseForFolderW
但是,在x64模式下运行的代码只能找到其中的4个
SHGetFolderPathW,
ShellExecuteW,
SHGetFileInfoW,
FindExecutableW
此时pThunk-&gt; Ordinal返回0,导致循环终止
我还没有在这个阶段有机会做一些调试来试图弄清楚发生了什么,虽然它可能是x64中变化的大小。同时请注意,只要主机针对x86(这足以满足我的需求),这似乎可以正常工作。如果我找到了根本原因,我会让大家知道。