// SPDX-License-Identifier: GPL-3.0-or-later #include "elf.h" #include namespace start { const std::uint8_t* Span(const Bytes& data, std::uint64_t offset, std::uint64_t size) { if (offset > data.size() || size > data.size() - offset) throw Error("out of bounds: " + Hex(offset) + "+" + Hex(size) + " / " + Hex(data.size())); return data.data() + offset; } namespace { constexpr std::uint64_t kLimit = 512ull << 20; struct Header { std::uint64_t entry, phoff; std::uint16_t phentsize, phnum; }; Header ReadHeader(const Bytes& data, std::uint64_t at) { const std::uint8_t* h = Span(data, at, 64); if (std::memcmp(h, "\x7f" "ELF\x02\x01\x01", 7) || Load(h + 18) != 62) throw Error("expected little-endian x86-64 ELF"); Header header{Load(h + 24), Load(h + 32), Load(h + 54), Load(h + 56)}; if (header.phentsize != 56 || header.phnum == 0 || header.phnum >= 256) throw Error("unsupported program headers"); return header; } Ph ReadPh(const Bytes& data, std::uint64_t at) { const std::uint8_t* p = Span(data, at, 56); return Ph{Load(p), Load(p + 4), Load(p + 8), Load(p + 16), Load(p + 24), Load(p + 32), Load(p + 40), Load(p + 48)}; } bool Required(std::uint32_t type) { return type == 1 || type == 2 || type == 0x61000000 || type == 0x61000010; } Executable ParseElf(const Bytes& data) { const Header header = ReadHeader(data, 0); Executable out; out.entry = header.entry; for (int i = 0; i < header.phnum; ++i) out.ph.push_back(ReadPh(data, header.phoff + std::uint64_t(i) * 56)); std::uint64_t end = 0; for (const Ph& p : out.ph) end = std::max(end, p.offset + p.filesz); if (end > kLimit) throw Error("probe image exceeds 512 MiB limit"); out.elf.assign(end, 0); std::copy_n(data.begin(), std::min(end, data.size()), out.elf.begin()); for (int i = 0; i < int(out.ph.size()); ++i) { const Ph& p = out.ph[i]; if (p.filesz && p.offset + p.filesz > data.size()) { out.missing.push_back(i); if (Required(p.type)) throw Error("required segment " + std::to_string(i) + " is unavailable"); } } return out; } } Executable ParseSelf(const Bytes& data) { if (std::memcmp(Span(data, 0, 4), "\x7f" "ELF", 4) == 0) return ParseElf(data); if (std::memcmp(Span(data, 0, 4), "O\x15=\x1d", 4)) throw Error("expected a PS4 executable (SELF or ELF)"); const std::uint16_t count = Load(Span(data, 24, 2)); const std::uint64_t base = 32 + std::uint64_t(count) * 32; const Header header = ReadHeader(data, base); Executable out; out.entry = header.entry; for (int i = 0; i < header.phnum; ++i) out.ph.push_back(ReadPh(data, base + header.phoff + std::uint64_t(i) * 56)); std::uint64_t end = 0; for (const Ph& p : out.ph) end = std::max(end, p.offset + p.filesz); if (end > kLimit) throw Error("probe image exceeds 512 MiB limit"); out.elf.assign(end, 0); const std::uint64_t header_end = header.phoff + std::uint64_t(header.phnum) * 56; const std::uint8_t* head = Span(data, base, header_end); if (header_end > out.elf.size()) out.elf.resize(header_end, 0); std::copy_n(head, header_end, out.elf.begin()); std::vector> covered{{0, header_end}}; for (int i = 0; i < count; ++i) { const std::uint8_t* s = Span(data, 32 + std::uint64_t(i) * 32, 32); const std::uint64_t flags = Load(s), off = Load(s + 8), size = Load(s + 16), mem = Load(s + 24); if (!(flags & 0x800)) continue; if (flags & 10) throw Error("encrypted/compressed SELF segment is unsupported"); const std::uint64_t index = (flags >> 20) & 4095; if (index >= out.ph.size()) throw Error("invalid segment index"); const Ph& p = out.ph[index]; if (size != p.filesz || size != mem) throw Error("unsupported blocked segment layout"); const std::uint8_t* src = Span(data, off, size); if (p.offset + size > out.elf.size()) out.elf.resize(p.offset + size, 0); std::copy_n(src, size, out.elf.begin() + std::ptrdiff_t(p.offset)); covered.emplace_back(p.offset, p.offset + size); } for (int i = 0; i < int(out.ph.size()); ++i) { const Ph& p = out.ph[i]; if (!p.filesz) continue; const bool present = std::any_of(covered.begin(), covered.end(), [&](const auto& range) { return range.first <= p.offset && p.offset + p.filesz <= range.second; }); if (!present) { out.missing.push_back(i); if (Required(p.type)) throw Error("required segment " + std::to_string(i) + " is unavailable"); } } return out; } std::map ParseSfo(const Bytes& data) { const std::uint8_t* h = Span(data, 0, 20); if (std::memcmp(h, "\0PSF", 4)) throw Error("bad SFO signature"); const std::uint32_t keys = Load(h + 8), values = Load(h + 12), count = Load(h + 16); std::map out; for (std::uint32_t i = 0; i < count; ++i) { const std::uint8_t* e = Span(data, 20 + std::uint64_t(i) * 16, 16); const std::uint16_t key = Load(e), format = Load(e + 2); const std::uint32_t size = Load(e + 4), offset = Load(e + 12); const std::uint64_t name_at = std::uint64_t(keys) + key; Span(data, name_at, 1); const auto name_end = std::find(data.begin() + std::ptrdiff_t(name_at), data.end(), 0); if (name_end == data.end()) throw Error("bad SFO key"); const std::string name(data.begin() + std::ptrdiff_t(name_at), name_end); const std::uint8_t* v = Span(data, std::uint64_t(values) + offset, size); if (format == 0x404) { if (size < 4) throw Error("bad SFO integer"); out[name] = Load(v); } else { std::string text(reinterpret_cast(v), size); while (!text.empty() && text.back() == '\0') text.pop_back(); out[name] = text; } } return out; } std::string SfoText(const std::map& sfo, const std::string& key, std::string_view fallback) { const auto it = sfo.find(key); if (it == sfo.end()) return std::string(fallback); if (const auto* text = std::get_if(&it->second)) return *text; return std::to_string(std::get(it->second)); } std::vector ProgramHeaders(const Bytes& elf) { const std::uint64_t phoff = Load(Span(elf, 0x20, 8)); const std::uint16_t entsize = Load(Span(elf, 0x36, 2)); const std::uint16_t count = Load(Span(elf, 0x38, 2)); std::vector out; for (int i = 0; i < count; ++i) out.push_back(ReadPh(elf, phoff + std::uint64_t(i) * entsize)); return out; } std::optional OriginalBytes(const Bytes& elf, std::uint64_t offset, std::size_t length) { for (const Ph& p : ProgramHeaders(elf)) { if (p.type == 1 && p.vaddr <= offset && offset + length <= p.vaddr + p.memsz) { const std::uint64_t start = offset - p.vaddr; Bytes data(length, 0); if (start < p.filesz) { const std::uint64_t n = std::min(p.filesz - start, length); const std::uint64_t from = p.offset + start; const std::uint64_t avail = from < elf.size() ? std::min(n, elf.size() - from) : 0; std::copy_n(elf.begin() + std::ptrdiff_t(from), avail, data.begin()); } return data; } } return std::nullopt; } }