Skip to content

All Options

Each entry lists what the switch does, an example, the JSON key it adds, and the matching GUI view.

Point at a layer inside the file

Every switch also works on a payload inside the file: ppee-cli --analysis 'drop.exe#resource:RT_RCDATA/101'. See Analyse a layer inside a file.

Switch Short description
--headers DOS, NT, File and Optional header fields
--dirs The 16-entry data directory table
--sections Section headers
--hashes CRC32, MD5, SHA-1, SHA-256, SSDEEP, TLSH, ImpHash, Authentihash, entropy
--similarity Look the file up in, and add it to, the similarity DB
--imports Imported modules and functions
--exports Exported functions
--basereloc Base relocations
--tls TLS directory and callbacks
--debug Debug directory (CodeView/PDB, POGO, …)
--bound-imports Bound imports
--delay-imports Delay-load imports
--resources Resource tree (type/name/language)
--exception Exception directory (x64/ARM64 unwind info)
--security Authenticode certificate table
--loadconfig Load configuration, CFG, SafeSEH
--net .NET CLR header, metadata root, streams and VTableFixups
--richheader Rich header (toolchain records)
--appmanifest Embedded application manifest
--analysis Derived views for .NET, Go, Rust and NativeAOT builds, and the Code analysis of x86/x64 code
--analysis-deep --analysis plus the deep pass
--strings ASCII, Unicode, URL, registry and suspicious strings
--disasm TARGET Disassemble x86/x64 code at the entry point, a TLS callback, an export or an address (--count N)
--xrefs TARGET Where an import, address or string is used in the code (--max-sites N)
--functions Function starts found by the code scan
--all Everything above except --disasm, --xrefs and --functions
--json Emit JSON instead of text
--timing Per-stage timings on stderr
--set NAME=VALUE Patch a field (repeatable)
--save Write edits back
-o PATH Write edits to another file
--no-similarity Never touch the similarity DB
--no-update-check Skip the startup update check
--scan-budget N Code scan budget, in millions of instructions
--no-code-scan Skip the implicit code scan (--analysis, MCP string references)
--mcp Run as an MCP server
--mcp-allow-write Enable the MCP patch_pe tool
-h, --help Show usage
--version Print version

Analyse a layer inside a file

Got a dropper with a payload in a resource or the overlay? Don't carve it out. Add # steps to the file name and every switch works on that layer, read into memory and never written to disk:

Path Layer
drop.exe#overlay The data after the last section
drop.exe#resource:RT_RCDATA/101 A resource (TYPE/NAME, optionally /LANGUAGE; RT_RCDATA, RCDATA and 10 all work)
drop.exe#section:.rsrc A section's raw data (#section:#3 by position)
drop.exe#offset:0x5000+0x2000 A byte range (#offset:0x5000 to the end)
drop.exe#overlay#resource:CABINET/1 Steps nest

WannaCry's launcher.dll keeps its next stage in resource W/101, 4 bytes in. Hash it where it is:

$ ppee-cli --hashes '9487edf9….exe#resource:W/101#offset:4'
…/9487edf9….exe#resource:W/101#offset:4: 5242876 bytes, PE32

FileInfo:
  MD5:          07B2930FB73F97171400953DDF6F28FB
  SHA256:       4F3B1943B2D1CAE320D92C3BEB0BBA2FBFEC8F9D14C95E3FFB11D2FAFA26F9AC
  ImpHash:      9ECEE117164E0B870A53DD187CDD7174
  …
PS C:\> ppee-cli.exe --hashes 'C:\MalwareSamples\9487edf9b75f4c15e3ba6ccbae23588ee3dc9c4983417f1b469278af17fc3847.exe#resource:W/101#offset:4'
C:\MalwareSamples\9487edf9b75f4c15e3ba6ccbae23588ee3dc9c4983417f1b469278af17fc3847.exe#resource:W/101#offset:4: 5242876 bytes, PE32

FileInfo:
  CRC32:        94E15948
  Entropy:      4.16148
  MD5:          07B2930FB73F97171400953DDF6F28FB
  SHA1:         B79F48B5DC79606F3C3CF684EC5D2987406514A2
  SHA256:       4F3B1943B2D1CAE320D92C3BEB0BBA2FBFEC8F9D14C95E3FFB11D2FAFA26F9AC
  SSDEEP:       49152:QnnMSPbcKQej/1Iix+TSqTdX1HkQo6SAARdhnv:QnPouz1bxcSUDk36SAEdhv
  TLSH:         T12936339971BC91FCC205297484AB8E22A2B23C7925FE5E0F9F4089761D53F56FB90B43
  ImpHash:      9ECEE117164E0B870A53DD187CDD7174
  Authentihash: 0AA2F61297768D185E2B2C4984008E50D46E7F9BC60F05F226F3265A2C1E229A

Swap --hashes for --analysis, --imports, --disasm ep or anything else. Quote the path: # starts a comment in most shells. A wrong step says what is there:

failed to load '…/9487edf9….exe#resource:W/9': #resource:W/9: no such resource. Resources: W/101, RT_MANIFEST/2

A file whose real name contains # is still read as that file.


Section filters

Section filters combine freely. If none is given, all of them are on (the same as --all). Output always follows a fixed order, whatever order the switches were given in.

--headers

Includes every field of the DOS header, NT signature, File header and Optional header. The names printed here are the names --set accepts.

$ ppee-cli --headers aepic.dll
Headers:
  DosHeader.e_magic                        = 5A4D
  ...
  FileHeader.Machine                       = 8664
  FileHeader.NumberOfSections              = 0007
  FileHeader.TimeDateStamp                 = F8929A64
  ...
  OptionalHeader.AddressOfEntryPoint       = 0001A190
  OptionalHeader.ImageBase                 = 0000000180000000
  OptionalHeader.Subsystem                 = 0002
  OptionalHeader.DllCharacteristics        = 4160

--dirs

Includes the data directory table: the RVA and size of all 16 directories (Export, Import, Resource, Exception, Security, BaseReloc, Debug, Architecture, GlobalPtr, TLS, LoadConfig, BoundImport, IAT, DelayImport, COM descriptor, Reserved).

$ ppee-cli --dirs sample.exe
Data directories:
  DataDirectory[0].VirtualAddress          = 00000000
  DataDirectory[0].Size                    = 00000000
  DataDirectory[1].VirtualAddress          = 00425638
  DataDirectory[1].Size                    = 00000AB4
  ...

Note

For the Security directory (index 4), VirtualAddress is a file offset, not an RVA. That is how the PE format defines it.

--sections

