22

How to check if Windows executable is 64-bit reading only its binary. Without executing it and not using any tools like the SDK tool dumpbin.exe with the /headers option.

perror
  • 19,083
  • 29
  • 87
  • 150
ST3
  • 849
  • 2
  • 8
  • 25

4 Answers4

26

Executable type is indicated by PE header, download documentation.

The first word (two bytes) of PE header indicates target machine, here is a list of possible values:

0x0000 - The contents of this field are assumed to be applicable to any machine type
0x01d3 - Matsushita AM33
0x8664 - x64
0x01c0 - ARM little endian
0x01c4 - ARMv7 (or higher) Thumb mode only
0xaa64 - ARMv8 in 64-bit mode
0x0ebc - EFI byte code
0x014c - Intel 386 or later processors and compatible processors
0x0200 - Intel Itanium processor family
0x9041 - Mitsubishi M32R little endian
0x0266 - MIPS16
0x0366 - MIPS with FPU
0x0466 - MIPS16 with FPU
0x01f0 - Power PC little endian
0x01f1 - Power PC with floating point support
0x0166 - MIPS little endian
0x01a2 - Hitachi SH3
0x01a3 - Hitachi SH3 DSP
0x01a6 - Hitachi SH4
0x01a8 - Hitachi SH5
0x01c2 - ARM or Thumb (“interworking”)
0x0169 - MIPS little-endian WCE v2

So to check if it is 64-bit, we need to look for:

0x8664 - x64
0xaa64 - ARMv8 in 64-bit mode
0x0200 - Intel Itanium processor family

And as Bob mentioned, here is a list of some more machine types (see 11 pg.), however it is not very likely to find them.

ST3
  • 849
  • 2
  • 8
  • 25
  • 6
    Don't forget 0x0200 (Itanium). That would be IA-64, as opposed to x86-64 (AMD64). There's a couple more "64-bit" machine types possible, but you're very unlikely to find them in the wild (0x0284 is the 64-bit Alpha AXP, from the 1999 documentation, and not supported by Windows after NT4/2000RC). – Bob Aug 08 '14 at 10:55
  • For a visual reference, image showing the location of the PE header with 0x8664 (highlighted): http://i.imgur.com/yHvcgdn.png (notepad++ hex-editor plugin) – zamnuts Aug 08 '14 at 14:47
  • 1
    To quickly find the target machine value: Take the DWORD at offset 0x3C. Add four to this value. That is the offset of the Machine WORD. (Both are little endian) – user1354557 Aug 25 '14 at 21:45
  • Where do you find 0xaa64 - ARMv8 in 64-bit mode? I have not such #define in winnt.h. – SerG Feb 12 '15 at 14:34
7

It's easier to check Optional Header magic number.

For a valid exe, only two values are possible:

0x10B => PE32  => 32 bit
0x20B => PE32+ => 64 bit
JayXon
  • 231
  • 1
  • 3
  • Might be worth mentioning the offset to find it at (0x18 bytes from the NT Header which starts at the "PE" magic). – Jordan Aug 06 '19 at 23:02
5

Here's a fun, quick little trick if in a Windows environment and you're checking for an x86 or x64 binary:

  1. Right-click on the application.
  2. Click Properties.
  3. Click the Compatibility tab.
  4. In the Compatibility mode section, check the Run this program in compatibility mode for: box.
  5. If you see Windows 95 as an option from the drop-down, then it's a 32-bit application. If you do not, then it's a 64-bit application.
dsasmblr
  • 2,234
  • 10
  • 18
3

dumping the contents of ST3's answer into a powershell snippet in a ready to use format

if($args.Count -eq 0) { "provide a file name or path to file";exit }
if((test-path -path $args) -ne $true) { "file doesnt seem to exist" ; exit }
$fs = New-Object IO.Filestream($args , [Io.FileMode]::Open)
$br = New-Object IO.BinaryReader($fs)
if($br.Readchar()-ne'M'){"no mz";exit};if($br.Readchar()-ne'Z'){"no mz";exit}
$fs.Seek(0x3c,[IO.SeekOrigin]::Begin) | Out-Null
$elfaw_new = $br.ReadUInt32();
$peheader=$fs.Seek($elfaw_new,[IO.SeekOrigin]::Begin) 
if($br.Readchar()-ne'P'){"no pe";exit};if($br.Readchar()-ne'E'){"no pe";exit}
$mctypeoff = $fs.seek($peheader+4,[IO.SeekOrigin]::Begin)
$mctype= $br.ReadUInt16()
switch($mctype) {
  0x0000 { "{0:x4} {1}" -f  $mctype , "Unknown machine type"}
  0x01d3 { "{0:x4} {1}" -f  $mctype , "Matsushita AM33"}
  0x8664 { "{0:x4} {1}" -f  $mctype , "x64"}
  0x01c0 { "{0:x4} {1}" -f  $mctype , "ARM little endian"}
  0x01c4 { "{0:x4} {1}" -f  $mctype , "ARMv7 (or higher) Thumb mode only"}
  0xaa64 { "{0:x4} {1}" -f  $mctype , "ARMv8 in 64-bit mode"}
  0x0ebc { "{0:x4} {1}" -f  $mctype , "EFI byte code"}
  0x014c { "{0:x4} {1}" -f  $mctype , "Intel 386 or later family processors"}
  0x0200 { "{0:x4} {1}" -f  $mctype , "Intel Itanium processor family"}
  0x9041 { "{0:x4} {1}" -f  $mctype , "Mitsubishi M32R little endian"}
  0x0266 { "{0:x4} {1}" -f  $mctype , "MIPS16"}
  0x0366 { "{0:x4} {1}" -f  $mctype , "MIPS with FPU"}
  0x0466 { "{0:x4} {1}" -f  $mctype , "MIPS16 with FPU"}
  0x01f0 { "{0:x4} {1}" -f  $mctype , "Power PC little endian"}
  0x01f1 { "{0:x4} {1}" -f  $mctype , "Power PC with floating point support"}
  0x0166 { "{0:x4} {1}" -f  $mctype , "MIPS little endian"}
  0x01a2 { "{0:x4} {1}" -f  $mctype , "Hitachi SH3"}
  0x01a3 { "{0:x4} {1}" -f  $mctype , "Hitachi SH3 DSP"}
  0x01a6 { "{0:x4} {1}" -f  $mctype , "Hitachi SH4"}
  0x01a8 { "{0:x4} {1}" -f  $mctype , "Hitachi SH5"}
  0x01c2 { "{0:x4} {1}" -f  $mctype , "ARM or Thumb (`“interworking`”)"}
  0x0169 { "{0:x4} {1}" -f  $mctype , "MIPS little-endian WCE v2"}
};$fs.close()

usage as follows

:\>powershell -f binstreamtest.ps1
provide a file name or path to file

:\>powershell -f binstreamtest.ps1 1
file doesnt seem to exist

:\>powershell -f binstreamtest.ps1 shell32.dll
014c Intel 386 or later family processors

:\>powershell -f binstreamtest.ps1 c:\WINDOWS\system32\ntkrnlpa.exe
014c Intel 386 or later family processors

:\>powershell -f binstreamtest.ps1 xxx\test32.exe
014c Intel 386 or later family processors

:\>powershell -f binstreamtest.ps1 xxx\test64.exe
8664 x64
blabb
  • 16,376
  • 1
  • 15
  • 30
  • Note exit will actually kill the parent terminal, if you replace it with return, it will work. In Powershell, return is really more for control-flow, because values are implicitly outputted . The only time it doesn't, is when writing a class – ninMonkey Aug 20 '22 at 01:40