0

I have a C# application, compiled in Visual Studio 2017 on the 'Any CPU' target, with the 'Prefer 32-bit' option disabled. In this application, I am trying to pinvoke kernel32!GetBinaryType(). When running with 'Prefer 32-bit' enabled, it works fine. When running in either 32 or 64-bit mode from a C++ executable, it works fine. I am not sure what I am doing wrong with the 64-bit C# application.

This is my pinvoke signature:

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetBinaryTypeW([MarshalAs(UnmanagedType.LPWStr)] string path, out UInt32 result);

Calling this from 64 bit mode, GetLastError() returns 193, which according to https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx is

ERROR_BAD_EXE_FORMAT.

I have attached a 64-bit debugger to both my C# application and my C++ application to make sure that the string is reaching GetBinaryTypeW() in the correct place on the stack, and it appears to me that it is.

I am concerned that I have run into some subtle bug in the marshaler.

What is wrong with my approach?

Update

The question referenced in the comments does not match this situation. In that question, a call to LoadLibrary() failed because it was being used to try and load a 32 bit DLL into a 64 bit process. I am not trying to load any DLL. I am merely trying to use GetBinaryType() to examine the PE header of an executable.

namreeb
  • 21
  • 5
  • 1
    Possible duplicate of [LoadLibrary doesn't work when compiled to AnyCpu](https://stackoverflow.com/questions/17146185/loadlibrary-doesnt-work-when-compiled-to-anycpu) – Clint Jun 02 '17 at 21:17
  • Not a duplicate in terms of what you're trying to achieve, but likely a duplicate considering outcome. – Clint Jun 02 '17 at 21:18
  • I don't understand. The path doesn't change between the 32 and 64 bit binaries. There is nothing special about the path. There are no NTFS links, network shares, etc. involved. I even tried relocating the file to `C:\my.exe` and the behavior remained the same. Edit: Oh, you were referring to redirection for kernel32.dll, being in a 64 bit environment. I see. – namreeb Jun 02 '17 at 21:18
  • Your `[DllImport` statement **is** attempting to access a 32-bit library (kernel-32). See here: https://stackoverflow.com/questions/10852634/using-a-32bit-or-64bit-dll-in-c-sharp-dllimport – Clint Jun 02 '17 at 21:31
  • 2
    `kernel32.dll` is also the name of the 64-bit binary. On 64-bit Windows, `C:\Windows\System32\kernel32.dll` is the 64-bit DLL, and `C:\Windows\SysWOW64\kernel32.dll` is the 32-bit DLL. – Michael Gunter Jun 02 '17 at 21:33
  • There is both a 32 bit and a 64 bit version of kernel32.dll. And I can verify that the 64 bit version is being called, as I attached a debugger and observed its execution. – namreeb Jun 02 '17 at 21:34
  • @clint not true, read about file system redirector please. – David Heffernan Jun 02 '17 at 21:35
  • 1
    We can't see the code that checks for errors. Where is the [mcve]? – David Heffernan Jun 02 '17 at 21:41
  • @DavidHeffernan I'm sorry I still don't understand your answer. Where exactly does the 'misunderstanding of file redirection' come into play? The path to the executable I want to pass to `GetBinaryType()` is not a folder that would be subject to redirection. Edit: I will post MCVE. – namreeb Jun 02 '17 at 21:42
  • 1
    There's not going to be a bug in Windows is there? Problem is at your end. Embrace that. Show us your mcve. – David Heffernan Jun 02 '17 at 21:46
  • Is that a joke? There are bugs fixed in Windows (and even the marshaler) all the time. I'm sure the problem is at my end, but I am equally certain the 'problem' is not knowing some subtle counter-intuitive idiosyncrasy (which one might call a 'bug') in Windows. MCVE: https://github.com/namreeb/bin_type_mcve – namreeb Jun 02 '17 at 21:57

1 Answers1

3

GetBinaryType does some funny things when the process is running in the WOW6432 space, and the .NET runtime may exacerbate this. Unfortunately, I don't recall all the details right now. However, here's a solution which is more robust and which works for both EXEs and DLLs. Note that this only detects the PE binary type. If you are dealing with .NET assemblies that are compiled as AnyCPU, you may or may not get the result you expect. I will leave any further investigation as an exercise for the reader.

public enum BinaryType : uint
{
    SCS_32BIT_BINARY = 0,
    SCS_64BIT_BINARY = 6,
    SCS_DOS_BINARY = 1,
    SCS_OS216_BINARY = 5,
    SCS_PIF_BINARY = 3,
    SCS_POSIX_BINARY = 4,
    SCS_WOW_BINARY = 2
}

public static BinaryType? GetBinaryType(string path)
{
    using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
    {
        stream.Seek(0x3C, SeekOrigin.Begin);
        using (var reader = new BinaryReader(stream))
        {
            if (stream.Position + sizeof(int) > stream.Length)
                return null;
            var peOffset = reader.ReadInt32();
            stream.Seek(peOffset, SeekOrigin.Begin);
            if (stream.Position + sizeof(uint) > stream.Length)
                return null;
            var peHead = reader.ReadUInt32();
            if (peHead != 0x00004550) // "PE\0\0"
                return null;
            if (stream.Position + sizeof(ushort) > stream.Length)
                return null;
            switch (reader.ReadUInt16())
            {
                case 0x14c:
                    return BinaryType.SCS_32BIT_BINARY;
                case 0x8664:
                    return BinaryType.SCS_64BIT_BINARY;
                default:
                    return null;
            }
        }
    }
}
Michael Gunter
  • 12,528
  • 1
  • 24
  • 58
  • Thank you, this worked. As for details, I can tell you that when I was stepping through the execution of `kernel32!GetBinaryType()`, it calls `kernel32!RtlImageNtHeader()` and that call fails with the same error code. Execution returns out of `kernel32` at that point. – namreeb Jun 02 '17 at 23:10