Includes every section header: name, virtual address and size, raw pointer and size, relocation and line-number fields, and characteristics.

Example: a UPX-packed dropper, 77549422a5306f905d68153b1f649745d330d45e564c927046132ecc1d20ae3e.exe:

$ ppee-cli --sections 77549422….exe
Sections:
  Section[0].Name                          = UPX0
  Section[0].VirtualAddress                = 00001000
  Section[0].VirtualSize                   = 00008000
  Section[0].PointerToRawData              = 00000200
  Section[0].SizeOfRawData                 = 00000000
  Section[0].Characteristics               = E0000080
  Section[1].Name                          = UPX1
  Section[1].VirtualAddress                = 00009000
  Section[1].VirtualSize                   = 00002000
  Section[1].SizeOfRawData                 = 00001600
  Section[1].Characteristics               = E0000040
  ...
PS C:\> ppee-cli.exe --sections C:\MalwareSamples\77549422a5306f905d68153b1f649745d330d45e564c927046132ecc1d20ae3e.exe
C:\MalwareSamples\77549422a5306f905d68153b1f649745d330d45e564c927046132ecc1d20ae3e.exe: 7168 bytes, PE32

Sections:
  Section[0].Name                          = UPX0
  Section[0].VirtualAddress                = 00001000
  Section[0].VirtualSize                   = 00008000
  Section[0].PointerToRawData              = 00000200
  Section[0].SizeOfRawData                 = 00000000
  Section[0].PointerToRelocations          = 00000000
  Section[0].PointerToLinenumbers          = 00000000
  Section[0].NumberOfRelocations           = 0000
  Section[0].NumberOfLinenumbers           = 0000
  Section[0].Characteristics               = E0000080
  Section[1].Name                          = UPX1
  Section[1].VirtualAddress                = 00009000
  Section[1].VirtualSize                   = 00002000
  Section[1].PointerToRawData              = 00000200
  Section[1].SizeOfRawData                 = 00001600
  Section[1].PointerToRelocations          = 00000000
  Section[1].PointerToLinenumbers          = 00000000
  Section[1].NumberOfRelocations           = 0000
  Section[1].NumberOfLinenumbers           = 0000
  Section[1].Characteristics               = E0000040
  Section[2].Name                          = .rsrc
  Section[2].VirtualAddress                = 0000B000
  Section[2].VirtualSize                   = 00001000
  Section[2].PointerToRawData              = 00001800
  Section[2].SizeOfRawData                 = 00000400
  Section[2].PointerToRelocations          = 00000000
  Section[2].PointerToLinenumbers          = 00000000
  Section[2].NumberOfRelocations           = 0000
  Section[2].NumberOfLinenumbers           = 0000
  Section[2].Characteristics               = C0000040

UPX0 takes 32 KB in memory but 0 bytes in the file, and both sections are E00000xx (read + write + execute): the unpacker decompresses UPX1 into the empty UPX0 at run time.

--hashes

Computes whole-file CRC32, MD5, SHA-1, SHA-256, SSDEEP, TLSH, ImpHash, Authentihash and Shannon entropy. The passes run in parallel on multi-core machines.

$ ppee-cli --hashes 77549422….exe
FileInfo:
  CRC32:        CBBCCE59
  Entropy:      6.92387
  MD5:          B578369C8E42C88CF5DBA6093E4D5601
  SHA1:         0E067777F022FFACC522FED4734FB98E50221FD7
  SHA256:       77549422A5306F905D68153B1F649745D330D45E564C927046132ECC1D20AE3E
  SSDEEP:       192:zXzdrr1FG1WDCgmjPZKYjRnMoyCeFMUA:Lprr1gkDCgS9jlMWIMB
  TLSH:         T128E17E972E3A10EBD0DA7231528FC26631AFA0E1A7D50AC88A6CEF5F34665149975704
  ImpHash:      A3581BFE28E762682DBC13D06BF2FDA0
  Authentihash: C2C14EAB7EEECC4C525BC839784729F57F7A4A7F109E2179EE3121FF9A4F2553
PS C:\> ppee-cli.exe --hashes C:\MalwareSamples\77549422a5306f905d68153b1f649745d330d45e564c927046132ecc1d20ae3e.exe
C:\MalwareSamples\77549422a5306f905d68153b1f649745d330d45e564c927046132ecc1d20ae3e.exe: 7168 bytes, PE32

FileInfo:
  CRC32:        CBBCCE59
  Entropy:      6.92388
  MD5:          B578369C8E42C88CF5DBA6093E4D5601
  SHA1:         0E067777F022FFACC522FED4734FB98E50221FD7
  SHA256:       77549422A5306F905D68153B1F649745D330D45E564C927046132ECC1D20AE3E
  SSDEEP:       192:zXzdrr1FG1WDCgmjPZKYjRnMoyCeFMUA:Lprr1gkDCgS9jlMWIMB
  TLSH:         T128E17E972E3A10EBD0DA7231528FC26631AFA0E1A7D50AC88A6CEF5F34665149975704
  ImpHash:      A3581BFE28E762682DBC13D06BF2FDA0
  Authentihash: C2C14EAB7EEECC4C525BC839784729F57F7A4A7F109E2179EE3121FF9A4F2553

The SHA-256 is the sample's name on MalwareBazaar; search it, or the ImpHash and TLSH, there or on VirusTotal to find relatives.

  • JSON: fileInfo → crc32, entropy, md5, sha1, sha256, ssdeep, tlsh, impHash, authentihash
  • GUI: File Information
  • MCP: get_hashes
  • See also: Hashes & Entropy

--similarity

Hashes the file, compares it with every file previously recorded in the local similarity database, and then records it in the database. Implies the hash computation from --hashes.

$ ppee-cli --similarity explorer2.exe
Similarity DB: 2 record(s)
Match against 1 peer(s):
  SHA256 identical - /samples/explorer.exe
  • JSON: similarity → available, dbRecordCount, matches[] (peerPath, kind)
  • If the database can't be opened (for example on a read-only location), PPEE prints warning: similarity database '…' could not be opened … on stderr, reports available: false / Similarity DB: unavailable, and skips the lookup.
  • GUI: similarity toast and history window (Similarity Alerts)
  • MCP: check_similarity
  • See also: Similarity Engine, --no-similarity

This switch writes to disk

--similarity (and therefore --all, and running with no filters) creates or updates ppee-cli.similarity.db next to the executable. Use --no-similarity on read-only media or when you don't want samples remembered.

--imports

Includes the import directory: every imported module with its OriginalFirstThunk, TimeDateStamp and FirstThunk, and every function imported by name (with hint) or by ordinal.

