How can I determine for which platform an executable is compiled?

C#PowershellCpu Architecture

C# Problem Overview


I have a need to work with Windows executables which are made for x86, x64, and IA64. I'd like to programmatically figure out the platform by examining the files themselves.

My target language is PowerShell but a C# example will do. Failing either of those, if you know the logic required that would be great.

C# Solutions


Solution 1 - C#

If you have Visual Studio installed you can use dumpbin.exe. There's also the Get-PEHeader cmdlet in the PowerShell Community Extensions that can be used to test for executable images.

Dumpbin will report DLLs as machine (x86) or machine (x64)

Get-PEHeader will report DLLs as either PE32 or PE32+

Solution 2 - C#

(from another Q, since removed)

Machine type: This is a quick little bit of code I based on some that gets the linker timestamp. This is in the same header, and it seems to work - it returns I386 when compiled -any cpu-, and x64 when compiled with that as the target platform.

The Exploring PE Headers (K. Stanton,MSDN) blog entry that showed me the offset, as another response noted.

public enum MachineType {
    Native = 0, I386 = 0x014c, Itanium = 0x0200, x64 = 0x8664
}

public static MachineType GetMachineType(string fileName)
{
    const int PE_POINTER_OFFSET = 60;            
    const int MACHINE_OFFSET = 4;
    byte[] data = new byte[4096];
    using (Stream s = new FileStream(fileName, FileMode.Open, FileAccess.Read)) {
        s.Read(data, 0, 4096);
    }
    // dos header is 64 bytes, last element, long (4 bytes) is the address of the PE header
    int PE_HEADER_ADDR = BitConverter.ToInt32(data, PE_POINTER_OFFSET);
    int machineUint = BitConverter.ToUInt16(data, PE_HEADER_ADDR + MACHINE_OFFSET);
    return (MachineType)machineUint;
}

Solution 3 - C#

You need the GetBinaryType win32 function. This will return the relevant parts of the PE-format executable.

Typically, you'll get either SCS_32BIT_BINARY or SCS_64BIT_BINARY in the BinaryType field,

Alternativaly you can check the PE format itself to see what architecture the executable is compiled for.

The IMAGE_FILE_HEADER.Machine field will have "IMAGE_FILE_MACHINE_IA64" set for IA64 binaries, IMAGE_FILE_MACHINE_I386 for 32-bit and IMAGE_FILE_MACHINE_AMD64 for 64-bit (ie x86_64).

There's a MSDN magazine article to help you get going.

Addendum: This may help you a little more. You read the binary as a file: check the first 2 bytes say "MZ", then skip the next 58 bytes and read the magic 32-bit value at 60 bytes into the image (which equals 0x00004550 for PE executables). The following bytes are this header, the first 2 bytes of which tell you which machine the binary is designed for (0x8664 = x86_64, 0x0200 = IA64, 0x014c = i386).

(executive summary: read bytes 65 and 66 of the file to get the image type)

Solution 4 - C#

Assembly assembly = Assembly.LoadFile(Path.GetFullPath("ConsoleApplication1.exe"));
Module manifestModule = assembly.ManifestModule;
PortableExecutableKinds peKind;
ImageFileMachine machine;
manifestModule.GetPEKind(out peKind, out machine);

The target machine should then be in machine.

That'll only work with .NET assemblies though.

Solution 5 - C#

According to "10 Ways to Determine if Application is Compiled for 32-bit or 64-bit", you can check if a DLL or EXE is 32-bit or 64-bit by opening it with Notepad and looking for PE at the beginning - if the 3rd letter after that is:

  • an L the platform is 32-bit: Notepad with x32 binary
  • a d the platform is 64-bit: Notepad with x64 binary

I tried it on my DLLs and it seems to be accurate.

Solution 6 - C#

dumpbin.exe available under bin directory of Visual Studio works for both .lib and .dll

 dumpbin.exe /headers *.dll |findstr machine
 dumpbin.exe /headers *.lib |findstr machine

Solution 7 - C#

I can offer a link to some C# code for accessing the IMAGE_FILE_HEADER, which I think could be (easily) compiled into a PowerShell cmdlet. I'm reasonably sure you can't use that method in PowerShell script directly, since it lacks pointers and PInvoke capability.

