# PE format review

An original Python standard-library CLI for inspecting a local Portable Executable file's structure. It reads bytes and prints a JSON report. It never executes the input, extracts an archive, deserializes objects, calls a network service, or changes application settings. No inspected executable, archive README, or archive script is included here.

Requires Python 3.10 or newer. Run from this directory:

```powershell
python -B inspect_pe.py "C:\local\sample.bin"
python -B inspect_pe.py "C:\local\samples.zip" --archive-member "folder/sample.bin"
python -B inspect_pe.py "C:\local\sample.bin" --max-bytes 16777216
python -B -m unittest -v test_inspect_pe.py
```

JSON goes to standard output; redirect it to a report file if needed. Successful inspection exits with code `0`. Unsupported, unreadable, malformed, or oversized inputs exit with code `2` and an error object on standard error. Reports contain no input file paths. ZIP member selection is exact and case-sensitive. Other members are never decompressed or interpreted; the central directory is inspected to select and bound the requested member.

## Reading the report

| Field | Meaning |
| --- | --- |
| `file` | Observed byte length and SHA-256 of the local file or selected decompressed member. |
| `dos_signature`, `pe_signature`, `pe_offset` | Validated format markers and the PE header's file offset. These are not cryptographic signatures. |
| `coff`, `optional_header` | Machine identifier, PE32/PE32+ format, header sizes, declared image size, and related claims. An unfamiliar machine code is preserved. |
| `sections` | Names, raw file ranges, declared virtual RVA ranges, and unrounded extents using `max(VirtualSize, SizeOfRawData)`. All ranges have an exclusive end. No memory is mapped. |
| `last_claimed_section_raw_end` | Maximum end among nonempty section raw ranges; `null` if every section has zero raw size. |
| `trailing_file_bytes` | Bytes after the larger of `SizeOfHeaders` and the last nonempty section raw end. Certificate tables, installer data, or other content can occupy this region. Trailing bytes are not automatically malicious. |
| `security_directory` | Whether directory slot 4 is declared, whether it is nonzero, and its bounded file range. Its address is a **file offset**, unlike ordinary RVA directories. Certificate bytes are not interpreted or verified. |

A hash identifies observed bytes; it does not establish who produced them, whether they are authentic, or whether they are safe. A present security directory does not establish a valid Authenticode signature. This tool produces no malware classification and does not validate certificate chains, timestamps, publisher identity, or PE checksums.

## Bounds and supported structure

The default and hard upper input limit is 128 MiB (`134217728` bytes). `--max-bytes` can lower that limit. It applies separately to the archive container and the selected member. Payload reads and decompression use chunks of at most 1 MiB; central-directory metadata is read within a separate 4 MiB cap; the final selected file is held in memory, with a temporary additional copy during buffer conversion. No memory is allocated according to a PE virtual size.

The parser checks the DOS header, PE offset and marker, COFF header, optional header's fixed fields and declared directory array, complete section table, declared headers, section raw and virtual bounds, and security-directory range. It allows 1–96 sections. Nonempty raw ranges cannot overlap the headers or each other. Zero raw size and raw offset zero are accepted. The security directory cannot overlap headers or section raw data and must start on an eight-byte boundary. These are structural checks for this inspector, not a complete model of the Windows loader: directory contents, symbol tables, imports, relocations, alignment rules, and virtual section overlap are not validated. Unusual files may be rejected even when some other tool accepts them.

ZIP input supports standard, single-disk ZIP archives with stored or deflated members. The container is preflighted before constructing `ZipFile`: at most 4,096 entries and 4 MiB of central-directory metadata, with actual counts and byte ranges checked. The selected member must have a unique safe relative name and be a regular file; encrypted members, ZIP64, unsupported extensions, links, traversal names, and ambiguous local records are rejected. The maximum selected-member compression ratio is 200:1. Compressed and inflated byte counts, end-of-stream, local/central metadata consistency, optional data descriptor, and CRC are checked. A ZIP CRC is an integrity check, not authentication. A restrictive compression-ratio limit can reject benign, highly compressible files.

Only local regular input files are accepted. UNC/device/URL paths, mapped network drives on Windows, symlinks, and Windows reparse points are rejected. Use a stable local copy: this tool is not a sandbox and does not defend against a separate process racing to replace filesystem components while they are being opened. It checks for ordinary file size and modification-time changes during reading.

## Verification and source

The tests generate inert byte arrays and temporary ZIP fixtures. They cover PE32/PE32+, truncated headers and section data, malformed offsets and counts, overlapping raw ranges, zero-raw-size sections, security-directory semantics, ZIP bounds and traversal, duplicate names, encryption flags, metadata disagreement, CRC errors, and deflate streams that understate their inflated size. No test launches an inspected sample.

Format interpretation follows Microsoft's primary [PE/COFF specification](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format), especially the DOS stub, optional-header bounds, section table, and certificate-table address rules. The implementation and synthetic fixtures are original. No claim is made that static format inspection establishes runtime behavior.
