mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-16 03:00:57 +00:00
2435 lines
81 KiB
C#
2435 lines
81 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
using System.Buffers.Binary;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Linq;
|
|
using SharpEmu.Core;
|
|
using SharpEmu.Core.Cpu;
|
|
using SharpEmu.Core.Memory;
|
|
using SharpEmu.HLE;
|
|
using SharpEmu.Logging;
|
|
|
|
namespace SharpEmu.Core.Loader;
|
|
|
|
public sealed class SelfLoader : ISelfLoader
|
|
{
|
|
private static readonly SharpEmuLogger Log = SharpEmuLog.For("Loader");
|
|
private const uint ElfMagic = 0x7F454C46;
|
|
private const uint Ps4SelfMagic = 0x4F153D1D;
|
|
private const uint Ps5SelfMagic = 0x5414F5EE;
|
|
private const ulong SelfSegmentFlag = 0x800;
|
|
private const int PageSize = 0x1000;
|
|
private const ulong ImportStubBaseAddress = 0x0000_7000_0000_0000UL;
|
|
private const ulong ImportStubAddressStride = 0x0000_0000_0100_0000UL;
|
|
private const ulong ImportStubSlotSize = 0x10;
|
|
private const byte StubTrapOpcode = 0xCC;
|
|
private const byte StubReturnOpcode = 0xC3;
|
|
|
|
private const int DynamicEntrySize = 16;
|
|
private const int ElfSymbolSize = 24;
|
|
private const int ElfRelocationSize = 24;
|
|
private const int ElfSectionHeaderSize = 64;
|
|
private const uint SectionTypeSymbolTable = 2;
|
|
private const uint SectionTypeRela = 4;
|
|
|
|
private const long DtNull = 0;
|
|
private const long DtPltRelSize = 0x02;
|
|
private const long DtPltGot = 0x03;
|
|
private const long DtStrTab = 0x05;
|
|
private const long DtSymTab = 0x06;
|
|
private const long DtRela = 0x07;
|
|
private const long DtRelaSize = 0x08;
|
|
private const long DtInit = 0x0C;
|
|
private const long DtStrSize = 0x0A;
|
|
private const long DtJmpRel = 0x17;
|
|
private const long DtInitArray = 0x19;
|
|
private const long DtInitArraySize = 0x1B;
|
|
private const long DtPreInitArray = 0x20;
|
|
private const long DtPreInitArraySize = 0x21;
|
|
private const long DtSceJmpRel = 0x61000029;
|
|
private const long DtScePltRelSize = 0x6100002D;
|
|
private const long DtSceRela = 0x6100002F;
|
|
private const long DtSceRelaSize = 0x61000031;
|
|
private const long DtSceStrTab = 0x61000035;
|
|
private const long DtSceStrSize = 0x61000037;
|
|
private const long DtSceSymTab = 0x61000039;
|
|
private const long DtSceSymTabSize = 0x6100003F;
|
|
|
|
private const uint RelocationTypeAbsolute64 = 1;
|
|
private const uint RelocationTypeGlobalData = 6;
|
|
private const uint RelocationTypeJumpSlot = 7;
|
|
private const uint RelocationTypeRelative = 8;
|
|
private const uint RelocationTypeTlsModuleId = 16;
|
|
private const ulong Ps5MainImageBase = 0x0000000800000000UL;
|
|
private const ulong Ps4MainImageBase = 0x0000000000400000UL;
|
|
private const ulong Ps5ModuleSearchStart = 0x0000000804000000UL;
|
|
private const ulong Ps5ModuleSearchEnd = 0x0000000900000000UL;
|
|
private const ulong Ps4ModuleSearchStart = 0x0000000002000000UL;
|
|
private const ulong Ps4ModuleSearchEnd = 0x0000000040000000UL;
|
|
private const ulong ModulePlacementStep = 0x00200000UL;
|
|
private const ulong FocusRelocGuestStart = 0x0000000807BA25B0UL;
|
|
private const ulong FocusRelocGuestEnd = 0x0000000807BA2608UL;
|
|
private const byte SymbolBindLocal = 0;
|
|
private const byte SymbolBindGlobal = 1;
|
|
private const byte SymbolBindWeak = 2;
|
|
private const byte SymbolTypeObject = 1;
|
|
|
|
private IModuleManager? _moduleManager;
|
|
private uint _nextTlsModuleId = 1;
|
|
|
|
private static readonly IReadOnlyDictionary<ulong, string> EmptyImportStubs = new Dictionary<ulong, string>();
|
|
private static readonly IReadOnlyDictionary<string, ulong> EmptyRuntimeSymbols =
|
|
new Dictionary<string, ulong>(StringComparer.Ordinal);
|
|
private static readonly IReadOnlyList<ulong> EmptyInitializerFunctions = Array.Empty<ulong>();
|
|
private static readonly int SelfHeaderSize = Unsafe.SizeOf<SelfHeader>();
|
|
private static readonly int SelfSegmentSize = Unsafe.SizeOf<SelfSegment>();
|
|
private static readonly int ProgramHeaderSize = Unsafe.SizeOf<ProgramHeader>();
|
|
|
|
public SelfImage Load(ReadOnlySpan<byte> imageData, IVirtualMemory virtualMemory)
|
|
{
|
|
return Load(imageData, virtualMemory, fs: null, mountRoot: null);
|
|
}
|
|
|
|
public SelfImage Load(ReadOnlySpan<byte> imageData, IVirtualMemory virtualMemory, IFileSystem? fs, string? mountRoot)
|
|
{
|
|
return LoadCore(
|
|
imageData,
|
|
virtualMemory,
|
|
fs,
|
|
mountRoot,
|
|
clearVirtualMemory: true,
|
|
readParamJson: true);
|
|
}
|
|
|
|
public SelfImage Load(ReadOnlySpan<byte> imageData, IVirtualMemory virtualMemory, IModuleManager moduleManager)
|
|
{
|
|
return Load(imageData, virtualMemory, moduleManager, fs: null, mountRoot: null);
|
|
}
|
|
|
|
public SelfImage Load(ReadOnlySpan<byte> imageData, IVirtualMemory virtualMemory, IModuleManager moduleManager, IFileSystem? fs, string? mountRoot)
|
|
{
|
|
_moduleManager = moduleManager;
|
|
return LoadCore(
|
|
imageData,
|
|
virtualMemory,
|
|
fs,
|
|
mountRoot,
|
|
clearVirtualMemory: true,
|
|
readParamJson: true);
|
|
}
|
|
|
|
public SelfImage LoadAdditional(ReadOnlySpan<byte> imageData, IVirtualMemory virtualMemory, IModuleManager moduleManager, IFileSystem? fs, string? mountRoot)
|
|
{
|
|
_moduleManager = moduleManager;
|
|
return LoadCore(
|
|
imageData,
|
|
virtualMemory,
|
|
fs,
|
|
mountRoot,
|
|
clearVirtualMemory: false,
|
|
readParamJson: false);
|
|
}
|
|
|
|
private SelfImage LoadCore(
|
|
ReadOnlySpan<byte> imageData,
|
|
IVirtualMemory virtualMemory,
|
|
IFileSystem? fs,
|
|
string? mountRoot,
|
|
bool clearVirtualMemory,
|
|
bool readParamJson)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(virtualMemory);
|
|
|
|
if (imageData.IsEmpty)
|
|
{
|
|
throw new InvalidDataException("Input image is empty.");
|
|
}
|
|
|
|
var applicationInfo = readParamJson
|
|
? TryLoadParamJson(fs, mountRoot)
|
|
: default;
|
|
|
|
if (clearVirtualMemory)
|
|
{
|
|
virtualMemory.Clear();
|
|
_nextTlsModuleId = 1;
|
|
}
|
|
|
|
var tlsModuleId = _nextTlsModuleId == 0 ? 1u : _nextTlsModuleId;
|
|
Log.Debug($"TLS load_start clear={clearVirtualMemory} next={_nextTlsModuleId} assigned={tlsModuleId}");
|
|
|
|
var loadContext = ParseLayout(imageData);
|
|
var elfHeader = ReadUnmanaged<ElfHeader>(imageData, loadContext.ElfOffset);
|
|
ValidateElfHeader(elfHeader);
|
|
|
|
var programHeaders = ParseProgramHeaders(imageData, loadContext, elfHeader);
|
|
|
|
var totalImageSize = CalculateTotalImageSize(programHeaders);
|
|
Console.WriteLine($"Total image size needed: 0x{totalImageSize:X} ({totalImageSize} bytes)");
|
|
var isNextGen = elfHeader.AbiVersion == 2;
|
|
var imageBase = DetermineRequestedImageBase(virtualMemory, totalImageSize, isNextGen, clearVirtualMemory);
|
|
|
|
if (virtualMemory is PhysicalVirtualMemory physicalVm)
|
|
{
|
|
if (clearVirtualMemory)
|
|
{
|
|
if (!physicalVm.TryAllocateAtExact(imageBase, totalImageSize, executable: true, out var allocatedBase))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Could not allocate main image at required base 0x{imageBase:X16} (size=0x{totalImageSize:X}).");
|
|
}
|
|
|
|
imageBase = allocatedBase;
|
|
}
|
|
else if (!TryAllocateAdditionalImageAtExact(physicalVm, imageBase, totalImageSize, isNextGen, out imageBase))
|
|
{
|
|
var allocatedBase = physicalVm.AllocateAt(imageBase, totalImageSize, executable: true);
|
|
if (allocatedBase != imageBase)
|
|
{
|
|
Console.WriteLine($"[LOADER] Could not allocate module at preferred base 0x{imageBase:X16}");
|
|
Console.WriteLine($"[LOADER] Allocated module at 0x{allocatedBase:X16} instead.");
|
|
}
|
|
|
|
imageBase = allocatedBase;
|
|
}
|
|
}
|
|
|
|
MapLoadSegments(imageData, loadContext, programHeaders, virtualMemory, imageBase);
|
|
var importStubs = ResolveAndPatchImportStubs(
|
|
imageData,
|
|
loadContext,
|
|
elfHeader,
|
|
programHeaders,
|
|
virtualMemory,
|
|
imageBase,
|
|
_moduleManager,
|
|
tlsModuleId,
|
|
out var importedRelocations);
|
|
var effectiveImportStubs = importStubs.Count == 0
|
|
? new Dictionary<ulong, string>()
|
|
: new Dictionary<ulong, string>(importStubs);
|
|
var runtimeSymbols = new Dictionary<string, ulong>(StringComparer.Ordinal);
|
|
RegisterRuntimeSymbolsAndHooks(
|
|
imageData,
|
|
loadContext,
|
|
programHeaders,
|
|
elfHeader,
|
|
virtualMemory,
|
|
imageBase,
|
|
effectiveImportStubs,
|
|
runtimeSymbols);
|
|
var finalizedImportStubs = effectiveImportStubs.Count == 0
|
|
? EmptyImportStubs
|
|
: effectiveImportStubs;
|
|
var finalizedRuntimeSymbols = runtimeSymbols.Count == 0
|
|
? EmptyRuntimeSymbols
|
|
: runtimeSymbols;
|
|
CollectInitializerFunctions(
|
|
imageData,
|
|
loadContext,
|
|
programHeaders,
|
|
virtualMemory,
|
|
imageBase,
|
|
out var initFunctionEntryPoint,
|
|
out var preInitializerFunctions,
|
|
out var initializerFunctions);
|
|
var procParamAddress = ResolveProcParamAddress(programHeaders, imageBase);
|
|
|
|
Console.WriteLine($"[LOADER] ELF e_entry: 0x{elfHeader.EntryPoint:X16}");
|
|
Console.WriteLine($"[LOADER] Generation: {(isNextGen ? "Gen5 (PS5)" : "Gen4 (PS4)")}");
|
|
Console.WriteLine($"[LOADER] Using image base: 0x{imageBase:X16}");
|
|
Console.WriteLine($"[LOADER] Final entry point: 0x{elfHeader.EntryPoint + imageBase:X16}");
|
|
if (procParamAddress != 0)
|
|
{
|
|
Console.WriteLine($"[LOADER] ProcParam: 0x{procParamAddress:X16}");
|
|
}
|
|
|
|
int count = ((IReadOnlyList<ProgramHeader>)programHeaders).Count;
|
|
int phCountToLog = count < 3 ? count : 3;
|
|
for (var i = 0; i < phCountToLog; i++)
|
|
{
|
|
var ph = programHeaders[i];
|
|
Console.WriteLine($"[LOADER] PH[{i}]: type={ph.HeaderType}, vaddr=0x{ph.VirtualAddress:X16} -> 0x{ph.VirtualAddress + imageBase:X16}, memsz=0x{ph.MemorySize:X}");
|
|
}
|
|
|
|
if (_nextTlsModuleId == tlsModuleId && _nextTlsModuleId < uint.MaxValue)
|
|
{
|
|
_nextTlsModuleId++;
|
|
}
|
|
Log.Debug($"TLS load_done assigned={tlsModuleId} next={_nextTlsModuleId}");
|
|
|
|
return new SelfImage(
|
|
loadContext.IsSelf,
|
|
elfHeader,
|
|
programHeaders,
|
|
virtualMemory.SnapshotRegions(),
|
|
finalizedImportStubs,
|
|
finalizedRuntimeSymbols,
|
|
importedRelocations,
|
|
preInitializerFunctions,
|
|
initializerFunctions,
|
|
initFunctionEntryPoint,
|
|
imageBase,
|
|
procParamAddress,
|
|
applicationInfo.Title,
|
|
applicationInfo.TitleId,
|
|
applicationInfo.Version);
|
|
}
|
|
|
|
private static (string? Title, string? TitleId, string? Version) TryLoadParamJson(
|
|
IFileSystem? fs,
|
|
string? mountRoot)
|
|
{
|
|
if (fs == null)
|
|
{
|
|
Console.WriteLine("[LOADER] param.json not found (no filesystem provided).");
|
|
return default;
|
|
}
|
|
|
|
string[] possiblePaths = string.IsNullOrEmpty(mountRoot)
|
|
? new[] { "sce_sys/param.json", "param.json" }
|
|
: new[] { $"{mountRoot}/sce_sys/param.json", $"{mountRoot}/param.json" };
|
|
|
|
string? foundPath = null;
|
|
foreach (var path in possiblePaths)
|
|
{
|
|
if (fs.Exists(path))
|
|
{
|
|
foundPath = path;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (foundPath == null)
|
|
{
|
|
Console.WriteLine("[LOADER] param.json not found (no root path / unknown layout).");
|
|
return default;
|
|
}
|
|
|
|
var applicationInfo = Ps5ParamJsonReader.TryReadPs5Param(fs, foundPath);
|
|
Console.WriteLine($"[LOADER] Loading param.json at {foundPath}");
|
|
Console.WriteLine(
|
|
$"[LOADER] Title: {applicationInfo.Title ?? "(unknown)"}, " +
|
|
$"TitleId: {applicationInfo.TitleId ?? "(unknown)"}, " +
|
|
$"Version: {applicationInfo.Version ?? "(unknown)"}");
|
|
return applicationInfo;
|
|
}
|
|
|
|
private static LoadContext ParseLayout(ReadOnlySpan<byte> imageData)
|
|
{
|
|
if (imageData.Length < Unsafe.SizeOf<ElfHeader>())
|
|
{
|
|
throw new InvalidDataException("Input image is too small to contain an ELF header.");
|
|
}
|
|
|
|
var magic = BinaryPrimitives.ReadUInt32BigEndian(imageData[..sizeof(uint)]);
|
|
if (magic is Ps4SelfMagic or Ps5SelfMagic)
|
|
{
|
|
var selfHeader = ReadUnmanaged<SelfHeader>(imageData, 0);
|
|
if (!selfHeader.HasKnownLayout || selfHeader.Unknown != 0x22)
|
|
{
|
|
throw new InvalidDataException("SELF header signature is not recognized.");
|
|
}
|
|
|
|
var segmentCount = selfHeader.SegmentCount;
|
|
var elfOffset = checked(SelfHeaderSize + (segmentCount * SelfSegmentSize));
|
|
EnsureRange(imageData.Length, (ulong)elfOffset, (ulong)Unsafe.SizeOf<ElfHeader>());
|
|
|
|
var segments = segmentCount == 0 ? Array.Empty<SelfSegment>() : GC.AllocateUninitializedArray<SelfSegment>(segmentCount);
|
|
for (var i = 0; i < segmentCount; i++)
|
|
{
|
|
var segmentOffset = checked(SelfHeaderSize + (i * SelfSegmentSize));
|
|
segments[i] = ReadUnmanaged<SelfSegment>(imageData, segmentOffset);
|
|
}
|
|
|
|
return new LoadContext(IsSelf: true, elfOffset, selfHeader.FileSize, segments);
|
|
}
|
|
|
|
if (magic != ElfMagic)
|
|
{
|
|
throw new InvalidDataException(
|
|
$"Unsupported executable signature 0x{magic:X8}");
|
|
}
|
|
|
|
return new LoadContext(IsSelf: false, ElfOffset: 0, SelfFileSize: 0, Array.Empty<SelfSegment>());
|
|
}
|
|
|
|
private static ProgramHeader[] ParseProgramHeaders(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
ElfHeader elfHeader)
|
|
{
|
|
if (elfHeader.ProgramHeaderCount == 0)
|
|
{
|
|
return Array.Empty<ProgramHeader>();
|
|
}
|
|
|
|
if (elfHeader.ProgramHeaderEntrySize < ProgramHeaderSize)
|
|
{
|
|
throw new InvalidDataException("Program header entry size is smaller than expected.");
|
|
}
|
|
|
|
var tableOffset = checked(loadContext.ElfOffset + (int)elfHeader.ProgramHeaderOffset);
|
|
EnsureRange(
|
|
imageData.Length,
|
|
(ulong)tableOffset,
|
|
(ulong)elfHeader.ProgramHeaderCount * elfHeader.ProgramHeaderEntrySize);
|
|
|
|
var headers = GC.AllocateUninitializedArray<ProgramHeader>(elfHeader.ProgramHeaderCount);
|
|
for (var i = 0; i < headers.Length; i++)
|
|
{
|
|
var entryOffset = checked(tableOffset + (i * elfHeader.ProgramHeaderEntrySize));
|
|
headers[i] = ReadUnmanaged<ProgramHeader>(imageData, entryOffset);
|
|
}
|
|
|
|
return headers;
|
|
}
|
|
|
|
private static void MapLoadSegments(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
IReadOnlyList<ProgramHeader> programHeaders,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase)
|
|
{
|
|
for (var index = 0; index < programHeaders.Count; index++)
|
|
{
|
|
var header = programHeaders[index];
|
|
if (header.HeaderType != ProgramHeaderType.Load || header.MemorySize == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (header.FileSize > header.MemorySize)
|
|
{
|
|
throw new InvalidDataException("ELF segment file size cannot exceed memory size.");
|
|
}
|
|
|
|
var sourceOffset = header.FileSize == 0
|
|
? 0UL
|
|
: ResolvePhysicalSegmentOffset(imageData.Length, loadContext, header, index);
|
|
|
|
var virtualAddress = header.VirtualAddress + imageBase;
|
|
|
|
Log.Debug($"Segment {index}: VAddr=0x{virtualAddress:X16}, FileSize=0x{header.FileSize:X}, MemSize=0x{header.MemorySize:X}, Align=0x{header.Alignment:X}");
|
|
if (header.Alignment > 1)
|
|
{
|
|
var vaddrMod = virtualAddress % header.Alignment;
|
|
var offsetMod = header.Offset % header.Alignment;
|
|
if (vaddrMod != offsetMod)
|
|
{
|
|
Log.Warning(
|
|
$"Segment {index} ELF alignment mismatch! " +
|
|
$"VAddr=0x{virtualAddress:X}, Offset=0x{header.Offset:X}, Align=0x{header.Alignment:X}, " +
|
|
$"VAddr%Align=0x{vaddrMod:X}, Offset%Align=0x{offsetMod:X}");
|
|
}
|
|
}
|
|
|
|
ReadOnlySpan<byte> fileData = ReadOnlySpan<byte>.Empty;
|
|
if (header.FileSize != 0)
|
|
{
|
|
if (header.FileSize > int.MaxValue)
|
|
{
|
|
throw new NotSupportedException("Segments larger than 2 GB are not currently supported.");
|
|
}
|
|
|
|
EnsureRange(imageData.Length, sourceOffset, header.FileSize);
|
|
fileData = imageData.Slice((int)sourceOffset, (int)header.FileSize);
|
|
}
|
|
|
|
virtualMemory.Map(
|
|
virtualAddress,
|
|
header.MemorySize,
|
|
sourceOffset,
|
|
fileData,
|
|
header.Flags);
|
|
}
|
|
}
|
|
|
|
private static ulong ResolveProcParamAddress(IReadOnlyList<ProgramHeader> programHeaders, ulong imageBase)
|
|
{
|
|
for (var index = 0; index < programHeaders.Count; index++)
|
|
{
|
|
var header = programHeaders[index];
|
|
if (header.HeaderType != ProgramHeaderType.SceProcParam)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return header.VirtualAddress + imageBase;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private static IReadOnlyDictionary<ulong, string> ResolveAndPatchImportStubs(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
ElfHeader elfHeader,
|
|
IReadOnlyList<ProgramHeader> programHeaders,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase,
|
|
IModuleManager? moduleManager,
|
|
uint tlsModuleId,
|
|
out IReadOnlyList<ImportedSymbolRelocation> importedRelocations)
|
|
{
|
|
importedRelocations = Array.Empty<ImportedSymbolRelocation>();
|
|
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex))
|
|
{
|
|
return EmptyImportStubs;
|
|
}
|
|
|
|
if (dynamicHeader.FileSize == 0)
|
|
{
|
|
return EmptyImportStubs;
|
|
}
|
|
|
|
if (dynamicHeader.FileSize > int.MaxValue)
|
|
{
|
|
throw new NotSupportedException("Dynamic metadata segments larger than 2 GB are not currently supported.");
|
|
}
|
|
|
|
if (!TryLoadDynamicTableBytes(
|
|
imageData,
|
|
loadContext,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicHeader,
|
|
dynamicHeaderIndex,
|
|
out var dynamicTable))
|
|
{
|
|
return EmptyImportStubs;
|
|
}
|
|
|
|
var elfData = imageData;
|
|
|
|
var dynamicInfo = ParseDynamicInfo(dynamicTable);
|
|
|
|
Console.WriteLine($"[LOADER] Dynamic Info: StrTab=0x{dynamicInfo.StrTabOffset:X}, StrTabSize=0x{dynamicInfo.StrTabSize:X}");
|
|
Console.WriteLine($"[LOADER] Dynamic Info: SymTab=0x{dynamicInfo.SymTabOffset:X}, SymTabSize=0x{dynamicInfo.SymTabSize:X}");
|
|
Console.WriteLine($"[LOADER] Dynamic Info: Rela=0x{dynamicInfo.RelaOffset:X}, RelaSize=0x{dynamicInfo.RelaSize:X}");
|
|
Console.WriteLine($"[LOADER] Dynamic Info: JmpRel=0x{dynamicInfo.JmpRelOffset:X}, JmpRelSize=0x{dynamicInfo.JmpRelSize:X}");
|
|
Console.WriteLine($"[LOADER] Dynamic Info: PltGot=0x{dynamicInfo.PltGotOffset:X}");
|
|
Console.WriteLine($"[LOADER] TLS module id: {tlsModuleId}");
|
|
Console.WriteLine($"[LOADER] HasImportMetadata: {dynamicInfo.HasImportMetadata}");
|
|
|
|
var relocations = new List<ElfRelocation>(512);
|
|
|
|
if (dynamicInfo.RelaSize != 0 &&
|
|
TryLoadTableBytes(elfData, virtualMemory, imageBase, dynamicInfo.RelaOffset, dynamicInfo.RelaSize, out var relaBytes))
|
|
{
|
|
CollectRelocations(relaBytes, relocations);
|
|
}
|
|
|
|
if (dynamicInfo.JmpRelSize != 0 &&
|
|
TryLoadTableBytes(elfData, virtualMemory, imageBase, dynamicInfo.JmpRelOffset, dynamicInfo.JmpRelSize, out var jmpRelBytes))
|
|
{
|
|
CollectRelocations(jmpRelBytes, relocations);
|
|
}
|
|
|
|
if (!dynamicInfo.HasImportMetadata)
|
|
{
|
|
Console.WriteLine($"[LOADER] No import metadata found in ELF!");
|
|
}
|
|
|
|
if (relocations.Count != 0)
|
|
{
|
|
Console.WriteLine($"[LOADER] ImageBase runtime: 0x{imageBase:X16}");
|
|
Console.WriteLine($"[LOADER] Processing {relocations.Count} relocations...");
|
|
}
|
|
|
|
uint maxSymbolIndex = 0;
|
|
foreach (var relocation in relocations)
|
|
{
|
|
if (!IsSupportedRelocationType(relocation.Type))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (relocation.Type is RelocationTypeRelative or RelocationTypeTlsModuleId)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (relocation.SymbolIndex > maxSymbolIndex)
|
|
{
|
|
maxSymbolIndex = relocation.SymbolIndex;
|
|
}
|
|
}
|
|
|
|
byte[] stringTable = Array.Empty<byte>();
|
|
byte[] symbolTable = Array.Empty<byte>();
|
|
if (maxSymbolIndex != 0)
|
|
{
|
|
if (!TryLoadTableBytes(
|
|
elfData,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicInfo.StrTabOffset,
|
|
dynamicInfo.StrTabSize,
|
|
out stringTable))
|
|
{
|
|
return EmptyImportStubs;
|
|
}
|
|
|
|
var symTableSize = dynamicInfo.SymTabSize != 0
|
|
? dynamicInfo.SymTabSize
|
|
: checked(((ulong)maxSymbolIndex + 1) * ElfSymbolSize);
|
|
if (!TryLoadTableBytes(
|
|
elfData,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicInfo.SymTabOffset,
|
|
symTableSize,
|
|
out symbolTable))
|
|
{
|
|
return EmptyImportStubs;
|
|
}
|
|
}
|
|
|
|
var descriptors = new List<RelocationDescriptor>(256);
|
|
var orderedImportNids = new List<string>(128);
|
|
var seenImportNids = new HashSet<string>(StringComparer.Ordinal);
|
|
AppendRelocationDescriptors(
|
|
relocations,
|
|
symbolTable,
|
|
stringTable,
|
|
virtualMemory,
|
|
imageBase,
|
|
tlsModuleId,
|
|
descriptors,
|
|
orderedImportNids,
|
|
seenImportNids);
|
|
|
|
if (descriptors.Count == 0)
|
|
{
|
|
var sectionFallbackRelocCount = AppendSectionRelocationDescriptors(
|
|
imageData,
|
|
loadContext,
|
|
elfHeader,
|
|
virtualMemory,
|
|
imageBase,
|
|
tlsModuleId,
|
|
descriptors,
|
|
orderedImportNids,
|
|
seenImportNids);
|
|
if (sectionFallbackRelocCount != 0)
|
|
{
|
|
Console.WriteLine(
|
|
$"[LOADER] Section relocation fallback recovered {sectionFallbackRelocCount} relocation entries, {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors");
|
|
}
|
|
}
|
|
|
|
Console.WriteLine($"[LOADER] Found {orderedImportNids.Count} unique NIDs, {descriptors.Count} descriptors");
|
|
|
|
if (descriptors.Count == 0)
|
|
{
|
|
Console.WriteLine($"[LOADER] No relocation descriptors!");
|
|
return EmptyImportStubs;
|
|
}
|
|
|
|
importedRelocations = BuildImportedRelocations(descriptors);
|
|
|
|
var stubsByAddress = CreateImportStubMapping(virtualMemory, orderedImportNids);
|
|
Console.WriteLine($"[LOADER] Created {stubsByAddress.Count} import stubs");
|
|
|
|
int printCount = Math.Min(10, orderedImportNids.Count);
|
|
for (int i = 0; i < printCount; i++)
|
|
{
|
|
var nid = orderedImportNids[i];
|
|
var addr = stubsByAddress.First(x => x.Value == nid).Key;
|
|
}
|
|
|
|
var nidNames = Aerolib.Instance.GetAllNidNames();
|
|
|
|
var nidCounts = new Dictionary<string, int>(StringComparer.Ordinal);
|
|
foreach (var descriptor in descriptors)
|
|
{
|
|
if (descriptor.ImportNid is not null)
|
|
{
|
|
nidCounts.TryGetValue(descriptor.ImportNid, out var count);
|
|
nidCounts[descriptor.ImportNid] = count + 1;
|
|
}
|
|
}
|
|
|
|
var addressesByNid = new Dictionary<string, ulong>(orderedImportNids.Count, StringComparer.Ordinal);
|
|
foreach (var entry in stubsByAddress)
|
|
{
|
|
addressesByNid[entry.Value] = entry.Key;
|
|
}
|
|
|
|
foreach (var descriptor in descriptors)
|
|
{
|
|
ulong targetValue;
|
|
if (descriptor.ImportNid is null)
|
|
{
|
|
targetValue = AddSigned(descriptor.SymbolValue, descriptor.Addend);
|
|
}
|
|
else
|
|
{
|
|
if (!addressesByNid.TryGetValue(descriptor.ImportNid, out var stubAddress))
|
|
{
|
|
throw new InvalidOperationException($"Import stub not found for NID '{descriptor.ImportNid}'.");
|
|
}
|
|
|
|
targetValue = AddSigned(stubAddress, descriptor.Addend);
|
|
}
|
|
|
|
if (targetValue < 0x1000)
|
|
{
|
|
if (descriptor.ValueKind == RelocationValueKind.TlsModuleId)
|
|
{
|
|
Log.Debug(
|
|
$"Patching DTPMOD64 at 0x{descriptor.TargetAddress:X} with module id 0x{targetValue:X}");
|
|
}
|
|
else
|
|
{
|
|
Log.Warning($"!!! CRITICAL !!! Patching address 0x{descriptor.TargetAddress:X} with INVALID value 0x{targetValue:X} for NID {descriptor.ImportNid ?? "(null)"}");
|
|
Log.Warning($" SymbolValue=0x{descriptor.SymbolValue:X}, Addend=0x{descriptor.Addend:X}, StubAddress=0x{(addressesByNid.TryGetValue(descriptor.ImportNid ?? "", out var sa) ? sa : 0):X}");
|
|
}
|
|
}
|
|
|
|
if (!TryWriteUInt64(virtualMemory, descriptor.TargetAddress, targetValue))
|
|
{
|
|
throw new InvalidDataException($"Failed to patch relocation at 0x{descriptor.TargetAddress:X16}.");
|
|
}
|
|
|
|
if (descriptor.TargetAddress >= 0x00000008030FC300UL &&
|
|
descriptor.TargetAddress <= 0x00000008030FC3F0UL)
|
|
{
|
|
Log.Debug(
|
|
$"[RELOC] target=0x{descriptor.TargetAddress:X16} value=0x{targetValue:X16} addend=0x{descriptor.Addend:X} nid={(descriptor.ImportNid ?? "<sym>")}");
|
|
}
|
|
}
|
|
|
|
return stubsByAddress;
|
|
}
|
|
|
|
private static int AppendSectionRelocationDescriptors(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
ElfHeader elfHeader,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase,
|
|
uint tlsModuleId,
|
|
ICollection<RelocationDescriptor> descriptors,
|
|
IList<string> orderedImportNids,
|
|
ISet<string> seenImportNids)
|
|
{
|
|
if (elfHeader.SectionHeaderOffset == 0 ||
|
|
elfHeader.SectionHeaderCount == 0 ||
|
|
elfHeader.SectionHeaderEntrySize < ElfSectionHeaderSize)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var appendedRelocations = 0;
|
|
for (var sectionIndex = 0; sectionIndex < elfHeader.SectionHeaderCount; sectionIndex++)
|
|
{
|
|
if (!TryReadSectionHeader(imageData, loadContext, elfHeader, sectionIndex, out var relocationHeader) ||
|
|
relocationHeader.Type != SectionTypeRela ||
|
|
relocationHeader.Size == 0 ||
|
|
relocationHeader.EntrySize < ElfRelocationSize)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!TryReadElfRelativeSlice(imageData, loadContext, relocationHeader.Offset, relocationHeader.Size, out var relocationTable))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var relocations = new List<ElfRelocation>(checked((int)(relocationHeader.Size / relocationHeader.EntrySize)));
|
|
CollectRelocations(relocationTable, relocations);
|
|
if (relocations.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ReadOnlySpan<byte> symbolTable = ReadOnlySpan<byte>.Empty;
|
|
ReadOnlySpan<byte> stringTable = ReadOnlySpan<byte>.Empty;
|
|
if (relocationHeader.Link < elfHeader.SectionHeaderCount &&
|
|
TryReadSectionHeader(imageData, loadContext, elfHeader, (int)relocationHeader.Link, out var symbolHeader) &&
|
|
symbolHeader.Size != 0 &&
|
|
symbolHeader.EntrySize >= ElfSymbolSize &&
|
|
TryReadElfRelativeSlice(imageData, loadContext, symbolHeader.Offset, symbolHeader.Size, out symbolTable) &&
|
|
symbolHeader.Link < elfHeader.SectionHeaderCount &&
|
|
TryReadSectionHeader(imageData, loadContext, elfHeader, (int)symbolHeader.Link, out var stringHeader) &&
|
|
stringHeader.Size != 0 &&
|
|
TryReadElfRelativeSlice(imageData, loadContext, stringHeader.Offset, stringHeader.Size, out stringTable))
|
|
{
|
|
}
|
|
|
|
AppendRelocationDescriptors(
|
|
relocations,
|
|
symbolTable,
|
|
stringTable,
|
|
virtualMemory,
|
|
imageBase,
|
|
tlsModuleId,
|
|
descriptors,
|
|
orderedImportNids,
|
|
seenImportNids);
|
|
appendedRelocations += relocations.Count;
|
|
}
|
|
|
|
return appendedRelocations;
|
|
}
|
|
|
|
private static void AppendRelocationDescriptors(
|
|
IReadOnlyList<ElfRelocation> relocations,
|
|
ReadOnlySpan<byte> symbolTable,
|
|
ReadOnlySpan<byte> stringTable,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase,
|
|
uint tlsModuleId,
|
|
ICollection<RelocationDescriptor> descriptors,
|
|
IList<string> orderedImportNids,
|
|
ISet<string> seenImportNids)
|
|
{
|
|
foreach (var relocation in relocations)
|
|
{
|
|
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
|
{
|
|
Log.Debug(
|
|
$"[FOCUS][SCAN] off=0x{relocation.Offset:X16} type={relocation.Type} sym={relocation.SymbolIndex} addend=0x{relocation.Addend:X}");
|
|
}
|
|
|
|
if (!IsSupportedRelocationType(relocation.Type))
|
|
{
|
|
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
|
{
|
|
Log.Debug($"[FOCUS][SKIP] unsupported type={relocation.Type}");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!TryResolveMappedAddress(virtualMemory, relocation.Offset, imageBase, sizeof(ulong), out var targetAddress))
|
|
{
|
|
if (IsFocusRelocationOffset(relocation.Offset, imageBase))
|
|
{
|
|
Log.Debug("[FOCUS][SKIP] target address not mapped");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (relocation.Type == RelocationTypeRelative)
|
|
{
|
|
descriptors.Add(new RelocationDescriptor(
|
|
targetAddress,
|
|
relocation.Addend,
|
|
null,
|
|
imageBase,
|
|
RelocationValueKind.Pointer,
|
|
IsDataImport: false));
|
|
continue;
|
|
}
|
|
|
|
if (relocation.Type == RelocationTypeTlsModuleId)
|
|
{
|
|
var dtpmodValue = tlsModuleId == 0 ? 1u : tlsModuleId;
|
|
descriptors.Add(new RelocationDescriptor(
|
|
targetAddress,
|
|
0,
|
|
null,
|
|
dtpmodValue,
|
|
RelocationValueKind.TlsModuleId,
|
|
IsDataImport: false));
|
|
continue;
|
|
}
|
|
|
|
var symbolIndex = relocation.SymbolIndex;
|
|
ElfSymbol symbol;
|
|
if (symbolIndex == 0)
|
|
{
|
|
symbol = default;
|
|
}
|
|
else if (!TryReadSymbol(symbolTable, symbolIndex, out symbol))
|
|
{
|
|
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
|
{
|
|
Log.Debug($"[FOCUS][SKIP] symbol read failed index={symbolIndex}");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
var addend = relocation.Type is RelocationTypeGlobalData or RelocationTypeJumpSlot ? 0 : relocation.Addend;
|
|
var symbolBind = GetSymbolBind(symbol.Info);
|
|
if (symbolBind == SymbolBindLocal)
|
|
{
|
|
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
|
|
if (symbolAddress == 0)
|
|
{
|
|
Log.Warning(
|
|
$"Skipping local relocation with invalid symbol value 0x{symbol.Value:X} " +
|
|
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
|
|
}
|
|
|
|
descriptors.Add(new RelocationDescriptor(
|
|
targetAddress,
|
|
addend,
|
|
null,
|
|
symbolAddress,
|
|
RelocationValueKind.Pointer,
|
|
IsDataImport: false));
|
|
continue;
|
|
}
|
|
|
|
if (symbol.Value != 0)
|
|
{
|
|
var symbolAddress = ResolveMappedAddressOrFallback(virtualMemory, symbol.Value, imageBase);
|
|
if (symbolAddress == 0)
|
|
{
|
|
Log.Warning(
|
|
$"Skipping relocation with invalid symbol value 0x{symbol.Value:X} " +
|
|
$"at target 0x{targetAddress:X16}, type={relocation.Type}, sym={symbolIndex}");
|
|
}
|
|
|
|
descriptors.Add(new RelocationDescriptor(
|
|
targetAddress,
|
|
addend,
|
|
null,
|
|
symbolAddress,
|
|
RelocationValueKind.Pointer,
|
|
IsDataImport: false));
|
|
continue;
|
|
}
|
|
|
|
if (symbolBind is not (SymbolBindGlobal or SymbolBindWeak))
|
|
{
|
|
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
|
{
|
|
Log.Debug($"[FOCUS][SKIP] bind={symbolBind} not importable");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
|
|
{
|
|
if (targetAddress >= FocusRelocGuestStart && targetAddress <= FocusRelocGuestEnd)
|
|
{
|
|
Log.Debug($"[FOCUS][SKIP] symbol name read failed offset={symbol.NameOffset}");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
var nid = ExtractNid(symbolName);
|
|
if (string.IsNullOrWhiteSpace(nid))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (seenImportNids.Add(nid))
|
|
{
|
|
orderedImportNids.Add(nid);
|
|
}
|
|
|
|
descriptors.Add(new RelocationDescriptor(
|
|
targetAddress,
|
|
addend,
|
|
nid,
|
|
0,
|
|
RelocationValueKind.Pointer,
|
|
IsDataImport: GetSymbolType(symbol.Info) == SymbolTypeObject));
|
|
}
|
|
}
|
|
|
|
private static void RegisterRuntimeSymbolsAndHooks(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
IReadOnlyList<ProgramHeader> programHeaders,
|
|
ElfHeader elfHeader,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase,
|
|
IDictionary<ulong, string> importStubs,
|
|
IDictionary<string, ulong> runtimeSymbols)
|
|
{
|
|
var sectionSymbols = RegisterSectionRuntimeSymbols(imageData, loadContext, elfHeader, imageBase, importStubs, runtimeSymbols);
|
|
var dynamicSymbols = RegisterDynamicRuntimeSymbols(
|
|
imageData,
|
|
loadContext,
|
|
programHeaders,
|
|
virtualMemory,
|
|
imageBase,
|
|
importStubs,
|
|
runtimeSymbols);
|
|
|
|
if (sectionSymbols > 0 || dynamicSymbols > 0)
|
|
{
|
|
Log.Info(
|
|
$"Runtime symbol index populated: section={sectionSymbols}, dynamic={dynamicSymbols}, total={runtimeSymbols.Count}");
|
|
}
|
|
}
|
|
|
|
private static void CollectInitializerFunctions(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
IReadOnlyList<ProgramHeader> programHeaders,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase,
|
|
out ulong initFunctionEntryPoint,
|
|
out IReadOnlyList<ulong> preInitializerFunctions,
|
|
out IReadOnlyList<ulong> initializerFunctions)
|
|
{
|
|
initFunctionEntryPoint = 0;
|
|
preInitializerFunctions = EmptyInitializerFunctions;
|
|
initializerFunctions = EmptyInitializerFunctions;
|
|
|
|
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex) ||
|
|
dynamicHeader.FileSize == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!TryLoadDynamicTableBytes(
|
|
imageData,
|
|
loadContext,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicHeader,
|
|
dynamicHeaderIndex,
|
|
out var dynamicTable))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var dynamicInfo = ParseDynamicInfo(dynamicTable);
|
|
var preInitializers = new List<ulong>(4);
|
|
var initializers = new List<ulong>(8);
|
|
initFunctionEntryPoint = ResolveMappedAddressOrFallback(virtualMemory, dynamicInfo.InitOffset, imageBase);
|
|
if (initFunctionEntryPoint < 0x10000)
|
|
{
|
|
initFunctionEntryPoint = 0;
|
|
}
|
|
|
|
AppendInitializerArrayEntries(
|
|
preInitializers,
|
|
imageData,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicInfo.PreInitArrayOffset,
|
|
dynamicInfo.PreInitArraySize);
|
|
|
|
AppendResolvedInitializer(initializers, dynamicInfo.InitOffset, virtualMemory, imageBase);
|
|
AppendInitializerArrayEntries(
|
|
initializers,
|
|
imageData,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicInfo.InitArrayOffset,
|
|
dynamicInfo.InitArraySize);
|
|
|
|
if (preInitializers.Count != 0)
|
|
{
|
|
preInitializerFunctions = preInitializers;
|
|
}
|
|
|
|
if (initializers.Count != 0)
|
|
{
|
|
initializerFunctions = initializers;
|
|
}
|
|
|
|
if (preInitializers.Count != 0 || initializers.Count != 0)
|
|
{
|
|
Log.Info(
|
|
$"Initializers discovered: preinit={preInitializers.Count}, init={initializers.Count}");
|
|
}
|
|
}
|
|
|
|
private static void AppendInitializerArrayEntries(
|
|
ICollection<ulong> destination,
|
|
ReadOnlySpan<byte> imageData,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase,
|
|
ulong arrayOffset,
|
|
ulong arraySize)
|
|
{
|
|
if (arrayOffset == 0 || arraySize < sizeof(ulong))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!TryLoadTableBytes(imageData, virtualMemory, imageBase, arrayOffset, arraySize, out var arrayBytes))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var entryCount = arrayBytes.Length / sizeof(ulong);
|
|
for (var i = 0; i < entryCount; i++)
|
|
{
|
|
var entryOffset = i * sizeof(ulong);
|
|
var entryAddress = BinaryPrimitives.ReadUInt64LittleEndian(arrayBytes.AsSpan(entryOffset, sizeof(ulong)));
|
|
AppendResolvedInitializer(destination, entryAddress, virtualMemory, imageBase);
|
|
}
|
|
}
|
|
|
|
private static void AppendResolvedInitializer(
|
|
ICollection<ulong> destination,
|
|
ulong functionAddress,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase)
|
|
{
|
|
var resolvedAddress = ResolveMappedAddressOrFallback(virtualMemory, functionAddress, imageBase);
|
|
if (resolvedAddress < 0x10000)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (var existing in destination)
|
|
{
|
|
if (existing == resolvedAddress)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
destination.Add(resolvedAddress);
|
|
}
|
|
|
|
private static IReadOnlyList<ImportedSymbolRelocation> BuildImportedRelocations(
|
|
IReadOnlyList<RelocationDescriptor> descriptors)
|
|
{
|
|
if (descriptors.Count == 0)
|
|
{
|
|
return Array.Empty<ImportedSymbolRelocation>();
|
|
}
|
|
|
|
var importedRelocations = new List<ImportedSymbolRelocation>(descriptors.Count);
|
|
foreach (var descriptor in descriptors)
|
|
{
|
|
if (descriptor.ImportNid is null || descriptor.ValueKind != RelocationValueKind.Pointer)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
importedRelocations.Add(new ImportedSymbolRelocation(
|
|
descriptor.TargetAddress,
|
|
descriptor.Addend,
|
|
descriptor.ImportNid,
|
|
descriptor.IsDataImport));
|
|
}
|
|
|
|
return importedRelocations.Count == 0
|
|
? Array.Empty<ImportedSymbolRelocation>()
|
|
: importedRelocations;
|
|
}
|
|
|
|
private static int RegisterSectionRuntimeSymbols(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
ElfHeader elfHeader,
|
|
ulong imageBase,
|
|
IDictionary<ulong, string> importStubs,
|
|
IDictionary<string, ulong> runtimeSymbols)
|
|
{
|
|
if (elfHeader.SectionHeaderOffset == 0 ||
|
|
elfHeader.SectionHeaderCount == 0 ||
|
|
elfHeader.SectionHeaderEntrySize < ElfSectionHeaderSize)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var added = 0;
|
|
for (var sectionIndex = 0; sectionIndex < elfHeader.SectionHeaderCount; sectionIndex++)
|
|
{
|
|
if (!TryReadSectionHeader(imageData, loadContext, elfHeader, sectionIndex, out var sectionHeader))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (sectionHeader.Type != SectionTypeSymbolTable ||
|
|
sectionHeader.Size == 0 ||
|
|
sectionHeader.EntrySize < ElfSymbolSize ||
|
|
sectionHeader.Link >= elfHeader.SectionHeaderCount)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!TryReadSectionHeader(imageData, loadContext, elfHeader, (int)sectionHeader.Link, out var stringHeader))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!TryReadElfRelativeSlice(imageData, loadContext, stringHeader.Offset, stringHeader.Size, out var stringTable))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var symbolCount = sectionHeader.Size / sectionHeader.EntrySize;
|
|
for (ulong symbolIndex = 0; symbolIndex < symbolCount; symbolIndex++)
|
|
{
|
|
var symbolOffset = sectionHeader.Offset + (symbolIndex * sectionHeader.EntrySize);
|
|
if (!TryReadElfRelativeSlice(imageData, loadContext, symbolOffset, ElfSymbolSize, out var symbolBytes))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var symbol = ReadUnmanaged<ElfSymbol>(symbolBytes, 0);
|
|
if (symbol.Value == 0 || symbol.NameOffset == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var symbolAddress = symbol.Value >= imageBase
|
|
? symbol.Value
|
|
: unchecked(imageBase + symbol.Value);
|
|
if (symbolAddress < 0x10000)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (RegisterRuntimeSymbol(runtimeSymbols, importStubs, symbolName, symbolAddress))
|
|
{
|
|
added++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return added;
|
|
}
|
|
|
|
private static int RegisterDynamicRuntimeSymbols(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
IReadOnlyList<ProgramHeader> programHeaders,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase,
|
|
IDictionary<ulong, string> importStubs,
|
|
IDictionary<string, ulong> runtimeSymbols)
|
|
{
|
|
if (!TryGetProgramHeader(programHeaders, ProgramHeaderType.Dynamic, out var dynamicHeader, out var dynamicHeaderIndex))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (dynamicHeader.FileSize == 0 || dynamicHeader.FileSize > int.MaxValue)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!TryLoadDynamicTableBytes(
|
|
imageData,
|
|
loadContext,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicHeader,
|
|
dynamicHeaderIndex,
|
|
out var dynamicTable))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var dynamicInfo = ParseDynamicInfo(dynamicTable);
|
|
if (dynamicInfo.SymTabOffset == 0 || dynamicInfo.StrTabOffset == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!TryLoadTableBytes(
|
|
imageData,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicInfo.StrTabOffset,
|
|
dynamicInfo.StrTabSize,
|
|
out var stringTable) ||
|
|
stringTable.Length == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ulong symTableSize = dynamicInfo.SymTabSize;
|
|
if (symTableSize == 0 || symTableSize > int.MaxValue)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!TryLoadTableBytes(
|
|
imageData,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicInfo.SymTabOffset,
|
|
symTableSize,
|
|
out var symbolTable) ||
|
|
symbolTable.Length < ElfSymbolSize)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var symbolCount = (uint)(symbolTable.Length / ElfSymbolSize);
|
|
var added = 0;
|
|
for (uint symbolIndex = 0; symbolIndex < symbolCount; symbolIndex++)
|
|
{
|
|
if (!TryReadSymbol(symbolTable, symbolIndex, out var symbol) ||
|
|
symbol.Value == 0 ||
|
|
symbol.NameOffset == 0 ||
|
|
!TryReadNullTerminatedAscii(stringTable, symbol.NameOffset, out var symbolName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var symbolAddress = symbol.Value >= imageBase
|
|
? symbol.Value
|
|
: unchecked(imageBase + symbol.Value);
|
|
if (symbolAddress < 0x10000)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (RegisterRuntimeSymbol(runtimeSymbols, importStubs, symbolName, symbolAddress))
|
|
{
|
|
added++;
|
|
}
|
|
}
|
|
|
|
return added;
|
|
}
|
|
|
|
private static bool RegisterRuntimeSymbol(
|
|
IDictionary<string, ulong> runtimeSymbols,
|
|
IDictionary<ulong, string> importStubs,
|
|
string symbolName,
|
|
ulong symbolAddress)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(symbolName) || symbolAddress < 0x10000)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var addedAny = false;
|
|
if (!runtimeSymbols.ContainsKey(symbolName))
|
|
{
|
|
runtimeSymbols[symbolName] = symbolAddress;
|
|
addedAny = true;
|
|
}
|
|
|
|
var nid = ExtractNid(symbolName);
|
|
if (!string.IsNullOrWhiteSpace(nid) &&
|
|
!string.Equals(symbolName, nid, StringComparison.Ordinal) &&
|
|
!runtimeSymbols.ContainsKey(nid))
|
|
{
|
|
runtimeSymbols[nid] = symbolAddress;
|
|
addedAny = true;
|
|
}
|
|
|
|
if (symbolName.Length > 1 &&
|
|
symbolName[0] == '_' &&
|
|
!runtimeSymbols.ContainsKey(symbolName[1..]))
|
|
{
|
|
runtimeSymbols[symbolName[1..]] = symbolAddress;
|
|
addedAny = true;
|
|
}
|
|
|
|
if (string.Equals(symbolName, "kernel_dynlib_dlsym", StringComparison.Ordinal))
|
|
{
|
|
importStubs[symbolAddress] = RuntimeStubNids.KernelDynlibDlsym;
|
|
}
|
|
|
|
return addedAny;
|
|
}
|
|
|
|
private static bool TryReadSectionHeader(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
ElfHeader elfHeader,
|
|
int sectionIndex,
|
|
out ElfSectionHeader sectionHeader)
|
|
{
|
|
if ((uint)sectionIndex >= elfHeader.SectionHeaderCount)
|
|
{
|
|
sectionHeader = default;
|
|
return false;
|
|
}
|
|
|
|
var headerOffset = elfHeader.SectionHeaderOffset + ((ulong)sectionIndex * elfHeader.SectionHeaderEntrySize);
|
|
if (!TryReadElfRelativeSlice(imageData, loadContext, headerOffset, ElfSectionHeaderSize, out var sectionBytes))
|
|
{
|
|
sectionHeader = default;
|
|
return false;
|
|
}
|
|
|
|
sectionHeader = ReadUnmanaged<ElfSectionHeader>(sectionBytes, 0);
|
|
return true;
|
|
}
|
|
|
|
private static bool TryLoadDynamicTableBytes(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase,
|
|
ProgramHeader dynamicHeader,
|
|
int dynamicHeaderIndex,
|
|
out ReadOnlySpan<byte> dynamicTable)
|
|
{
|
|
if (TryLoadTableBytes(
|
|
imageData,
|
|
virtualMemory,
|
|
imageBase,
|
|
dynamicHeader.VirtualAddress,
|
|
dynamicHeader.FileSize,
|
|
out var loadedDynamicTable))
|
|
{
|
|
dynamicTable = loadedDynamicTable;
|
|
return true;
|
|
}
|
|
|
|
var dynamicOffset = ResolvePhysicalSegmentOffset(imageData.Length, loadContext, dynamicHeader, dynamicHeaderIndex);
|
|
if (!TrySlice(imageData, dynamicOffset, dynamicHeader.FileSize, out dynamicTable))
|
|
{
|
|
dynamicTable = default;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool TryReadElfRelativeSlice(
|
|
ReadOnlySpan<byte> imageData,
|
|
LoadContext loadContext,
|
|
ulong elfRelativeOffset,
|
|
ulong size,
|
|
out ReadOnlySpan<byte> slice)
|
|
{
|
|
try
|
|
{
|
|
var absoluteOffset = checked((ulong)loadContext.ElfOffset + elfRelativeOffset);
|
|
return TrySlice(imageData, absoluteOffset, size, out slice);
|
|
}
|
|
catch (OverflowException)
|
|
{
|
|
slice = default;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static Dictionary<ulong, string> CreateImportStubMapping(IVirtualMemory virtualMemory, IReadOnlyList<string> orderedImportNids)
|
|
{
|
|
if (orderedImportNids.Count == 0)
|
|
{
|
|
return new Dictionary<ulong, string>();
|
|
}
|
|
|
|
var requiredBytes = checked((ulong)orderedImportNids.Count * ImportStubSlotSize);
|
|
var mapSize = AlignUp(Math.Max(requiredBytes, (ulong)PageSize), PageSize);
|
|
Log.Debug(
|
|
$"CreateImportStubMapping: nids={orderedImportNids.Count}, required=0x{requiredBytes:X}, map_size=0x{mapSize:X}");
|
|
if (mapSize > int.MaxValue)
|
|
{
|
|
throw new NotSupportedException("Import stub mapping exceeds 2 GB and is not supported.");
|
|
}
|
|
|
|
var stubData = new byte[(int)mapSize];
|
|
for (var i = 0; i < orderedImportNids.Count; i++)
|
|
{
|
|
var slotOffset = checked((int)((ulong)i * ImportStubSlotSize));
|
|
stubData[slotOffset] = StubTrapOpcode;
|
|
stubData[slotOffset + 1] = StubReturnOpcode;
|
|
var nid = orderedImportNids[i];
|
|
var nidHash = NidToUInt32(nid);
|
|
BinaryPrimitives.WriteUInt32LittleEndian(stubData.AsSpan(slotOffset + 8), nidHash);
|
|
}
|
|
|
|
var stubBaseAddress = TryMapImportStubRegion(virtualMemory, mapSize, stubData);
|
|
var byAddress = new Dictionary<ulong, string>(orderedImportNids.Count);
|
|
for (var i = 0; i < orderedImportNids.Count; i++)
|
|
{
|
|
var address = stubBaseAddress + ((ulong)i * ImportStubSlotSize);
|
|
byAddress[address] = orderedImportNids[i];
|
|
}
|
|
|
|
return byAddress;
|
|
}
|
|
|
|
private static ulong TryMapImportStubRegion(IVirtualMemory virtualMemory, ulong mapSize, ReadOnlySpan<byte> mapData)
|
|
{
|
|
for (var i = 0; i < 64; i++)
|
|
{
|
|
var candidateBase = ImportStubBaseAddress - ((ulong)i * ImportStubAddressStride);
|
|
if (IsAddressRangeMapped(virtualMemory, candidateBase, mapSize))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
virtualMemory.Map(
|
|
candidateBase,
|
|
mapSize,
|
|
fileOffset: 0,
|
|
mapData,
|
|
ProgramHeaderFlags.Read | ProgramHeaderFlags.Execute);
|
|
return candidateBase;
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException("Unable to reserve an import stub region in virtual memory.");
|
|
}
|
|
|
|
private static bool IsAddressRangeMapped(IVirtualMemory virtualMemory, ulong start, ulong size)
|
|
{
|
|
var end = checked(start + size);
|
|
var regions = virtualMemory.SnapshotRegions();
|
|
for (var i = 0; i < regions.Count; i++)
|
|
{
|
|
var region = regions[i];
|
|
var regionStart = region.VirtualAddress;
|
|
var regionEnd = checked(regionStart + region.MemorySize);
|
|
if (start < regionEnd && end > regionStart)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static void CollectRelocations(ReadOnlySpan<byte> relocationTable, ICollection<ElfRelocation> relocations)
|
|
{
|
|
for (var offset = 0; offset + ElfRelocationSize <= relocationTable.Length; offset += ElfRelocationSize)
|
|
{
|
|
relocations.Add(ReadRelocation(relocationTable, offset));
|
|
}
|
|
}
|
|
|
|
private static bool TryGetProgramHeader(
|
|
IReadOnlyList<ProgramHeader> programHeaders,
|
|
ProgramHeaderType targetType,
|
|
out ProgramHeader header,
|
|
out int headerIndex)
|
|
{
|
|
for (var i = 0; i < programHeaders.Count; i++)
|
|
{
|
|
if (programHeaders[i].HeaderType != targetType)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
header = programHeaders[i];
|
|
headerIndex = i;
|
|
return true;
|
|
}
|
|
|
|
header = default;
|
|
headerIndex = -1;
|
|
return false;
|
|
}
|
|
|
|
private static DynamicInfo ParseDynamicInfo(ReadOnlySpan<byte> dynamicTable)
|
|
{
|
|
ulong strTabOffset = 0;
|
|
ulong strTabSize = 0;
|
|
ulong symTabOffset = 0;
|
|
ulong symTabSize = 0;
|
|
ulong initOffset = 0;
|
|
ulong relaOffset = 0;
|
|
ulong relaSize = 0;
|
|
ulong jmpRelOffset = 0;
|
|
ulong jmpRelSize = 0;
|
|
ulong pltGotOffset = 0;
|
|
ulong initArrayOffset = 0;
|
|
ulong initArraySize = 0;
|
|
ulong preInitArrayOffset = 0;
|
|
ulong preInitArraySize = 0;
|
|
|
|
for (var offset = 0; offset + DynamicEntrySize <= dynamicTable.Length; offset += DynamicEntrySize)
|
|
{
|
|
var tag = BinaryPrimitives.ReadInt64LittleEndian(dynamicTable.Slice(offset, sizeof(long)));
|
|
var value = BinaryPrimitives.ReadUInt64LittleEndian(dynamicTable.Slice(offset + sizeof(long), sizeof(ulong)));
|
|
if (tag == DtNull)
|
|
{
|
|
break;
|
|
}
|
|
|
|
switch (tag)
|
|
{
|
|
case DtStrTab:
|
|
if (strTabOffset == 0)
|
|
{
|
|
strTabOffset = value;
|
|
}
|
|
|
|
break;
|
|
case DtStrSize:
|
|
if (strTabSize == 0)
|
|
{
|
|
strTabSize = value;
|
|
}
|
|
|
|
break;
|
|
case DtSymTab:
|
|
if (symTabOffset == 0)
|
|
{
|
|
symTabOffset = value;
|
|
}
|
|
|
|
break;
|
|
case DtRela:
|
|
if (relaOffset == 0)
|
|
{
|
|
relaOffset = value;
|
|
}
|
|
|
|
break;
|
|
case DtRelaSize:
|
|
if (relaSize == 0)
|
|
{
|
|
relaSize = value;
|
|
}
|
|
|
|
break;
|
|
case DtInit:
|
|
if (initOffset == 0)
|
|
{
|
|
initOffset = value;
|
|
}
|
|
|
|
break;
|
|
case DtJmpRel:
|
|
if (jmpRelOffset == 0)
|
|
{
|
|
jmpRelOffset = value;
|
|
}
|
|
|
|
break;
|
|
case DtInitArray:
|
|
if (initArrayOffset == 0)
|
|
{
|
|
initArrayOffset = value;
|
|
}
|
|
|
|
break;
|
|
case DtInitArraySize:
|
|
if (initArraySize == 0)
|
|
{
|
|
initArraySize = value;
|
|
}
|
|
|
|
break;
|
|
case DtPreInitArray:
|
|
if (preInitArrayOffset == 0)
|
|
{
|
|
preInitArrayOffset = value;
|
|
}
|
|
|
|
break;
|
|
case DtPreInitArraySize:
|
|
if (preInitArraySize == 0)
|
|
{
|
|
preInitArraySize = value;
|
|
}
|
|
|
|
break;
|
|
case DtPltRelSize:
|
|
if (jmpRelSize == 0)
|
|
{
|
|
jmpRelSize = value;
|
|
}
|
|
|
|
break;
|
|
case DtPltGot:
|
|
if (pltGotOffset == 0)
|
|
{
|
|
pltGotOffset = value;
|
|
}
|
|
|
|
break;
|
|
case DtSceStrTab:
|
|
strTabOffset = value;
|
|
break;
|
|
case DtSceStrSize:
|
|
strTabSize = value;
|
|
break;
|
|
case DtSceSymTab:
|
|
symTabOffset = value;
|
|
break;
|
|
case DtSceSymTabSize:
|
|
symTabSize = value;
|
|
break;
|
|
case DtSceRela:
|
|
relaOffset = value;
|
|
break;
|
|
case DtSceRelaSize:
|
|
relaSize = value;
|
|
break;
|
|
case DtSceJmpRel:
|
|
jmpRelOffset = value;
|
|
break;
|
|
case DtScePltRelSize:
|
|
jmpRelSize = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new DynamicInfo(
|
|
strTabOffset,
|
|
strTabSize,
|
|
symTabOffset,
|
|
symTabSize,
|
|
initOffset,
|
|
relaOffset,
|
|
relaSize,
|
|
jmpRelOffset,
|
|
jmpRelSize,
|
|
pltGotOffset,
|
|
initArrayOffset,
|
|
initArraySize,
|
|
preInitArrayOffset,
|
|
preInitArraySize);
|
|
}
|
|
|
|
private static bool IsSupportedRelocationType(uint relocationType)
|
|
{
|
|
return relocationType is
|
|
RelocationTypeAbsolute64 or
|
|
RelocationTypeGlobalData or
|
|
RelocationTypeJumpSlot or
|
|
RelocationTypeRelative or
|
|
RelocationTypeTlsModuleId;
|
|
}
|
|
|
|
private static ulong DetermineRequestedImageBase(
|
|
IVirtualMemory virtualMemory,
|
|
ulong totalImageSize,
|
|
bool isNextGen,
|
|
bool clearVirtualMemory)
|
|
{
|
|
if (clearVirtualMemory)
|
|
{
|
|
return isNextGen ? Ps5MainImageBase : Ps4MainImageBase;
|
|
}
|
|
|
|
var (searchStart, searchEnd) = GetModuleSearchRange(isNextGen);
|
|
var alignedSize = AlignUp(Math.Max(totalImageSize, (ulong)PageSize), (ulong)PageSize);
|
|
var candidate = searchStart;
|
|
foreach (var region in virtualMemory.SnapshotRegions())
|
|
{
|
|
if (region.VirtualAddress >= searchEnd)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var regionEnd = region.VirtualAddress + region.MemorySize;
|
|
if (regionEnd <= searchStart)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var regionAlignedEnd = AlignUp(regionEnd + ModulePlacementStep, (ulong)PageSize);
|
|
if (regionAlignedEnd > candidate)
|
|
{
|
|
candidate = regionAlignedEnd;
|
|
}
|
|
}
|
|
|
|
if (candidate < searchStart)
|
|
{
|
|
candidate = searchStart;
|
|
}
|
|
|
|
if (candidate + alignedSize > searchEnd)
|
|
{
|
|
candidate = searchStart;
|
|
}
|
|
|
|
return AlignUp(candidate, (ulong)PageSize);
|
|
}
|
|
|
|
private static bool TryAllocateAdditionalImageAtExact(
|
|
PhysicalVirtualMemory physicalVm,
|
|
ulong preferredBase,
|
|
ulong totalImageSize,
|
|
bool isNextGen,
|
|
out ulong allocatedBase)
|
|
{
|
|
var (searchStart, searchEnd) = GetModuleSearchRange(isNextGen);
|
|
var alignedSize = AlignUp(Math.Max(totalImageSize, (ulong)PageSize), (ulong)PageSize);
|
|
if (preferredBase < searchStart || preferredBase + alignedSize > searchEnd)
|
|
{
|
|
preferredBase = searchStart;
|
|
}
|
|
|
|
for (var attempt = 0; attempt < 256; attempt++)
|
|
{
|
|
var candidate = AlignUp(preferredBase + ((ulong)attempt * ModulePlacementStep), (ulong)PageSize);
|
|
if (candidate + alignedSize > searchEnd)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (physicalVm.TryAllocateAtExact(candidate, alignedSize, executable: true, out allocatedBase))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
allocatedBase = 0;
|
|
return false;
|
|
}
|
|
|
|
private static (ulong Start, ulong End) GetModuleSearchRange(bool isNextGen)
|
|
{
|
|
return isNextGen
|
|
? (Ps5ModuleSearchStart, Ps5ModuleSearchEnd)
|
|
: (Ps4ModuleSearchStart, Ps4ModuleSearchEnd);
|
|
}
|
|
|
|
private static ulong CalculateTotalImageSize(IReadOnlyList<ProgramHeader> programHeaders)
|
|
{
|
|
ulong minAddr = ulong.MaxValue;
|
|
ulong maxAddr = 0;
|
|
|
|
foreach (var header in programHeaders)
|
|
{
|
|
if (header.HeaderType == ProgramHeaderType.Load && header.MemorySize > 0)
|
|
{
|
|
if (header.VirtualAddress < minAddr)
|
|
minAddr = header.VirtualAddress;
|
|
|
|
var endAddr = header.VirtualAddress + header.MemorySize;
|
|
if (endAddr > maxAddr)
|
|
maxAddr = endAddr;
|
|
}
|
|
}
|
|
|
|
if (minAddr == ulong.MaxValue)
|
|
return 0;
|
|
|
|
return maxAddr - minAddr;
|
|
}
|
|
|
|
private static ulong ComputeImageBase(IReadOnlyList<ProgramHeader> programHeaders)
|
|
{
|
|
var hasBase = false;
|
|
ulong imageBase = 0;
|
|
|
|
for (var i = 0; i < programHeaders.Count; i++)
|
|
{
|
|
var header = programHeaders[i];
|
|
if (header.HeaderType != ProgramHeaderType.Load || header.MemorySize == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!hasBase || header.VirtualAddress < imageBase)
|
|
{
|
|
imageBase = header.VirtualAddress;
|
|
hasBase = true;
|
|
}
|
|
}
|
|
|
|
return hasBase ? imageBase : 0;
|
|
}
|
|
|
|
private static bool TryLoadTableBytes(
|
|
ReadOnlySpan<byte> elfData,
|
|
IVirtualMemory virtualMemory,
|
|
ulong imageBase,
|
|
ulong location,
|
|
ulong size,
|
|
out byte[] tableBytes)
|
|
{
|
|
if (size == 0 || size > int.MaxValue)
|
|
{
|
|
Console.WriteLine($"[LOADER] TryLoadTableBytes: size=0 or too big (0x{size:X})");
|
|
tableBytes = Array.Empty<byte>();
|
|
return false;
|
|
}
|
|
|
|
Console.WriteLine($"[LOADER] TryLoadTableBytes: trying location=0x{location:X}, size=0x{size:X}, imageBase=0x{imageBase:X}");
|
|
|
|
tableBytes = GC.AllocateUninitializedArray<byte>((int)size);
|
|
|
|
var guestAddr = location + imageBase;
|
|
Log.Debug($"TryLoadTableBytes: trying guest address 0x{guestAddr:X}");
|
|
if (virtualMemory.TryRead(guestAddr, tableBytes))
|
|
{
|
|
Log.Debug($"TryLoadTableBytes: loaded from guest memory at 0x{guestAddr:X}");
|
|
return true;
|
|
}
|
|
|
|
if (virtualMemory.TryRead(location, tableBytes))
|
|
{
|
|
Log.Debug($"TryLoadTableBytes: loaded from absolute guest address 0x{location:X}");
|
|
return true;
|
|
}
|
|
|
|
if (!elfData.IsEmpty && location <= int.MaxValue && location + size <= (ulong)elfData.Length)
|
|
{
|
|
var slice = elfData.Slice((int)location, (int)size);
|
|
tableBytes = slice.ToArray();
|
|
Log.Debug($"TryLoadTableBytes: loaded from elfData as file offset at 0x{location:X}");
|
|
return true;
|
|
}
|
|
|
|
Log.Warning($"TryLoadTableBytes: FAILED for location 0x{location:X}");
|
|
tableBytes = Array.Empty<byte>();
|
|
return false;
|
|
}
|
|
|
|
private static bool TryReadSymbol(ReadOnlySpan<byte> symbolTable, uint symbolIndex, out ElfSymbol symbol)
|
|
{
|
|
symbol = default;
|
|
var offset64 = (long)symbolIndex * ElfSymbolSize;
|
|
if (offset64 < 0 || offset64 + ElfSymbolSize > symbolTable.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var offset = (int)offset64;
|
|
var entry = symbolTable.Slice(offset, ElfSymbolSize);
|
|
symbol = new ElfSymbol(
|
|
BinaryPrimitives.ReadUInt32LittleEndian(entry.Slice(0, sizeof(uint))),
|
|
entry[4],
|
|
entry[5],
|
|
BinaryPrimitives.ReadUInt16LittleEndian(entry.Slice(6, sizeof(ushort))),
|
|
BinaryPrimitives.ReadUInt64LittleEndian(entry.Slice(8, sizeof(ulong))),
|
|
BinaryPrimitives.ReadUInt64LittleEndian(entry.Slice(16, sizeof(ulong))));
|
|
return true;
|
|
}
|
|
|
|
private static byte GetSymbolBind(byte info)
|
|
{
|
|
return (byte)(info >> 4);
|
|
}
|
|
|
|
private static byte GetSymbolType(byte info)
|
|
{
|
|
return (byte)(info & 0x0F);
|
|
}
|
|
|
|
private static bool IsFocusRelocationOffset(ulong relocationOffset, ulong imageBase)
|
|
{
|
|
if (relocationOffset >= FocusRelocGuestStart && relocationOffset <= FocusRelocGuestEnd)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (relocationOffset <= ulong.MaxValue - imageBase)
|
|
{
|
|
var rebased = relocationOffset + imageBase;
|
|
if (rebased >= FocusRelocGuestStart && rebased <= FocusRelocGuestEnd)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static ulong ResolveMappedAddressOrFallback(IVirtualMemory virtualMemory, ulong address, ulong imageBase)
|
|
{
|
|
Log.Debug($"ResolveMappedAddressOrFallback addr=0x{address:X} imageBase=0x{imageBase:X16}");
|
|
|
|
if (address == 0)
|
|
{
|
|
Log.Debug("-> return 0 (null)");
|
|
return 0;
|
|
}
|
|
|
|
if (TryResolveMappedAddress(virtualMemory, address, imageBase, 1, out var resolved))
|
|
{
|
|
Log.Debug($"-> resolved raw 0x{resolved:X16}");
|
|
return resolved;
|
|
}
|
|
|
|
if (address <= ulong.MaxValue - imageBase)
|
|
{
|
|
var rebased = address + imageBase;
|
|
if (TryResolveMappedAddress(virtualMemory, rebased, imageBase, 1, out var resolvedRebased))
|
|
{
|
|
Log.Debug($"-> resolved rebased 0x{resolvedRebased:X16}");
|
|
return resolvedRebased;
|
|
}
|
|
}
|
|
|
|
if (address < 0x10000)
|
|
{
|
|
Log.Debug($"-> reject small 0x{address:X}");
|
|
return 0;
|
|
}
|
|
|
|
Log.Debug($"-> fallback raw 0x{address:X}");
|
|
return address;
|
|
}
|
|
|
|
private static bool TryResolveMappedAddress(
|
|
IVirtualMemory virtualMemory,
|
|
ulong address,
|
|
ulong imageBase,
|
|
int requiredBytes,
|
|
out ulong resolvedAddress)
|
|
{
|
|
if (CanAccessAddress(virtualMemory, address, requiredBytes))
|
|
{
|
|
resolvedAddress = address;
|
|
return true;
|
|
}
|
|
|
|
if (address <= ulong.MaxValue - imageBase)
|
|
{
|
|
var rebased = address + imageBase;
|
|
if (CanAccessAddress(virtualMemory, rebased, requiredBytes))
|
|
{
|
|
resolvedAddress = rebased;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
resolvedAddress = 0;
|
|
return false;
|
|
}
|
|
|
|
private static bool CanAccessAddress(IVirtualMemory virtualMemory, ulong address, int requiredBytes)
|
|
{
|
|
if (requiredBytes <= 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Span<byte> probe = stackalloc byte[sizeof(ulong)];
|
|
var length = Math.Min(requiredBytes, probe.Length);
|
|
return virtualMemory.TryRead(address, probe[..length]);
|
|
}
|
|
|
|
private static ElfRelocation ReadRelocation(ReadOnlySpan<byte> relocationTable, int offset)
|
|
{
|
|
var entry = relocationTable.Slice(offset, ElfRelocationSize);
|
|
return new ElfRelocation(
|
|
BinaryPrimitives.ReadUInt64LittleEndian(entry.Slice(0, sizeof(ulong))),
|
|
BinaryPrimitives.ReadUInt64LittleEndian(entry.Slice(8, sizeof(ulong))),
|
|
BinaryPrimitives.ReadInt64LittleEndian(entry.Slice(16, sizeof(long))));
|
|
}
|
|
|
|
private static bool TryReadNullTerminatedAscii(ReadOnlySpan<byte> source, uint offset, out string value)
|
|
{
|
|
if (offset >= (uint)source.Length)
|
|
{
|
|
value = string.Empty;
|
|
return false;
|
|
}
|
|
|
|
var slice = source[(int)offset..];
|
|
var terminatorIndex = slice.IndexOf((byte)0);
|
|
if (terminatorIndex < 0)
|
|
{
|
|
value = string.Empty;
|
|
return false;
|
|
}
|
|
|
|
value = Encoding.ASCII.GetString(slice[..terminatorIndex]);
|
|
return true;
|
|
}
|
|
|
|
private static string ExtractNid(string symbolName)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(symbolName))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var separator = symbolName.IndexOf('#');
|
|
return separator <= 0 ? symbolName : symbolName[..separator];
|
|
}
|
|
|
|
private static bool TryWriteUInt64(IVirtualMemory virtualMemory, ulong address, ulong value)
|
|
{
|
|
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
|
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
|
return virtualMemory.TryWrite(address, buffer);
|
|
}
|
|
|
|
private static ulong AlignUp(ulong value, ulong alignment)
|
|
{
|
|
var mask = alignment - 1;
|
|
return (value + mask) & ~mask;
|
|
}
|
|
|
|
private static ulong AddSigned(ulong value, long addend)
|
|
{
|
|
if (addend >= 0)
|
|
{
|
|
return unchecked(value + (ulong)addend);
|
|
}
|
|
|
|
var magnitude = unchecked((ulong)(-(addend + 1))) + 1;
|
|
return unchecked(value - magnitude);
|
|
}
|
|
|
|
private static uint NidToUInt32(string nid)
|
|
{
|
|
if (string.IsNullOrEmpty(nid))
|
|
return 0;
|
|
|
|
uint hash = 0;
|
|
for (int i = 0; i < Math.Min(nid.Length, 8); i++)
|
|
{
|
|
hash = (hash << 4) | (byte)nid[i];
|
|
}
|
|
|
|
hash ^= (uint)nid.Length;
|
|
|
|
return hash;
|
|
}
|
|
|
|
private static bool TrySlice(ReadOnlySpan<byte> source, ulong offset, ulong size, out ReadOnlySpan<byte> slice)
|
|
{
|
|
if (size == 0)
|
|
{
|
|
slice = ReadOnlySpan<byte>.Empty;
|
|
return true;
|
|
}
|
|
|
|
if (size > int.MaxValue || offset > (ulong)source.Length)
|
|
{
|
|
slice = default;
|
|
return false;
|
|
}
|
|
|
|
var end = offset + size;
|
|
if (end < offset || end > (ulong)source.Length)
|
|
{
|
|
slice = default;
|
|
return false;
|
|
}
|
|
|
|
slice = source.Slice((int)offset, (int)size);
|
|
return true;
|
|
}
|
|
|
|
private static ulong ResolvePhysicalSegmentOffset(int imageLength, LoadContext loadContext, ProgramHeader header, int headerIndex)
|
|
{
|
|
if (!loadContext.IsSelf)
|
|
{
|
|
return checked((ulong)loadContext.ElfOffset + header.Offset);
|
|
}
|
|
|
|
if (!TryResolveSelfSegmentOffset(
|
|
imageLength,
|
|
loadContext.SelfSegments,
|
|
header,
|
|
headerIndex,
|
|
out var offset,
|
|
out var resolveStatus))
|
|
{
|
|
if (TryResolveSelfFallbackOffset(imageLength, loadContext, header, out var fallbackOffset))
|
|
{
|
|
return fallbackOffset;
|
|
}
|
|
|
|
if (resolveStatus is SelfSegmentResolveStatus.Encrypted or SelfSegmentResolveStatus.Compressed)
|
|
{
|
|
throw new NotSupportedException(
|
|
$"SELF segment for program header {headerIndex} is marked as {resolveStatus.ToString().ToLowerInvariant()} and no dumped payload could be resolved. " +
|
|
"Runtime decryption is not implemented yet. Use a decrypted ELF/FSELF image.");
|
|
}
|
|
|
|
throw new NotSupportedException($"SELF segment mapping for program header {headerIndex} could not be resolved.");
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
private static bool TryResolveSelfFallbackOffset(
|
|
int imageLength,
|
|
LoadContext loadContext,
|
|
ProgramHeader header,
|
|
out ulong offset)
|
|
{
|
|
if (TryIsInRange(imageLength, header.Offset, header.FileSize))
|
|
{
|
|
offset = header.Offset;
|
|
return true;
|
|
}
|
|
|
|
var elfRelative = checked((ulong)loadContext.ElfOffset + header.Offset);
|
|
if (TryIsInRange(imageLength, elfRelative, header.FileSize))
|
|
{
|
|
offset = elfRelative;
|
|
return true;
|
|
}
|
|
|
|
if (loadContext.SelfFileSize <= (ulong)imageLength)
|
|
{
|
|
var tailSize = (ulong)imageLength - loadContext.SelfFileSize;
|
|
if (tailSize == header.FileSize && TryIsInRange(imageLength, loadContext.SelfFileSize, header.FileSize))
|
|
{
|
|
offset = loadContext.SelfFileSize;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
offset = 0;
|
|
return false;
|
|
}
|
|
|
|
private static bool TryIsInRange(int imageLength, ulong offset, ulong size)
|
|
{
|
|
if (offset > (ulong)imageLength)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var end = offset + size;
|
|
return end >= offset && end <= (ulong)imageLength;
|
|
}
|
|
|
|
private static bool TryResolveSelfSegmentOffset(
|
|
int imageLength,
|
|
IReadOnlyList<SelfSegment> selfSegments,
|
|
ProgramHeader header,
|
|
int headerIndex,
|
|
out ulong offset,
|
|
out SelfSegmentResolveStatus status)
|
|
{
|
|
foreach (var segment in selfSegments)
|
|
{
|
|
if (!segment.IsBlocked)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var phdrId = segment.ProgramHeaderId;
|
|
if (phdrId != (ulong)headerIndex)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (segment.IsEncrypted || segment.IsCompressed)
|
|
{
|
|
if (TryIsInRange(imageLength, segment.Offset, header.FileSize))
|
|
{
|
|
offset = segment.Offset;
|
|
status = SelfSegmentResolveStatus.ResolvedDumped;
|
|
return true;
|
|
}
|
|
|
|
offset = 0;
|
|
status = segment.IsEncrypted
|
|
? SelfSegmentResolveStatus.Encrypted
|
|
: SelfSegmentResolveStatus.Compressed;
|
|
return false;
|
|
}
|
|
|
|
if (!TryIsInRange(imageLength, segment.Offset, header.FileSize))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
offset = segment.Offset;
|
|
status = SelfSegmentResolveStatus.Resolved;
|
|
return true;
|
|
}
|
|
|
|
offset = 0;
|
|
status = SelfSegmentResolveStatus.NotFound;
|
|
return false;
|
|
}
|
|
|
|
private static void ValidateElfHeader(ElfHeader header)
|
|
{
|
|
if (!header.HasElfMagic)
|
|
{
|
|
throw new InvalidDataException("Input does not contain a valid ELF header.");
|
|
}
|
|
|
|
if (!header.Is64Bit)
|
|
{
|
|
throw new InvalidDataException("Only ELF64 images are currently supported.");
|
|
}
|
|
|
|
if (!header.IsLittleEndian)
|
|
{
|
|
throw new InvalidDataException("Only little-endian ELF images are currently supported.");
|
|
}
|
|
|
|
if (header.ProgramHeaderEntrySize != ProgramHeaderSize)
|
|
{
|
|
throw new InvalidDataException($"Unsupported ELF program header entry size: {header.ProgramHeaderEntrySize}.");
|
|
}
|
|
}
|
|
|
|
private static unsafe T ReadUnmanaged<T>(ReadOnlySpan<byte> source, int offset)
|
|
where T : unmanaged
|
|
{
|
|
var size = Unsafe.SizeOf<T>();
|
|
EnsureRange(source.Length, checked((ulong)offset), (ulong)size);
|
|
|
|
fixed (byte* ptr = &MemoryMarshal.GetReference(source))
|
|
{
|
|
return Unsafe.ReadUnaligned<T>(ptr + offset);
|
|
}
|
|
}
|
|
|
|
private static void EnsureRange(int length, ulong offset, ulong size)
|
|
{
|
|
if (offset > (ulong)length)
|
|
{
|
|
throw new InvalidDataException("Segment offset exceeds image length.");
|
|
}
|
|
|
|
var end = offset + size;
|
|
if (end < offset || end > (ulong)length)
|
|
{
|
|
throw new InvalidDataException("Segment extends beyond image bounds.");
|
|
}
|
|
}
|
|
|
|
private readonly record struct LoadContext(bool IsSelf, int ElfOffset, ulong SelfFileSize, IReadOnlyList<SelfSegment> SelfSegments);
|
|
|
|
private readonly record struct DynamicInfo(
|
|
ulong StrTabOffset,
|
|
ulong StrTabSize,
|
|
ulong SymTabOffset,
|
|
ulong SymTabSize,
|
|
ulong InitOffset,
|
|
ulong RelaOffset,
|
|
ulong RelaSize,
|
|
ulong JmpRelOffset,
|
|
ulong JmpRelSize,
|
|
ulong PltGotOffset,
|
|
ulong InitArrayOffset,
|
|
ulong InitArraySize,
|
|
ulong PreInitArrayOffset,
|
|
ulong PreInitArraySize)
|
|
{
|
|
public bool HasImportMetadata =>
|
|
StrTabOffset != 0 &&
|
|
StrTabSize != 0 &&
|
|
SymTabOffset != 0 &&
|
|
(RelaSize != 0 || JmpRelSize != 0);
|
|
}
|
|
|
|
private readonly record struct ElfSectionHeader(
|
|
uint NameOffset,
|
|
uint Type,
|
|
ulong Flags,
|
|
ulong Address,
|
|
ulong Offset,
|
|
ulong Size,
|
|
uint Link,
|
|
uint Info,
|
|
ulong AddressAlign,
|
|
ulong EntrySize);
|
|
|
|
private readonly record struct ElfSymbol(
|
|
uint NameOffset,
|
|
byte Info,
|
|
byte Other,
|
|
ushort SectionIndex,
|
|
ulong Value,
|
|
ulong Size);
|
|
|
|
private readonly record struct ElfRelocation(ulong Offset, ulong Info, long Addend)
|
|
{
|
|
public uint SymbolIndex => (uint)(Info >> 32);
|
|
|
|
public uint Type => (uint)(Info & uint.MaxValue);
|
|
}
|
|
|
|
private enum RelocationValueKind : byte
|
|
{
|
|
Pointer = 0,
|
|
TlsModuleId = 1
|
|
}
|
|
|
|
private readonly record struct RelocationDescriptor(
|
|
ulong TargetAddress,
|
|
long Addend,
|
|
string? ImportNid,
|
|
ulong SymbolValue,
|
|
RelocationValueKind ValueKind,
|
|
bool IsDataImport);
|
|
|
|
private enum SelfSegmentResolveStatus
|
|
{
|
|
NotFound = 0,
|
|
Resolved = 1,
|
|
ResolvedDumped = 2,
|
|
Encrypted = 3,
|
|
Compressed = 4,
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
private readonly struct SelfHeader
|
|
{
|
|
private readonly byte _ident0;
|
|
private readonly byte _ident1;
|
|
private readonly byte _ident2;
|
|
private readonly byte _ident3;
|
|
private readonly byte _ident4;
|
|
private readonly byte _ident5;
|
|
private readonly byte _ident6;
|
|
private readonly byte _ident7;
|
|
private readonly byte _ident8;
|
|
private readonly byte _ident9;
|
|
private readonly byte _ident10;
|
|
private readonly byte _ident11;
|
|
private readonly ushort _size1;
|
|
private readonly ushort _size2;
|
|
private readonly ulong _fileSize;
|
|
private readonly ushort _segmentCount;
|
|
private readonly ushort _unknown;
|
|
private readonly uint _padding;
|
|
|
|
public ushort SegmentCount => _segmentCount;
|
|
|
|
public ushort Unknown => _unknown;
|
|
|
|
public ulong FileSize => _fileSize;
|
|
|
|
public bool HasKnownLayout =>
|
|
((_ident0 == 0x4F &&
|
|
_ident1 == 0x15 &&
|
|
_ident2 == 0x3D &&
|
|
_ident3 == 0x1D) ||
|
|
(_ident0 == 0x54 &&
|
|
_ident1 == 0x14 &&
|
|
_ident2 == 0xF5 &&
|
|
_ident3 == 0xEE)) &&
|
|
_ident4 == 0x00 &&
|
|
_ident5 == 0x01 &&
|
|
_ident6 == 0x01 &&
|
|
_ident7 == 0x12 &&
|
|
_ident8 == 0x01 &&
|
|
_ident9 == 0x01 &&
|
|
_ident10 == 0x00 &&
|
|
_ident11 == 0x00;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
private readonly struct SelfSegment
|
|
{
|
|
private readonly ulong _type;
|
|
private readonly ulong _offset;
|
|
private readonly ulong _compressedSize;
|
|
private readonly ulong _decompressedSize;
|
|
|
|
public ulong Type => _type;
|
|
|
|
public bool IsBlocked => (Type & SelfSegmentFlag) != 0;
|
|
|
|
public ulong ProgramHeaderId => (Type >> 20) & 0xFFF;
|
|
|
|
public bool IsEncrypted => (Type & 0x2) != 0;
|
|
|
|
public bool IsCompressed => (Type & 0x8) != 0;
|
|
|
|
public ulong Offset => _offset;
|
|
|
|
public ulong CompressedSize => _compressedSize;
|
|
|
|
public ulong DecompressedSize => _decompressedSize;
|
|
}
|
|
}
|