292 lines
11 KiB
C++
292 lines
11 KiB
C++
#include <relinker/parsing/ElfReader.hpp>
|
|
#include <elfpatcher/general/ElfConstants.hpp>
|
|
#include <relinker/domain/Types.hpp>
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
namespace Relinker {
|
|
|
|
using namespace Elfpatcher;
|
|
|
|
namespace {
|
|
|
|
bool _rangeFits(const std::uint64_t offset, const std::uint64_t length, const std::size_t size) {
|
|
return offset <= size && length <= size - offset;
|
|
}
|
|
|
|
}
|
|
|
|
ElfReader::ElfReader(std::vector<std::uint8_t> fileBuffer)
|
|
: _fileBuffer(std::move(fileBuffer))
|
|
{
|
|
}
|
|
|
|
const std::vector<std::uint8_t>& ElfReader::GetRawBytes() const {
|
|
return _fileBuffer;
|
|
}
|
|
|
|
std::string ElfReader::formatMagic() const {
|
|
static constexpr char digits[] = "0123456789abcdef";
|
|
std::string text;
|
|
for (std::size_t index = 0; index < 4; ++index) {
|
|
if (index != 0) text += ' ';
|
|
text += digits[_fileBuffer[index] >> 4];
|
|
text += digits[_fileBuffer[index] & 0x0f];
|
|
}
|
|
return text;
|
|
}
|
|
|
|
std::uint8_t ElfReader::_readU8At(FileByteOffset fileByteOffset) const {
|
|
if (fileByteOffset >= _fileBuffer.size()) {
|
|
throw RelinkerException("FileByteOffset out of bounds", fileByteOffset);
|
|
}
|
|
return _fileBuffer[fileByteOffset];
|
|
}
|
|
|
|
std::uint16_t ElfReader::_readU16At(FileByteOffset fileByteOffset) const {
|
|
if (!_rangeFits(fileByteOffset, 2, _fileBuffer.size())) {
|
|
throw RelinkerException("FileByteOffset out of bounds", fileByteOffset);
|
|
}
|
|
std::uint16_t value;
|
|
std::memcpy(&value, _fileBuffer.data() + fileByteOffset, 2);
|
|
return value;
|
|
}
|
|
|
|
std::uint32_t ElfReader::_readU32At(FileByteOffset fileByteOffset) const {
|
|
if (!_rangeFits(fileByteOffset, 4, _fileBuffer.size())) {
|
|
throw RelinkerException("FileByteOffset out of bounds", fileByteOffset);
|
|
}
|
|
std::uint32_t value;
|
|
std::memcpy(&value, _fileBuffer.data() + fileByteOffset, 4);
|
|
return value;
|
|
}
|
|
|
|
std::uint64_t ElfReader::_readU64At(FileByteOffset fileByteOffset) const {
|
|
if (!_rangeFits(fileByteOffset, 8, _fileBuffer.size())) {
|
|
throw RelinkerException("FileByteOffset out of bounds", fileByteOffset);
|
|
}
|
|
std::uint64_t value;
|
|
std::memcpy(&value, _fileBuffer.data() + fileByteOffset, 8);
|
|
return value;
|
|
}
|
|
|
|
ElfHeader ElfReader::ReadHeader() const {
|
|
if (_fileBuffer.size() < 0x40) {
|
|
throw RelinkerException("File too small for ELF header");
|
|
}
|
|
|
|
const std::uint32_t magic = _readU32At(0);
|
|
if (magic == 0x1d3d154f || magic == 0xeef51454) {
|
|
throw RelinkerException("The input is a SELF container, not an ELF");
|
|
}
|
|
|
|
if (_fileBuffer[0] != 0x7f || _fileBuffer[1] != 'E' ||
|
|
_fileBuffer[2] != 'L' || _fileBuffer[3] != 'F') {
|
|
throw RelinkerException("Invalid ELF magic number: " + formatMagic());
|
|
}
|
|
if (_fileBuffer[4] != 2 || _fileBuffer[5] != 1 || _fileBuffer[6] != 1)
|
|
throw RelinkerException("Expected little-endian ELF64 version 1");
|
|
if (_readU16At(0x12) != 62)
|
|
throw RelinkerException("Expected an x86-64 ELF");
|
|
if (_readU32At(0x14) != 1)
|
|
throw RelinkerException("Unsupported ELF object version: expected 1", 0x14);
|
|
if (_readU16At(0x34) != 64)
|
|
throw RelinkerException("Invalid ELF header size: expected 64 bytes", 0x34);
|
|
|
|
ElfHeader header{};
|
|
header.Machine = _readU16At(0x12);
|
|
header.Type = _readU16At(0x10);
|
|
header.OsAbi = _readU8At(0x07);
|
|
header.AbiVersion = _readU8At(0x08);
|
|
header.EntryPoint = _readU64At(0x18);
|
|
header.ProgramHeaderOffset = _readU64At(0x20);
|
|
header.SectionHeaderOffset = _readU64At(0x28);
|
|
header.ProgramHeaderEntrySize = _readU16At(0x36);
|
|
header.ProgramHeaderCount = _readU16At(0x38);
|
|
header.SectionHeaderEntrySize = _readU16At(0x3a);
|
|
header.SectionHeaderCount = _readU16At(0x3c);
|
|
header.SectionHeaderStringIndex = _readU16At(0x3e);
|
|
|
|
return header;
|
|
}
|
|
|
|
std::vector<ProgramHeader> ElfReader::ReadProgramHeaders() const {
|
|
const ElfHeader header = ReadHeader();
|
|
if (header.ProgramHeaderCount != 0 && header.ProgramHeaderEntrySize != 56) {
|
|
throw RelinkerException("Invalid ELF program header entry size: expected 56 bytes", 0x36);
|
|
}
|
|
|
|
std::vector<ProgramHeader> headers;
|
|
FileByteOffset offset = header.ProgramHeaderOffset;
|
|
|
|
for (std::uint16_t i = 0; i < header.ProgramHeaderCount; ++i) {
|
|
ProgramHeader ph{};
|
|
ph.Type = _readU32At(offset);
|
|
ph.Flags = _readU32At(offset + 0x04);
|
|
ph.Offset = _readU64At(offset + 0x08);
|
|
ph.MappedAddress = _readU64At(offset + 0x10);
|
|
ph.PhysicalAddress = _readU64At(offset + 0x18);
|
|
ph.FileSize = _readU64At(offset + 0x20);
|
|
ph.MemorySize = _readU64At(offset + 0x28);
|
|
ph.Alignment = _readU64At(offset + 0x30);
|
|
|
|
headers.push_back(ph);
|
|
offset += header.ProgramHeaderEntrySize;
|
|
}
|
|
|
|
for (auto& relro : headers) {
|
|
if (relro.Type != PT_OS_RELRO || relro.MemorySize == 0) continue;
|
|
bool covered = false;
|
|
for (const auto& load : headers) {
|
|
if (load.Type != PT_LOAD || load.MemorySize == 0) continue;
|
|
const bool overlaps = relro.MappedAddress < load.MappedAddress ? load.MappedAddress - relro.MappedAddress < relro.MemorySize : relro.MappedAddress - load.MappedAddress < load.MemorySize;
|
|
if (!overlaps) continue;
|
|
if (relro.MappedAddress < load.MappedAddress || relro.MemorySize > load.MemorySize - (relro.MappedAddress - load.MappedAddress))
|
|
throw RelinkerException("PT_SCE_RELRO partially overlaps a PT_LOAD segment", relro.MappedAddress);
|
|
covered = true;
|
|
}
|
|
if (!covered) {
|
|
relro.Type = PT_LOAD;
|
|
relro.Flags = PF_R | PF_W;
|
|
}
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
std::vector<SectionHeader> ElfReader::ReadSectionHeaders() const {
|
|
const ElfHeader header = ReadHeader();
|
|
if (header.SectionHeaderCount != 0 && header.SectionHeaderEntrySize != kShdrEntrySize) {
|
|
throw RelinkerException("Invalid ELF section header entry size: expected 64 bytes", 0x3a);
|
|
}
|
|
const ByteCount tableSize = static_cast<ByteCount>(header.SectionHeaderCount) * header.SectionHeaderEntrySize;
|
|
if (!_rangeFits(header.SectionHeaderOffset, tableSize, _fileBuffer.size())) {
|
|
throw RelinkerException("Section header table out of bounds", header.SectionHeaderOffset);
|
|
}
|
|
if (header.SectionHeaderStringIndex != 0 && header.SectionHeaderStringIndex >= header.SectionHeaderCount) {
|
|
throw RelinkerException("Section header string table index out of bounds", 0x3e);
|
|
}
|
|
|
|
std::vector<SectionHeader> headers;
|
|
FileByteOffset offset = header.SectionHeaderOffset;
|
|
|
|
for (std::uint16_t i = 0; i < header.SectionHeaderCount; ++i) {
|
|
SectionHeader sh;
|
|
const std::uint32_t nameOffset = _readU32At(offset);
|
|
sh.Type = _readU32At(offset + 0x04);
|
|
sh.Flags = _readU64At(offset + 0x08);
|
|
sh.MappedAddress = _readU64At(offset + 0x10);
|
|
sh.Offset = _readU64At(offset + 0x18);
|
|
sh.SectionSize = _readU64At(offset + 0x20);
|
|
sh.Link = _readU32At(offset + 0x28);
|
|
sh.Info = _readU32At(offset + 0x2c);
|
|
sh.EntrySize = _readU64At(offset + kShdrEntSizeOffset);
|
|
|
|
sh.Name = _resolveShdrName(nameOffset, header);
|
|
|
|
headers.push_back(sh);
|
|
offset += header.SectionHeaderEntrySize;
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
std::string ElfReader::_resolveShdrName(std::uint32_t nameOffset, const ElfHeader& header) const {
|
|
if (header.SectionHeaderStringIndex == 0) {
|
|
return "";
|
|
}
|
|
|
|
FileByteOffset shstrOffset = header.SectionHeaderOffset +
|
|
(header.SectionHeaderStringIndex * header.SectionHeaderEntrySize);
|
|
|
|
if (_readU32At(shstrOffset + kShdrTypeOffset) != SHT_STRTAB) {
|
|
throw RelinkerException("Section names do not reference a string table", shstrOffset);
|
|
}
|
|
const FileByteOffset strTableOffset = _readU64At(shstrOffset + 0x18);
|
|
const ByteCount strTableSize = _readU64At(shstrOffset + kShdrSizeOffset);
|
|
if (!_rangeFits(strTableOffset, strTableSize, _fileBuffer.size())) {
|
|
throw RelinkerException("Section name string table out of bounds", strTableOffset);
|
|
}
|
|
if (strTableSize == 0 && nameOffset == 0) {
|
|
return "";
|
|
}
|
|
if (nameOffset >= strTableSize) {
|
|
throw RelinkerException("Section name offset out of bounds", nameOffset);
|
|
}
|
|
const auto first = _fileBuffer.begin() + strTableOffset + nameOffset;
|
|
const auto end = _fileBuffer.begin() + strTableOffset + strTableSize;
|
|
const auto terminator = std::find(first, end, 0);
|
|
if (terminator == end) {
|
|
throw RelinkerException("Unterminated section name", strTableOffset + nameOffset);
|
|
}
|
|
return std::string(first, terminator);
|
|
}
|
|
|
|
std::vector<DynamicTag> ElfReader::ReadDynamicTags(const ProgramHeader& dynamicHeader) const {
|
|
if (!_rangeFits(dynamicHeader.Offset, dynamicHeader.FileSize, _fileBuffer.size())) {
|
|
throw RelinkerException("Dynamic segment out of bounds", dynamicHeader.Offset);
|
|
}
|
|
if (dynamicHeader.FileSize % 16 != 0) {
|
|
throw RelinkerException("Invalid dynamic segment size", dynamicHeader.Offset);
|
|
}
|
|
|
|
std::vector<DynamicTag> tags;
|
|
FileByteOffset offset = dynamicHeader.Offset;
|
|
const FileByteOffset end = dynamicHeader.Offset + dynamicHeader.FileSize;
|
|
|
|
while (offset < end) {
|
|
DynamicTag tag;
|
|
tag.Tag = static_cast<std::int64_t>(_readU64At(offset));
|
|
tag.Value = _readU64At(offset + 0x08);
|
|
|
|
if (tag.Tag == 0) {
|
|
return tags;
|
|
}
|
|
|
|
tags.push_back(tag);
|
|
offset += 16;
|
|
}
|
|
|
|
throw RelinkerException("Unterminated dynamic segment", dynamicHeader.Offset);
|
|
}
|
|
|
|
FileByteOffset ElfReader::TranslateVirtualAddress(VirtualAddress address) const {
|
|
for (const auto& segment : ReadProgramHeaders()) {
|
|
if (segment.Type == PT_LOAD && address >= segment.MappedAddress &&
|
|
address - segment.MappedAddress < segment.FileSize) {
|
|
if (!_rangeFits(segment.Offset, segment.FileSize, _fileBuffer.size())) {
|
|
throw RelinkerException("PT_LOAD file mapping out of bounds", segment.Offset);
|
|
}
|
|
return segment.Offset + (address - segment.MappedAddress);
|
|
}
|
|
}
|
|
|
|
throw RelinkerException("Virtual address not mapped by any PT_LOAD segment", address);
|
|
}
|
|
|
|
std::vector<std::uint8_t> ElfReader::ReadSection(const SectionHeader& header) const {
|
|
if (!_rangeFits(header.Offset, header.SectionSize, _fileBuffer.size())) {
|
|
throw RelinkerException("Section offset out of bounds", header.Offset);
|
|
}
|
|
|
|
return std::vector<std::uint8_t>(
|
|
_fileBuffer.begin() + header.Offset,
|
|
_fileBuffer.begin() + header.Offset + header.SectionSize);
|
|
}
|
|
|
|
std::vector<std::uint8_t> ElfReader::ReadSegment(const ProgramHeader& header) const {
|
|
if (!_rangeFits(header.Offset, header.FileSize, _fileBuffer.size())) {
|
|
throw RelinkerException("Segment offset out of bounds", header.Offset);
|
|
}
|
|
|
|
return std::vector<std::uint8_t>(
|
|
_fileBuffer.begin() + header.Offset,
|
|
_fileBuffer.begin() + header.Offset + header.FileSize);
|
|
}
|
|
|
|
std::uint64_t ElfReader::GetFileSize() const {
|
|
return _fileBuffer.size();
|
|
}
|
|
|
|
}
|