However, you should be able to use your by now extensive knowledge of the PE header format ;-) to just go "straight" to the right bytes and figure it out. This will work in PowerShell script, and you should be able to just convert this C# code from Tasos' blog to script. I won't bother repeating the code here since it's not mine.

Solution 8 - C#

Here's a C++ MFC console application that writes out the file header information. You can check the machine type (IMAGE_FILE_HEADER Machine member) or the IMAGE_FILE_32BIT_MACHINE flag in the Characteristics to see what platform the file is built for. See WinNT.h for more info on the structures.

#include "stdafx.h"

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
  int nRetCode = 0;
  int nrd;

  IMAGE_DOS_HEADER idh;
  IMAGE_NT_HEADERS inth;
  IMAGE_FILE_HEADER ifh;

  // initialize MFC and print and error on failure
  if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
  {
    _tprintf(_T("Fatal Error: MFC initialization failed\n"));
    nRetCode = 1;
    return 1;
  }
  if (argc != 2) {
    _ftprintf(stderr, _T("Usage: %s filename\n"), argv[0]);
    return 1;
  }
  // Try to open the file
  CFile ckf;
  CFileException ex;
  DWORD flags = CFile::modeRead | CFile::shareDenyNone;

  if (!ckf.Open(argv[1], flags, &ex)) {
    TCHAR szError[1024];
    ex.GetErrorMessage(szError, 1024);
    _tprintf_s(_T("Couldn't open file: %1024s"), szError);
    return 2;
  }

  // The following is adapted from:
  // https://stackoverflow.com/questions/495244/how-can-i-test-a-windows-dll-file-to-determine-if-it-is-32-bit-or-64-bit
  // https://stackoverflow.com/questions/46024914/how-to-parse-exe-file-and-get-data-from-image-dos-header-structure-using-c-and
  // Seek to beginning of file
  ckf.Seek(0, CFile::begin);

  // Read DOS header
  int nbytes = sizeof(IMAGE_DOS_HEADER);
  nrd = ckf.Read(&idh, nbytes);

  // The idh.e_lfanew member is the offset to the NT_HEADERS structure
  ckf.Seek(idh.e_lfanew, CFile::begin);

  // Read NT headers
  nbytes = sizeof(IMAGE_NT_HEADERS);
  nrd = ckf.Read(&inth, nbytes);

  ifh = inth.FileHeader;

  _ftprintf(stdout, _T("File machine type: "));
  switch (ifh.Machine) {
     case IMAGE_FILE_MACHINE_I386:
       _ftprintf(stdout, _T("I386\n"));
       break;
     case IMAGE_FILE_MACHINE_IA64:
       _ftprintf(stdout, _T("IA64\n"));
       break;
     case IMAGE_FILE_MACHINE_AMD64:
       _ftprintf(stdout, _T("AMD64\n"));
       break;
     default:
       _ftprintf(stdout, _T("Unknown (%d = %X)\n"), ifh.Machine, ifh.Machine);
       break;
  }

  // Write characteristics (see WinNT.h)
  _ftprintf(stdout, _T("Characteristics:\n"));
  _ftprintf(stdout, _T("RELOCS_STRIPPED Relocation info stripped from file: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_RELOCS_STRIPPED ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("EXECUTABLE_IMAGE File is executable  (i.e. no unresolved externel references): %c\n"),
    (ifh.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("LINE_NUMS_STRIPPED Line nunbers stripped from file: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_LINE_NUMS_STRIPPED ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("LOCAL_SYMS_STRIPPED Local symbols stripped from file: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_LOCAL_SYMS_STRIPPED ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("AGGRESIVE_WS_TRIM Agressively trim working set: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_AGGRESIVE_WS_TRIM ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("LARGE_ADDRESS_AWARE App can handle >2gb addresses: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_LARGE_ADDRESS_AWARE ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("BYTES_REVERSED_LO Bytes of machine word are reversed: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_BYTES_REVERSED_LO ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("32BIT_MACHINE 32 bit word machine: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_32BIT_MACHINE ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("DEBUG_STRIPPED Debugging info stripped from file in .DBG file: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_DEBUG_STRIPPED ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("REMOVABLE_RUN_FROM_SWAP If Image is on removable media, copy and run from the swap file: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("NET_RUN_FROM_SWAP If Image is on Net, copy and run from the swap file: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_NET_RUN_FROM_SWAP ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("SYSTEM System File: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_SYSTEM ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("DLL File is a DLL: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_DLL ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("UP_SYSTEM_ONLY File should only be run on a UP machine: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_UP_SYSTEM_ONLY ? _T('Y') : _T('N')));
  _ftprintf(stdout, _T("BYTES_REVERSED_HI Bytes of machine word are reversed: %c\n"),
    (ifh.Characteristics & IMAGE_FILE_BYTES_REVERSED_HI ? _T('Y') : _T('N')));


  ckf.Close();

  return nRetCode;
}

Solution 9 - C#

Unix OS have a utility called "file" which identifies files. The rules for identifying are kept in a description file called "magic". You could try file to see if it is able to identify your files correctly and grab the appropriate rules out of the magic file.

Solution 10 - C#

Here is my own implementation of this which has several more checks in place and always returns a result.

// the enum of known pe file types
public enum FilePEType : ushort
{
    IMAGE_FILE_MACHINE_UNKNOWN = 0x0,
    IMAGE_FILE_MACHINE_AM33 = 0x1d3,
    IMAGE_FILE_MACHINE_AMD64 = 0x8664,
    IMAGE_FILE_MACHINE_ARM = 0x1c0,
    IMAGE_FILE_MACHINE_EBC = 0xebc,
    IMAGE_FILE_MACHINE_I386 = 0x14c,
    IMAGE_FILE_MACHINE_IA64 = 0x200,
    IMAGE_FILE_MACHINE_M32R = 0x9041,
    IMAGE_FILE_MACHINE_MIPS16 = 0x266,
    IMAGE_FILE_MACHINE_MIPSFPU = 0x366,
    IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466,
    IMAGE_FILE_MACHINE_POWERPC = 0x1f0,
    IMAGE_FILE_MACHINE_POWERPCFP = 0x1f1,
    IMAGE_FILE_MACHINE_R4000 = 0x166,
    IMAGE_FILE_MACHINE_SH3 = 0x1a2,
    IMAGE_FILE_MACHINE_SH3DSP = 0x1a3,
    IMAGE_FILE_MACHINE_SH4 = 0x1a6,
    IMAGE_FILE_MACHINE_SH5 = 0x1a8,
    IMAGE_FILE_MACHINE_THUMB = 0x1c2,
    IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169,
}

// pass the path to the file and check the return
public static FilePEType GetFilePE(string path)
{
    FilePEType pe = new FilePEType();
    pe = FilePEType.IMAGE_FILE_MACHINE_UNKNOWN;
    if(File.Exists(path))
    {
        using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
        {
            byte[] data = new byte[4096];
            fs.Read(data, 0, 4096);
            ushort result = BitConverter.ToUInt16(data, BitConverter.ToInt32(data, 60) + 4);
            try
            {
                pe = (FilePEType)result;
            } catch (Exception)
            {
                pe = FilePEType.IMAGE_FILE_MACHINE_UNKNOWN;
            }
        }
    }
    return pe;
}

How to use :

string myfile = @"c:\windows\explorer.exe"; // the file
FilePEType pe = GetFilePE( myfile );

System.Diagnostics.WriteLine( pe.ToString() );

For the enum values used here, they were obtained from pe.go . The reason why this works is that for each binary ditribution of 'go' must have the correct flag in the assembly to let it pass the operating systems 'can you run here ?' check. Since 'go' is cross platform (all platforms), it is a good base to get this information. There are probably other sources for this information, but they seem to be nested knee-deep in google ca-ca requiring a 10th dan black-belt in Google-fu to locate.

Solution 11 - C#

Here is an implementation in C.

// Determines if DLL is 32-bit or 64-bit.
#include <stdio.h>

int sGetDllType(const char *dll_name);

int main()
{
  int ret;
  const char *fname = "sample_32.dll";
  //const char *fname = "sample_64.dll";
  ret = sGetDllType(fname);
}

static int sGetDllType(const char *dll_name) {
  const int PE_POINTER_OFFSET = 60;
  const int MACHINE_TYPE_OFFSET = 4;
  FILE *fp;
  unsigned int ret = 0;
  int peoffset;
  unsigned short machine;

  fp = fopen(dll_name, "rb");
  unsigned char data[4096];
  ret = fread(data, sizeof(char), 4096, fp);
  fclose(fp);
  if (ret == 0)
    return -1;

  if ( (data[0] == 'M') && (data[1] == 'Z') ) {
    // Initial magic header is good
    peoffset = data[PE_POINTER_OFFSET + 3];
    peoffset = (peoffset << 8) + data[PE_POINTER_OFFSET + 2];
    peoffset = (peoffset << 8) + data[PE_POINTER_OFFSET + 1];
    peoffset = (peoffset << 8) + data[PE_POINTER_OFFSET];

    // Check second header
    if ((data[peoffset] == 'P') && (data[peoffset + 1] == 'E')) {
      machine = data[peoffset + MACHINE_TYPE_OFFSET];
      machine = (machine)+(data[peoffset + MACHINE_TYPE_OFFSET + 1] << 8);

      if (machine == 0x014c)
        return 32;
      if (machine == 0x8664)
        return 64;

      return -1;
    }
    return -1;
  }
  else
    return -1;
}

Solution 12 - C#

Here's another solution using C/C++ as a standalone tool, ready to be adapted to whatever you need:

// Fri May 28, 2021 -two

#include <stdio.h>
#include <io.h>
#include <stdint.h>
#include <iostream.h>
using namespace std;

bool queryExeMachineType( const char *filename )
{
	FILE *fp = fopen( filename, "rb" );

	if (fp == NULL)
		return false;

	// DOS header is 64 bytes
	const uint32_t fsize = filelength( fileno( fp ) );
	char magic[ 2 ] = { 0 };
	uint32_t offset = 0;
	uint16_t machine = 0;

	if (fread( magic, 1, 2, fp ) != 2 || magic[ 0 ] != 'M' || magic[ 1 ] != 'Z')
	{
		cerr << "not an executable file" << endl;
		fclose( fp );
		return false;
	}
	fseek( fp, 60, SEEK_SET );
	fread( &offset, 1, 4, fp );

	if (offset >= fsize)
	{
		cerr << "invalid pe offset" << endl;
		fclose( fp );
		return false;
	}
	fseek( fp, offset, SEEK_SET );

	if (fread( magic, 1, 2, fp ) != 2 || magic[ 0 ] != 'P' || magic[ 1 ] != 'E')
	{
		cerr << "not a pe executable" << endl;
		fclose( fp );
		return false;
	}
	fread( magic, 1, 2, fp );
	fread( &machine, 1, 2, fp );

	switch (machine)
	{
		case 0x014c:
			cout << "i386" << endl;  // x86
			break;

		case 0x8664:
			cout << "amd64" << endl; // x86_64
			break;

       case 0x0200:
			cout << "ia64" << endl;  // itanium
			break;

		default:
			cerr << "unknown machine 0x" << hex << machine << endl;
			break;
	}
	fclose( fp );
	return true;
}

int main( int argc, char *argv[] )
{
	const char *fn = (argc > 1) ? argv[ 1 ] : "test.dll";

	if (queryExeMachineType( fn ))
		cerr << "succeeded" << endl;
	else
		cerr << "failed" << endl;

	return 0;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionhalr9000View Question on Stackoverflow
Solution 1 - C#Keith HillView Answer on Stackoverflow
Solution 2 - C#AndrewView Answer on Stackoverflow
Solution 3 - C#gbjbaanbView Answer on Stackoverflow
Solution 4 - C#ICRView Answer on Stackoverflow
Solution 5 - C#defaultView Answer on Stackoverflow
Solution 6 - C#Saad SaadiView Answer on Stackoverflow
Solution 7 - C#JaykulView Answer on Stackoverflow
Solution 8 - C#John MatthewsView Answer on Stackoverflow
Solution 9 - C#SecView Answer on Stackoverflow
Solution 10 - C#Kraang PrimeView Answer on Stackoverflow
Solution 11 - C#JiminionView Answer on Stackoverflow
Solution 12 - C#risingballsView Answer on Stackoverflow