mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-24 21:50:43 -04:00
261 lines
8.2 KiB
C++
261 lines
8.2 KiB
C++
#include "prepatch.hpp"
|
|
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "aurora/lib/logging.hpp"
|
|
|
|
#if DUSK_HAS_PREPATCH
|
|
#include <mach-o/dyld.h>
|
|
#include <mach-o/loader.h>
|
|
#endif
|
|
|
|
namespace dusk::mods::prepatch {
|
|
namespace {
|
|
|
|
aurora::Module Log("dusk::mods::prepatch");
|
|
|
|
constexpr std::string_view kSiteMagic = "PS01";
|
|
constexpr size_t kSiteHeaderSize = 12;
|
|
constexpr size_t kGatewaySize = 28;
|
|
constexpr size_t kOriginalStubOffset = 20;
|
|
|
|
constexpr uint32_t kBranchMask = 0xfc000000u;
|
|
constexpr uint32_t kBranch = 0x14000000u;
|
|
constexpr uint32_t kLdarX16X16 = 0xc8dffe10u;
|
|
constexpr uint32_t kCbzX16Stub = 0xb4000050u;
|
|
constexpr uint32_t kBrX16 = 0xd61f0200u;
|
|
constexpr uint32_t kBtiC = 0xd503245fu;
|
|
|
|
struct SiteHeader {
|
|
char magic[4];
|
|
int32_t targetDelta;
|
|
int32_t slotDelta;
|
|
};
|
|
static_assert(sizeof(SiteHeader) == kSiteHeaderSize);
|
|
|
|
struct Range {
|
|
uintptr_t begin = 0;
|
|
uintptr_t end = 0;
|
|
|
|
bool contains(uintptr_t address, size_t size) const {
|
|
return address >= begin && address <= end && size <= end - address;
|
|
}
|
|
};
|
|
|
|
struct State {
|
|
std::vector<Range> executableRanges;
|
|
std::vector<Range> writableRanges;
|
|
std::string failureReason = "not initialized";
|
|
bool loaded = false;
|
|
bool initialized = false;
|
|
};
|
|
State s_state;
|
|
|
|
void fail(std::string reason) {
|
|
s_state.failureReason = std::move(reason);
|
|
Log.error("prepatch backend unavailable: {}", s_state.failureReason);
|
|
}
|
|
|
|
void unavailable(std::string reason) {
|
|
s_state.failureReason = std::move(reason);
|
|
Log.info("prepatch backend unavailable: {}", s_state.failureReason);
|
|
}
|
|
|
|
bool add_delta(uintptr_t address, int64_t delta, uintptr_t& out) {
|
|
if (delta >= 0) {
|
|
const auto offset = static_cast<uintptr_t>(delta);
|
|
if (address > std::numeric_limits<uintptr_t>::max() - offset) {
|
|
return false;
|
|
}
|
|
out = address + offset;
|
|
} else {
|
|
const auto offset = static_cast<uintptr_t>(-(delta + 1)) + 1;
|
|
if (address < offset) {
|
|
return false;
|
|
}
|
|
out = address - offset;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool branch_target(uintptr_t address, uint32_t instruction, uintptr_t& out) {
|
|
if ((instruction & kBranchMask) != kBranch) {
|
|
return false;
|
|
}
|
|
int64_t words = instruction & 0x03ffffffu;
|
|
if ((words & 0x02000000) != 0) {
|
|
words -= int64_t{1} << 26;
|
|
}
|
|
return add_delta(address, words * 4, out);
|
|
}
|
|
|
|
const Range* containing(const std::vector<Range>& ranges, uintptr_t address, size_t size) {
|
|
for (const auto& range : ranges) {
|
|
if (range.contains(address, size)) {
|
|
return ⦥
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#if DUSK_HAS_PREPATCH
|
|
bool slide_address(intptr_t slide, uint64_t vmaddr, uintptr_t& out) {
|
|
if (vmaddr > std::numeric_limits<uintptr_t>::max()) {
|
|
return false;
|
|
}
|
|
return add_delta(static_cast<uintptr_t>(vmaddr), slide, out);
|
|
}
|
|
#endif
|
|
|
|
} // namespace
|
|
|
|
void initialize() {
|
|
if (s_state.initialized) {
|
|
return;
|
|
}
|
|
s_state.initialized = true;
|
|
|
|
#if DUSK_HAS_PREPATCH
|
|
const auto* imageHeader = reinterpret_cast<const mach_header_64*>(_dyld_get_image_header(0));
|
|
if (imageHeader == nullptr || imageHeader->magic != MH_MAGIC_64 ||
|
|
imageHeader->cputype != CPU_TYPE_ARM64 ||
|
|
(imageHeader->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E ||
|
|
(imageHeader->cpusubtype & CPU_SUBTYPE_ARM64_PTR_AUTH_MASK) != 0)
|
|
{
|
|
fail("main image is not a supported 64-bit arm64 Mach-O image");
|
|
return;
|
|
}
|
|
|
|
const intptr_t slide = _dyld_get_image_vmaddr_slide(0);
|
|
const auto* commands = reinterpret_cast<const uint8_t*>(imageHeader + 1);
|
|
size_t commandOffset = 0;
|
|
for (uint32_t i = 0; i < imageHeader->ncmds; ++i) {
|
|
if (commandOffset > imageHeader->sizeofcmds ||
|
|
imageHeader->sizeofcmds - commandOffset < sizeof(load_command))
|
|
{
|
|
fail("main image has malformed load commands");
|
|
return;
|
|
}
|
|
const auto* command = reinterpret_cast<const load_command*>(commands + commandOffset);
|
|
if (command->cmdsize < sizeof(load_command) ||
|
|
command->cmdsize > imageHeader->sizeofcmds - commandOffset)
|
|
{
|
|
fail("main image has malformed load commands");
|
|
return;
|
|
}
|
|
if (command->cmd == LC_SEGMENT_64) {
|
|
if (command->cmdsize < sizeof(segment_command_64)) {
|
|
fail("main image has a truncated segment command");
|
|
return;
|
|
}
|
|
const auto* segment = reinterpret_cast<const segment_command_64*>(command);
|
|
const uint64_t vmEnd = segment->vmaddr + segment->vmsize;
|
|
if (vmEnd < segment->vmaddr) {
|
|
fail("main image segment range overflows");
|
|
return;
|
|
}
|
|
uintptr_t begin = 0;
|
|
uintptr_t end = 0;
|
|
if (!slide_address(slide, segment->vmaddr, begin) ||
|
|
!slide_address(slide, vmEnd, end) || end < begin)
|
|
{
|
|
fail("main image runtime segment range overflows");
|
|
return;
|
|
}
|
|
constexpr vm_prot_t kRx = VM_PROT_READ | VM_PROT_EXECUTE;
|
|
constexpr vm_prot_t kRw = VM_PROT_READ | VM_PROT_WRITE;
|
|
if (segment->initprot == kRx && segment->maxprot == kRx) {
|
|
s_state.executableRanges.push_back({begin, end});
|
|
} else if (segment->initprot == kRw && segment->maxprot == kRw) {
|
|
s_state.writableRanges.push_back({begin, end});
|
|
}
|
|
}
|
|
commandOffset += command->cmdsize;
|
|
}
|
|
if (commandOffset != imageHeader->sizeofcmds || s_state.executableRanges.empty() ||
|
|
s_state.writableRanges.empty())
|
|
{
|
|
fail("main image has no usable executable or writable segments");
|
|
return;
|
|
}
|
|
|
|
s_state.failureReason.clear();
|
|
s_state.loaded = true;
|
|
#else
|
|
unavailable("prepatch support is not available in this build");
|
|
#endif
|
|
}
|
|
|
|
bool available() {
|
|
return s_state.loaded;
|
|
}
|
|
|
|
const char* unavailable_reason() {
|
|
return s_state.failureReason.c_str();
|
|
}
|
|
|
|
std::optional<Site> lookup(void* runtimeTarget) {
|
|
if (!s_state.loaded || runtimeTarget == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
const auto target = reinterpret_cast<uintptr_t>(runtimeTarget);
|
|
if (containing(s_state.executableRanges, target, sizeof(uint32_t)) == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
uint32_t entry = 0;
|
|
std::memcpy(&entry, reinterpret_cast<const void*>(target), sizeof(entry));
|
|
uintptr_t gateway = 0;
|
|
if (!branch_target(target, entry, gateway) || gateway < kSiteHeaderSize) {
|
|
return std::nullopt;
|
|
}
|
|
const uintptr_t headerAddress = gateway - kSiteHeaderSize;
|
|
if (containing(s_state.executableRanges, headerAddress, kSiteHeaderSize + kGatewaySize) ==
|
|
nullptr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
SiteHeader header{};
|
|
std::memcpy(&header, reinterpret_cast<const void*>(headerAddress), sizeof(header));
|
|
if (std::memcmp(header.magic, kSiteMagic.data(), kSiteMagic.size()) != 0) {
|
|
return std::nullopt;
|
|
}
|
|
uintptr_t recordedTarget = 0;
|
|
uintptr_t slot = 0;
|
|
if (!add_delta(gateway, header.targetDelta, recordedTarget) || recordedTarget != target ||
|
|
!add_delta(gateway, header.slotDelta, slot) || (slot & (alignof(void*) - 1)) != 0 ||
|
|
containing(s_state.writableRanges, slot, sizeof(void*)) == nullptr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
uint32_t instructions[7]{};
|
|
std::memcpy(instructions, reinterpret_cast<const void*>(gateway), sizeof(instructions));
|
|
uintptr_t original = 0;
|
|
if (instructions[2] != kLdarX16X16 || instructions[3] != kCbzX16Stub ||
|
|
instructions[4] != kBrX16 || instructions[5] != kBtiC ||
|
|
target > std::numeric_limits<uintptr_t>::max() - 4 ||
|
|
!branch_target(gateway + 24, instructions[6], original) || original != target + 4)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
return Site{
|
|
reinterpret_cast<void**>(slot), reinterpret_cast<void*>(gateway + kOriginalStubOffset)};
|
|
}
|
|
|
|
void publish(const Site& site, void* trampoline) {
|
|
std::atomic_ref slot{*site.slot};
|
|
slot.store(trampoline, std::memory_order_release);
|
|
}
|
|
|
|
} // namespace dusk::mods::prepatch
|