pe
Read Windows executables (PE: .exe, .dll, .sys) without running them: architecture, compile time, entry point, sections with their entropy and permissions, imports and exports, whether the file is signed, and a list of suspicious indicators (packer section names, writable+executable sections, injection or keylogging imports, odd entry points). Pure Python; also works in the playground on bytes.
import pe
Example
import binary
import pe
# A tiny 32-bit Windows program, built byte by byte: a DOS header, the PE
# headers and one section named like a packer's (UPX0), writable and runnable.
let dos = binary.concat(b"MZ", binary.zeros(58), binary.pack("<I", 64))
let coff = binary.pack("<4sHHIIIHH", b"PE\x00\x00", 0x14c, 1, 1700000000, 0, 0, 224, 0x0102)
let optional = binary.concat(
binary.pack(
"<HBBIIIIIIIIIHHHHHHIIIIHHIIIIII",
0x10b,
14,
0,
512,
0,
0,
0x1000,
0x1000,
0x2000,
0x400000,
0x1000,
512,
6,
0,
0,
0,
6,
0,
0,
0x2000,
512,
0,
3,
0,
0x100000,
0x1000,
0x100000,
0x1000,
0,
16,
),
binary.zeros(128),
)
let section = binary.pack("<8sIIIIIIHHI", b"UPX0", 512, 0x1000, 512, 512, 0, 0, 0, 0, 0xE0000020)
let code = binary.concat(b"\x6a\x00\xff\x15", binary.zeros(508))
let header = binary.concat(dos, coff, optional, section)
let data = binary.concat(header, binary.zeros(512 - len(header)), code)
let info = pe.parse_bytes(data)
say(f"{info.format} for {info.architecture}, built {info.compiled}")
for s in info.sections {
say(f" section {s.name}: {s.size} bytes, entropy {s.entropy}")
}
for hit in pe.indicators(info) {
say(f" [{hit.level}] {hit.detail}")
}
Functions
parse(path)
returns mapEverything pe knows about a file. Fails if the file isn't a PE.
Fields
"format"pe32 or pe64."architecture"the CPU type, like "x86" or "x64"."machine"the CPU's longer name, like "x86-64 (64-bit)"."is_dll", "is_driver", "is_dotnet", "is_signed"true or false.- "subsystem"
"timestamp", "compiled"when it was built, as a number and as text."entry_point"the address where the program starts."entry_hex"the same address in hex, like "0x401000"."entry_section"the section the entry point is in.- "image_base"
"sections"a list of maps with name, virtual_address, virtual_size, raw_size, raw_offset, size, entropy, flags, readable, writable and executable."imports"a map of dll -> list of names.- "import_count"
"exports"a list of names.- "export_name", "pdb", "directories", "overlay_size"
"imphash"the import hash pefile and VirusTotal show (nil without imports)."rich_header"nil, or {offset, key, checksum_valid, hash, entries}. Each entry is {product_id, build, count}: a Microsoft tool that built the file."resources"a list of {type, name, language, size, offset, codepage, entropy}. At most 16384 tree entries are read; entropy is nil past the first 16 MB of resource data."signature"nil if unsigned, else {type, digest_algorithm, signer, certificates, verified, problem}. Each certificate has subject, issuer, serial, not_before, not_after and is_signer. Nothing is checked: verified is always false."truncated"true if the file is cut off.- "entropy", "size"
"indicators"a list of {level, name, detail} findings.
Visualizehow pe.parse works
pe.parse("demo.exe") gives {"format": "pe64", "architecture": "x64", "machine": "x86-64 (64-bit)", "is_dll": false, …
Real result: worked out by running this example with TB while the site was built.
parse_bytes(data)
returns mapLike pe.parse() but for bytes already in memory.
Visualizehow pe.parse_bytes works
pe.parse_bytes(exe) gives {"format": "pe64", "architecture": "x64", "machine": "x86-64 (64-bit)", "is_dll": false, …
Real result: worked out by running this example with TB while the site was built.
is_pe(path)
returns boolTrue if the file starts like a Windows PE executable (MZ header and PE signature).
Visualizehow pe.is_pe works
pe.is_pe("demo.exe") gives truepe.is_pe("notes.txt") gives false
Real result: worked out by running this example with TB while the site was built.
sections(path)
returns listJust the sections: name, sizes, offsets, entropy and permissions.
Visualizehow pe.sections works
pe.sections("demo.exe") gives [{"name": ".text", "virtual_address": 4096, "virtual_size": 204, "raw_size": 512, "raw_of…
Real result: worked out by running this example with TB while the site was built.
imports(path)
returns mapThe import table: {"KERNEL32.dll": ["CreateFileW", ...], ...}.
Visualizehow pe.imports works
pe.imports("demo.exe") gives {"KERNEL32.dll": ["CreateFileW", "ReadFile", "VirtualAllocEx", "WriteProcessMemory", "Cre…
Real result: worked out by running this example with TB while the site was built.
exports(path)
returns listThe names a DLL exports (empty for most .exe files).
Visualizehow pe.exports works
pe.exports("demo.dll") gives ["Install", "Run", "Uninstall"]pe.exports("demo.exe") gives []
Real result: worked out by running this example with TB while the site was built.
indicators(path_or_info)
returns listSuspicious indicators for a file (or for a map from pe.parse()): [{"level": high/medium/low/info, "name", "detail"}].
Visualizehow pe.indicators works
pe.indicators("demo.exe") gives [{"level": "high", "name": "imports_injection", "detail": "Imports injection functions: C…
Real result: worked out by running this example with TB while the site was built.
summary(path)
returns textA short human-readable description, like "pe64 x64 executable, 3 sections, 42 imports, compiled 2024-01-05, unsigned".
Visualizehow pe.summary works
pe.summary("demo.exe") gives "pe64 x64 executable, 4 sections, 7 imports, compiled 2024-01-11, unsigned, 1 high-risk i…
Real result: worked out by running this example with TB while the site was built.