{"record":{"id":"2b5b4dbb55e55677","repo":"t8y2/dbx","slug":"pe-string-offset-is-outside-the-file","errorCode":null,"errorMessage":"PE string offset is outside the file","messagePattern":"PE string offset is outside the file","errorType":"exception","errorClass":"PeFormatError","httpStatus":null,"severity":"error","filePath":"agents/scripts/validate_windows_pe_dependencies.py","lineNumber":26,"sourceCode":"class PeFormatError(ValueError):\n    pass\n\n\ndef _read_u16(data: bytes, offset: int) -> int:\n    if offset < 0 or offset + 2 > len(data):\n        raise PeFormatError(\"unexpected end of PE file\")\n    return struct.unpack_from(\"<H\", data, offset)[0]\n\n\ndef _read_u32(data: bytes, offset: int) -> int:\n    if offset < 0 or offset + 4 > len(data):\n        raise PeFormatError(\"unexpected end of PE file\")\n    return struct.unpack_from(\"<I\", data, offset)[0]\n\n\ndef _read_c_string(data: bytes, offset: int) -> str:\n    if offset < 0 or offset >= len(data):\n        raise PeFormatError(\"PE string offset is outside the file\")\n    end = data.find(b\"\\0\", offset)\n    if end < 0:\n        raise PeFormatError(\"unterminated PE string\")\n    try:\n        return data[offset:end].decode(\"ascii\")\n    except UnicodeDecodeError as error:\n        raise PeFormatError(\"PE import name is not ASCII\") from error\n\n\ndef imported_dlls(path: Path) -> list[str]:\n    data = path.read_bytes()\n    if len(data) < 64 or data[:2] != b\"MZ\":\n        raise PeFormatError(\"missing DOS header\")\n\n    pe_offset = _read_u32(data, 0x3C)\n    if data[pe_offset : pe_offset + 4] != b\"PE\\0\\0\":\n        raise PeFormatError(\"missing PE signature\")\n","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/scripts/validate_windows_pe_dependencies.py#L8-L44","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Re-obtain the binary from a trusted source / re-download or rebuild it; a truncated file is the most common cause.","If the file is intentionally malformed, catch PeFormatError around imported_dlls and treat it as 'cannot validate' rather than letting it propagate.","If you are parsing an unpacked/modified PE, fix the section headers or Name RVAs so import names resolve inside the mapped file data."],"exampleFix":"// before\nimports = imported_dlls(Path(\"app.exe\"))  # raises PeFormatError\n// after\ntry:\n    imports = imported_dlls(Path(\"app.exe\"))\nexcept PeFormatError as error:\n    print(f\"cannot validate {path}: {error}\")  # treat as unparseable PE","handlingStrategy":"try-catch","validationCode":"data = path.read_bytes()\nif len(data) < 64 or data[:2] != b\"MZ\":\n    raise SystemExit(\"not a PE\")\n# cheap sanity: import name offsets must lie inside the file;\n# prefer a real check with pefile if available\nimport pefile\npe = pefile.PE(data=data)\nassert all(entry.dll for entry in pe.DIRECTORY_ENTRY_IMPORT)","typeGuard":"def is_parseable_pe(data: bytes) -> bool:\n    return len(data) >= 64 and data[:2] == b\"MZ\" and 0 < len(data) - 4","tryCatchPattern":"try:\n    imports = imported_dlls(path)\nexcept PeFormatError as error:\n    print(f\"unparseable PE ({path}): {error}\")\n    imports = None  # or fail the check","preventionTips":["Validate the whole file exists and its size matches the build manifest before parsing","Run `objdump -x` or pefile over binaries once at build time to catch malformed PEs early","Do not feed truncated downloads into the validator; verify checksums","Treat any PeFormatError as 'cannot prove compliance' and fail closed in CI"],"tags":["pe","binary-parsing","malformed-input","offset-out-of-bounds"],"backgroundTag":"pe-format-invalid","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}