From 3e9bfaec0ef50dec477178da51ea0eb65628d64d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=97=B1=20PixelyIon?= Date: Thu, 26 Dec 2019 00:33:57 +0530 Subject: [PATCH] Introduce Basic Junction Patching This commit introduces basic junction patching or patching code to jump to a junction then execute intermediate functions and jump back. --- .idea/codeStyles/Project.xml | 3 + .idea/misc.xml | 2 +- app/CMakeLists.txt | 2 + app/src/main/cpp/skyline/common.h | 82 +------- app/src/main/cpp/skyline/guest.S | 46 +++++ app/src/main/cpp/skyline/guest.h | 8 + app/src/main/cpp/skyline/loader/loader.h | 26 --- app/src/main/cpp/skyline/loader/nro.cpp | 7 +- app/src/main/cpp/skyline/nce.cpp | 248 ++++++++++++++++++++++- app/src/main/cpp/skyline/nce.h | 9 + 10 files changed, 318 insertions(+), 115 deletions(-) create mode 100644 app/src/main/cpp/skyline/guest.S create mode 100644 app/src/main/cpp/skyline/guest.h diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index a21dfa7e..7e6f4752 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -15,10 +15,13 @@ - + diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 0e38efee..852c4ca0 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(source_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp) set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -flto=full -Wno-unused-command-line-argument") +enable_language(ASM) if (uppercase_CMAKE_BUILD_TYPE STREQUAL "RELEASE") add_compile_definitions(NDEBUG) endif() @@ -21,6 +22,7 @@ include_directories(${source_DIR}/skyline) add_library(skyline SHARED ${source_DIR}/main.cpp ${source_DIR}/skyline/common.cpp + ${source_DIR}/skyline/guest.S ${source_DIR}/skyline/nce.cpp ${source_DIR}/skyline/jvm.cpp ${source_DIR}/skyline/gpu.cpp diff --git a/app/src/main/cpp/skyline/common.h b/app/src/main/cpp/skyline/common.h index a269a986..999bdf21 100644 --- a/app/src/main/cpp/skyline/common.h +++ b/app/src/main/cpp/skyline/common.h @@ -47,7 +47,7 @@ namespace skyline { // Loader constexpr u32 NroMagic = 0x304F524E; //!< "NRO0" in reverse, this is written at the start of every NRO file // NCE - constexpr u8 NumRegs = 31; //!< The amount of registers that ARMv8 has + constexpr u8 NumRegs = 30; //!< The amount of registers that ARMv8 has constexpr u16 SvcLast = 0x7F; //!< The index of the last SVC constexpr u16 BrkRdy = 0xFF; //!< This is reserved for our kernel's to know when a process/thread is ready constexpr u32 TpidrroEl0 = 0x5E83; //!< ID of TPIDRRO_EL0 in MRS @@ -105,86 +105,6 @@ namespace skyline { NSP, //!< The NSP format from "nspwn" exploit: https://switchbrew.org/wiki/Switch_System_Flaws }; - namespace instr { - /** - * @brief A bit-field struct that encapsulates a BRK instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction. - */ - struct Brk { - /** - * @brief Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes - * @param val The immediate value of the instruction - */ - Brk(u16 val) { - start = 0x0; // First 5 bits of a BRK instruction are 0 - value = val; - end = 0x6A1; // Last 11 bits of a BRK instruction stored as u16 - } - - /** - * @brief Returns if the opcode is valid or not - * @return If the opcode represents a valid BRK instruction - */ - bool Verify() { - return (start == 0x0 && end == 0x6A1); - } - - u8 start : 5; - u32 value : 16; - u16 end : 11; - }; - - static_assert(sizeof(Brk) == sizeof(u32)); - - /** - * @brief A bit-field struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call. - */ - struct Svc { - /** - * @brief Returns if the opcode is valid or not - * @return If the opcode represents a valid SVC instruction - */ - bool Verify() { - return (start == 0x1 && end == 0x6A0); - } - - u8 start : 5; - u32 value : 16; - u16 end : 11; - }; - - static_assert(sizeof(Svc) == sizeof(u32)); - - /** - * @brief A bit-field struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register. - */ - struct Mrs { - /** - * @brief Creates a MRS instruction, used for generating BRK opcodes - * @param srcReg The source system register - * @param dstReg The destination Xn register - */ - Mrs(u32 srcReg, u8 dstReg) { - this->srcReg = srcReg; - this->dstReg = dstReg; - end = 0xD53; // Last 12 bits of a MRS instruction stored as u16 - } - - /** - * @brief Returns if the opcode is valid or not - * @return If the opcode represents a valid MRS instruction - */ - bool Verify() { - return (end == 0xD53); - } - - u8 dstReg : 5; - u32 srcReg : 15; - u16 end : 12; - }; - - static_assert(sizeof(Mrs) == sizeof(u32)); - }; - /** * Read about ARMv8 registers here: https://developer.arm.com/docs/100878/latest/registers */ diff --git a/app/src/main/cpp/skyline/guest.S b/app/src/main/cpp/skyline/guest.S new file mode 100644 index 00000000..27976782 --- /dev/null +++ b/app/src/main/cpp/skyline/guest.S @@ -0,0 +1,46 @@ +.text +.global saveCtx +saveCtx: + STR LR, [SP, #-16]! + MRS LR, TPIDR_EL0 + #LDR LR, [LR] + STP X0, X1, [LR, #0] + STP X2, X3, [LR, #16] + STP X4, X5, [LR, #32] + STP X6, X7, [LR, #48] + STP X8, X9, [LR, #64] + STP X10, X11, [LR, #80] + STP X12, X13, [LR, #96] + STP X14, X15, [LR, #112] + STP X16, X17, [LR, #128] + STP X18, X19, [LR, #144] + STP X20, X21, [LR, #160] + STP X22, X23, [LR, #176] + STP X24, X25, [LR, #192] + STP X26, X27, [LR, #208] + STP X28, X29, [LR, #224] + LDR LR, [SP], #16 + RET + +.global loadCtx +loadCtx: + STR LR, [SP, #-16]! + MRS LR, TPIDR_EL0 + #LDR LR, [LR] + LDP X0, X1, [LR, #0] + LDP X2, X3, [LR, #16] + LDP X4, X5, [LR, #32] + LDP X6, X7, [LR, #48] + LDP X8, X9, [LR, #64] + LDP X10, X11, [LR, #80] + LDP X12, X13, [LR, #96] + LDP X14, X15, [LR, #112] + LDP X16, X17, [LR, #128] + LDP X18, X19, [LR, #144] + LDP X20, X21, [LR, #160] + LDP X22, X23, [LR, #176] + LDP X24, X25, [LR, #192] + LDP X26, X27, [LR, #208] + LDP X28, X29, [LR, #224] + LDR LR, [SP], #16 + RET diff --git a/app/src/main/cpp/skyline/guest.h b/app/src/main/cpp/skyline/guest.h new file mode 100644 index 00000000..9deb5919 --- /dev/null +++ b/app/src/main/cpp/skyline/guest.h @@ -0,0 +1,8 @@ +#pragma once + +namespace skyline::guest { + constexpr size_t saveCtxSize = 20 * sizeof(u32); + constexpr size_t loadCtxSize = 20 * sizeof(u32); + extern "C" void saveCtx(void); + extern "C" void loadCtx(void); +} diff --git a/app/src/main/cpp/skyline/loader/loader.h b/app/src/main/cpp/skyline/loader/loader.h index 41f91463..d3b94649 100644 --- a/app/src/main/cpp/skyline/loader/loader.h +++ b/app/src/main/cpp/skyline/loader/loader.h @@ -21,32 +21,6 @@ namespace skyline::loader { pread64(romFd, output, size, offset); } - /** - * @brief This patches specific parts of the code - * @param code A vector with the code to be patched - */ - inline void PatchCode(std::vector &code) { - u32 *address = reinterpret_cast(code.data()); - u32 *end = address + (code.size() / sizeof(u32)); - while (address < end) { - auto instrSvc = reinterpret_cast(address); - auto instrMrs = reinterpret_cast(address); - if (instrSvc->Verify()) { - instr::Brk brk(static_cast(instrSvc->value)); - *address = *reinterpret_cast(&brk); - } else if (instrMrs->Verify()) { - if (instrMrs->srcReg == constant::TpidrroEl0) { - instr::Brk brk(static_cast(constant::SvcLast + 1 + instrMrs->dstReg)); - *address = *reinterpret_cast(&brk); - } else if (instrMrs->srcReg == constant::CntpctEl0) { - instr::Mrs mrs(constant::CntvctEl0, instrMrs->dstReg); - *address = *reinterpret_cast(&mrs); - } - } - address++; - } - } - public: /** * @param filePath The path to the ROM file diff --git a/app/src/main/cpp/skyline/loader/nro.cpp b/app/src/main/cpp/skyline/loader/nro.cpp index 4d645dde..f4d12622 100644 --- a/app/src/main/cpp/skyline/loader/nro.cpp +++ b/app/src/main/cpp/skyline/loader/nro.cpp @@ -17,11 +17,12 @@ namespace skyline::loader { ReadOffset(rodata.data(), header.ro.offset, header.ro.size); ReadOffset(data.data(), header.data.offset, header.data.size); - PatchCode(text); + std::vector patch = state.nce->PatchCode(text, header.text.size + header.ro.size + header.data.size + header.bssSize); u64 textSize = text.size(); u64 rodataSize = rodata.size(); u64 dataSize = data.size(); + u64 patchSize = patch.size() * sizeof(u32); process->MapPrivateRegion(constant::BaseAddr, textSize, {true, true, true}, memory::Type::CodeStatic, memory::Region::Text); // R-X state.logger->Debug("Successfully mapped region .text @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr, textSize); @@ -35,8 +36,12 @@ namespace skyline::loader { process->MapPrivateRegion(constant::BaseAddr + textSize + rodataSize + dataSize, header.bssSize, {true, true, true}, memory::Type::CodeMutable, memory::Region::Bss); // RWX state.logger->Debug("Successfully mapped region .bss @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + textSize + rodataSize + dataSize, header.bssSize); + process->MapPrivateRegion(constant::BaseAddr + textSize + rodataSize + dataSize + header.bssSize, patchSize, {true, true, true}, memory::Type::CodeStatic, memory::Region::Text); // RWX + state.logger->Debug("Successfully mapped region .patch @ 0x{0:X}, Size = 0x{1:X}", constant::BaseAddr + textSize + rodataSize + dataSize + header.bssSize, patchSize); + process->WriteMemory(text.data(), constant::BaseAddr, textSize); process->WriteMemory(rodata.data(), constant::BaseAddr + textSize, rodataSize); process->WriteMemory(data.data(), constant::BaseAddr + textSize + rodataSize, dataSize); + process->WriteMemory(patch.data(), constant::BaseAddr + textSize + rodataSize + dataSize + header.bssSize, patchSize); } } diff --git a/app/src/main/cpp/skyline/nce.cpp b/app/src/main/cpp/skyline/nce.cpp index 38dae7c2..616fb57f 100644 --- a/app/src/main/cpp/skyline/nce.cpp +++ b/app/src/main/cpp/skyline/nce.cpp @@ -3,11 +3,182 @@ #include #include "os.h" #include "jvm.h" +#include "guest.h" extern bool Halt; extern std::mutex jniMtx; namespace skyline { + + namespace instr { + /** + * @brief A bit-field struct that encapsulates a BRK instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/brk-breakpoint-instruction. + */ + struct Brk { + /** + * @brief Creates a BRK instruction with a specific immediate value, used for generating BRK opcodes + * @param value The immediate value of the instruction + */ + explicit Brk(u16 value) { + start = 0x0; // First 5 bits of a BRK instruction are 0 + this->value = value; + end = 0x6A1; // Last 11 bits of a BRK instruction stored as u16 + } + + /** + * @brief Returns if the opcode is valid or not + * @return If the opcode represents a valid BRK instruction + */ + inline bool Verify() { + return (start == 0x0 && end == 0x6A1); + } + + union { + struct { + u8 start : 5; + u32 value : 16; + u16 end : 11; + }; + u32 raw{}; + }; + }; + + static_assert(sizeof(Brk) == sizeof(u32)); + + /** + * @brief A bit-field struct that encapsulates a SVC instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/svc-supervisor-call. + */ + struct Svc { + /** + * @brief Returns if the opcode is valid or not + * @return If the opcode represents a valid SVC instruction + */ + inline bool Verify() { + return (start == 0x1 && end == 0x6A0); + } + + union { + struct { + u8 start : 5; + u32 value : 16; + u16 end : 11; + }; + u32 raw{}; + }; + }; + + static_assert(sizeof(Svc) == sizeof(u32)); + + /** + * @brief A bit-field struct that encapsulates a MRS instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/mrs-move-system-register. + */ + struct Mrs { + /** + * @brief Creates a MRS instruction, used for generating BRK opcodes + * @param srcReg The source system register + * @param dstReg The destination Xn register + */ + Mrs(u32 srcReg, u8 dstReg) { + this->srcReg = srcReg; + this->dstReg = dstReg; + end = 0xD53; // Last 12 bits of a MRS instruction stored as u16 + } + + /** + * @brief Returns if the opcode is valid or not + * @return If the opcode represents a valid MRS instruction + */ + inline bool Verify() { + return (end == 0xD53); + } + + union { + struct { + u8 dstReg : 5; + u32 srcReg : 15; + u16 end : 12; + }; + u32 raw{}; + }; + }; + + static_assert(sizeof(Mrs) == sizeof(u32)); + + /** + * @brief A bit-field struct that encapsulates a B instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/b-branch. + */ + struct B { + public: + explicit B(i64 offset) { + this->offset = static_cast(offset / 4); + end = 0x5; + } + + /** + * @brief Returns the offset of the instruction + * @return The offset encoded within the instruction + */ + inline i32 Offset() { + return offset * 4; + } + + /** + * @brief Returns if the opcode is valid or not + * @return If the opcode represents a valid Branch instruction + */ + inline bool Verify() { + return (end == 0x5); + } + + union { + struct { + i32 offset : 26; + u8 end : 6; + }; + u32 raw{}; + }; + }; + + static_assert(sizeof(B) == sizeof(u32)); + + /** + * @brief A bit-field struct that encapsulates a BL instruction. See https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order/b-branch. + */ + struct BL { + public: + explicit BL(i64 offset) { + this->offset = static_cast(offset / 4); + end = 0x25; + } + + /** + * @brief Returns the offset of the instruction + * @return The offset encoded within the instruction + */ + inline i32 Offset() { + return offset * 4; + } + + /** + * @brief Returns if the opcode is valid or not + * @return If the opcode represents a valid Branch instruction + */ + inline bool Verify() { + return (end == 0x85); + } + + union { + struct { + i32 offset : 26; + u8 end : 6; + }; + u32 raw{}; + }; + }; + + static_assert(sizeof(BL) == sizeof(u32)); + } + void NCE::ReadRegisters(user_pt_regs ®isters, pid_t pid) const { iovec iov = {®isters, sizeof(registers)}; long status = ptrace(PTRACE_GETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov); @@ -56,8 +227,10 @@ namespace skyline { SetRegister(static_cast(instr.value - (constant::SvcLast + 1)), state.thisThread->tls); } else if (instr.value == constant::BrkRdy) continue; - else + else { + ProcessTrace(); throw exception("Received unhandled BRK: 0x{:X}", static_cast(instr.value)); + } } currRegs.pc += sizeof(u32); WriteRegisters(currRegs); @@ -181,12 +354,11 @@ namespace skyline { raw += fmt::format("{:08X}", instr); } state.logger->Debug("Raw Instructions: 0x{}", raw); - state.logger->Debug("CPU Context:"); - state.logger->Debug("SP: 0x{:X}", regs.sp); - state.logger->Debug("PSTATE: 0x{:X}", regs.pstate); - for (u16 index = 0; index < constant::NumRegs - 2; index++) { - state.logger->Debug("X{}: 0x{:X}", index, regs.regs[index]); + std::string regStr; + for (u16 index = 0; index < constant::NumRegs - 1; index+=2) { + regStr += fmt::format("\nX{}: 0x{:X}, X{}: 0x{:X}", index, regs.regs[index], index+1, regs.regs[index+1]); } + state.logger->Debug("CPU Context:\nSP: 0x{:X}\nLR: 0x{:X}\nPSTATE: 0x{:X}{}", regs.sp, regs.regs[30], regs.pstate, regStr); } u64 NCE::GetRegister(Xreg regId, pid_t pid) { @@ -228,4 +400,68 @@ namespace skyline { registerMap.at(pid).pstate = value; } } + + std::vector NCE::PatchCode(std::vector& code, i64 offset) { + u32 *address = reinterpret_cast(code.data()); + u32 *end = address + (code.size() / sizeof(u32)); + i64 patchOffset = offset; + + std::vector patch; + patch.resize((guest::saveCtxSize + guest::loadCtxSize) / sizeof(u32)); + std::memcpy(patch.data(), reinterpret_cast(&guest::saveCtx), guest::saveCtxSize); + offset += guest::saveCtxSize; + + std::memcpy(reinterpret_cast(patch.data()) + guest::saveCtxSize, + reinterpret_cast(&guest::loadCtx), guest::loadCtxSize); + offset += guest::loadCtxSize; + + while (address < end) { + auto instrSvc = reinterpret_cast(address); + auto instrMrs = reinterpret_cast(address); + + if (instrSvc->Verify()) { + instr::B bjunc(offset); + constexpr u32 strLr = 0xF81F0FFE; // STR LR, [SP, #-16]! + offset += sizeof(strLr); + instr::Brk brk(static_cast(instrSvc->value)); + offset += sizeof(brk); + instr::BL bSvCtx(patchOffset - offset); + offset += sizeof(bSvCtx); + instr::BL bLdCtx((patchOffset + guest::saveCtxSize) - offset); + offset += sizeof(bLdCtx); + constexpr u32 ldrLr = 0xF84107FE; // LDR LR, [SP], #16 + offset += sizeof(ldrLr); + instr::B bret(-offset + sizeof(u32)); + offset += sizeof(bret); + + *address = bjunc.raw; + patch.push_back(strLr); + patch.push_back(brk.raw); + patch.push_back(bSvCtx.raw); + patch.push_back(bLdCtx.raw); + patch.push_back(ldrLr); + patch.push_back(bret.raw); + } else if (instrMrs->Verify()) { + if (instrMrs->srcReg == constant::TpidrroEl0) { + instr::B bjunc(offset); + instr::Brk brk(static_cast(constant::SvcLast + 1 + instrMrs->dstReg)); + offset += sizeof(u32); + instr::B bret(-offset + sizeof(u32)); + offset += sizeof(u32); + + *address = bjunc.raw; + patch.push_back(brk.raw); + patch.push_back(bret.raw); + } else if (instrMrs->srcReg == constant::CntpctEl0) { + instr::Mrs mrs(constant::CntvctEl0, instrMrs->dstReg); + *address = mrs.raw; + } + } + address++; + offset -= sizeof(u32); + patchOffset -= sizeof(u32); + } + return patch; + } } + diff --git a/app/src/main/cpp/skyline/nce.h b/app/src/main/cpp/skyline/nce.h index c1ecbfd1..3c66ad7e 100644 --- a/app/src/main/cpp/skyline/nce.h +++ b/app/src/main/cpp/skyline/nce.h @@ -9,6 +9,9 @@ #include "kernel/types/KSharedMemory.h" namespace skyline { + namespace instr { + struct Brk; + } /** * @brief The NCE (Native Code Execution) class is responsible for managing the state of catching instructions and directly controlling processes/threads */ @@ -140,5 +143,11 @@ namespace skyline { * @param pid The PID of the process (Defaults to currPid) */ void SetRegister(Sreg regId, u32 value, pid_t pid = 0); + + /** + * @brief This patches specific parts of the code + * @param code A vector with the code to be patched + */ + std::vector PatchCode(std::vector &code, i64 offset); }; }