$ ppee-cli --imports 77549422….exe
Imports (7 module(s)):
  KERNEL32.DLL (OFT=0 TimeDateStamp=0 FT=B14C) - 6 function(s)
    LoadLibraryA (hint=0)
    GetProcAddress (hint=0)
    VirtualProtect (hint=0)
    VirtualAlloc (hint=0)
    VirtualFree (hint=0)
    ExitProcess (hint=0)
  advapi32.dll (OFT=0 TimeDateStamp=0 FT=B168) - 1 function(s)
    RegCloseKey (hint=0)
  ...
PS C:\> ppee-cli.exe --imports C:\MalwareSamples\77549422a5306f905d68153b1f649745d330d45e564c927046132ecc1d20ae3e.exe
C:\MalwareSamples\77549422a5306f905d68153b1f649745d330d45e564c927046132ecc1d20ae3e.exe: 7168 bytes, PE32

Imports (7 module(s)):
  KERNEL32.DLL (OFT=0 TimeDateStamp=0 FT=B14C) - 6 function(s)
    LoadLibraryA (hint=0)
    GetProcAddress (hint=0)
    VirtualProtect (hint=0)
    VirtualAlloc (hint=0)
    VirtualFree (hint=0)
    ExitProcess (hint=0)
  advapi32.dll (OFT=0 TimeDateStamp=0 FT=B168) - 1 function(s)
    RegCloseKey (hint=0)
  comctl32.dll (OFT=0 TimeDateStamp=0 FT=B170) - 1 function(s)
    InitCommonControls (hint=0)
  gdi32.dll (OFT=0 TimeDateStamp=0 FT=B178) - 1 function(s)
    CreateFontIndirectA (hint=0)
  shell32.dll (OFT=0 TimeDateStamp=0 FT=B180) - 1 function(s)
    ShellExecuteA (hint=0)
  shlwapi.dll (OFT=0 TimeDateStamp=0 FT=B188) - 1 function(s)
    PathMatchSpecA (hint=0)
  user32.dll (OFT=0 TimeDateStamp=0 FT=B190) - 1 function(s)
    EndPaint (hint=0)

The classic packer import table: LoadLibraryA + GetProcAddress to rebuild the real imports, VirtualAlloc/VirtualProtect for the unpacked code, and one function from each other DLL, only so the loader maps it.

  • JSON: imports[] → name, originalFirstThunk, timeDateStamp, forwarderChain, firstThunk, functions[]
  • GUI: DIR_ENTRY_IMPORT (n), where selecting a module lists its functions, including demangled C++ names
  • MCP: list_imports
  • See also: Import directory, ImpHash

--exports

Includes the export directory: the DLL name, ordinal base, counts, and every exported function with its ordinal, name, RVA and forwarder.

$ ppee-cli --exports aepic.dll
Exports: AEPIC.dll (base=1, 12 function(s)):
  #1      GetAppInventoryCore rva=29CB0
  #2      UpdateSoftwareInventoryTC2 rva=4A3F0
  #3      DllCanUnloadNow rva=41BE0
  ...
  • JSON: exports → present, name, base, numberOfFunctions, numberOfNames, functions[]
  • GUI: DIR_ENTRY_EXPORT (n)
  • MCP: list_exports
  • See also: Exports

--basereloc

Includes the base relocation blocks and the entries in each (type, offset, RVA, value and target).

$ ppee-cli --basereloc sample.exe
Base relocations: 56 block(s), 10938 entrie(s)
  page=0036B000 size=996 entries=494
  ...
  • JSON: baseRelocations[] → pageRVA, sizeOfBlock, entries[]
  • GUI: DIR_ENTRY_BASERELOC, with a rebase preview
  • See also: Relocations

--tls

Includes the TLS directory and its callback list. TLS callbacks run before the entry point, which malware uses for anti-debugging.

$ ppee-cli --tls ef431e36….dll
TLS: index=6DAF6044 zeroFill=0 callbacks=2
  callback VA=6D741870
  callback VA=6D741830
PS C:\> ppee-cli.exe --tls C:\MalwareSamples\ef431e36a8ac12051af588d800e1d8128d583a9d4c68187709263a13564081cd.dll
C:\MalwareSamples\ef431e36a8ac12051af588d800e1d8128d583a9d4c68187709263a13564081cd.dll: 3880960 bytes, PE32

TLS: index=6DAF6044 zeroFill=0 callbacks=2
  callback VA=6D741870
  callback VA=6D741830

ef431e36a8ac12051af588d800e1d8128d583a9d4c68187709263a13564081cd.dll. Two callbacks run before DllMain. Read them with --disasm tls:0: here they check the reason argument and set a flag, the MinGW runtime's own TLS code, not a trick. Only callbacks that don't look like that are worth a closer look.

  • JSON: tls → present, startAddressOfRawData, endAddressOfRawData, addressOfIndex, addressOfCallBacks, callbacks[]
  • GUI: DIR_ENTRY_TLS (n)
  • See also: TLS

--debug

Includes the debug directory: CodeView (RSDS PDB path, GUID and age), POGO, FPO, VC feature and repro entries.

$ ppee-cli --debug STEALERDLL.dll
Debug directory: 4 entrie(s)
  type=2 sizeOfData=71 pointerToRawData=119EDC [RSDS pdb=D:\Mktmp\StealerDLL\Release.x64\STEALERDLL.pdb age=212 guid=99F662A8-FCA0-49B6-B44E-FB7204BF0677]
  type=12 sizeOfData=20 pointerToRawData=119F24
  type=13 sizeOfData=852 pointerToRawData=119F38
  type=14 sizeOfData=0 pointerToRawData=0
PS C:\> ppee-cli.exe --debug C:\MalwareSamples\STEALERDLL.dll
C:\MalwareSamples\STEALERDLL.dll: 1282048 bytes, PE32+

Debug directory: 4 entrie(s)
  type=2 sizeOfData=71 pointerToRawData=119EDC [RSDS pdb=D:\Mktmp\StealerDLL\Release.x64\STEALERDLL.pdb age=212 guid=99F662A8-FCA0-49B6-B44E-FB7204BF0677]
  type=12 sizeOfData=20 pointerToRawData=119F24
  type=13 sizeOfData=852 pointerToRawData=119F38
  type=14 sizeOfData=0 pointerToRawData=0

