Implement ldr:ro LoadModule

This commit is contained in:
Billy Laws 2022-10-30 16:13:00 +00:00
parent e571066409
commit 01e27bd2dd
4 changed files with 200 additions and 28 deletions

View File

@ -264,6 +264,12 @@ namespace skyline::util {
FillRandomBytes(std::span(reinterpret_cast<typename IntegerFor<T>::Type *>(&object), IntegerFor<T>::Count));
}
template<IsPointerOrUnsignedIntegral T>
T RandomNumber(T min, T max) {
std::uniform_int_distribution dist(PointerValue(min), PointerValue(max));
return ValuePointer<T>(dist(detail::generator));
}
/**
* @brief A temporary shim for C++ 20's bit_cast to make transitioning to it easier
*/
@ -353,4 +359,8 @@ namespace skyline::util {
std::array<T, Size> MergeInto(TSrcs &&... srcs) {
return MergeInto<T>(std::make_index_sequence<Size>(), std::forward<TSrcs>(srcs)...);
}
inline std::string HexDump(std::span<u8> data) {
return std::accumulate(data.begin(), data.end(), std::string{}, [](std::string str, u8 el) { return std::move(str) + fmt::format("{:02X}", el); });
}
}

View File

@ -6,40 +6,42 @@
#include "loader.h"
namespace skyline::loader {
struct NroSegmentHeader {
u32 offset;
u32 size;
};
struct NroHeader {
u32 _pad0_;
u32 modOffset; //!< The offset of the MOD metadata
u64 _pad1_;
u32 magic; //!< The NRO magic "NRO0"
u32 version; //!< The version of the application
u32 size; //!< The size of the NRO
u32 flags; //!< The flags used with the NRO
NroSegmentHeader text; //!< The .text segment header
NroSegmentHeader ro; //!< The .rodata segment header
NroSegmentHeader data; //!< The .data segment header
u32 bssSize; //!< The size of the bss segment
u32 _pad2_;
std::array<u64, 4> buildId; //!< The build ID of the NRO
u64 _pad3_;
NroSegmentHeader apiInfo; //!< The .apiInfo segment header
NroSegmentHeader dynstr; //!< The .dynstr segment header
NroSegmentHeader dynsym; //!< The .dynsym segment header
};
/**
* @brief The NroLoader class abstracts access to an NRO file through the Loader interface
* @url https://switchbrew.org/wiki/NRO
*/
class NroLoader : public Loader {
private:
struct NroSegmentHeader {
u32 offset;
u32 size;
};
struct NroHeader {
u32 _pad0_;
u32 modOffset; //!< The offset of the MOD metadata
u64 _pad1_;
u32 magic; //!< The NRO magic "NRO0"
u32 version; //!< The version of the application
u32 size; //!< The size of the NRO
u32 flags; //!< The flags used with the NRO
NroSegmentHeader text; //!< The .text segment header
NroSegmentHeader ro; //!< The .rodata segment header
NroSegmentHeader data; //!< The .data segment header
u32 bssSize; //!< The size of the bss segment
u32 _pad2_;
std::array<u64, 4> buildId; //!< The build ID of the NRO
u64 _pad3_;
NroSegmentHeader apiInfo; //!< The .apiInfo segment header
NroSegmentHeader dynstr; //!< The .dynstr segment header
NroSegmentHeader dynsym; //!< The .dynsym segment header
} header{};
NroHeader header{};
struct NroAssetSection {
u64 offset;

View File

@ -1,8 +1,110 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <fmt/ranges.h>
#include <mbedtls/sha256.h>
#include <loader/nro.h>
#include <nce.h>
#include "IRoInterface.h"
namespace skyline::service::ro {
IRoInterface::IRoInterface(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {}
Result IRoInterface::LoadModule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
u64 pid{request.Pop<u64>()};
u64 nroAddress{request.Pop<u64>()};
u64 nroSize{request.Pop<u64>()};
u64 bssAddress{request.Pop<u64>()};
u64 bssSize{request.Pop<u64>()};
if (!util::IsPageAligned(nroAddress) || !util::IsPageAligned(bssAddress))
return result::InvalidAddress;
if (!util::IsPageAligned(nroSize) || !nroSize || !util::IsPageAligned(bssSize))
return result::InvalidSize;
auto data{span{reinterpret_cast<u8 *>(nroAddress), nroSize}};
auto &header{data.as<loader::NroHeader>()};
std::array<u8, 0x20> hash{};
mbedtls_sha256_ret(data.data(), data.size(), hash.data(), 0);
if (!loadedNros.emplace(hash).second)
return result::AlreadyLoaded;
// We don't handle NRRs here since they're purely used for signature verification which we will never do
if (bssSize != header.bssSize)
return result::InvalidNro;
loader::Executable executable{};
executable.text.offset = 0;
executable.text.contents.resize(header.text.size);
span(executable.text.contents).copy_from(data.subspan(header.text.offset, header.text.size));
executable.ro.offset = header.text.size;
executable.ro.contents.resize(header.ro.size);
span(executable.ro.contents).copy_from(data.subspan(header.ro.offset, header.ro.size));
executable.data.offset = header.text.size + header.ro.size;
executable.data.contents.resize(header.data.size);
span(executable.data.contents).copy_from(data.subspan(header.data.offset, header.data.size));
executable.bssSize = header.bssSize;
if (header.dynsym.offset > header.ro.offset && header.dynsym.offset + header.dynsym.size < header.ro.offset + header.ro.size && header.dynstr.offset > header.ro.offset && header.dynstr.offset + header.dynstr.size < header.ro.offset + header.ro.size) {
executable.dynsym = {header.dynsym.offset, header.dynsym.size};
executable.dynstr = {header.dynstr.offset, header.dynstr.size};
}
u64 textSize{executable.text.contents.size()};
u64 roSize{executable.ro.contents.size()};
u64 dataSize{executable.data.contents.size() + executable.bssSize};
auto patch{state.nce->GetPatchData(executable.text.contents)};
auto size{patch.size + textSize + roSize + dataSize};
u8 *ptr{};
do {
ptr = util::AlignDown(util::RandomNumber(state.process->memory.base.data(), std::prev(state.process->memory.base.end()).base()), constant::PageSize) - size;
if (state.process->memory.heap.contains(ptr) || state.process->memory.alias.contains(ptr))
continue;
auto desc{state.process->memory.Get(ptr)};
if (!desc || desc->state != memory::states::Unmapped || (static_cast<size_t>(ptr - desc->ptr) + size) < desc->size)
continue;
} while (!ptr);
auto loadInfo{state.loader->LoadExecutable(state.process, state, executable, static_cast<size_t>(ptr - state.process->memory.base.data()), util::HexDump(hash) + ".nro")};
response.Push(loadInfo.entry);
return {};
}
Result IRoInterface::UnloadModule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
Logger::Error("Module unloading is unimplemented!");
return {};
}
Result IRoInterface::RegisterModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return {};
}
Result IRoInterface::UnregisterModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return {};
}
Result IRoInterface::RegisterProcessHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return {};
}
Result IRoInterface::RegisterProcessModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return {};
}
}

