如何通过编程确定Windows可执行文件的DLL依赖关系?

12

如何使用编程方法确定二进制文件依赖的DLL?

需要澄清的是,我不是在尝试确定运行中exe的DLL依赖性,而是任意一个可能缺少所需DLL的exe。我正在寻找一种在C/C++应用程序中实现的解决方案。这是需要由我的应用程序在运行时完成,并且不能由第三方应用程序(如depends)完成。


请查看此问题 - alex2k8
6个回答

10
请看 IMAGE_LOAD_FUNCTION API。它将返回一个指向LOADED_IMAGE结构的指针,您可以使用该结构来访问PE文件的各个部分。
您可以在这里找到一些描述结构布局的文章:herehere。您可以从here下载文章的源代码。
我认为这应该给你需要的一切。
更新: 我刚刚下载了文章的源代码。如果您打开EXEDUMP.CPP并查看DumpImportsSection,它应该有您需要的代码。

谢谢您的建议。特别是提供源示例链接。正是我正在寻找的。 - user72104

8

基于pedump代码,需要76行代码来实现这个功能(不要忘记添加Imagehlp.lib作为依赖项):

#include <stdio.h>
#include "windows.h" //DONT REMOVE IT
#include "ImageHlp.h"
#include "stdafx.h"

template <class T> PIMAGE_SECTION_HEADER GetEnclosingSectionHeader(DWORD rva, T* pNTHeader) // 'T' == PIMAGE_NT_HEADERS 
{
    PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(pNTHeader);
    unsigned i;

    for ( i=0; i < pNTHeader->FileHeader.NumberOfSections; i++, section++ )
    {
        // This 3 line idiocy is because Watcom's linker actually sets the
        // Misc.VirtualSize field to 0.  (!!! - Retards....!!!)
        DWORD size = section->Misc.VirtualSize;
        if ( 0 == size )
            size = section->SizeOfRawData;

        // Is the RVA within this section?
        if ( (rva >= section->VirtualAddress) && 
             (rva < (section->VirtualAddress + size)))
            return section;
    }

    return 0;
}

template <class T> LPVOID GetPtrFromRVA( DWORD rva, T* pNTHeader, PBYTE imageBase ) // 'T' = PIMAGE_NT_HEADERS 
{
    PIMAGE_SECTION_HEADER pSectionHdr;
    INT delta;

    pSectionHdr = GetEnclosingSectionHeader( rva, pNTHeader );
    if ( !pSectionHdr )
        return 0;

    delta = (INT)(pSectionHdr->VirtualAddress-pSectionHdr->PointerToRawData);
    return (PVOID) ( imageBase + rva - delta );
}


void DumpDllFromPath(wchar_t* path) {
    char name[300];
    wcstombs(name,path,300);

    PLOADED_IMAGE image=ImageLoad(name,0);

    if (image->FileHeader->OptionalHeader.NumberOfRvaAndSizes>=2) {
        PIMAGE_IMPORT_DESCRIPTOR importDesc=
            (PIMAGE_IMPORT_DESCRIPTOR)GetPtrFromRVA(
                image->FileHeader->OptionalHeader.DataDirectory[1].VirtualAddress,
                image->FileHeader,image->MappedAddress);
        while ( 1 )
        {
            // See if we've reached an empty IMAGE_IMPORT_DESCRIPTOR
            if ( (importDesc->TimeDateStamp==0 ) && (importDesc->Name==0) )
                break;

            printf("  %s\n", GetPtrFromRVA(importDesc->Name,
                                           image->FileHeader,
                                           image->MappedAddress) );
            importDesc++;
        }
    }
    ImageUnload(image);

}

//Pass exe or dll as argument 
int _tmain(int argc, _TCHAR* argv[])
{
    DumpDllFromPath(argv[1]);

    return 0;
}

7

这是不可能确定的。至少不需要大量的工作。任何二进制文件都可以调用LoadLibrary来加载DLL。即使您要扫描所有对LoadLibrary的调用代码,您也必须确定正在使用哪些字符串来标识库。追踪动态内存中字符串放置的位置将比您想象的更困难。


1
为什么在这里给我投反对票?据我所知,这个答案在技术上是准确的。 - Steve Rowe
我猜这个答案被投票否决的原因是:它没有区分隐式依赖关系(可以确定,参见alex2k8的链接)和显式依赖关系(这就是你所说的)。不要沮丧,这个答案是半对的! - jdigital
@jdigital:这个答案并不完全正确。它是一个正确的答案,用于编程上找到可执行图像的所有依赖项的问题。这个问题是要求通用情况下的解决方法,因此你必须考虑到运行时动态链接,使用LoadLibrary或在实例化COM对象时隐式加载的模块。Dependency Walker有一个分析模式,试图解决在运行时加载的二进制文件的问题,但你不能保证100%的代码覆盖率,所以即使使用那个尝试也不行。无论你喜不喜欢,这就是正确的答案。 - IInspectable

1

性能分析只能看到你触发的那些模块的加载。除非你能保证代码覆盖率达到100%,否则无法确定找到所有依赖项。然后,您还将看到加载了不严格要求程序的模块(例如,在打开“文件保存”对话框时加载的shell扩展)。 - IInspectable

1
简而言之,您需要扫描PE文件的导入部分,以查找可执行文件使用的每个DLL。然后递归地定位和扫描每个dll,直到找到所有依赖项为止。
当然,应用程序可以使用LoadLibrary函数族来获取所需或可选功能。这种方法无法检测到它们。

0

你觉得使用一个DLL来计算所有这些信息并将答案作为CStrings数组返回如何?

PE格式DLL可以为您完成此操作。附带源代码,没有GPL限制。 PE文件资源管理器是一个使用该DLL的GUI应用程序,同样附带源代码(无GPL)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接