STEALERDLL.dll. The author left the project name and folder in the PDB path. age=212 says the project was rebuilt about 200 times: an actively developed tool. The GUID links this build to its PDB, and to other builds from the same one.

  • JSON: debug[] → type, timeDateStamp, sizeOfData, pointerToRawData, codeView{format,pdbPath,age,guid}
  • GUI: DIR_ENTRY_DEBUG (n)
  • See also: Debug directory

--bound-imports

Includes bound imports: module timestamps and forwarder references that were pre-resolved at bind time.

$ ppee-cli --bound-imports legacy.dll
Bound imports (6 module(s)):
  msvcrt.dll (TimeDateStamp=4A5BDFBE) - 1 forwarder(s)
    -> NTDLL.DLL
  • JSON: boundImports[]
  • GUI: DIR_ENTRY_BOUND_IMPORT (n)
  • MCP: part of list_imports

--delay-imports

Includes delay-load imports, the modules that are loaded on first call rather than at process start.

$ ppee-cli --delay-imports sample.exe
Delay-load imports (60 module(s)):
  SndVolSSO.DLL (attrs=1) - 4 function(s)
    Ordinal #1
  WINTRUST.dll (attrs=1) - 1 function(s)
    WTGetSignatureInfo (hint=91)
  • JSON: delayImports[] → name, attributes, timeDateStamp, functions[]
  • GUI: DIR_ENTRY_DELAY_IMPORT (n)
  • MCP: part of list_imports

--resources

Includes the full resource tree at all three levels (type → name → language). For each leaf it gives the file offset, size, code page, entropy, MD5, the detected content type (PNG, ICO, XML, PE, ZIP, …) and a first-bytes preview.

$ ppee-cli --resources 3c6b036f….exe
Resources: 4 type(s), 9 name(s), 9 language variant(s):
  #3 (RT_ICON) [...]
  #10 (RT_RCDATA) [...]
    #100 [...]
      lang=#1033 offset=4C500 size=1515568 codePage=0 entropy=6.49614 md5=AC7A167EE7269BD790F220BB104CCA22 typeDetected=PE File
        first bytes: 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 | MZ.........ÿÿ..
  #14 (RT_GROUP_ICON) [...]
  #16 (RT_VERSION) [...]
PS C:\> ppee-cli.exe --resources C:\MalwareSamples\3c6b036f2eebc124c17db51960d9f6c9b39e236e9a33d5ac0c3a3a2cabe36833.exe
C:\MalwareSamples\3c6b036f2eebc124c17db51960d9f6c9b39e236e9a33d5ac0c3a3a2cabe36833.exe: 1826352 bytes, PE32+

