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.
This commit is contained in:
◱ PixelyIon 2019-12-26 00:33:57 +05:30 committed by ◱ PixelyIon
parent f7ad017726
commit 3e9bfaec0e
10 changed files with 318 additions and 115 deletions

View File

@ -15,10 +15,13 @@
<option name="FUNCTION_TOP_AFTER_RETURN_TYPE_WRAP" value="0" />
<option name="FUNCTION_PARAMETERS_WRAP" value="5" />
<option name="FUNCTION_CALL_ARGUMENTS_WRAP" value="5" />
<option name="SHIFT_OPERATION_WRAP" value="0" />
<option name="TEMPLATE_DECLARATION_STRUCT_WRAP" value="1" />
<option name="TEMPLATE_DECLARATION_FUNCTION_WRAP" value="1" />
<option name="TEMPLATE_CALL_ARGUMENTS_WRAP" value="5" />
<option name="TEMPLATE_CALL_ARGUMENTS_ALIGN_MULTILINE" value="true" />
<option name="CLASS_CONSTRUCTOR_INIT_LIST_WRAP" value="0" />
<option name="SUPERCLASS_LIST_WRAP" value="0" />
<option name="ALIGN_INIT_LIST_IN_COLUMNS" value="false" />
<option name="HEADER_GUARD_STYLE_PATTERN" value="${PROJECT_NAME}_${PROJECT_REL_PATH}_${FILE_NAME}_${EXT}" />
<option name="MACROS_NAMING_CONVENTION">

2
.idea/misc.xml generated
View File

@ -43,7 +43,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -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

View File

@ -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
*/

View File

@ -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

View File

@ -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);
}

View File

@ -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<u8> &code) {
u32 *address = reinterpret_cast<u32 *>(code.data());
u32 *end = address + (code.size() / sizeof(u32));
while (address < end) {
auto instrSvc = reinterpret_cast<instr::Svc *>(address);
auto instrMrs = reinterpret_cast<instr::Mrs *>(address);
if (instrSvc->Verify()) {
instr::Brk brk(static_cast<u16>(instrSvc->value));
*address = *reinterpret_cast<u32 *>(&brk);
} else if (instrMrs->Verify()) {
if (instrMrs->srcReg == constant::TpidrroEl0) {
instr::Brk brk(static_cast<u16>(constant::SvcLast + 1 + instrMrs->dstReg));
*address = *reinterpret_cast<u32 *>(&brk);
} else if (instrMrs->srcReg == constant::CntpctEl0) {
instr::Mrs mrs(constant::CntvctEl0, instrMrs->dstReg);
*address = *reinterpret_cast<u32 *>(&mrs);
}
}
address++;
}
}
public:
/**
* @param filePath The path to the ROM file

View File

@ -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<u32> 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);
}
}

View File

@ -3,11 +3,182 @@
#include <linux/elf.h>
#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<i32>(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<i32>(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 &registers, pid_t pid) const {
iovec iov = {&registers, sizeof(registers)};
long status = ptrace(PTRACE_GETREGSET, pid ? pid : currPid, NT_PRSTATUS, &iov);
@ -56,8 +227,10 @@ namespace skyline {
SetRegister(static_cast<Xreg>(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<u64>(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<u32> NCE::PatchCode(std::vector<u8>& code, i64 offset) {
u32 *address = reinterpret_cast<u32 *>(code.data());
u32 *end = address + (code.size() / sizeof(u32));
i64 patchOffset = offset;
std::vector<u32> patch;
patch.resize((guest::saveCtxSize + guest::loadCtxSize) / sizeof(u32));
std::memcpy(patch.data(), reinterpret_cast<void*>(&guest::saveCtx), guest::saveCtxSize);
offset += guest::saveCtxSize;
std::memcpy(reinterpret_cast<u8*>(patch.data()) + guest::saveCtxSize,
reinterpret_cast<void*>(&guest::loadCtx), guest::loadCtxSize);
offset += guest::loadCtxSize;
while (address < end) {
auto instrSvc = reinterpret_cast<instr::Svc *>(address);
auto instrMrs = reinterpret_cast<instr::Mrs *>(address);
if (instrSvc->Verify()) {
instr::B bjunc(offset);
constexpr u32 strLr = 0xF81F0FFE; // STR LR, [SP, #-16]!
offset += sizeof(strLr);
instr::Brk brk(static_cast<u16>(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<u16>(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;
}
}

View File

@ -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<u32> PatchCode(std::vector<u8> &code, i64 offset);
};
}