View File

@ -3,15 +3,73 @@
#pragma once
#include <unordered_set>
#include <services/serviceman.h>
namespace skyline::service::ro {
namespace result {
constexpr Result AlreadyLoaded{22, 3};
constexpr Result InvalidNro{22, 4};
constexpr Result InvalidNrr{22, 6};
constexpr Result InvalidAddress{22, 1025};
constexpr Result InvalidSize{22, 1026};
}
/**
* @brief IRoInterface or ldr:ro is used by applications to dynamically load nros
* @url https://switchbrew.org/wiki/RO_services#LoadModule
*/
class IRoInterface : public BaseService {
private:
enum class NrrKind : u8 {
User = 0,
JitPlugin = 1
};
std::unordered_set<std::array<u8, 0x20>, util::ObjectHash<std::array<u8, 0x20>>> loadedNros{};
Result RegisterModuleInfoImpl(u64 nrrAddress, u64 nrrSize, NrrKind nrrKind);
public:
IRoInterface(const DeviceState &state, ServiceManager &manager);
/**
* @url https://switchbrew.org/wiki/RO_services#LoadModule
*/
Result LoadModule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @url https://switchbrew.org/wiki/RO_services#UnloadModule
*/
Result UnloadModule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @url https://switchbrew.org/wiki/RO_services#RegisterModuleInfo
*/
Result RegisterModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @url https://switchbrew.org/wiki/RO_services#UnregisterModuleInfo
*/
Result UnregisterModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @url https://switchbrew.org/wiki/RO_services#Initialize
*/
Result RegisterProcessHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @url https://switchbrew.org/wiki/RO_services#RegisterProcessModuleInfo
*/
Result RegisterProcessModuleInfo(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
SERVICE_DECL(
SFUNC(0x0, IRoInterface, LoadModule),
SFUNC(0x1, IRoInterface, UnloadModule),
SFUNC(0x2, IRoInterface, RegisterModuleInfo),
SFUNC(0x3, IRoInterface, UnregisterModuleInfo),
SFUNC(0x4, IRoInterface, RegisterProcessHandle),
SFUNC(0xA, IRoInterface, RegisterProcessModuleInfo)
)
};
}