t8y2/dbx · error · PeFormatError

PE string offset is outside the file

Error message

PE string offset is outside the file

What it means

PeFormatError raised by _read_c_string when the offset it is asked to read a NUL-terminated string from is negative or at/after the end of the file bytes. The library throws it instead of letting Python raise IndexError/struct errors, so callers get a uniform PE-format exception while walking the import name table. It almost always means the RVA-to-file-offset mapping produced an out-of-bounds offset, i.e. the file is malformed, truncated, or not a real PE.

Source

Thrown at agents/scripts/validate_windows_pe_dependencies.py:26

class PeFormatError(ValueError):
    pass


def _read_u16(data: bytes, offset: int) -> int:
    if offset < 0 or offset + 2 > len(data):
        raise PeFormatError("unexpected end of PE file")
    return struct.unpack_from("<H", data, offset)[0]


def _read_u32(data: bytes, offset: int) -> int:
    if offset < 0 or offset + 4 > len(data):
        raise PeFormatError("unexpected end of PE file")
    return struct.unpack_from("<I", data, offset)[0]


def _read_c_string(data: bytes, offset: int) -> str:
    if offset < 0 or offset >= len(data):
        raise PeFormatError("PE string offset is outside the file")
    end = data.find(b"\0", offset)
    if end < 0:
        raise PeFormatError("unterminated PE string")
    try:
        return data[offset:end].decode("ascii")
    except UnicodeDecodeError as error:
        raise PeFormatError("PE import name is not ASCII") from error


def imported_dlls(path: Path) -> list[str]:
    data = path.read_bytes()
    if len(data) < 64 or data[:2] != b"MZ":
        raise PeFormatError("missing DOS header")

    pe_offset = _read_u32(data, 0x3C)
    if data[pe_offset : pe_offset + 4] != b"PE\0\0":
        raise PeFormatError("missing PE signature")

View on GitHub (pinned to c0390bff16)

Solutions

  1. Verify the file is intact and a genuine PE (size matches expected bytes, valid section table) with a tool like `objdump -x` or pefile before re-running.
  2. Re-obtain the binary from a trusted source / re-download or rebuild it; a truncated file is the most common cause.
  3. If the file is intentionally malformed, catch PeFormatError around imported_dlls and treat it as 'cannot validate' rather than letting it propagate.
  4. If you are parsing an unpacked/modified PE, fix the section headers or Name RVAs so import names resolve inside the mapped file data.

Example fix

// before
imports = imported_dlls(Path("app.exe"))  # raises PeFormatError
// after
try:
    imports = imported_dlls(Path("app.exe"))
except PeFormatError as error:
    print(f"cannot validate {path}: {error}")  # treat as unparseable PE
Defensive patterns

Strategy: try-catch

Validate before calling

data = path.read_bytes()
if len(data) < 64 or data[:2] != b"MZ":
    raise SystemExit("not a PE")
# cheap sanity: import name offsets must lie inside the file;
# prefer a real check with pefile if available
import pefile
pe = pefile.PE(data=data)
assert all(entry.dll for entry in pe.DIRECTORY_ENTRY_IMPORT)

Type guard

def is_parseable_pe(data: bytes) -> bool:
    return len(data) >= 64 and data[:2] == b"MZ" and 0 < len(data) - 4

Try / catch

try:
    imports = imported_dlls(path)
except PeFormatError as error:
    print(f"unparseable PE ({path}): {error}")
    imports = None  # or fail the check

Prevention

When it happens

Trigger: imported_dlls() resolves an import-descriptor name_rva via rva_to_offset() and passes the result to _read_c_string; the offset lands outside data. Concretely: a crafted/corrupt PE whose import directory or name RVAs point beyond the mapped sections, a section table whose raw_offset/raw_size values overshoot the file, or a name_rva that maps to exactly EOF.

Common situations: Inspecting a truncated or partially-downloaded .exe/.dll, feeding a non-PE binary that still passes the MZ/PE checks (e.g. an SFX stub or .NET single-file bundle with odd sections), analyzing malware samples with deliberately corrupted import tables, or a self-modified binary where the section headers were patched.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/2b5b4dbb55e55677. Report an issue: GitHub.