unwrap_self copies one output region per PT_LOAD and nothing required the
regions to be disjoint in the input. Only the alignment padding was capped, so
200 PT_LOADs that all point at the same 2 MiB of a 2 MiB container produced
400 MiB, and the 65535 program headers the parser accepts could ask for
hundreds of gigabytes, ending in MemoryError or an OOM kill instead of a
Refused message.
The output size is now checked before the buffer is allocated. It may be up to
four times the container, and never less than 256 MiB.
Reconfigure sys.stdout and sys.stderr with backslashreplace error handling at the entrypoint of main() so unwrapping reports and error messages do not fail on Windows consoles configured with non-UTF-8 code pages.
unwrap_self copied every segment that does not sit inside a PT_LOAD with
alignment 1. On real dumps that puts the comment and version segments
(p_align 0x10) right after the last load segment, at offsets that break
p_offset % p_align == p_vaddr % p_align. Those regions now use the
segment's own p_vaddr and p_align, the same as the load segments, which
is what the module docstring already promises. Load segment placement
and bytes do not change.
unwrap_self reads the vaddr-less segments that follow the recorded
comment segment back to back from right after its data, and refuses the
whole container when one of them runs past the end of the file. Some
dumps end right after the last recorded segment and do not store the
version segment at all. The sce_module/libc.prx of PPSA15552 is one:
its last record, the 0x58-byte comment at 0x13b590, ends at 0x13b5e8,
which is the file size, so the 0x1a-byte PT_SCE_VERSION has no bytes
and the tool failed with "segment 12 does not fit the file" although
every load, dynamic and comment segment is complete.
A trailing segment that would run past the end of the file is now
emitted without file bytes, the same way as the PT_NOTE the container
leaves out. The relinker does not read PT_SCE_VERSION
(GuestImageReader skips it). Containers that store the version segment
unwrap byte for byte as before.
unwrap_self matched SELF records to PT_LOADs by size only and placed
every segment without a vaddr by the shift of the last mapped one. In the
19 modules of PPSA03743/PPSA03363 (Soul Hackers 2) the SCE comment
segment has a record of its own, after a 0x20-byte digest block, so it
and the version segment behind it were read 0x20 bytes too early, and in
the sce_module libraries, where the ELF puts a PT_NOTE between them, the
version segment was cut to a few bytes at the end of the file.
A record with flag 0x800 now places the program header whose index is in
flags bits 20 and up. Segments without a vaddr that follow the last
recorded one are read back to back after it; the PT_NOTE among them is
not stored in the container and is emitted without file bytes.
When a decrypted SELF contains a non-PT_LOAD segment (such as PT_TLS for pure TBSS) with p_filesz == 0 whose virtual address lies outside any PT_LOAD memory range, unwrap_self previously refused the container with 'segment N vaddr 0x... is in no PT_LOAD'.
Allow zero-size segments without a matching PT_LOAD host to be placed with source offset 0 and new offset 0, preserving valid ELF headers for titles like Unity/IL2CPP PS5 builds.
Also allow file-empty PT_LOAD segments with stale out-of-file source offsets to skip the segment-extent bounds check, preserving memory sizes, addresses and alignments without reading container metadata.
Co-authored-by: simonbronk <[email protected]>
unwrap_self refused a container when a non-load segment with no file data
sat exactly at the end of a PT_LOAD, as an empty PT_TLS does in Sifu
(PPSA03001): "segment 6 vaddr 0x88cb7a0 is in no PT_LOAD". The eboot and
36 of its 37 bundled modules carry such a segment. A segment of size zero
now matches the load segment it ends, so it is placed at that segment's end.