0

There is GetBinaryType() for determining if an .exe file is 32-bit or 64-bit, but how can I do this for a .dll file? I want to ensure that a DLL is the right architecture before trying to load it with LoadLibrary().

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Ryan Glenn
  • 1,325
  • 4
  • 17
  • 30
  • 5
    Just try loading it with `LoadLibrary` and catch the error. If the architecture is not that of the current process, `LoadLibrary` will return 0. Not sure what will the GetLastError() be, but it'd be easy to check. – Seva Alekseyev Jun 03 '22 at 00:32
  • 1
    no sense in check if you want load dll for execute. simply do this. in general case - can use `LoadLibraryExW` with `LOAD_LIBRARY_AS_DATAFILE` got it `PIMAGE_NT_HEADERS` pointer and check for `FileHeader.Machine` or if need only 32/64 - `OptionalHeader.Magic` – RbMm Jun 03 '22 at 00:39
  • I want to check before loading it. I do not want to load it before checking. – Ryan Glenn Jun 03 '22 at 00:41
  • 2
    what sense in check ? load by self is check. dll with wrong architecture will be not loaded – RbMm Jun 03 '22 at 00:43
  • I want to do this without loading the dll file. That is what I mean. I want to see the architecture type before doing anything. I am not loading the library. I simply want to check a libraries architecture. – Ryan Glenn Jun 03 '22 at 00:58
  • 2
    so do in this case LoadLibraryExW with LOAD_LIBRARY_AS_DATAFILE and look for it IMAGE_NT_HEADERS. or by self open file, create section and query section ( NtQuerySection(SectionImageInformation) ) – RbMm Jun 03 '22 at 01:01
  • you can just read a field in the header [How can I test a Windows DLL file to determine if it is 32 bit or 64 bit?](https://stackoverflow.com/a/495305/995714), [Windows command to tell whether a .dll file is 32 bit or 64 bit?](https://stackoverflow.com/a/14562060/995714) – phuclv Jun 03 '22 at 01:14
  • @phuclv that doesn't seem like a good solution. You have to load the image just to query information from it...? – Ryan Glenn Jun 03 '22 at 01:18
  • @RyanGlenn who said that you need to load the DLL? Just read the header of the file and parse it, just like how you parse any normal binary files – phuclv Jun 03 '22 at 01:20
  • If you want to load the DLL anyway after, it think it is pointless to verify it before as you will waste time unless you expect to have a lot of DLLs of the wrong type (maybe if you scan a folder with hundredths of DLLs but even them, you should probably measure performance to see if you have enough gain to worth more complex code) – Phil1970 Jun 03 '22 at 01:36
  • For normal users, there is no reason why a DLL for the wrong architecture should be on the computer in the first place. People who do have a good reason, know what they are doing. – Karl Knechtel Jun 03 '22 at 01:53
  • 2
    If you want to check but will not load it then it is reasonable to parse the headers. But if you will try to load if it is the correct bitness then you should try and load. Makes no sense not to. – David Heffernan Jun 03 '22 at 05:37
  • @kar There's WoW64, so *"normal users"* that have an x64 version of Windows installed also have x86 binaries. What is and isn't the wrong architecture is determined by the bitness of the process, lots of opportunities for *"normal users"* to have DLLs with the *"wrong architecture"* on their systems. Plus, ARM64 builds of Windows can emulate x64. And all of this is ignoring installable plugin systems. What you describe is a wishful alternative situation that's distinctly different from reality. – IInspectable Jun 03 '22 at 07:07
  • Do you check if a file exists before you actually open it? No, you don't. You just open the file and the OS tells you if something went wrong. Now read again the very first comment. – Jabberwocky Jun 03 '22 at 07:22
  • If you are ultimately trying to load the module anyway, then the check-then-load is just going to introduce bugs without solving anything. First and foremost, this introduces a [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) race. Someone can replace the module in between your check and the attempt to load it, so you're going to have to be prepared for failures anyway. Plus, this does nothing in the way of checking the *dependencies*. Another opportunity for failures you have to be prepared for. And then there's anti-malware services getting in your way, too. – IInspectable Jun 03 '22 at 08:46

1 Answers1

5

if you want use dll for call functions or load resource from it - just try load it. if it was wrong architecture - you got error ERROR_BAD_EXE_FORMAT and dll wil be not loaded. check before this nothing more give. the try load is check already.

if you need check for some other reasons, exist several ways. most correct is next - open file, create image section from it and check section (last operation is undocumented)

HRESULT CheckImage( _In_ PCWSTR lpLibFileName, _Out_ PUSHORT Machine)
{
    HANDLE hFile = CreateFileW(lpLibFileName, FILE_EXECUTE|FILE_READ_DATA, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    if (hFile != INVALID_HANDLE_VALUE)
    {
        HANDLE hSection = CreateFileMappingW(hFile, 0, PAGE_EXECUTE_READ|SEC_IMAGE, 0, 0, 0);
        NtClose(hFile);
        if (hSection)
        {
            SECTION_IMAGE_INFORMATION sii;
            NTSTATUS status = ZwQuerySection(hSection, SectionImageInformation, &sii, sizeof(sii), 0);
            NtClose(hSection);

            *Machine = sii.Machine;

            return status ? HRESULT_FROM_NT(status) : S_OK;
        }
    }
    return HRESULT_FROM_WIN32(GetLastError());
}

you got or some error from this func (file not found, bad image, etc) or some IMAGE_FILE_MACHINE_*. usually IMAGE_FILE_MACHINE_AMD64 or IMAGE_FILE_MACHINE_I386

another way - use LoadLibraryExW with LOAD_LIBRARY_AS_DATAFILE and check IMAGE_NT_HEADERS of mapped image - really this way do all what first (including ZwQuerySection call internally) + mapped dll to memory - last is not need. so this is less efficient way.

HRESULT CheckImage2( _In_ PCWSTR lpLibFileName, _Out_ PUSHORT Machine)
{
    if (HMODULE hmod = LoadLibraryExW(lpLibFileName, 0, LOAD_LIBRARY_AS_DATAFILE))
    {
        HRESULT hr = S_OK;

        if (PIMAGE_NT_HEADERS pinth = RtlImageNtHeader(PAGE_ALIGN(hmod)))
        {
            *Machine = pinth->FileHeader.Machine;
        }
        else
        {
            hr = HRESULT_FROM_NT(STATUS_INVALID_IMAGE_NOT_MZ);
        }
        FreeLibrary(hmod);

        return hr;
    }
    return HRESULT_FROM_WIN32(GetLastError());
}

else one way - direct read file and check it headers. this from one side is fastest, from another side - even if headers is correct - no guarantee that whole file is ok and not corrupted

HRESULT CheckImage3( _In_ PCWSTR lpLibFileName, _Out_ PUSHORT Machine, _Out_ PBOOL Is64Bit)
{
    HANDLE hFile = CreateFileW(lpLibFileName, FILE_READ_DATA, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    
    if (hFile != INVALID_HANDLE_VALUE)
    {
        HRESULT hr = HRESULT_FROM_NT(STATUS_INVALID_IMAGE_NOT_MZ);

        union {
            IMAGE_DOS_HEADER idh;
            IMAGE_NT_HEADERS inth;
        };

        OVERLAPPED ov {};

        ULONG dwBytesRead;
        if (ReadFile(hFile, &idh, sizeof(idh), &dwBytesRead, &ov))
        {
            if (dwBytesRead == sizeof(idh) && idh.e_magic == IMAGE_DOS_SIGNATURE)
            {
                hr = HRESULT_FROM_NT(STATUS_INVALID_IMAGE_FORMAT);

                ov.Offset = idh.e_lfanew;

                if (ReadFile(hFile, &inth, sizeof(inth), &dwBytesRead, &ov))
                {
                    if (dwBytesRead == sizeof(inth) && inth.Signature == IMAGE_NT_SIGNATURE)
                    {
                        switch (inth.OptionalHeader.Magic)
                        {
                        case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
                            *Is64Bit = FALSE;
                            hr = S_OK;
                            break;
                        case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
                            *Is64Bit = TRUE;
                            hr = S_OK;
                            break;
                        }

                        *Machine = inth.FileHeader.Machine;
                    }
                }
            }
        }

        CloseHandle(hFile);

        return hr;
    }

    return HRESULT_FROM_WIN32(GetLastError());
}
RbMm
  • 31,280
  • 3
  • 35
  • 56