Resources: 4 type(s), 9 name(s), 9 language variant(s):
  [root] Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=4
  #3 (RT_ICON) [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=6]
    #1 [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
      lang=#1033 offset=1BE530 size=296 codePage=0 entropy=2.52572 md5=7D3C73068D397DDE148769EF8EB9B3FA typeDetected=Icon Image
        first bytes: 28 00 00 00 10 00 00 00 20 00 00 00 01 00 04 00 | (....... .......
    #2 [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
      lang=#1033 offset=1BE658 size=744 codePage=0 entropy=2.54823 md5=996902231372D924A013649C8240E990 typeDetected=Icon Image
        first bytes: 28 00 00 00 20 00 00 00 40 00 00 00 01 00 04 00 | (... ...@.......
    #3 [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
      lang=#1033 offset=1BE940 size=1640 codePage=0 entropy=2.32902 md5=DB628C61BB8A57188CC7751951E7DE41 typeDetected=Icon Image
        first bytes: 28 00 00 00 30 00 00 00 60 00 00 00 01 00 04 00 | (...0...`.......
    #4 [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
      lang=#1033 offset=1BEFA8 size=176 codePage=0 entropy=1.98919 md5=60D6E880DCE25CE3906D70E6D1BF4155 typeDetected=Icon Image
        first bytes: 28 00 00 00 10 00 00 00 20 00 00 00 01 00 01 00 | (....... .......
    #5 [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
      lang=#1033 offset=1BF058 size=304 codePage=0 entropy=3.13529 md5=5C23901F0E9D4D1783B6CB81F2360E50 typeDetected=Icon Image
        first bytes: 28 00 00 00 20 00 00 00 40 00 00 00 01 00 01 00 | (... ...@.......
    #6 [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
      lang=#1033 offset=1BF188 size=816 codePage=0 entropy=2.49222 md5=CB0C36F92C8BAE5BE460B34A8647F2C2 typeDetected=Icon Image
        first bytes: 28 00 00 00 30 00 00 00 60 00 00 00 01 00 01 00 | (...0...`.......
  #10 (RT_RCDATA) [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
    #100 [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
      lang=#1033 offset=4C500 size=1515568 codePage=0 entropy=6.49614 md5=AC7A167EE7269BD790F220BB104CCA22 typeDetected=PE File
        first bytes: 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 | MZ�.........��..
  #14 (RT_GROUP_ICON) [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
    #101 [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
      lang=#1033 offset=1BF4B8 size=90 codePage=0 entropy=2.74417 md5=D148C75E59377AA79C180396F45F355C typeDetected=Icon
        first bytes: 00 00 01 00 06 00 10 10 10 00 01 00 04 00 28 01 | ..............(.
  #16 (RT_VERSION) [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
    #1 [Characteristics=00000000 TimeDateStamp=00000000 MajorVersion=0 MinorVersion=0 NumberOfNamedEntries=0 NumberOfIdEntries=1]
      lang=#1033 offset=4C220 size=736 codePage=0 entropy=3.36507 md5=656AB2DEAD54829FB5EBCD956A562C39 typeDetected=Version Resource
        first bytes: E0 02 34 00 00 00 56 00 53 00 5F 00 56 00 45 00 | �.4...V.S._.V.E.

RemusStealer dropper, 3c6b036f2eebc124c17db51960d9f6c9b39e236e9a33d5ac0c3a3a2cabe36833.exe. A 1.5 MB executable (MZ, typeDetected=PE File) sits in RT_RCDATA #100, most of the file. offset is the resource's RVA, as the format stores it. Extract it with the GUI's Dump… or MCP extract_payload; see the resource page for the whole walkthrough.

  • JSON: resources → types[] → names[] → languages
  • GUI: DIR_ENTRY_RESOURCE (n), with previews and Dump… to extract a resource
  • See also: Resources

--exception

Includes the exception directory (.pdata): RUNTIME_FUNCTION entries and decoded unwind info for AMD64 and ARM64.

$ ppee-cli --exception sample.exe
Exception directory (AMD64): 12411 entrie(s)
  begin=00001008 end=000012ED unwindInfo=0040914C [version=1 flags=3 sizeOfProlog=38 countOfCodes=9]
  • JSON: exception → present, machine, entries[] → beginAddress, endAddress, unwindInfoAddress, unwindInfo
  • GUI: DIR_ENTRY_EXCEPTION (AMD64 | ARM64, n)
  • See also: Exception directory

--security

Includes the certificate table, with every chained entry (including nested signatures): program name, publisher link, digest algorithm, the embedded digest compared with the computed Authentihash, the signer and timestamp certificates (subject, issuer, serial number, validity period, key, EKU), and the timestamp kind.

$ ppee-cli --security 847d8f49….exe
Security (certificate table): 1 entrie(s)
  offset=2DB000 length=2432 revision=0200 type=0002 sha256=417C5AC0038A8E7912703191B4F4B9F25D6FB2E6652E27045E944FF80351EB8E
  Validity: Not available on this platform -- Signature validity is checked by the Windows version of ppee
  Signature #1 (certificate[0])
    Digest Algorithm: SHA1
    Signature Algorithm: RSA
    Embedded Digest (SHA1): 92522BB8270AC121C54FF11FCAD37AE131B3AF61
    Authentihash (SHA1): 92522BB8270AC121C54FF11FCAD37AE131B3AF61
    Signer Certificate:
      Serial Number:  24 31 0E CA B7 5B FF EA
      Issuer Name: jaweralima.com (C=US, ST=bId7Hmyw, L=IsolE, O=421ZtfNiHkua2p, CN=jaweralima.com)
      Subject Name: jaweralima.com (C=US, ST=bId7Hmyw, L=IsolE, O=421ZtfNiHkua2p, CN=jaweralima.com)
      Valid: 2026/08/05 07:25:56 - 2027/08/05 07:25:56 UTC
      Signature Algorithm: SHA256 RSA
      Public Key: RSA 4096-bit
      Enhanced Key Usage: 1.3.6.1.5.5.7.3.1, 1.3.6.1.5.5.7.3.2
PS C:\> ppee-cli.exe --security C:\MalwareSamples\847d8f4998d22fde37eb76f99b6d91012c42965b741fe2cec453ca5876cdf147.exe
C:\MalwareSamples\847d8f4998d22fde37eb76f99b6d91012c42965b741fe2cec453ca5876cdf147.exe: 2996608 bytes, PE32+

Security (certificate table): 1 entrie(s)
  offset=2DB000 length=2432 revision=0200 type=0002 sha256=417C5AC0038A8E7912703191B4F4B9F25D6FB2E6652E27045E944FF80351EB8E
  Validity: Broken
  Signature #1 (certificate[0])
    Digest Algorithm: SHA1
    Signature Algorithm: RSA
    Embedded Digest (SHA1): 92522BB8270AC121C54FF11FCAD37AE131B3AF61
    Authentihash (SHA1): 92522BB8270AC121C54FF11FCAD37AE131B3AF61
    Signer Certificate:
      Serial Number:  24 31 0E CA B7 5B FF EA
      Issuer Name: jaweralima.com (C=US, ST=bId7Hmyw, L=IsolE, O=421ZtfNiHkua2p, CN=jaweralima.com)
      Subject Name: jaweralima.com (C=US, ST=bId7Hmyw, L=IsolE, O=421ZtfNiHkua2p, CN=jaweralima.com)
      Valid: 2026/08/05 07:25:56 - 2027/08/05 07:25:56 UTC
      Signature Algorithm: SHA256 RSA
      Public Key: RSA 4096-bit
      Enhanced Key Usage: 1.3.6.1.5.5.7.3.1, 1.3.6.1.5.5.7.3.2

RemusStealer, 847d8f4998d22fde37eb76f99b6d91012c42965b741fe2cec453ca5876cdf147.exe. "Signed" does not mean trusted. The digests match, so the file is intact, but Issuer = Subject (self-signed), the state, city and organization are random strings, the EKUs are TLS server/client auth (no code signing, 1.3.6.1.5.5.7.3.3), and there is no timestamp. Windows will not trust it; it exists to make the file look signed.

Parsing vs trust

PPEE parses signatures on every platform. Whether a signature is trusted (valid chain, not revoked) is checked with WinVerifyTrust, so only the Windows build reports it.

--loadconfig

Includes the load configuration directory: security cookie, SafeSEH handler table, Control Flow Guard (function table, address-taken IAT, long-jump targets, EH continuation targets), guard flags, CHPE and volatile metadata.

$ ppee-cli --loadconfig sample.exe
LoadConfig (PE32+):
  Size=118 TimeDateStamp=0 Version=0.0
  EditList=0 SecurityCookie=140431C68
  GuardCFCheckFunctionPointer=1403A0280 GuardCFDispatchFunctionPointer=1403A0288
  GuardCFFunctionTable=1403A097C GuardCFFunctionCount=F71 GuardFlags=417500
  GuardCF function table (3953):
    RVA=4F10
  • JSON: loadConfig → header, safeSeh, guardCFFunction, guardAddressTakenIat, guardLongJumpTarget, guardEHContinuation
  • GUI: DIR_ENTRY_LOAD_CONFIG, with Safe SEH, Guard … and Volatile Metadata children
  • See also: Load Config & CFG

--net

Includes the .NET (CLR / COR20) header, the metadata root, the stream directory (#~, #Strings, #US, #GUID, #Blob) and the VTableFixups.

Metadata tables are shown in the GUI

The rows of the metadata tables (TypeDef, MethodDef, …) and the heap contents are browsed in the GUI. From the CLI you can patch both table cells and heap cells with --set.

$ ppee-cli --net 05570fc8….exe
.NET (COR20 header):
  cb=48 RuntimeVersion=2.5 Flags=3 EntryPointToken=600000A
  MetaData: RVA=5D64 Size=7B54
  Resources: RVA=D8B8 Size=5A11F0
  StrongNameSignature: RVA=0 Size=0
  ...
  Metadata root: Signature=424A5342 Version=1.1 VersionString="v4.0.30319" Flags=0 Streams=5
    #~         offset=6C size=2F48
    #Strings   offset=2FB4 size=2C2C
PS C:\> ppee-cli.exe --net C:\MalwareSamples\05570fc80953707bdb230ecca534a259977d8185071c64468c0ce0d810b7cbc5.exe
C:\MalwareSamples\05570fc80953707bdb230ecca534a259977d8185071c64468c0ce0d810b7cbc5.exe: 5972992 bytes, PE32

.NET (COR20 header):
  cb=48 RuntimeVersion=2.5 Flags=3 EntryPointToken=600000A
  MetaData: RVA=5D64 Size=7B54
  Resources: RVA=D8B8 Size=5A11F0
  StrongNameSignature: RVA=0 Size=0
  CodeManagerTable: RVA=0 Size=0
  VTableFixups: RVA=0 Size=0
  ExportAddressTableJumps: RVA=0 Size=0
  ManagedNativeHeader: RVA=0 Size=0
  Metadata root: Signature=424A5342 Version=1.1 VersionString="v4.0.30319" Flags=0 Streams=5
    #~         offset=6C size=2F48
    #Strings   offset=2FB4 size=2C2C
    #US        offset=5BE0 size=11F8
    #GUID      offset=6DD8 size=10
    #Blob      offset=6DE8 size=D6C

05570fc80953707bdb230ecca534a259977d8185071c64468c0ce0d810b7cbc5.exe. About 31 KB of metadata but 5.6 MB of managed resources (Resources Size=5A11F0): almost the whole file is embedded data. --analysis names them (update.g.resources 4.2 MB, update.Properties.Resources.resources 1.5 MB) with their entropy.

  • JSON: net → present, header, metadataRoot, vTableFixups
  • GUI: DIR_ENTRY_COM_DESCRIPTOR → MetaData → #~ / heaps
  • See also: .NET Metadata

--richheader

Includes the decoded Rich header: the XOR key, a checksum check, and one record per toolchain component (product ID, build, count), with Visual Studio version names.

$ ppee-cli --richheader sample.exe
Rich Header:
  CheckSum(XOR key)            = D34DFA25
  DanS sign                    = 536E6144   DanS
  Product ID                   = 0104       C object, VS2015
  Minor Compiler Version       = 6B14       Build 27412
  Count                        = 00000020
  • JSON: richHeader → present, rows[] (member, value, comment)
  • GUI: Rich Header
  • See also: Rich Header

--appmanifest

Includes the parsed application manifest (RT_MANIFEST): assembly identity, requested execution level (UAC), uiAccess, supported OS, DPI awareness and dependencies.

$ ppee-cli --appmanifest 0617ade6….exe.sample
AppManifest:
  ManifestVersion              = 1.0
  AssemblyIdentity:
          Name                 = MyApplication.app
          Version              = 1.0.0.0
  RequestedPrivileges:
          Level                = requireAdministrator           Full administrator permissions
          uiAccess             = false
PS C:\> ppee-cli.exe --appmanifest C:\MalwareSamples\0617ade62f8b8df572e467459bd63869cb90ab95d98e4fa88d4003abf7bcf0b9.exe.sample
C:\MalwareSamples\0617ade62f8b8df572e467459bd63869cb90ab95d98e4fa88d4003abf7bcf0b9.exe.sample: 521216 bytes, PE32

AppManifest:
  ManifestVersion              = 1.0
                               =
  AssemblyIdentity:            =
          Name                 = MyApplication.app
          Version              = 1.0.0.0
                               =
  RequestedPrivileges:         =
          Level                = requireAdministrator           Full administrator permissions
          uiAccess             = false
                               =

0617ade62f8b8df572e467459bd63869cb90ab95d98e4fa88d4003abf7bcf0b9.exe.sample. The file asks for a UAC prompt at every start, and its identity is Visual Studio's template default (MyApplication.app), never edited.

--analysis

Includes derived analysis for every runtime PPEE detects in the file: .NET (summary, imports, native imports, exports, resources), Go (summary, packages and functions, modules, build settings), Rust (summary, crates, project files) and NativeAOT (summary, assemblies, methods). It reports what the runtime metadata says, not PE structures, so it is kept apart from every structure key.

For every x86/x64 file it also runs the Code analysis: entry-point anomalies (packer stubs), the imported APIs the code really calls, and code patterns, each with instruction evidence. This scans the code in-line, so it adds about a second per 5 million instructions.

$ ppee-cli --analysis merlin.dll
Analysis (derived views, not PE structures):

Go
  Detected: Go build info at offset 0x5B0220; Go 1.20+ pclntab at offset 0x8C61E0

Go > Summary  (built from the Go build info, build ID and pclntab, and the file layout)
  - Not yet read: the entropy of appended data. Run the deep pass... [run with --analysis-deep]
Identity
  Go version: go1.24.2 [offset 0x5B0220]

Bracketed hints

Text output shows where each fact came from in brackets, for example [offset 0x5B0220] or [Analysis > Go > Build settings]. Anything the deep pass would add says [run with --analysis-deep].

--analysis-deep

--analysis plus the deep pass: for .NET, every method body is read to find the ones that don't decode as IL (encrypted or junk code) and the entropy of managed resources, appended data and the metadata's section is measured; for Go, Rust and NativeAOT it measures the entropy of appended data. It is off by default because it reads more of the file than --analysis does. In the GUI it also asks for confirmation first.

$ ppee-cli --analysis-deep crackme.exe
.NET > Summary  (built from the COR20 header, the metadata tables and heaps, and the file layout)
  - Deep pass: 586 method bodies read (30.0 KB of IL), 2829 could not be located
  [*] 572 method bodies contain bytes that do not decode as IL [Method row 10]
PS C:\> ppee-cli.exe --analysis-deep C:\MalwareSamples\crackme.exe
C:\MalwareSamples\crackme.exe: 66048 bytes, PE32

Analysis (derived views, not PE structures):

Code
  Detected: x86 machine code

Code > Summary  (built from entry point, TLS callbacks, all decoded code)
Entry point
  Address: 0x401000 [code 0x401000: entry point]  in .text
        00401000  push 0x0
        00401002  call 0x40134E
        00401007  mov dword ptr [0x406206], eax
        0040100C  push esi
        0040100D  push 0xA
        0040100F  push 0x1F7
        00401014  push dword ptr [0x406206]
        0040101A  call 0x401348
        0040101F  push eax
        00401020  push eax
        00401021  push dword ptr [0x406206]
        00401027  call 0x40136C

What the code does
  - Nothing notable in the decoded code.

Imports in use
  Other processes: ResumeThread (2), SuspendThread (2)
  Run-time linking: GetModuleHandleA (2)

Coverage
  Decoded: 1905 instructions, 49 functions
  - Found by following direct calls and jumps from the entry point, TLS callbacks, exports, .pdata, the CFG function table and relocated pointers; code reached only through computed jumps is not covered, so every count is a lower bound.

Code > API call sites (32)  (built from decoded code, import table)
API                           Topic             Call sites
kernel32.ResumeThread         Other processes   2  [code 0x4013A6: kernel32.ResumeThread call]
    Function         Address   Instruction
    sub_401384+0x22  0x4013A6  call 0x4040DC  [code 0x4013A6: 0x4013A6]
    sub_4040DC       0x4040DC  jmp dword ptr [kernel32.ResumeThread]  [code 0x4040DC: 0x4040DC]
kernel32.GetModuleHandleA     Run-time linking  2  [code 0x401002: kernel32.GetModuleHandleA call]
    Function        Address   Instruction
    EntryPoint+0x2  0x401002  call 0x40134E  [code 0x401002: 0x401002]
    sub_40134E      0x40134E  jmp dword ptr [kernel32.GetModuleHandleA]  [code 0x40134E: 0x40134E]
kernel32.SuspendThread        Other processes   2  [code 0x4013AD: kernel32.SuspendThread call]
  ...
  • MCP: section analysis-deep
  • GUI: the Summary's Run the deep pass... link (details)

--strings

Scans the whole file for ASCII and UTF-16 (Unicode) strings and tags URLs, registry paths and suspicious keywords (from Suspicious.txt). Each hit comes with its file offset and the section it is in.

$ ppee-cli --strings 86c6bd80….exe
  ASCII (15208):
  ...
  URL (12):
    0003C4F8  .rdata [R] (#2 section)  ASCII     www.google.com
    0003C508  .rdata [R] (#2 section)  ASCII     www.microsoft.com
    ...
    0003C7A0  .rdata [R] (#2 section)  ASCII     http://95.164.53.193:5001/uos.bin
  Registry (8):
  Suspicious (0):
PS C:\> ppee-cli.exe --strings C:\MalwareSamples\86c6bd8098246eaf4527d9caa406221b3ff1ce551cf3b029cfc71d2e5e7a7963.exe
C:\MalwareSamples\86c6bd8098246eaf4527d9caa406221b3ff1ce551cf3b029cfc71d2e5e7a7963.exe: 1048576 bytes, PE32+

Strings in file:
  ASCII (15208):
    00000000  Inside header area    MZ
    0000004D  Inside header area    !This program cannot be run in DOS mode.
    000000A7  Inside header area    p?=
    000000AF  Inside header area    p?=
    000000B7  Inside header area    p?=
    000000CB  Inside header area    p$
    000000CF  Inside header area    p*;
    000000D7  Inside header area    p*;op
    000000DF  Inside header area    p*;
    000000E7  Inside header area    pRich
    00000108  Inside header area    PE
    00000112  Inside header area    dj
    00000198  Inside header area    D(
    000001AC  Inside header area    X)
    00000210  Inside header area    .text
    00000237  Inside header area    `.rdata
    0000025F  Inside header area    @.data
    00000268  Inside header area    @'
    00000288  Inside header area    .pdata
    00000290  Inside header area    X)
    000002AF  Inside header area    @.fptable
    000002D8  Inside header area    .rsrc
    000002FF  Inside header area    @.reloc
    0000040B  .text [RX] (First section)  u3H
    0000040F  .text [RX] (First section)  \$0H
    0000041B  .text [RX] (First section)  |$ H
    00000420  .text [RX] (First section)  =;E
    00000430  .text [RX] (First section)  (H;
    00000438  .text [RX] (First section)  |$ H
    0000043D  .text [RX] (First section)  \$0H
    00000483  .text [RX] (First section)  (H
    00000487  .text [RX] (First section)  EI
    000004A3  .text [RX] (First section)  qV
    000004B0  .text [RX] (First section)  @SH
    000004B5  .text [RX] (First section)   H
    000004DF  .text [RX] (First section)   [
    00000512  .text [RX] (First section)  \$
    00000515  .text [RX] (First section)  WH
    00000519  .text [RX] (First section)   H
    00000546  .text [RX] (First section)  \$0H
    0000054F  .text [RX] (First section)   _
  ...

RemusStealer loader, 86c6bd8098246eaf4527d9caa406221b3ff1ce551cf3b029cfc71d2e5e7a7963.exe. A list of popular sites (an "am I online / in a sandbox?" check) and one raw-IP URL fetching a .bin file: a second-stage download. ppee-cli --xrefs string:uos.bin shows the code that uses it (see Disassembly & Cross-references).

  • JSON: strings → ascii[], unicode[], url[], registry[], suspicious[] (offset, sectionName, text, plus type for tagged hits)
  • GUI: Strings
  • MCP: get_strings (with filters and limits)
  • See also: Strings

Large output

A typical DLL yields tens of thousands of strings. If the scan would need more memory than the machine has to spare, PPEE prints a warning on stderr and continues. Filter the output afterwards, for example with jq '.strings.url'.

--disasm, --xrefs, --functions

Code switches for x86/x64 files: disassemble at a target, list the uses of an API, address or string, and list function starts. They have their own page with worked malware examples: Disassembly & Cross-references.

$ ppee-cli --xrefs CryptUnprotectData --max-sites 1 STEALERDLL.dll
Cross-references to crypt32.CryptUnprotectData (import at 0x1800FE070): 3 site(s), first ones listed
  sub_18008F140+0x143
    …
  > 000000018008F283  call qword ptr [crypt32.CryptUnprotectData]
PS C:\> ppee-cli.exe --xrefs CryptUnprotectData --max-sites 1 C:\MalwareSamples\STEALERDLL.dll
C:\MalwareSamples\STEALERDLL.dll: 1282048 bytes, PE32+

Cross-references to crypt32.CryptUnprotectData (import at 0x1800FE070): 3 site(s), first ones listed
  sub_18008F140+0x143
    000000018008F273  mov qword ptr [rsp+0x20], 0x0
    000000018008F27C  xor edx, edx
    000000018008F27E  mov dword ptr [rsp+0x40], r14d
  > 000000018008F283  call qword ptr [crypt32.CryptUnprotectData]

--decode, --decode-text, --hash-range

Show the text in a range, decode a range without a script, and hash any range. The steps, the key search and the range forms are those of MCP decode_bytes and hash_range.

Switch Description
--decode AT The bytes at AT: off:X, rva:X, va:X, section:NAME, overlay, headers, file. Without --steps: their hex and their ASCII and UTF-16 (u) strings in file order
--strings-offset N Without --steps: start the strings list at the Nth string (paging)
--decode-text TEXT Decode TEXT (a base64 string, hex text) instead of the file's bytes; no file needed
--steps "xor:5A, base64" The steps: xor/add/sub:KEY, rol/ror:N, not, reverse, base64[:ALPHABET], hex, rc4:KEY, inflate, zlib, gzip, lznt1, skip/take:N
--find-key TEXT The XOR/ADD/ROL keys that make the bytes contain TEXT
--length N Bytes to read (default: the whole section or overlay; 4096 from an address)
--hash-range [AT] MD5, SHA-1, SHA-256, CRC32, entropy, ssdeep, TLSH of AT (with --length), or of the headers, every section, the overlay and the file
$ ppee-cli --decode section:.rdata --find-key https:// 106710ac….exe.sample
Source: 0x2200, 3072 bytes, in .rdata
Result: 3072 bytes, entropy 4.270

Keys that reveal 'https://':
  xor:B5                   at 0x2270  https://kidsko.striawork/telepuzhop/files/telemePanasonicInd\Hel

$ ppee-cli --decode off:0x2270 --length 64 --steps xor:B5 106710ac….exe.sample
Source: 0x2270, 64 bytes, in .rdata
Steps:  xor:B5
Result: 64 bytes, entropy 4.383
…
https://kidsko.striawork/telepuzhop/files/telemePanasonicInd\Hel

$ ppee-cli --hash-range section:.text [email protected]
.text  offset=0x400 size=178688 entropy=6.586
  md5     f10df8e94d500c375ae989cf5ed4b360
  sha1    1459f1ebe7691a9060913f30a316c094280f02cc
  sha256  eb41b7fea88ccad49e4214c487ea79bc89103fe4d100760cec3a5899b8ba72d4
  …

With --json, the output is MCP's document.

--all

Turns on every section filter above, including --analysis (but not the slower --analysis-deep, nor the code switches --disasm, --xrefs and --functions). This is also the default when no filter is given.

ppee-cli --json --all sample.exe > sample.full.json
ppee-cli --json --all --no-similarity sample.exe   # everything except the DB write

Pipelines: name the sections you need

--all and running with no filter now also run the analyzers and can be large (Go and Rust binaries produce thousands of table rows). Scripts and CI jobs should list only the sections they use, for example --json --hashes --imports --security.


Output options

--json

Emits one JSON document on stdout instead of the text report. The document always starts with path, size and is64Bit, followed by one key per selected section. Numbers taken from the file are hex strings ("8664"), and counts and indexes are JSON numbers. The output is streamed, so memory use stays flat even for very large files.

ppee-cli --json --headers --sections sample.exe | jq '.headers["FileHeader.Machine"]'
# "8664"

--timing

Prints a per-stage wall-clock breakdown to stderr when the run ends. Stdout is unchanged, so it is safe to use together with --json.

$ ppee-cli --timing --all --no-similarity aepic.dll > /dev/null
timing: Load                          0.952 ms
timing: FieldTable                    0.614 ms
timing: ParseImports                  0.167 ms
...
timing: Strings                       8.266 ms
timing: FileInfo                     10.322 ms
timing: Analysis                    154.316 ms
timing: Emit                         27.701 ms
timing: total                       184.832 ms

Analysis is mostly the code scan behind the Code analysis. With --no-code-scan it drops to about 3 ms here; on a large file the difference is seconds.

Stages can run concurrently, so total is elapsed time since startup, not the sum of the stages.


Editing options

--set NAME=VALUE

Patches one field in memory. You can repeat it; edits are applied in the order given, before any output is produced, so the report shows the edited values. NAME is one of:

  • a header, directory or section field name as printed by --headers, --dirs or --sections (OptionalHeader.DllCharacteristics, Section[2].Name)
  • a Cell: address for any list-view cell (imports, resources, .NET tables, …)
  • RawOffset:<hex>:<width> to write raw little-endian bytes
  • RawBytes:<hex>, RawBytes:rva:<hex> or RawBytes:va:<hex> to write a byte string ("90 90 C3"; ?? keeps a byte)
  • OptionalHeader.CheckSum=auto to recompute the checksum after the other edits
  • String:<hex>:ascii|unicode:<maxChars> to rewrite a string in place

Numeric values are hex, unless a Cell: address ends in :dec.

# Preview an edit without saving (the report shows the new value)
ppee-cli --headers --set FileHeader.TimeDateStamp=5F000000 sample.exe

If any edit fails, PPEE prints --set failed: unknown field or bad value '<NAME>', and the exit code is 1.

Full syntax and examples: Editing with --set.

--save

Writes the edited image back to disk. The write is all or nothing: if any --set failed, nothing is written and not saved: an --set edit failed, nothing written is printed.

$ ppee-cli --headers --set OptionalHeader.DllCharacteristics=8120 --save sample.exe > /dev/null
applied 1 field edit(s)
saved '/home/me/sample.exe'

Without -o, the input file is overwritten

Keep a backup or use -o.

-o PATH

Used with --save: writes to PATH instead of overwriting the input file.

ppee-cli --set FileHeader.TimeDateStamp=0 --save -o sample.repro.exe sample.exe > /dev/null

Behavior options

--no-similarity

Turns the similarity engine off completely. It overrides --similarity and --all whatever the order, and guarantees that the DB is neither opened nor written. Use it on read-only file systems, in containers with --read-only, and whenever you don't want files remembered.

ppee-cli --json --no-similarity sample.exe

--no-update-check

Skips the startup check for a newer release. The check only runs on Windows builds and prints to stderr. Use this switch for offline and scripted runs. The Docker image always passes it.

--scan-budget N

The budget of the x86/x64 code scan, in millions of decoded instructions (default 5, 1–50). It applies to --analysis, --xrefs, --functions and, given with --mcp, to every MCP tool call. A file with more code is covered in part, and the counts say so. The GUI setting is Settings → Disassembly.

--no-code-scan

Don't scan the code where the scan is implicit: --analysis then checks only the entry point and TLS callbacks, and MCP's get_strings / get_iocs leave out codeRefs / referencedByCode. --xrefs and --functions still scan, since you asked for them. Use it for fast batch triage of large files.

ppee-cli --json --analysis --no-code-scan big.exe     # entry-point checks only, milliseconds
ppee-cli --mcp --scan-budget 20                      # MCP server with a bigger budget

--mcp

Runs ppee-cli as a Model Context Protocol server on stdin/stdout instead of analyzing a file. It speaks JSON-RPC 2.0, one message per line, and serves until stdin closes. No file argument is allowed.

ppee-cli --mcp

--mcp-allow-write

Used with --mcp: also exposes the patch_pe tool, so the AI client can write patched copies of files. It is off by default. Read the security model first.

ppee-cli --mcp --mcp-allow-write

-h, --help

Prints usage and exits with code 0.

--version

Prints ppee-cli <version> and exits with code 0.

$ ppee-cli --version
ppee-cli 2.0.0