I want to extract information from executable files, such as the names and sizes of sections, symbols, etc. The Wikipedia article has a diagram showing the layout of a PE. See also this diagram

It seems Mathematica isn't able to decode portable executables out of the box:

exe = Import["C:\\Windows\\System32\\notepad.exe"];
(* cannot infer the format *)

But we can at least read the bytes:

Import["C:\\Windows\\System32\\notepad.exe", "Byte"]

(* or alternatively *)


... so I've tried loading the bytes in myself and decoding the PE byte-by-byte:

(* extract bytes at zero-indexed offsets *)
at[bytes_, i_] := bytes[[i + 1]]
at[bytes_, i_, j_] := bytes[[i + 1 ;; j + 1]]

(* convert little-endian bytes to uint *)
uint[bytes_] := FromDigits[Reverse@bytes, 16]

(* Create a null terminated string from some bytes *)
nulltermstr[bytes_] := FromCharacterCode[TakeWhile[bytes, # > 0 &]]

exe = OpenRead["C:\\Windows\\System32\\notepad.exe", BinaryFormat -> True];
bytes = BinaryReadList[exe];

peHeaderOffset = uint@at[bytes, 60, 63];
peMagic = FromCharacterCode[at[bytes, peHeaderOffset, peHeaderOffset + 1]]
(* result: PE *)

optionalHeaderSize = uint[at[bytes, peHeaderOffset + 5*4, peHeaderOffset + 5*4 + 1]];
sectionsStartOffset = peHeaderOffset + 3*8 + optionalHeaderSize;
sectionSize = 5*8;
sectionNames = Table[
  nulltermstr[at[bytes, sectionsStartOffset + sectionSize*i, sectionsStartOffset + sectionSize*i + 7]]
  , {i, 0, 6}]
(* result: {".text",".rdata",".data",".pdata",".didat",".rsrc",".reloc"} *)

It's quite tedious extracting pointers and getting the offsets from the binary format. As you can see, there are 'magic numbers' in my code which is not ideal.

Is there a better, ideally more declarative way to specify the format so that Mathematica can parse these binary headers? Ideally I'd like to be able to declare the structure of the PE file in a way similar to this tool: http://kaitai.io which would make decoding all sorts of binary formats much simpler from within Mathematica.

  • 2
    $\begingroup$ Import["C:\\Windows\\System32\\notepad.exe", "Byte"] works for me. So does BinaryReadList["C:\\Windows\\System32\\notepad.exe"] $\endgroup$ – Sjoerd Smit Jun 25 '20 at 16:06
  • $\begingroup$ @SjoerdSmit thanks I will remove that part of the question since I was just using it wrongly. $\endgroup$ – flinty Jun 25 '20 at 17:20

This is not a pure Mathematica solution, but I ended up calling Python and using the pefile module:

{sections, sectionsEntropy, importedSymbols} =
  ExternalEvaluate["Python", {
     "import pefile",
     "pe = pefile.PE('C:/Windows/System32/notepad.exe')", 
     "[x.Name.decode('ascii').rstrip('\\x00') for x in pe.sections]",
     "[x.get_entropy() for x in pe.sections]",
     "[[[x.dll.decode('utf-8'),[s.name.decode('utf-8'), s.address]] for s in x.imports if s.name] for x in pe.DIRECTORY_ENTRY_IMPORT]"
  }][[3 ;;]];

BarChart[sectionsEntropy, ChartLabels -> sections, AxesLabel -> "Entropy"]

(* show the symbols imported from in msvcrt.dll. Here it's the 3rd dll import *)
Grid[Flatten /@ importedSymbols[[3, All, 2 ;;]]]

executable sections entropy and symbols


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.