commit c19666c0a0068feff8bbb2f8075b69a39a4c7dcf Author: Arksama-PHU Date: Sun May 24 20:31:15 2026 +0200 Initial release : Lapy JB Daemon v1.2 multi-firmware standalone payload (3.00 -> 12.00) Standalone homebrew daemon for PS5 that mimics etaHEN's jailbreak-on-demand API. Apps that write /download0/etahen_jailbreak from inside their sandbox get their ucred bumped (caps + authid + uid + sceAttr + sandbox escape via fd_rdir/jdir) the same way etaHEN does it. NO etaHEN required. Multi-fw via ps5-payload-sdk's runtime KERNEL_ADDRESS_* externs : the SDK auto-resolves kernel symbols per-fw at boot, so the binary works on every fw covered by the SDK + kstuff (3.00 -> 12.00) with no fw-specific code. Build : export PS5_PAYLOAD_SDK= && make Install : copy lapy_jb_daemon.elf + homebrew.js to /data/homebrew/lapy_jb/ Credits : libhijacker (illusion + LightingMod), ps5-payload-sdk (John Tornblom), offsets_v940 multi-fw module (Team PHU / Arksama). diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..953107d --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Build artifacts +*.o +*.d +*.a + +# Python scripts (helpers, not part of release) +*.py +__pycache__/ + +# Backup files +*.bak +*.bak_* +*.orig +*~ + +# IDE / editor +.vscode/ +.idea/ +*.swp +.DS_Store + +# OS +Thumbs.db +desktop.ini diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d1a687 --- /dev/null +++ b/LICENSE @@ -0,0 +1,38 @@ +MIT License + +Copyright (c) 2026 Arksama (Team PHU) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +Credits to original author when re-used in derivative works appreciated. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +DISCLAIMER + +This software is provided for educational and interoperability purposes only. +It is intended to be used on PS5 consoles owned by the user, on firmware +versions that have been publicly jailbroken via third-party tools. + +The daemon mimics etaHEN's jailbreak-on-demand API for homebrew apps that +expect that environment. It does not bypass any DRM or grant access to +copyrighted material. Use it only with software you have the right to run. + +Use of this software in ways that violate Sony's Terms of Service is at +your own risk. The authors take no responsibility for misuse. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b796fbd --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +# Lapy JB Daemon v1.2 — multi-firmware standalone payload. +# +# Build flow : +# - main.o + offsets_v940.o compiled from source/ +# - linked against vanilla libhijacker.a (place at extern/libhijacker/) +# - offsets_v940.o is listed BEFORE -lhijacker so the linker resolves the +# offsets:: namespace symbols from that object and skips libhijacker's +# vanilla offsets.cpp.o (which would hardcode fw 9.40 only). +# +# Result : a binary that works on every fw covered by ps5-payload-sdk + kstuff +# (3.00 -> 12.00), with no fw-specific code in this repo. + +PS5_HOST ?= 192.168.1.28 +PS5_PORT ?= 9021 + +ifndef PS5_PAYLOAD_SDK + $(error PS5_PAYLOAD_SDK undefined. Set to your ps5-payload-sdk path) +endif + +include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk + +ELF := lapy_jb_daemon.elf + +LIBHIJACKER_DIR := extern/libhijacker +LIBHIJACKER_INC := $(LIBHIJACKER_DIR)/include + +CXXFLAGS := -std=c++20 -Wall -O2 -fno-rtti -fno-exceptions +CXXFLAGS += -I$(LIBHIJACKER_INC) -Isource + +LDFLAGS := -L$(LIBHIJACKER_DIR) +LDLIBS := -lhijacker -lkernel_sys -lSceNotification + +OBJS := source/main.o source/offsets_v940.o + +all: $(ELF) + +source/%.o: source/%.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +# offsets_v940.o is listed BEFORE -lhijacker in the link line so its symbols +# satisfy libhijacker's offsets:: refs before the archive's offsets.cpp.o +# can be pulled in. +$(ELF): $(OBJS) $(LIBHIJACKER_DIR)/libhijacker.a + $(CXX) $(CXXFLAGS) -o $@ $(OBJS) $(LDFLAGS) $(LDLIBS) + +clean: + rm -f $(ELF) $(OBJS) + +.PHONY: all clean diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..4b66336 --- /dev/null +++ b/README.txt @@ -0,0 +1,108 @@ +================================================================================ + Lapy JB Daemon — standalone homebrew payload for PS5-Xplorer / similar apps + v1.2 — multi-firmware (3.00 -> 12.00, every fw covered by ps5-payload-sdk) +================================================================================ + +WHAT IT DOES +------------ +Mimics etaHEN's jailbreak-on-demand daemon API for Sony app PRXes (= the +prx.cpp shipped with PS5-Xplorer / universalps5-style apps that depend on +etaHEN being active to unsandbox themselves). + +When this daemon runs, your PS5 app's PRX writes a request file to its +sandbox /download0/etahen_jailbreak with its PID. The daemon detects the +file, walks allproc, finds the process, bumps its ucred (caps + authID + +uid + attr) via libhijacker, then deletes the file. Same effect as etaHEN +doing it. + +NO etaHEN required. + +REQUIREMENTS +------------ + - PS5 jailbroken with kstuff active + - Homebrew Launcher (HBL) or autoload.txt configured + +INSTALL +------- + 1. Copy the entire `lapy_jb` folder to your PS5 at : + /data/homebrew/lapy_jb/ + (use FTP, USB, etc.) + + The folder must contain : + lapy_jb_daemon.elf + homebrew.js + + 2. Open HBL — you should see "lapy Jb daemon" in the list. + + 3. Tap to launch. Notification "Lapy JB Daemon active" will appear. + + 4. Now launch your app (e.g., PS5-Xplorer) — the daemon will detect its + jailbreak request and bump it automatically. + +OPTIONAL : auto-launch at boot +------------------------------- +Add to your /data/autoload.txt : + /data/homebrew/lapy_jb/lapy_jb_daemon.elf + +So the daemon launches at every PS5 boot, no manual HBL tap needed. + +KLOG (= debug) +-------------- +On launch : + [lapy_jb] Lapy JB Daemon v1.2 (multi-fw) + [lapy_jb] No etaHEN, no PHU required + [lapy_jb] kernel_base = 0x... + [lapy_jb] polling /mnt/sandbox/_/download0/etahen_jailbreak every 250ms + +When app launches : + [lapy_jb] request from _ : pid=N + [lapy_jb] jailbreak OK pid=N + (caps + authid + uid + sceAttr + sandbox escape via fd_rdir/jdir applied) + +If the first attempt fails ("getHijacker FAIL — process may have exited"), +just relaunch your app — second attempt usually succeeds (timing race). + +BUILD FROM SOURCE +----------------- +The repo ships libhijacker (headers + static library) under extern/, so +the only external dependency is the PS5 payload SDK : + + - ps5-payload-sdk (John Törnblom) : + https://github.com/ps5-payload-dev/sdk + Set environment variable PS5_PAYLOAD_SDK to the SDK root. + +Build : + export PS5_PAYLOAD_SDK=/path/to/ps5-payload-sdk + make + +Output : lapy_jb_daemon.elf + +About source/offsets_v940.cpp : +This file overrides libhijacker's vanilla offsets.cpp at link time. It's +what makes the binary multi-fw : ps5-payload-sdk already resolves kernel +symbols per-fw at boot via KERNEL_ADDRESS_* runtime externs, so +offsets_v940 just converts those absolute VAs to offsets the way +libhijacker's offsets:: namespace expects. The shipped extern/libhijacker/ +libhijacker.a has been prebuilt with this override already applied — if +you bring your own vanilla libhijacker.a, the standalone offsets_v940.o +takes precedence at link time and the vanilla offsets.cpp.o stays out. + +CREDITS +------- +Built on libhijacker by illusion + LightingMod. +Uses ps5-payload-sdk by John Törnblom. +Multi-fw offsets module (offsets_v940) by Team PHU / Arksama. + +CHANGELOG +--------- +v1.2 (2026-05-07) + - Multi-fw : works on every PS5 fw covered by ps5-payload-sdk + kstuff + (3.00 -> 12.00). Kernel symbols (allproc, rootvnode, security_flags, + qa_flags, utoken_flags) auto-resolved per-fw at boot via the SDK's + runtime KERNEL_ADDRESS_* externs (no hardcoded fw 9.40 path). + - Same libhijacker codepath as etaHEN's daemon : caps + authid + uid + + sceAttr at offset 0x83 + sandbox escape via fd_rdir/fd_jdir = + rootvnode. Identical effect to "etahen jailbreak true". + +v1.0 (initial) + - fw 9.40 only. diff --git a/extern/libhijacker/include/Platform.hpp b/extern/libhijacker/include/Platform.hpp new file mode 100644 index 0000000..88ef506 --- /dev/null +++ b/extern/libhijacker/include/Platform.hpp @@ -0,0 +1,32 @@ +#pragma once + +#if defined(_WIN32) +#define PLATFORM_WINDOWS +#pragma comment(lib, "Ws2_32.lib") +#include +#include +#define CloseSocket closesocket +#define SocketAvailable ioctlsocket +typedef SOCKET Socket; + +#elif defined(__linux__) +#define PLATFORM_LINUX +#include +#include +#include +#include +#define CloseSocket close +#define SocketAvailable ioctl +typedef int32_t Socket; + +#elif defined(__APPLE__) +#define PLATFORM_APPLE +#include +#include +#include +#include +#define CloseSocket close +#define SocketAvailable ioctl +typedef int32_t Socket; + +#endif diff --git a/extern/libhijacker/include/backtrace.hpp b/extern/libhijacker/include/backtrace.hpp new file mode 100644 index 0000000..40a61d3 --- /dev/null +++ b/extern/libhijacker/include/backtrace.hpp @@ -0,0 +1,15 @@ +#pragma once + +extern "C" { + #include +} + +struct Frame { + Frame *next; + uintptr_t addr; +}; +static auto constexpr CRASH_LOG_PATH = "/data/etaHEN/etaHEN_crash.log"; +const Frame *getFramePointer(); +void printBacktraceForCrash(); +void crash_log(const char *fmt, ...); +void printBacktraceForCrash(); \ No newline at end of file diff --git a/extern/libhijacker/include/dbg.hpp b/extern/libhijacker/include/dbg.hpp new file mode 100644 index 0000000..735cbe1 --- /dev/null +++ b/extern/libhijacker/include/dbg.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "dbg/dbg.hpp" // IWYU pragma: export diff --git a/extern/libhijacker/include/dbg/args.hpp b/extern/libhijacker/include/dbg/args.hpp new file mode 100644 index 0000000..33f9dea --- /dev/null +++ b/extern/libhijacker/include/dbg/args.hpp @@ -0,0 +1,138 @@ +#pragma once + +extern "C" { + #include +} + +enum DbgCommand : uint64_t { + CREATE_SCRATCH_CMD = 0x3, + READ_CMD = 0x12, + WRITE_CMD = 0x13, + PROCESS_LIST_CMD = 0x14, + THREAD_LIST_CMD = 0x15, + PROCESS_INFO_CMD = 0x18, + THREAD_INFO_CMD = 0x19, + ARG2_CMD = 0x1e +}; + +// NOLINTBEGIN(*) + +// aggressively zero all dbg args +template +struct DbgArg { + DbgArg() { + // because of the stupid wrong pure attribute in the sdk headers + // __builtin_memset must be used + __builtin_memset(this, 0, size); + } +}; + +struct DbgArg1 : DbgArg<0x20> { + uint32_t type; + uint32_t pad1; + DbgCommand cmd; + + DbgArg1(uint32_t type, DbgCommand cmd) : DbgArg(), type(type), cmd(cmd){} +private: + uint8_t __attribute__((unused)) pad[0x10]; +}; + +static_assert(sizeof(DbgArg1) == 0x20, "sizeof(DbgArg1) != 0x20"); + +struct DbgArg2 : DbgArg<0x40> { +}; + +struct DbgReadArg : DbgArg2 { + int pid; + uintptr_t src; + void *dst; + uint64_t length; + DbgReadArg(int pid, uintptr_t src, void *dst, uint64_t length) + : pid(pid), src(src), dst(dst), length(length) {} +private: + unsigned char __attribute__((unused)) pad[0x20]; +}; + +static_assert(sizeof(DbgReadArg) == 0x40, "sizeof(DbgReadArg) != 0x40"); + +struct DbgKickProcessArg : DbgArg2 { + uint32_t pid; + uint32_t __attribute__((unused)) pad1; + const uint32_t cmd = 16; + uint32_t __attribute__((unused)) pad2; + const uint32_t value = 3; + DbgKickProcessArg(int pid) : pid(pid) {} +private: + unsigned char __attribute__((unused)) pad[0x2c]; +}; + +static_assert(sizeof(DbgKickProcessArg) == 0x40, "sizeof(DbgKickProcessArg) != 0x40"); + +struct DbgGetPidsArg : DbgArg2 { + uint64_t buf; + uint64_t length; + + template + DbgGetPidsArg(int(&buf)[size]) : buf((uint64_t) &buf), length(size) {} + DbgGetPidsArg(int *buf, unsigned long size) : buf((uint64_t) buf), length(size) {} +private: + unsigned char __attribute__((unused)) pad[0x30]; +}; + +static_assert(sizeof(DbgGetPidsArg) == 0x40, "sizeof(DbgGetPidsArg) != 0x40"); + +struct DbgGetTidsArg : DbgArg2 { + uint32_t pid; + uint64_t buf; + uint64_t length; + + template + DbgGetTidsArg(int pid, int(&buf)[size]) : pid(pid), buf((uint64_t) &buf), length(size) {} + DbgGetTidsArg(int pid, int *buf, unsigned long size) : pid(pid), buf((uint64_t) buf), length(size) {} +private: + unsigned char __attribute__((unused)) pad[0x28]; +}; + +static_assert(sizeof(DbgGetTidsArg) == 0x40, "sizeof(DbgGetTidsArg) != 0x40"); + +struct DbgGetProcInfoArg : DbgArg2 { + int pid; + uintptr_t buf; + uint64_t length; + + DbgGetProcInfoArg(int pid, void *buf, uint64_t length) + : pid(pid), buf((uintptr_t) buf), length(length) {} +private: + unsigned char __attribute__((unused)) pad[0x28]; +}; + +static_assert(sizeof(DbgGetProcInfoArg) == 0x40, "sizeof(DbgGetProcInfoArg) != 0x40"); + + +struct DbgGetThreadInfoArg : DbgArg2 { + uint32_t pid; + uint32_t __attribute__((unused)) pad1; + uint32_t tid; + uint64_t buf; + uint64_t length; + + DbgGetThreadInfoArg(int pid, int tid, void *buf, uint64_t length) + : pid(pid), tid(tid), buf((uintptr_t) buf), length(length) {} +private: + unsigned char __attribute__((unused)) pad[0x20]; +}; + +static_assert(sizeof(DbgGetThreadInfoArg) == 0x40, "sizeof(DbgGetThreadInfoArg) != 0x40"); + +struct DbgArg3 : DbgArg<0x20> { + int64_t err; + uint64_t length; + + explicit DbgArg3() : length() {} +private: + unsigned char __attribute__((unused)) pad[0x10]; +}; + +static_assert(sizeof(DbgArg3) == 0x20, "sizeof(DbgArg3) != 0x20"); + +// NOLINTEND(*) diff --git a/extern/libhijacker/include/dbg/dbg.hpp b/extern/libhijacker/include/dbg/dbg.hpp new file mode 100644 index 0000000..b44174d --- /dev/null +++ b/extern/libhijacker/include/dbg/dbg.hpp @@ -0,0 +1,629 @@ +#pragma once + +#include "util.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { + int pthread_getthreadid_np(); + int getpid(); + size_t strnlen(const char *s, size_t maxlen); +} + +namespace dbg { + +static constexpr uint64_t DEBUGGER_AUTHID = 0x4800000000000006; +static constexpr uint64_t PTRACE_ID = 0x4800000000010003l; + +uint64_t setAuthId(uint64_t authid); + +class AuthidSwapper { + + uint64_t id; + static constexpr int AUTHID_OFFSET = 0x58; + +public: + AuthidSwapper(uint64_t authid) noexcept : id(setAuthId(authid)) { + } + AuthidSwapper(const AuthidSwapper&) = delete; + AuthidSwapper &operator=(const AuthidSwapper&) = delete; + AuthidSwapper(AuthidSwapper &&rhs) noexcept : id(rhs.id) { rhs.id = 0; } + AuthidSwapper &operator=(AuthidSwapper &&rhs) noexcept = delete; + ~AuthidSwapper() { + if (id != 0) { + setAuthId(id); + id = 0; + } + } +}; + +class IdArray { + int *ptr; + size_t size; + +public: + IdArray(decltype(nullptr)) : ptr(nullptr), size() {} + IdArray(const int *ptr, size_t length) : ptr(new int[length]), size(length) { + __builtin_memcpy(this->ptr, ptr, length * sizeof(int)); + } + IdArray(const IdArray&) = delete; + IdArray& operator=(const IdArray&) = delete; + IdArray(IdArray &&rhs) noexcept : ptr(rhs.ptr), size(rhs.size) { rhs.ptr = nullptr; } + IdArray& operator=(IdArray &&rhs) noexcept { + delete[] ptr; + ptr = rhs.ptr; + rhs.ptr = nullptr; + size = rhs.size; + return *this; + } + ~IdArray() { + delete[] ptr; + } + + int operator[](unsigned long i) const { + if (ptr == nullptr) { + return -1; + } + return ptr[i]; + } + + explicit operator bool() const { return ptr != nullptr; } + + size_t length() const { return size; } + + const int *begin() const { return ptr; } + const int *end() const { return ptr + size; } + bool contains(int value) const { + for (int id : *this) { + if (id == value) { + return true; + } + } + return false; + } +}; + +class ProcessInfo { + static constexpr unsigned int BUF_LENGTH = 0x51c; + static constexpr unsigned int NAME_OFFSET = 0x18; + static constexpr unsigned int NAME_LENGTH = 0x20; + static constexpr unsigned int PATH_OFFSET = 0x38; + static constexpr unsigned int PATH_LENGTH = 0x400; + + UniquePtr buf; + StringView _name; + StringView _path; + int _pid; + + void fillInfo(); + +public: + + ProcessInfo(int pid) : buf(new uint8_t[BUF_LENGTH]), _name(nullptr), _path(nullptr), _pid(pid) { + fillInfo(); + const char *str = reinterpret_cast(buf.get()); + size_t nameLength = strnlen(str + NAME_OFFSET, NAME_LENGTH); + _name = {str + NAME_OFFSET, nameLength}; + size_t pathLength = strnlen(str + PATH_OFFSET, PATH_LENGTH); + _path = {str + NAME_OFFSET, pathLength}; + } + ProcessInfo(const ProcessInfo&) = delete; + ProcessInfo &operator=(const ProcessInfo&) = delete; + ProcessInfo(ProcessInfo &&rhs) noexcept : buf(rhs.buf.release()), _name(rhs._name), _path(rhs._path), _pid(rhs._pid) {} + ProcessInfo &operator=(ProcessInfo &&rhs) noexcept { + buf = rhs.buf.release(); + _name = rhs._name; + _path = rhs._path; + _pid = rhs._pid; + return *this; + } + ~ProcessInfo() noexcept = default; + + const StringView &name() const { + return _name; + } + + const StringView &path() const { + return _path; + } + + int pid() const { + return _pid; + } +}; + + +class ThreadInfo { + static constexpr unsigned int BUF_LENGTH = 0x15c; + static constexpr unsigned int NAME_OFFSET = 0x64; + static constexpr unsigned int NAME_LENGTH = 0x20; + + UniquePtr buf; + StringView _name; + int _pid; + int _tid; + + void fillInfo(); + +public: + + ThreadInfo() : buf(new uint8_t[BUF_LENGTH]), _name(nullptr), _pid(getpid()), _tid(pthread_getthreadid_np()) { + fillInfo(); + const char *str = reinterpret_cast(buf.get()); + size_t nameLength = strnlen(str + NAME_OFFSET, NAME_LENGTH); + _name = {str + NAME_OFFSET, nameLength}; + } + + ThreadInfo(int tid) : buf(new uint8_t[BUF_LENGTH]), _name(nullptr), _pid(getpid()), _tid(tid) { + fillInfo(); + const char *str = reinterpret_cast(buf.get()); + size_t nameLength = strnlen(str + NAME_OFFSET, NAME_LENGTH); + _name = {str + NAME_OFFSET, nameLength}; + } + + ThreadInfo(int pid, int tid) : buf(new uint8_t[BUF_LENGTH]()), _name(nullptr), _pid(pid), _tid(tid) { + fillInfo(); + const char *str = reinterpret_cast(buf.get()); + size_t nameLength = strnlen(str + NAME_OFFSET, NAME_LENGTH); + _name = {str + NAME_OFFSET, nameLength}; + } + + ThreadInfo(const ThreadInfo&) = delete; + ThreadInfo &operator=(const ThreadInfo&) = delete; + ThreadInfo(ThreadInfo&&) noexcept = default; + ThreadInfo &operator=(ThreadInfo&&) noexcept = default; + ~ThreadInfo() noexcept = default; + + const StringView &name() const { + return _name; + } + + int pid() const { + return _pid; + } + + int tid() const { + return _tid; + } +}; + + +IdArray getAllPids(); +IdArray getAllTids(int pid); +void suspend(int pid); +void resume(int pid); + + +bool read(int pid, uintptr_t addr, void *dst, size_t length); +inline UniquePtr read(int pid, uintptr_t addr, size_t length) { + UniquePtr buf{new uint8_t[length]}; + if (!read(pid, addr, buf.get(), length)) + { + return nullptr; + } + return buf; +} + +bool write(int pid, uintptr_t dst, const void *src, size_t length); + +class ProcessInfoIterator { + + IdArray pids{getAllPids()}; + + class iterator { + + const int *ptr; + + public: + iterator(const int *ptr) : ptr(ptr) {} + + ProcessInfo operator*() { + return *ptr; + } + + iterator operator++(int) { + iterator it = ptr; + ptr++; + return it; + } + + iterator &operator++() { + ++ptr; + return *this; + } + + bool operator!=(const iterator &rhs) const { + return ptr != rhs.ptr; + } + }; + + public: + iterator begin() const { + return pids.begin(); + } + + iterator end() const { + return pids.end(); + } + + size_t length() const { + return pids.length(); + } +}; + +inline ProcessInfoIterator getProcesses() { + return {}; +} + +class ThreadInfoIterator { + + IdArray tids; + int pid; + + class iterator { + + const int *ptr; + int pid; + + public: + iterator(const int *ptr, int pid) : ptr(ptr), pid(pid) {} + + ThreadInfo operator*() { + return ThreadInfo{pid, *ptr}; + } + + iterator operator++(int) { + iterator it{ptr, pid}; + ptr++; + return it; + } + + iterator &operator++() { + ++ptr; + return *this; + } + + bool operator!=(const iterator &rhs) const { + return ptr != rhs.ptr; + } + }; + + public: + ThreadInfoIterator(int pid) : tids(getAllTids(pid)), pid(pid) {} + iterator begin() const { + return {tids.begin(), pid}; + } + + iterator end() const { + return {tids.end(), pid}; + } +}; + +inline ThreadInfoIterator getThreads(int pid) { + return {pid}; +} + +struct Registers : reg { // these are helpers as it is annoying that register_t is signed + uintptr_t rax() const noexcept { return r_rax; } + void rax(uintptr_t value) noexcept { r_rax = static_cast(value); } + uintptr_t rbx() const noexcept { return r_rbx; } + void rbx(uintptr_t value) noexcept { r_rbx = static_cast(value); } + uintptr_t rcx() const noexcept { return r_rcx; } + void rcx(uintptr_t value) noexcept { r_rcx = static_cast(value); } + uintptr_t rdx() const noexcept { return r_rdx; } + void rdx(uintptr_t value) noexcept { r_rdx = static_cast(value); } + uintptr_t rsi() const noexcept { return r_rsi; } + void rsi(uintptr_t value) noexcept { r_rsi = static_cast(value); } + uintptr_t rdi() const noexcept { return r_rdi; } + void rdi(uintptr_t value) noexcept { r_rdi = static_cast(value); } + uintptr_t rbp() const noexcept { return r_rbp; } + void rbp(uintptr_t value) noexcept { r_rbp = static_cast(value); } + uintptr_t rsp() const noexcept { return r_rsp; } + void rsp(uintptr_t value) noexcept { r_rsp = static_cast(value); } + uintptr_t rip() const noexcept { return r_rip; } + void rip(uintptr_t value) noexcept { r_rip = static_cast(value); } + uintptr_t r8() const noexcept { return r_r8; } + void r8(uintptr_t value) noexcept { r_r8 = static_cast(value); } + uintptr_t r9() const noexcept { return r_r9; } + void r9(uintptr_t value) noexcept { r_r9 = static_cast(value); } + uintptr_t r10() const noexcept { return r_r10; } + void r10(uintptr_t value) noexcept { r_r10 = static_cast(value); } + uintptr_t r11() const noexcept { return r_r11; } + void r11(uintptr_t value) noexcept { r_r11 = static_cast(value); } + uintptr_t r12() const noexcept { return r_r12; } + void r12(uintptr_t value) noexcept { r_r12 = static_cast(value); } + uintptr_t r13() const noexcept { return r_r13; } + void r13(uintptr_t value) noexcept { r_r13 = static_cast(value); } + uintptr_t r14() const noexcept { return r_r14; } + void r14(uintptr_t value) noexcept { r_r14 = static_cast(value); } + uintptr_t r15() const noexcept { return r_r15; } + void r15(uintptr_t value) noexcept { r_r15 = static_cast(value); } + uintptr_t cs() const noexcept { return r_cs; } + void cs(uintptr_t value) noexcept { r_cs = static_cast(value); } + uintptr_t ss() const noexcept { return r_ss; } + void ss(uintptr_t value) noexcept { r_ss = static_cast(value); } + uintptr_t rflags() const noexcept { return r_rflags; } + void rflags(uintptr_t value) noexcept { r_rflags = static_cast(value); } + uint32_t trapno() const noexcept { return r_trapno; } + void trapno(uint32_t value) noexcept { r_trapno = value; } + uint32_t err() const noexcept { return r_err; } + void err(uint32_t value) noexcept { r_err = value; } + uint16_t fs() const noexcept { return r_fs; } + void fs(uint16_t value) noexcept { r_fs = value; } + uint16_t gs() const noexcept { return r_gs; } + void gs(uint16_t value) noexcept { r_gs = value; } + uint16_t es() const noexcept { return r_es; } + void es(uint16_t value) noexcept { r_es = value; } + uint16_t ds() const noexcept { return r_ds; } + void ds(uint16_t value) noexcept { r_ds = value; } + + void setArgs(uintptr_t a) noexcept { + rdi(a); + } + void setArgs(uintptr_t a, uintptr_t b) noexcept { + rdi(a); rsi(b); + } + void setArgs(uintptr_t a, uintptr_t b, uintptr_t c) noexcept { + rdi(a); rsi(b); rdx(c); + } + void setArgs(uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d) noexcept { + rdi(a); rsi(b); rdx(c); rcx(d); + } + void setArgs(uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d, uintptr_t e) noexcept { + rdi(a); rsi(b); rdx(c); rcx(d); r8(e); + } + void setArgs(uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d, uintptr_t e, uintptr_t f) noexcept { + rdi(a); rsi(b); rdx(c); rcx(d); r8(e); r9(f); + } + void dump() const noexcept { + printf("rax: 0x%08llx\n", rax()); + printf("rbx: 0x%08llx\n", rbx()); + printf("rcx: 0x%08llx\n", rcx()); + printf("rdx: 0x%08llx\n", rdx()); + printf("rsi: 0x%08llx\n", rsi()); + printf("rdi: 0x%08llx\n", rdi()); + printf("r8: 0x%08llx\n", r8()); + printf("r9: 0x%08llx\n", r9()); + printf("r10: 0x%08llx\n", r10()); + printf("r11: 0x%08llx\n", r11()); + printf("r12: 0x%08llx\n", r12()); + printf("r13: 0x%08llx\n", r13()); + printf("r14: 0x%08llx\n", r14()); + printf("r15: 0x%08llx\n", r15()); + printf("rbp: 0x%08llx\n", rbp()); + printf("rsp: 0x%08llx\n", rsp()); + printf("rip: 0x%08llx\n", rip()); + } +}; + +class Tracer { + + static constexpr int JITSHM_CREATE = 533; + static constexpr int JITSHM_ALIAS = 534; + static constexpr int MMAP = 477; + static constexpr int MUNMAP = 73; + static constexpr int MPROTECT = 74; + static constexpr int CLOSE = 6; + static constexpr int SOCKET = 97; + static constexpr int PIPE = 42; + static constexpr int PIPE2 = 687; + static constexpr int SETSOCKOPT = 105; + + mutable uintptr_t syscall_addr; + mutable uintptr_t libkernel_base; + mutable uintptr_t errno_addr; + int pid; + + static int ptrace(int request, pid_t pid, caddr_t addr, int data) noexcept { + AuthidSwapper swapper{PTRACE_ID}; + return ::ptrace(request, pid, addr, data); + } + + uintptr_t call(uintptr_t addr, uintptr_t a) const noexcept { + const Registers backup = getRegisters(); + Registers jmp = backup; + jmp.rip(addr); + jmp.setArgs(a); + return call(backup, jmp); + } + uintptr_t call(uintptr_t addr, uintptr_t a, uintptr_t b) const noexcept { + const Registers backup = getRegisters(); + Registers jmp = backup; + jmp.rip(addr); + jmp.setArgs(a, b); + return call(backup, jmp); + } + uintptr_t call(uintptr_t addr, uintptr_t a, uintptr_t b, uintptr_t c) const noexcept { + const Registers backup = getRegisters(); + Registers jmp = backup; + jmp.rip(addr); + jmp.setArgs(a, b, c); + return call(backup, jmp); + } + uintptr_t call(uintptr_t addr, uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d) const noexcept { + const Registers backup = getRegisters(); + Registers jmp = backup; + jmp.rip(addr); + jmp.setArgs(a, b, c, d); + return call(backup, jmp); + } + uintptr_t call(uintptr_t addr, uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d, uintptr_t e) const noexcept { + const Registers backup = getRegisters(); + Registers jmp = backup; + jmp.rip(addr); + jmp.setArgs(a, b, c, d, e); + return call(backup, jmp); + } + uintptr_t call(uintptr_t addr, uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t d, uintptr_t e, uintptr_t f) const noexcept { + const Registers backup = getRegisters(); + Registers jmp = backup; + jmp.rip(addr); + jmp.setArgs(a, b, c, d, e, f); + return call(backup, jmp); + } + // any remaining attempts to use the templated call will fail + + uintptr_t call(const Registers &backup, Registers &jmp) const noexcept; + uintptr_t syscall(const Registers &backup, Registers &jmp) const noexcept; + + public: + Tracer(int pid) noexcept : syscall_addr(0), libkernel_base(0), errno_addr(0), pid(pid) { + if (pid == getpid()) { + this->pid = 0; + } else { + if (ptrace(PT_ATTACH, pid, 0, 0) < 0) { + ::perror("ptrace PT_ATTACH Tracer::Tracer"); + this->pid = 0; + } else { + wait(); + } + } + } + Tracer(const Tracer&) = delete; + Tracer &operator=(const Tracer&) = delete; + Tracer(Tracer &&rhs) noexcept : syscall_addr(rhs.syscall_addr), libkernel_base(rhs.libkernel_base), errno_addr(rhs.errno_addr), pid(rhs.pid) { + rhs.syscall_addr = 0; + rhs.errno_addr = 0; + rhs.libkernel_base = 0; + rhs.pid = 0; + } + Tracer &operator=(Tracer &&rhs) noexcept { + if (pid != 0) [[unlikely]] { + if (ptrace(PT_DETACH, pid, 0, 0) < 0) { + ::perror("ptrace PT_DETACH Tracer::operator="); + } + } + syscall_addr = rhs.syscall_addr; + libkernel_base = rhs.libkernel_base; + errno_addr = rhs.errno_addr; + pid = rhs.pid; + rhs.syscall_addr = 0; + rhs.libkernel_base = 0; + rhs.errno_addr = 0; + rhs.pid = 0; + return *this; + } + ~Tracer() noexcept { + if (pid != 0) { + if (ptrace(PT_DETACH, pid, 0, 0) < 0) { + ::perror("ptrace PT_DETACH Tracer::~Tracer"); + } + } + } + + explicit operator bool() const noexcept { + return pid != 0; + } + + int wait(int options=0) const noexcept { + int status = 0; + if (waitpid(pid, &status, options) < 0) { + ::perror("waitpid"); + return -1; + } + return status; + } + + Registers getRegisters() const noexcept { + Registers r; + ptrace(PT_GETREGS, pid, reinterpret_cast(&r), 0); + return r; + } + + bool getRegisters(Registers &r) const noexcept { + return ptrace(PT_GETREGS, pid, reinterpret_cast(&r), 0) >= 0; + } + + bool setRegisters(const Registers ®s) const noexcept { + struct reg *r = const_cast(®s); // NOLINT(*) + return ptrace(PT_SETREGS, pid, reinterpret_cast(r), 0) >= 0; + } + + int step() const noexcept { + ptrace(PT_STEP, pid, reinterpret_cast(1), 0); + return wait(); + } + + int run(bool wait=true) const noexcept { // continue is a keyword :/ + ptrace(PT_CONTINUE, pid, reinterpret_cast(1), 0); + if (wait) { + return this->wait(); + } + return 0; + } + + int kill() noexcept { + int res = ptrace(PT_KILL, pid, 0, 0); + if (res < 0) { + ::perror("kill"); + return res; + } + pid = 0; + return 0; + } + + template + R call(uintptr_t addr, Args ...args) const noexcept { + const Registers backup = getRegisters(); + Registers jmp = backup; + jmp.rip(addr); + // using C cast since it isn't known what cast is required + jmp.setArgs(((uintptr_t) args)...); // NOLINT(*) + return (R) call(backup, jmp); // NOLINT(*) + } + + template + R syscall(int num, Args ...args) const noexcept { + const Registers backup = getRegisters(); + Registers jmp = backup; + jmp.rax(static_cast(num)); + // using C cast since it isn't known what cast is required + jmp.setArgs(((uintptr_t) args)...); // NOLINT(*) + jmp.r10(jmp.rcx()); + return (R) syscall(backup, jmp); // NOLINT(*) + } + + int jitshm_create(uintptr_t name, size_t size, int flags) const noexcept { + return syscall(JITSHM_CREATE, name, size, flags); + } + + int jitshm_alias(int fd, int flags) const noexcept { + return syscall(JITSHM_ALIAS, fd, flags); + } + + uintptr_t mmap(uintptr_t addr, size_t len, int prot, int flags, int fd, off_t off) const noexcept { + return syscall(MMAP, addr, len, prot, flags, fd, off); + } + + int munmap(uintptr_t addr, size_t len) const noexcept { + return syscall(MUNMAP, addr, len); + } + + int mprotect(uintptr_t addr, size_t len, int prot) const noexcept { + return syscall(MPROTECT, addr, len, prot); + } + + int close(int fd) const noexcept { + return syscall(CLOSE, fd); + } + + int socket(int domain, int type, int protocol) const noexcept { + return syscall(SOCKET, domain, type, protocol); + } + + int pipe(int *fildes) const noexcept; + int dynlib_load_prx(const char *path, int *handle) const noexcept; + int dynlib_dlsym(int handle, const char *name, void **address) const noexcept; + + + int setsockopt(int s, int level, int optname, const void *optval, unsigned int optlen) const noexcept; + + void perror(const char *msg) const noexcept; +}; + +} // dbg diff --git a/extern/libhijacker/include/elf/elf.hpp b/extern/libhijacker/include/elf/elf.hpp new file mode 100644 index 0000000..efe7c07 --- /dev/null +++ b/extern/libhijacker/include/elf/elf.hpp @@ -0,0 +1,64 @@ +#pragma once + +extern "C" { + #include + #include + #include +} + +#include "hijacker.hpp" +#include "util.hpp" + +class ManagedResolver; + +constexpr auto i = sizeof(Elf64_Dyn); + +class Elf : Elf64_Ehdr { + + public: + struct MappedMemory { + uintptr_t mem; + size_t len; + }; + + private: + dbg::Tracer tracer; + const Elf64_Phdr *__restrict phdrs; + const char *__restrict strtab; + size_t strtabLength; + const Elf64_Sym *__restrict symtab; + size_t symtabLength; + const Elf64_Rela *__restrict relatbl; + size_t relaLength; + const Elf64_Rela *__restrict plt; + size_t pltLength; + Hijacker *__restrict hijacker; + size_t textOffset; + uintptr_t imagebase; + uint8_t *data; + UniquePtr resolver; + Array mappedMemory; + int jitFd; + + bool processProgramHeaders() noexcept; + bool parseDynamicTable() noexcept; + bool processRelocations() noexcept; + bool processPltRelocations() noexcept; + bool load() noexcept; + bool start(uintptr_t args) noexcept; + uintptr_t setupKernelRW() noexcept; + uintptr_t getSymbolAddress(const Elf64_Rela *__restrict rel) const noexcept; + + public: + Elf(Hijacker *hijacker, uint8_t *data) noexcept; + Elf(const Elf&) = delete; + Elf &operator=(const Elf&) = delete; + Elf(Elf&&) noexcept = default; + Elf &operator=(Elf&&) noexcept = default; + ~Elf() noexcept; // external linkage to prevent undefined behavior + + bool launch() noexcept; + explicit operator bool() const noexcept { + return static_cast(tracer); + } +}; diff --git a/extern/libhijacker/include/elf/loader.hpp b/extern/libhijacker/include/elf/loader.hpp new file mode 100644 index 0000000..3fdc3f2 --- /dev/null +++ b/extern/libhijacker/include/elf/loader.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "../thread.hpp" + +struct ElfLoaderBase { + void run(); +}; + +class ElfLoader : public ElfLoaderBase, public JThread { + friend struct ElfLoaderBase; + + public: + void run(); +}; diff --git a/extern/libhijacker/include/elf/nid/b64.hpp b/extern/libhijacker/include/elf/nid/b64.hpp new file mode 100644 index 0000000..9276ebf --- /dev/null +++ b/extern/libhijacker/include/elf/nid/b64.hpp @@ -0,0 +1,32 @@ +#pragma once + +extern "C" { + #include +} + +#include "../../nid.hpp" + +// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,cppcoreguidelines-pro-bounds-constant-array-index) +namespace { + +static inline constexpr char encoder[]{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"}; + +} + +static inline constexpr void b64encode(char (&dest)[NID_LENGTH+1], const unsigned char *src) { + for (unsigned int i = 0, j = 0; j < NID_LENGTH;) { + uint32_t a = src[i++]; + uint32_t b = src[i++]; + uint32_t c = src[i++]; + + uint32_t abc = (a << 16) | (b << 8) | c; + + dest[j++] = encoder[(abc >> 18) & 0x3F]; + dest[j++] = encoder[(abc >> 12) & 0x3F]; + dest[j++] = encoder[(abc >> 6) & 0x3F]; + dest[j++] = encoder[abc & 0x3F]; + } + dest[NID_LENGTH] = 0; +} + +// NOLINTEND(cppcoreguidelines-avoid-magic-numbers,cppcoreguidelines-pro-bounds-constant-array-index) diff --git a/extern/libhijacker/include/elf/nid/nid.hpp b/extern/libhijacker/include/elf/nid/nid.hpp new file mode 100644 index 0000000..3e0cd5f --- /dev/null +++ b/extern/libhijacker/include/elf/nid/nid.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "b64.hpp" +#include "../../nid.hpp" +#include "sha1.hpp" +#include "util.hpp" +#include + +static inline constexpr void fillNid(Nid &buf, const StringView &sym) { + constexpr size_t NID_DIGEST_LENGTH = 8; + constexpr size_t NID_SHA_LENGTH = 20; + uint8_t encodedDigest[NID_DIGEST_LENGTH + 1]{}; + uint8_t sha1[NID_SHA_LENGTH]{}; + genSha1(sha1, sym); + for (size_t i = 0; i < NID_DIGEST_LENGTH; i++) { + encodedDigest[i] = sha1[NID_DIGEST_LENGTH - 1 - i]; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index) + } + b64encode(buf.str, encodedDigest); +} diff --git a/extern/libhijacker/include/elf/nid/sha1.hpp b/extern/libhijacker/include/elf/nid/sha1.hpp new file mode 100644 index 0000000..dae65ca --- /dev/null +++ b/extern/libhijacker/include/elf/nid/sha1.hpp @@ -0,0 +1,306 @@ +#pragma once + +/* + SHA-1 in C + By Steve Reid + 100% Public Domain + */ + +extern "C" { +#include +} + +// NOLINTBEGIN(*) + +namespace { + +typedef struct +{ + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#include +#include + +/* for uint32_t */ +#include + + +#define rol __builtin_rotateleft32 + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) + +#define blk(i) (block->l[(i)&15] = rol(block->l[((i)+13)&15]^block->l[((i)+8)&15] \ + ^block->l[((i)+2)&15]^block->l[(i)&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+blk0(i)+0x5A827999+rol(v,5);(w)=rol(w,30); +#define R1(v,w,x,y,z,i) z+=(((w)&((x)^(y)))^(y))+blk(i)+0x5A827999+rol(v,5);(w)=rol(w,30); +#define R2(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0x6ED9EBA1+rol(v,5);(w)=rol(w,30); +#define R3(v,w,x,y,z,i) z+=((((w)|(x))&(y))|((w)&(x)))+blk(i)+0x8F1BBCDC+rol(v,5);(w)=rol(w,30); +#define R4(v,w,x,y,z,i) z+=((w)^(x)^(y))+blk(i)+0xCA62C1D6+rol(v,5);(w)=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +static inline void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) { + typedef union { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; + +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + + __builtin_memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer; +#endif + /* Copy context->state[] to working vars */ + uint32_t a = state[0]; + uint32_t b = state[1]; + uint32_t c = state[2]; + uint32_t d = state[3]; + uint32_t e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +#ifdef SHA1HANDSOFF + __builtin_memset(block, '\0', sizeof(block)); +#endif +} + + +/* SHA1Init - Initialize new context */ + +static inline void SHA1Init(SHA1_CTX * context) { + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +static inline void SHA1Update(SHA1_CTX *__restrict context, const unsigned char *data, uint32_t len) { + uint32_t i; + + uint32_t j; + + j = context->count[0]; + context->count[0] += len << 3; + if (context->count[0] < j) + context->count[1]++; + context->count[1] += (len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) + { + __builtin_memcpy(&context->buffer[j], data, (i = 64 - j)); + SHA1Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) + { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else + i = 0; + __builtin_memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +static inline void SHA1Final( + unsigned char *digest, + SHA1_CTX * context +) +{ + unsigned i; + + unsigned char finalcount[8]; + + unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t} +#else + for (i = 0; i < 8; i++) + { + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) + { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) + { + digest[i] = (unsigned char) + ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + /* Wipe variables */ + __builtin_memset(context, '\0', sizeof(*context)); + __builtin_memset(&finalcount, '\0', sizeof(finalcount)); +} + +static inline constexpr uint8_t NID_KEY[]{ + (uint8_t) 0x51, (uint8_t) 0x8D, (uint8_t) 0x64, (uint8_t) 0xA6, + (uint8_t) 0x35, (uint8_t) 0xDE, (uint8_t) 0xD8, (uint8_t) 0xC1, + (uint8_t) 0xE6, (uint8_t) 0xB0, (uint8_t) 0x39, (uint8_t) 0xB1, + (uint8_t) 0xC3, (uint8_t) 0xE5, (uint8_t) 0x52, (uint8_t) 0x30 +}; + +} + +struct Sha1 { + uint8_t hash[20]; +}; + +#include "util.hpp" + +static inline void genSha1(uint8_t *res, const StringView &str) { + SHA1_CTX ctx; + + SHA1Init(&ctx); + SHA1Update(&ctx, (const unsigned char*)str.c_str(), str.length()); + SHA1Update(&ctx, NID_KEY, sizeof(NID_KEY)); + SHA1Final(res, &ctx); +} + +// NOLINTEND(*) diff --git a/extern/libhijacker/include/hijacker.hpp b/extern/libhijacker/include/hijacker.hpp new file mode 100644 index 0000000..66aa063 --- /dev/null +++ b/extern/libhijacker/include/hijacker.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "hijacker/hijacker.hpp" // IWYU pragma: export +#include "hijacker/spawner.hpp" // IWYU pragma: export + +class ScopedSuspender { + Hijacker *hijacker; + + public: + ScopedSuspender(Hijacker *hijacker) : hijacker(hijacker) { hijacker->suspend(); } + ScopedSuspender(const ScopedSuspender&) = delete; + ScopedSuspender(ScopedSuspender&&) = delete; + ScopedSuspender &operator=(const ScopedSuspender&) = delete; + ScopedSuspender &operator=(ScopedSuspender&&) = delete; + ~ScopedSuspender() { hijacker->resume(); } +}; diff --git a/extern/libhijacker/include/hijacker/allocator.hpp b/extern/libhijacker/include/hijacker/allocator.hpp new file mode 100644 index 0000000..e8ae962 --- /dev/null +++ b/extern/libhijacker/include/hijacker/allocator.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "kernel.hpp" + +// "allocates" memory from another process out of EXISTING process memory +class ProcessMemoryAllocator { + static constexpr size_t ALIGNMENT = 0x10; + static constexpr size_t ALIGNMENT_MASK = 0xf; + const SharedLibSection *section; + size_t allocated; + + public: + ProcessMemoryAllocator(decltype(nullptr)) : section(nullptr), allocated() {} + ProcessMemoryAllocator(const SharedLibSection *section) : section(section), allocated() {} + + /** + * "Releases" all the allocated memory + */ + void release() { + allocated = 0; + } + + /** + * Allocated virtual memory from the end of the existing section. + * It is allocated from the end because this portion of memory is likely unused. + * @param size the size of memory to "allocate" + * @return the virtual address for the requested memory + */ + uintptr_t allocate(size_t size) { + if ((size & ALIGNMENT_MASK) != 0) { + size = (size & ~ALIGNMENT_MASK) + ALIGNMENT; + } + allocated += size; + return section->end() - allocated; + } + + operator bool() const { + return section != nullptr; + } + + bool operator==(decltype(nullptr)) const { + return section == nullptr; + } +}; diff --git a/extern/libhijacker/include/hijacker/hijacker.hpp b/extern/libhijacker/include/hijacker/hijacker.hpp new file mode 100644 index 0000000..fe39f35 --- /dev/null +++ b/extern/libhijacker/include/hijacker/hijacker.hpp @@ -0,0 +1,179 @@ +#pragma once + +#include "dbg.hpp" +#include "hijacker/allocator.hpp" +#include "memory.hpp" +#include "kernel.hpp" +#include "kernel/rtld.hpp" +#include "util.hpp" +#include "allocator.hpp" +#include + +class Hijacker { + +#ifndef LIBKERNEL_HANDLE + static constexpr int LIBKERNEL_HANDLE = 0x2001; +#endif + + UniquePtr obj; + + protected: + friend class Spawner; + ProcessMemoryAllocator textAllocator; + ProcessMemoryAllocator dataAllocator; + + private: + mutable UniquePtr libkernel; + mutable int mainThreadId = -1; + bool isMainThreadRunning = true; + + Hijacker(SharedObject *obj) : obj(obj), textAllocator(nullptr), dataAllocator(nullptr), libkernel(nullptr) { + auto eboot = this->obj->getEboot(); + while (eboot == nullptr) { + // this can happen when it is still loading + eboot = this->obj->getEboot(); + } + while (textAllocator == nullptr) { + textAllocator = ProcessMemoryAllocator(eboot->getTextSection()); + } + while (dataAllocator == nullptr) { + dataAllocator = ProcessMemoryAllocator(eboot->getDataSection()); + } + } + + RtldMeta *getLibKernelMetaData() const { + auto *meta = getLibKernel(); + return meta ? meta->getMetaData() : nullptr; + } + + public: + uintptr_t getLibKernelBase() const { + RtldMeta *meta = getLibKernelMetaData(); + return meta ? meta->imageBase : 0; + } + private: + int getMainThreadId() const; + + public: + static UniquePtr getHijacker(const StringView &processName); + static UniquePtr getHijacker(int pid) { + auto p = ::getProc(pid); + if (p == nullptr) [[unlikely]] { + return nullptr; + } + + auto obj = p->getSharedObject(); + + // obj may be a nullptr when racing process creation + return obj != nullptr ? new Hijacker{obj.release()} : nullptr; + } + + UniquePtr getProc() const { + return ::getProc(getPid()); + } + + void suspend() { + if (isMainThreadRunning) { + dbg::suspend(obj->pid); + isMainThreadRunning = false; + } + } + + void resume() { + if (!isMainThreadRunning) { + dbg::resume(obj->pid); + isMainThreadRunning = true; + } + } + + int getPid() const { + return obj->pid; + } + + SharedLib *getEboot() const { + return obj->getEboot(); + } + + uintptr_t imagebase() const { + auto eboot = obj->getEboot(); + return eboot ? eboot->imagebase() : 0; + } + + SharedLib *getLibKernel() const { + if (libkernel == nullptr) [[unlikely]] { + libkernel = obj->getLib(LIBKERNEL_HANDLE); + } + return libkernel.get(); + } + + UniquePtr getLib(int handle) const { + return obj->getLib(handle); + } + + UniquePtr getLib(const StringView &name) const { + return obj->getLib(name); + } + + SharedLibIterator getLibs() const { + return obj->getLibs(); + } + + UniquePtr getTrapFrame() const; + void jailbreak(bool escapeSandbox=true) const; + uintptr_t getFunctionAddress(const SharedLib *lib, const Nid &fname) const noexcept; + + uintptr_t getLibKernelFunctionAddress(const Nid &fname) const { + return getFunctionAddress(getLibKernel(), fname); + } + + uintptr_t getLibKernelAddress(const Nid &fname) const { + return getFunctionAddress(getLibKernel(), fname); + } + + ProcessMemoryAllocator &getDataAllocator() { + return dataAllocator; + } + + ProcessMemoryAllocator &getTextAllocator() { + return textAllocator; + } + + UniquePtr read(uintptr_t src, size_t size) { + return dbg::read(getPid(), src, size).release(); + } + + bool read(uintptr_t src, void *dst, size_t size) { + return dbg::read(getPid(), src, dst, size); + } + + template + T read(uintptr_t vaddr) { + T t; + read(vaddr, &t, sizeof(t)); + return t; + } + + template + bool write(uintptr_t vaddr, const uint8_t(&buf)[size]) { + return dbg::write(getPid(), vaddr, buf, size); + } + + bool write(uintptr_t vaddr, const void *src, size_t size) { + return dbg::write(getPid(), vaddr, src, size); + } + + template + bool write(uintptr_t vaddr, const T &value) { + return write(vaddr, &value, sizeof(value)); + } + + template + ProcessPointer getPointer(uintptr_t addr) const { + return {getPid(), addr}; + } + + void hexdump(uintptr_t addr, size_t size) { + auto buf = read(addr, size); + ::hexdump(buf.get(), size); + } +}; diff --git a/extern/libhijacker/include/hijacker/memory.hpp b/extern/libhijacker/include/hijacker/memory.hpp new file mode 100644 index 0000000..ab86069 --- /dev/null +++ b/extern/libhijacker/include/hijacker/memory.hpp @@ -0,0 +1,79 @@ +#pragma once + +extern "C" { + #include +} + +#include "dbg.hpp" + +template +class ProcessReference { + uintptr_t addr; + int pid; + + public: + ProcessReference() = default; + ProcessReference(int pid, uintptr_t addr) : addr(addr), pid(pid) {} + ProcessReference(const ProcessReference &rhs) : addr(rhs.addr), pid(rhs.pid) {} + ProcessReference(ProcessReference&&) noexcept = delete; + ProcessReference &operator=(const ProcessReference &rhs) { + addr = rhs.addr; + pid = rhs.pid; + return *this; + } + ProcessReference &operator=(ProcessReference&&) noexcept = delete; + ProcessReference &operator=(T value) { + dbg::write(pid, addr, &value, sizeof(value)); + return *this; + } + ~ProcessReference() noexcept = default; + operator T() const { + T value; + dbg::read(pid, addr, &value, sizeof(T)); + return value; + } +}; + +template <> +class ProcessReference { + uintptr_t addr; + int pid; + + public: + ProcessReference(int pid, uintptr_t addr) : addr(addr), pid(pid) {} + ProcessReference(const ProcessReference &rhs) : addr(rhs.addr), pid(rhs.pid) {} + ProcessReference(ProcessReference&&) noexcept = delete; + ProcessReference &operator=(const ProcessReference &rhs) { + addr = rhs.addr; + pid = rhs.pid; + return *this; + } + ProcessReference &operator=(bool value) { + dbg::write(pid, addr, &value, sizeof(value)); + return *this; + } + ProcessReference &operator=(ProcessReference&&) noexcept = delete; + ~ProcessReference() noexcept = default; + explicit operator bool() const { + bool value; // NOLINT(cppcoreguidelines-init-variables) + dbg::read(pid, addr, &value, sizeof(bool)); + return value; + } +}; + +template +class ProcessPointer { + uintptr_t addr; + int pid; + + public: + ProcessPointer() : addr(), pid() {} + ProcessPointer(int pid, uintptr_t addr) : addr(addr), pid(pid) {} + ProcessReference operator*() const { return {pid, addr}; } + T get() const { + T value{}; + dbg::read(pid, addr, &value, sizeof(T)); + return value; + } + uintptr_t address() const { return addr; } +}; diff --git a/extern/libhijacker/include/hijacker/spawner.hpp b/extern/libhijacker/include/hijacker/spawner.hpp new file mode 100644 index 0000000..83cfd61 --- /dev/null +++ b/extern/libhijacker/include/hijacker/spawner.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "hijacker.hpp" +#include "hijacker/hijacker.hpp" +#include "hijacker/spawner.hpp" +#include "memory.hpp" +#include "util.hpp" +#include + +extern "C" { + #include + #include +} + +class Spawner { + dbg::IdArray pids; + uintptr_t nanosleepOffset; + int pid; + + static inline constexpr Nid _nanosleep{"NhpspxdjEKU"}; + + static uintptr_t getNanosleepOffset(const Hijacker &hijacker) { + uintptr_t addr = hijacker.getLibKernelFunctionAddress(_nanosleep); + return addr - hijacker.getLibKernelBase(); + } + + static uintptr_t getNanosleepOffset() { + auto hijacker = Hijacker::getHijacker(getpid()); + if (hijacker == nullptr) [[unlikely]] { + return 0; + } + return getNanosleepOffset(*hijacker); + } + + public: + explicit Spawner() : pids(dbg::getAllPids()), nanosleepOffset(getNanosleepOffset()), pid(getpid()) {} + Spawner(const Hijacker &ptr) : pids(dbg::getAllPids()), nanosleepOffset(getNanosleepOffset(ptr)), pid(ptr.getPid()) {} + UniquePtr bootstrap(Hijacker &hijacker); + UniquePtr spawn(); +}; diff --git a/extern/libhijacker/include/kernel.hpp b/extern/libhijacker/include/kernel.hpp new file mode 100644 index 0000000..a95c0bf --- /dev/null +++ b/extern/libhijacker/include/kernel.hpp @@ -0,0 +1,23 @@ +#pragma once + +// IWYU pragma: begin_exports + +#include "kernel/kernel.hpp" +#include "kernel/rtld.hpp" +#include "kernel/proc.hpp" +#include "kernel/frame.hpp" + +// IWYU pragma: end_exports + +#include "util.hpp" + +bool createReadWriteSockets(const UniquePtr &proc, const int *sockets) noexcept; + +inline bool createReadWriteSockets(int pid, const int *sockets) noexcept { + auto proc = getProc(pid); + if (proc == nullptr) { + puts("createReadWriteSockets proc == nullptr"); + return false; + } + return createReadWriteSockets(proc, sockets); +} diff --git a/extern/libhijacker/include/kernel/frame.hpp b/extern/libhijacker/include/kernel/frame.hpp new file mode 100644 index 0000000..fa86d5d --- /dev/null +++ b/extern/libhijacker/include/kernel/frame.hpp @@ -0,0 +1,194 @@ +#pragma once + +#include "kernel.hpp" +#include "kernel/kernel.hpp" + +namespace { + +static constexpr size_t TRAPFRAME_SIZE = 0x110; + +} + +class TrapFrame : public KernelObject { + + static constexpr size_t RAX_OFFSET = 0x30; + static constexpr size_t RBX_OFFSET = 0x38; + static constexpr size_t RCX_OFFSET = 0x18; + static constexpr size_t RDX_OFFSET = 0x10; + static constexpr size_t RSI_OFFSET = 0x08; + static constexpr size_t RDI_OFFSET = 0x00; + static constexpr size_t R8_OFFSET = 0x20; + static constexpr size_t R9_OFFSET = 0x28; + static constexpr size_t R10_OFFSET = 0x48; + static constexpr size_t R11_OFFSET = 0x50; + static constexpr size_t R12_OFFSET = 0x58; + static constexpr size_t R13_OFFSET = 0x60; + static constexpr size_t R14_OFFSET = 0x68; + static constexpr size_t R15_OFFSET = 0x70; + static constexpr size_t RBP_OFFSET = 0x40; + static constexpr size_t RSP_OFFSET = 0x100; + static constexpr size_t RIP_OFFSET = 0xe8; + + + + public: + TrapFrame(uintptr_t addr) : KernelObject(addr) {} + + TrapFrame &setFrame(const TrapFrame *frame) { + memcpy(buf, frame->buf, sizeof(buf)); + return *this; + } + + uint64_t getRax() { + return get(); + } + + TrapFrame &setRax(uint64_t rax) { + set(rax); + return *this; + } + + uint64_t getRbx() { + return get(); + } + + TrapFrame &setRbx(uint64_t rbx) { + set(rbx); + return *this; + } + + uint64_t getRcx() { + return get(); + } + + + TrapFrame &setRcx(uint64_t rcx) { + set(rcx); + return *this; + } + + uint64_t getRdx() { + return get(); + } + + TrapFrame &setRdx(uint64_t rdx) { + set(rdx); + return *this; + } + + uint64_t getRsi() { + return get(); + } + + TrapFrame &setRsi(uint64_t rsi) { + set(rsi); + return *this; + } + + uint64_t getRdi() { + return get(); + } + + TrapFrame &setRdi(uint64_t rdi) { + set(rdi); + return *this; + } + + uint64_t getR8() { + return get(); + } + + TrapFrame &setR8(uint64_t r8) { + set(r8); + return *this; + } + + uint64_t getR9() { + return get(); + } + + TrapFrame &setR9(uint64_t r9) { + set(r9); + return *this; + } + + uint64_t getR10() { + return get(); + } + + TrapFrame &setR10(uint64_t r10) { + set(r10); + return *this; + } + + uint64_t getR11() { + return get(); + } + + TrapFrame &setR11(uint64_t r11) { + set(r11); + return *this; + } + + uint64_t getR12() { + return get(); + } + TrapFrame &setR12(uint64_t r12) { + set(r12); + return *this; + } + + uint64_t getR13() { + return get(); + } + + TrapFrame &setR13(uint64_t r13) { + set(r13); + return *this; + } + + uint64_t getR14() { + return get(); + } + + TrapFrame &setR14(uint64_t r14) { + set(r14); + return *this; + } + + uint64_t getR15() { + return get(); + } + + TrapFrame &setR15(uint64_t r15) { + set(r15); + return *this; + } + + uint64_t getRbp() { + return get(); + } + + TrapFrame &setRbp(uint64_t rbp) { + set(rbp); + return *this; + } + + uint64_t getRsp() { + return get(); + } + + TrapFrame &setRsp(uint64_t rsp) { + set(rsp); + return *this; + } + + uint64_t getRip() { + return get(); + } + + TrapFrame &setRip(uint64_t rip) { + set(rip); + return *this; + } +}; diff --git a/extern/libhijacker/include/kernel/kernel.hpp b/extern/libhijacker/include/kernel/kernel.hpp new file mode 100644 index 0000000..bc5c887 --- /dev/null +++ b/extern/libhijacker/include/kernel/kernel.hpp @@ -0,0 +1,234 @@ +#pragma once + +#include "util.hpp" +#include + +extern "C" { + +#include +#include +#include +#include + +} + +namespace { + +static constexpr size_t UCRED_SIZE = 0x168; + +} + +template +void kread(uintptr_t addr, T *dst) { + kernel_copyout(addr, dst, sizeof(T)); +} + +template +T kread(uintptr_t addr) { + T dst{}; + kernel_copyout(addr, &dst, sizeof(T)); + return dst; +} + +template +void kread(uintptr_t addr, uint8_t(&buf)[length]) { + kernel_copyout(addr, buf, length); +} + +template +void kwrite(uintptr_t addr, const T& value) { + kernel_copyin(const_cast(&value), addr, sizeof(T)); // NOLINT(cppcoreguidelines-pro-type-const-cast) +} + +template +void kwrite(uintptr_t addr, const uint8_t(&buf)[length]) { + kernel_copyin(const_cast(buf), addr, length); // NOLINT(cppcoreguidelines-pro-type-const-cast) +} + +inline void kwrite(uintptr_t addr, const void *src, size_t length) { + kernel_copyin(const_cast(src), addr, length); // NOLINT(cppcoreguidelines-pro-type-const-cast) +} + +template +class KernelObject; + +template +concept SizedKernelObject = requires { T::length; }; + +template +concept DerivedKernelObject = requires (T t) { + static_cast>(t); +}; + +template +concept KernelObjectBase = SizedKernelObject && DerivedKernelObject; + +template +class KIterable; + +String getKernelString(uintptr_t addr); + +template +class KernelObject { + + uintptr_t addr; + + protected: + template + friend class KIterable; + uint8_t buf[__restrict size]; + explicit KernelObject() = default; + KernelObject(uintptr_t addr) : addr(addr) { // NOLINT(cppcoreguidelines-pro-type-member-init) + #ifdef DEBUG + if (addr == 0) [[unlikely]] { + fatalf("kernel nullpointer dereference attempted\n"); + volatile void **tmp = nullptr; + *tmp = nullptr; // NOLINT(clang-analyzer-core.NullDereference) + } + #endif + kernel_copyout(addr, buf, size); + } + + void reload() { + kernel_copyout(addr, buf, size); + } + + template + T get() const { + static_assert(offset < size, "offset >= size"); + return *(T *)(buf + offset); + } + + template + void set(T value) { + static_assert(offset < size, "offset >= size"); + *(T *)(buf + offset) = value; + } + + template + String getString() const { + uintptr_t addr = get(); + return getKernelString(addr); + } + + public: + static constexpr size_t length = size; + KernelObject(KernelObject &&rhs) noexcept = default; + KernelObject &operator=(KernelObject &&rhs) noexcept = default; + KernelObject(const KernelObject &rhs) = default; + KernelObject &operator=(const KernelObject &rhs) = default; + ~KernelObject() noexcept = default; + + explicit operator bool() const { + return addr; + } + + const void *data() const { + return buf; + } + + uintptr_t address() const { + return addr; + } + + void flush() const { + #ifdef DEBUG + if (!addr) [[unlikely]] { + fatalf("nullptr dereference\n"); + } + #endif + kernel_copyin(const_cast(buf), addr, size); // NOLINT(cppcoreguidelines-pro-type-const-cast) + } +}; + + +template +class KPointer { + + protected: + uintptr_t addr; + + public: + KPointer(decltype(nullptr)) noexcept : addr(0) {} + KPointer(uintptr_t addr) noexcept : addr(addr) {} + T operator*() const { + T t; + kernel_copyout(addr, &t, sizeof(T)); + return t; + } + + explicit operator bool() const { + return addr; + } + + bool operator==(const KPointer &rhs) const { return addr == rhs.addr; } +}; + +template +struct KIterator; + +template +class KIterable { + + friend struct KIterator; + + protected: + + uintptr_t addr; + + KIterable(decltype(nullptr)) : addr() {} + + public: + KIterable(uintptr_t addr) : addr(addr) {} + + UniquePtr operator*() { + return {new T{addr}}; + } + + bool operator!=(const KIterable &rhs) const { return addr != rhs.addr; } + + KIterable &operator++() { + #ifdef DEBUG + if (addr == 0) [[unlikely]] { + return *this; + } + #endif + uintptr_t ptr = 0; + kernel_copyout(addr, &ptr, sizeof(ptr)); + addr = ptr; + return *this; + } + + explicit operator bool() const { return addr; } +}; + +template +struct KIterator { + + uintptr_t addr; + + KIterator(uintptr_t addr) : addr(addr) {} + KIterable begin() const { + return addr; + } + KIterable end() const { + return nullptr; + } + +}; + +class KUcred : public KernelObject { + + static constexpr size_t AUTHID_OFFSET = 0x58; + + public: + KUcred(uintptr_t addr) : KernelObject(addr) {} + + uint64_t authid() const { + return get(); + } + + void authid(uint64_t value) { + set(value); + } +}; diff --git a/extern/libhijacker/include/kernel/kthread.hpp b/extern/libhijacker/include/kernel/kthread.hpp new file mode 100644 index 0000000..c6b91b8 --- /dev/null +++ b/extern/libhijacker/include/kernel/kthread.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "kernel.hpp" +#include "frame.hpp" + +namespace { + +static constexpr size_t THREAD_SIZE = 0x680; + +} + +class KThread : public KernelObject { + + static constexpr size_t TID_OFFSET = 0x9c; + static constexpr size_t FRAME_OFFSET = 0x460; + + public: + KThread(uintptr_t addr) : KernelObject(addr) {} + + UniquePtr getFrame() { + uintptr_t ptr = get(); + return ptr ? new TrapFrame(ptr) : nullptr; + } + + int tid() const { + return get(); + } +}; diff --git a/extern/libhijacker/include/kernel/proc.hpp b/extern/libhijacker/include/kernel/proc.hpp new file mode 100644 index 0000000..1a252f9 --- /dev/null +++ b/extern/libhijacker/include/kernel/proc.hpp @@ -0,0 +1,206 @@ +#pragma once + +#include "kernel.hpp" +#include "kernel/kernel.hpp" +#include "rtld.hpp" +#include "kthread.hpp" +#include "../offsets.hpp" +#include "util.hpp" +#include + +extern "C" { + #include + extern int getpid(); + extern uint64_t kernel_base; +} + +namespace { + +static constexpr size_t SELFINFO_SIZE = 0x400; +static constexpr size_t SELFINFO_NAME_SIZE = 32; +static constexpr size_t SELFINFO_PATH_SIZE = 972; + +static constexpr size_t FILEDESCENT_SIZE = 0x30; +static constexpr size_t PROC_SIZE = 0xc90; + +} + +struct SelfInfo { + uint32_t type; + uint32_t ps4_sdk_version; + uint32_t ps5_sdk_version; + uint32_t beef_face; + uint32_t dbbcc; + char name[SELFINFO_NAME_SIZE]; + char path[SELFINFO_PATH_SIZE]; +}; + +static_assert(sizeof(SelfInfo) == SELFINFO_SIZE, "sizeof(SelfInfo) != 0x400"); + +class Filedescent : public KernelObject { + + static constexpr size_t FILE_OFFSET = 0; + static constexpr size_t FLAGS_OFFSET = 0x28; + static constexpr size_t SEQ_OFFSET = 0x2c; + + public: + Filedescent(uintptr_t addr) : KernelObject(addr) {} + uintptr_t file() const { + return get(); + } + + uint8_t flags() const { + return get(); + } + + uint32_t seq() const { + return get(); + } +}; + +class FdTbl { + uintptr_t addr; + size_t ntables; + + size_t checkIndex(size_t i) const { + #ifdef DEBUG + if (i >= ntables) [[unlikely]] { + fatalf("index %llu is out of bounds for length %llu\n", i, ntables); + } + #endif + return i; + } + + public: + FdTbl(uintptr_t addr) : addr(addr), ntables((uint32_t)kread(addr)) {} + + Filedescent operator[](size_t i) const { + return {addr + (checkIndex(i) * Filedescent::length) + sizeof(ntables)}; + } + + uintptr_t getFile(size_t i) const { + uintptr_t fp = addr + (checkIndex(i) * Filedescent::length) + sizeof(ntables); + return kread(fp); + } + + void setFile(size_t i, uintptr_t file) const { + uintptr_t fp = addr + (checkIndex(i) * Filedescent::length) + sizeof(ntables); + kwrite(fp, file); + } + + uintptr_t getFileData(size_t i) const { + return kread(getFile(i)); + } + + void setFileData(size_t i, uintptr_t data) const { + kwrite(getFile(i), data); + } + + size_t length() const { + return ntables; + } +}; + +class KProc : public KernelObject { + + static constexpr size_t UCRED_OFFSET = 0x40; + static constexpr size_t PATH_OFFSET = 0x3d8; + static constexpr size_t SHARED_OBJECT_OFFSET = 0x3e8; + static constexpr size_t PID_OFFSET = 0xbc; + static constexpr size_t THREADS_OFFSET = 0x10; + static constexpr size_t FD_OFFSET = 0x48; + static constexpr size_t TITLEID_OFFSET = 0x470; + static constexpr size_t SELFINFO_OFFSET = 0x588; + static constexpr size_t SELFINFO_NAME_OFFSET = 0x59C; + + public: + + KProc(uintptr_t addr) : KernelObject(addr) {} + uintptr_t p_ucred() const { + return get(); + } + + UniquePtr ucred() const { + return new KUcred{p_ucred()}; + } + + int p_pid() const { + return get(); + } + + int pid() const { + return p_pid(); + } + + UniquePtr getSharedObject() const { + auto obj = get(); + return obj != 0 ? new SharedObject{obj, pid()} : nullptr; + } + + String getPath() const { + return getString(); + } + + KIterator p_threads() const { + return address() + THREADS_OFFSET; + } + + KIterator getThreads() const { + return p_threads(); + } + + UniquePtr getThread(int tid) const { + for (auto td : getThreads()) { + if (tid == td->tid()) { + return td.release(); + } + } + return nullptr; + } + + uintptr_t p_fd() const { + return get(); + } + + FdTbl getFdTbl() const { + return {kread(p_fd())}; + } + + const SelfInfo *getSelfInfo() const { + return reinterpret_cast(buf + SELFINFO_OFFSET); + } + + // no flush required + void setName(const StringView &name, bool reload=false) { + const size_t length = name.length() < (SELFINFO_NAME_SIZE-1) ? name.length() : SELFINFO_NAME_SIZE; + kwrite(address() + SELFINFO_NAME_OFFSET, name.c_str(), length + 1); + if (reload) { + this->reload(); + } + } + + void setTID(const StringView &tid) { + const size_t length = tid.length() < (TITLEID_OFFSET-1) ? tid.length() : TITLEID_OFFSET; + kwrite(address() + TITLEID_OFFSET, tid.c_str(), length + 1); + } + + String getTitleId() const noexcept { + constexpr auto TITLE_ID_SIZE = 9; + return StringView{reinterpret_cast(buf) + TITLEID_OFFSET, TITLE_ID_SIZE}; + } + + String titleId() const + { + return getKernelString(address() + TITLEID_OFFSET); + } +}; + +inline KIterator getAllProcs() { + return {kernel_base + offsets::allproc()}; +} + +UniquePtr getProc(int pid); + +inline UniquePtr getProc() { + return getProc(getpid()); +} diff --git a/extern/libhijacker/include/kernel/rtld.hpp b/extern/libhijacker/include/kernel/rtld.hpp new file mode 100644 index 0000000..8188c81 --- /dev/null +++ b/extern/libhijacker/include/kernel/rtld.hpp @@ -0,0 +1,558 @@ +#pragma once + +#include "kernel.hpp" +#include "nid.hpp" +#include "util.hpp" +#include +#include +#include + +extern "C" { + #include +} + + +class RtldMeta; + +// namespace to hold these commonly named classes +// to prevent api conflict in the future +namespace rtld { + +class ElfSymbolTable; +class ElfSymbol; +class ElfStringTable; + +} + +namespace { + +static UniquePtr newRtldMeta(uintptr_t imageBase, uintptr_t addr); + +static UniquePtr newSymbolTable(const RtldMeta *meta); + +static UniquePtr newStringTable(const RtldMeta *meta); + +static constexpr size_t SHARED_LIB_SECTION_SIZE = 0x18; +static constexpr size_t SHARED_LIB_SIZE = 0x200; +static constexpr size_t SHARED_OBJECT_SIZE = 0x188; +static constexpr size_t RTLD_META_SIZE = 0x118; + +} + +enum class SectionType : uint32_t { + UNKNOWN = 0, + TEXT = 1, + XOTEXT = 2, + RODATA = 4, + RELRO = 8, + DATA = 16, + INVALID = (uint32_t)-1 +}; + +class SharedLibSection : public KernelObject { + + friend class SharedLib; + + static constexpr size_t TYPE_OFFSET = 8; + static constexpr size_t ADDRESS_OFFSET = 8; + static constexpr size_t LENGTH_OFFSET = 16; + + mutable SectionType _type; + + public: + SharedLibSection() = default; + SharedLibSection(uintptr_t addr) : KernelObject(addr), _type(SectionType::INVALID) {} + + SectionType type() const { + if (_type == SectionType::INVALID) { + _type = kread(get() + TYPE_OFFSET); + } + return _type; + } + + uintptr_t start() const { + return get(); + } + + uintptr_t end() const { + return start() + sectionLength(); + } + + size_t sectionLength() const { + return get(); + } +}; + +struct EhFrameInfo { + uintptr_t hdr; + size_t hdrSize; + uintptr_t frame; + size_t frameSize; +}; + +class SharedLib : public KernelObject { + + static constexpr size_t DYNLIB_OFFSET = 0x148; + static constexpr size_t HANDLE_OFFSET = 0x28; + static constexpr size_t PATH_OFFSET = 0x8; + static constexpr size_t NUM_SECTIONS_OFFSET = 0x48; + static constexpr size_t SECTIONS_OFFSET = 0x40; + static constexpr size_t IMAGEBASE_OFFSET = 0x30; + static constexpr size_t EHFRAME_OFFSET = 0xf8; + static constexpr size_t EHFRAME_HDR_SIZE_OFFSET = 0x100; + static constexpr size_t EHFRAME_HDR_OFFSET = 0x108; + static constexpr size_t EHFRAME_SIZE_OFFSET = 0x110; + + mutable String path; + mutable Array sections; + mutable UniquePtr meta; + + public: + int pid; + + SharedLib(uintptr_t addr, int pid) + : KernelObject(addr), path(nullptr), sections(nullptr), meta(nullptr), pid(pid) {} + + int handle() const { + return get(); + } + + StringView getPath() const { + if (path.length() == 0) [[unlikely]] { + path = getString(); + } + return {path.c_str(), path.length()}; + } + + const Array &getSections() const { + if (sections == nullptr) [[unlikely]] { + size_t numSections = get(); + uintptr_t ptr = get(); + sections = Array{numSections}; + for (size_t i = 0; i < numSections; i++) { + sections[i] = SharedLibSection{ptr + (i * SharedLibSection::length)}; + } + } + return sections; + } + + const SharedLibSection *getTextSection() const { + for (SharedLibSection §ion : getSections()) { + SectionType type = section.type(); + if (type == SectionType::XOTEXT || type == SectionType::TEXT) { + return §ion; + } + } + return nullptr; + } + + void setTextAddress(uintptr_t addr) { + const SharedLibSection *section = getTextSection(); + kwrite(section->address() + SharedLibSection::ADDRESS_OFFSET, addr); + } + + void setRoDataAddress(uintptr_t addr) { + const SharedLibSection *section = getRoDataSection(); + kwrite(section->address() + SharedLibSection::ADDRESS_OFFSET, addr); + } + + const SharedLibSection *getDataSection() const { + for (SharedLibSection §ion : getSections()) { + if (section.type() == SectionType::DATA) { + return §ion; + } + } + return nullptr; + } + + const SharedLibSection *getRoDataSection() const { + for (SharedLibSection §ion : getSections()) { + if (section.type() == SectionType::RODATA) { + return §ion; + } + } + return nullptr; + } + + uintptr_t imagebase() const { + return get(); + } + + void imagebase(uintptr_t base) { + kwrite(address() + IMAGEBASE_OFFSET, base); + } + + RtldMeta *getMetaData() const { + if (meta == nullptr) [[unlikely]] { + uintptr_t addr = getMetaDataAddress(); + if (addr) { + meta = newRtldMeta(imagebase(), addr); + } else { + puts("null metadata"); + } + } + return meta.get(); + } + + uintptr_t getMetaDataAddress() const { + return get(); + } + + EhFrameInfo getEhFrameInfo() const { + return get(); + } + + void setEhFrameInfo(const EhFrameInfo &info) const { + kwrite(address() + EHFRAME_OFFSET, info); + } + + uintptr_t getEhFrameHdr() const { + return get(); + } + + void setEhFrameHdr(uintptr_t value) { + set(value); + } + + size_t getEhFrameHdrSize() const { + return get(); + } + + void setEhFrameHdrSize(uintptr_t value) { + set(value); + } + + uintptr_t getEhFrame() const { + return get(); + } + + void setEhFrame(uintptr_t value) { + set(value); + } + + size_t getEhFrameSize() const { + return get(); + } + + void setEhFrameSize(uintptr_t value) { + set(value); + } +}; + +class SharedLibIterable { + + uintptr_t addr; + int pid; + + public: + SharedLibIterable(decltype(nullptr)) : addr(), pid() {} + SharedLibIterable(uintptr_t addr, int pid) : addr(addr), pid(pid) {} + bool operator!=(decltype(nullptr)) const { return addr != 0; } + UniquePtr operator*() { + return new SharedLib{addr, pid}; + } + SharedLibIterable &operator++() { + uintptr_t ptr = 0; + kernel_copyout(addr, &ptr, sizeof(ptr)); + addr = ptr; + return *this; + } + uintptr_t getAddress() const noexcept { + return addr; + } +}; + +struct SharedLibIterator { + + uintptr_t addr; + int pid; + + SharedLibIterator(uintptr_t addr, int pid) : addr(addr), pid(pid) {} + SharedLibIterable begin() const { + return {addr, pid}; + } + decltype(nullptr) end() const { + return nullptr; + } + +}; + +class SharedObject : KernelObject { + + static constexpr size_t EBOOT_OFFSET = 0; + + mutable UniquePtr eboot; + + public: + int pid; + + SharedObject(uintptr_t addr, int pid) + : KernelObject(addr), eboot(nullptr), pid(pid) {} + + SharedLibIterator getLibs() const { + return {get(), pid}; + } + + SharedLib *getEboot() const { + if (eboot == nullptr) [[unlikely]] { + const auto ptr = get(); + if (ptr == 0) [[unlikely]] { + return nullptr; + } + eboot = new SharedLib{ptr, pid}; + } + return eboot.get(); + } + + UniquePtr getLib(int handle) const { + for (auto lib : getLibs()) { + if (lib->handle() == handle) { + return lib.release(); + } + } + return nullptr; + } + + + UniquePtr getLib(const StringView &name) const { + String fullname = name; + if (!name.endswith(".sprx"_sv)) { + fullname += ".sprx"_sv; + } + for (auto lib : getLibs()) { + printf("lib: %s\n", lib->getPath().c_str()); + if (lib->getPath().endswith(fullname)) { + return lib.release(); + } + } + return nullptr; + } +}; + +class RtldMeta : public KernelObject { + + static constexpr size_t TOTAL_SIZE_OFFSET = 0x20; + static constexpr size_t SYMTAB_OFFSET = 0x28; + static constexpr size_t SYMTAB_SIZE_OFFSET = 0x30; + static constexpr size_t STRTAB_OFFSET = 0x38; + static constexpr size_t STRTAB_SIZE_OFFSET = 0x40; + static constexpr size_t PLTTAB_OFFSET = 0x48; + static constexpr size_t PLTTAB_SIZE_OFFSET = 0x50; + + // mutable members for caching + mutable UniquePtr symTable; + mutable UniquePtr strTable; + mutable Array pltTbl; + + public: + uintptr_t imageBase; + RtldMeta(uintptr_t imageBase, uintptr_t addr) : + KernelObject(addr), symTable(nullptr), strTable(nullptr), pltTbl(nullptr), imageBase(imageBase) {} + + size_t totalSize() const { + return get(); + } + + uintptr_t symtab() const { + return get(); + } + + size_t symtabSize() const { + return get(); + } + + size_t nSymbols() const { + return symtabSize() / sizeof(Elf64_Sym); + } + + uintptr_t strtab() const { + return get(); + } + + size_t strtabSize() const { + return get(); + } + + uintptr_t plttab() const { + return get(); + } + + size_t pltTblSize() const { + return get(); + } + + size_t nPlt() const { + return pltTblSize() / sizeof(Elf64_Rela); + } + + const rtld::ElfSymbolTable &getSymbolTable() const { + if (symTable == nullptr) { + symTable = newSymbolTable(this); + } + return *symTable; + } + + const rtld::ElfStringTable &getStringTable() const { + if (strTable == nullptr) { + strTable = newStringTable(this); + } + return *strTable; + } + + const Array &getPltTable() const { + if (pltTbl == nullptr) { + pltTbl = Array(nPlt()); + kernel_copyout(plttab(), pltTbl.begin(), pltTblSize()); + } + return pltTbl; + } +}; + +class rtld::ElfStringTable { + + size_t size; + UniquePtr tbl; + + public: + ElfStringTable(const RtldMeta *meta) : size(meta->strtabSize()), tbl(new char[size]) { + kernel_copyout(meta->strtab(), tbl.get(), size); + } + ElfStringTable(const ElfStringTable &) = delete; + ElfStringTable &operator=(const ElfStringTable &) = delete; + ElfStringTable(ElfStringTable&&) noexcept = default; + ElfStringTable &operator=(ElfStringTable&&) noexcept = default; + ~ElfStringTable() noexcept = default; + + StringView operator[](size_t offset) const { + #ifdef DEBUG + if (offset >= size) [[unlikely]] { + fatalf("offset %llu is out of bounds for size %llu\n", offset, size); + } + #endif + return tbl.get() + offset; + } + + Nid getNid(size_t offset) const { + return Nid{tbl.get() + offset}; + } + + const char *c_str() const noexcept { + return tbl.get(); + } +}; + +class rtld::ElfSymbol : public Elf64_Sym { + + static constexpr auto EXPORT_MASK = 0x30; + + const RtldMeta *meta; + + public: + ElfSymbol(decltype(nullptr)) : Elf64_Sym(), meta(nullptr) {} + ElfSymbol(const RtldMeta *meta, const Elf64_Sym &sym) : Elf64_Sym(sym), meta(meta) {} + + StringView name() const { + return meta->getStringTable()[st_name]; + } + + bool exported() const { + // exported if the binding is global or weak and shndx is not 0 + return (st_info & EXPORT_MASK) && st_shndx != 0; + } + + uintptr_t vaddr() const { + return meta->imageBase + st_value; + } + + Nid nid() const { + return meta->getStringTable().getNid(st_name); + } + + explicit operator bool() const { return meta; } + +}; + + +class rtld::ElfSymbolTable { + + const RtldMeta *meta; + UniquePtr symbols; + size_t size; + + Elf64_Sym *begin() const { + return symbols.get(); + } + + Elf64_Sym *end() const { + return symbols.get() + size; + } + + public: + ElfSymbolTable(const RtldMeta *meta) : meta(meta), symbols(new Elf64_Sym[meta->nSymbols()]), size(meta->nSymbols()) { + kernel_copyout(meta->symtab(), symbols.get(), size * sizeof(Elf64_Sym)); + } + + const ElfSymbol operator[](size_t i) const { + #ifdef DEBUG + if (i >= size) [[unlikely]] { + fatalf("index %llu is out of bounds for length %llu\n", i, size); + } + #endif + return {meta, symbols.get()[i]}; + } + + const Elf64_Sym *getSymbol(size_t i) const { + return symbols.get() + i; + } + + ssize_t getSymbolIndex(const Nid &nid) const { + const ssize_t len = static_cast(size); + for (ssize_t i = 0; i < len; i++) { + if (meta->getStringTable().getNid(symbols[i].st_name) == nid) { + return i; + } + } + return -1; + } + + const ElfSymbol operator[](const Nid &nid) const { + for (Elf64_Sym &esym : *this) { + ElfSymbol sym{meta, esym}; + if (!sym.exported()) { + continue; + } + if (sym.nid() == nid) { + return sym; + } + } + return nullptr; + } + + const ElfSymbol operator[](Nid &&nid) const { + return this->operator[](nid); + } + + size_t length() const { + return size; + } + + const Elf64_Sym *c_symbols() const noexcept { + return symbols.get(); + } +}; + +namespace { + +static inline UniquePtr newRtldMeta(uintptr_t imageBase, uintptr_t addr) { + return new RtldMeta(imageBase, addr); +} + +static inline UniquePtr newSymbolTable(const RtldMeta *meta) { + return new rtld::ElfSymbolTable(meta); +} + +static inline UniquePtr newStringTable(const RtldMeta *meta) { + return new rtld::ElfStringTable(meta); +} + +} diff --git a/extern/libhijacker/include/mutex.hpp b/extern/libhijacker/include/mutex.hpp new file mode 100644 index 0000000..bcf1910 --- /dev/null +++ b/extern/libhijacker/include/mutex.hpp @@ -0,0 +1,166 @@ +#pragma once + +#include + +template +concept MutexType = requires(T &t) { + { t.lock() }; + { t.unlock() }; +}; + +template +class LockGuard { + Mtx *mtx; + +public: + LockGuard(Mtx &mtx) noexcept : mtx(&mtx) { + mtx.lock(); + } + LockGuard(const LockGuard&) = delete; + LockGuard &operator=(const LockGuard&) = delete; + LockGuard(LockGuard &&rhs) noexcept : mtx(rhs.mtx) { rhs.mtx = nullptr; } + LockGuard &operator=(LockGuard &&rhs) noexcept { + if (mtx) { + mtx->unlock(); + } + mtx = rhs.mtx; + rhs.mtx = nullptr; + } + ~LockGuard() noexcept { + if (mtx) [[likely]] { + mtx->unlock(); + mtx = nullptr; + } + } +}; + +template < + typename native_handle_type, + typename attr_type, + int initializer(native_handle_type*, const attr_type*), + int destroyer(native_handle_type*), + int locker(native_handle_type*), + int try_locker(native_handle_type*), + int unlocker(native_handle_type*), + pthread_mutextype mutextype = pthread_mutextype(0) +> +class BaseMutex { + +protected: + native_handle_type handle; + + void init() noexcept { + if (handle) [[likely]] { + return; + } + attr_type attr = nullptr; + if constexpr(mutextype != 0) { + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, mutextype); + } + initializer(&handle, &attr); + if constexpr(mutextype != 0) { + pthread_mutexattr_destroy(&attr); + } + } + +public: + constexpr BaseMutex() noexcept : handle(nullptr) {} + BaseMutex(const BaseMutex&) = delete; + BaseMutex &operator=(const BaseMutex&) = delete; + constexpr BaseMutex(BaseMutex &&rhs) noexcept : handle(rhs.handle) { rhs.handle = nullptr; } + BaseMutex &operator=(BaseMutex &&rhs) noexcept { + if (handle) { + destroyer(&handle); + } + handle = rhs.handle; + return *this; + } + ~BaseMutex() noexcept { + if (handle) [[likely]] { + destroyer(&handle); + handle = nullptr; + } + } + + native_handle_type native_handle() noexcept { return handle; } + void lock() noexcept { + init(); + locker(&handle); + } + bool try_lock() noexcept { + init(); + return try_locker(&handle) == 0; + } + void unlock() noexcept { + unlocker(&handle); + } +}; + +class Mutex : + public BaseMutex< + pthread_mutex_t, + pthread_mutexattr_t, + pthread_mutex_init, + pthread_mutex_destroy, + pthread_mutex_lock, + pthread_mutex_trylock, + pthread_mutex_unlock + > +{}; + +class RecursiveMutex : + public BaseMutex< + pthread_mutex_t, + pthread_mutexattr_t, + pthread_mutex_init, + pthread_mutex_destroy, + pthread_mutex_lock, + pthread_mutex_trylock, + pthread_mutex_unlock, + PTHREAD_MUTEX_RECURSIVE + > +{}; + +class RWMutex : + public BaseMutex< + pthread_rwlock_t , + pthread_rwlockattr_t , + pthread_rwlock_init, + pthread_rwlock_destroy, + pthread_rwlock_wrlock, + pthread_rwlock_trywrlock, + pthread_rwlock_unlock + > { + + class RLocker { + pthread_rwlock_t *handle; + public: + RLocker(pthread_rwlock_t *handle) noexcept : handle(handle) {} + RLocker(const RLocker&) = delete; + RLocker &operator=(const RLocker&) = delete; + RLocker(RLocker &&rhs) noexcept : handle(rhs.handle) { rhs.handle = nullptr; } + RLocker &operator=(RLocker &&rhs) noexcept = delete; + ~RLocker() noexcept = default; + void lock() noexcept { + pthread_rwlock_rdlock(handle); + } + void unlock() noexcept { + pthread_rwlock_unlock(handle); + } + }; + +public: + void rdlock() noexcept { + init(); + pthread_rwlock_rdlock(&handle); + } + bool try_rdlock() noexcept { + init(); + return pthread_rwlock_tryrdlock(&handle) == 0; + } + RLocker rlocker() noexcept { + init(); + return &handle; + } +}; diff --git a/extern/libhijacker/include/nid.hpp b/extern/libhijacker/include/nid.hpp new file mode 100644 index 0000000..56ff360 --- /dev/null +++ b/extern/libhijacker/include/nid.hpp @@ -0,0 +1,104 @@ +#pragma once + +extern "C" { + #include + #include +} + +namespace { + +static constexpr size_t NID_LENGTH = 11; + +} + +union Nid { + + char str[NID_LENGTH + 1]; // 12th character is for NULL terminator to allow constexpr constructor + struct __attribute__((packed)) data_t { + int_fast64_t low; + int_fast32_t hi; + } data; + + Nid() noexcept = default; + + explicit constexpr Nid(const char *nid) noexcept : str{} { + __builtin_memcpy(str, nid, NID_LENGTH); + str[NID_LENGTH] = '\0'; + } + + constexpr int_fast64_t operator<=>(const Nid& rhs) const { + auto i = data.low - rhs.data.low; + if (i == 0) [[unlikely]] { + return data.hi - rhs.data.hi; + } + return i; + } + + constexpr bool operator==(const Nid &rhs) const { + return data.low == rhs.data.low && data.hi == rhs.data.hi; + } + + constexpr void setNullTerminator() { + str[NID_LENGTH] = '\0'; + } +}; + +namespace nid { + +static inline constexpr Nid get_authinfo{"igMefp4SAv0"}; +static inline constexpr Nid sceFsUmountGamePkg{"UQTSykySQ40"}; +static inline constexpr Nid abort{"L1SBTkC+Cvw"}; +static inline constexpr Nid strstr{"viiwFMaNamA"}; +static inline constexpr Nid sceSystemServiceGetAppStatus{"t5ShV0jWEFE"}; +static inline constexpr Nid sceSystemServiceAddLocalProcess{"0cl8SuwosPQ"}; +static inline constexpr Nid socketpair{"MZb0GKT3mo8"}; +static inline constexpr Nid usleep{"QcteRwbsnV0"}; +static inline constexpr Nid _errno{"9BcDykPmo1I"}; +static inline constexpr Nid sceSysmoduleLoadModuleInternal{"39iV5E1HoCk"}; +static inline constexpr Nid sceSysmoduleLoadModuleByNameInternal{"CU8m+Qs+HN4"}; +static inline constexpr Nid mmap{"BPE9s9vQQXo"}; +static inline constexpr Nid sceSysmoduleLoadModule{"g8cM39EUZ6o"}; +static inline constexpr Nid munmap{"UqDGjXA5yUM"}; +static inline constexpr Nid sceKernelJitCreateSharedMemory{"avvJ3J0H0EY"}; +static inline constexpr Nid sceKernelUnloadModule{"QKd0qM58Qes"}; +static inline constexpr Nid socket{"TU-d9PfIHPM"}; +static inline constexpr Nid pipe{"-Jp7F+pXxNg"}; +static inline constexpr Nid sceKernelDlsym{"LwG8g3niqwA"}; +static inline constexpr Nid setsockopt{"fFxGkxF2bVo"}; +static inline constexpr Nid execve{"kdguLiAheLI"}; +static inline constexpr Nid _nanosleep{"NhpspxdjEKU"}; +static inline constexpr Nid close{"bY-PO6JhzhQ"}; +static inline constexpr Nid connect{"XVL8So3QJUk"}; +static inline constexpr Nid send{"fZOeZIOEmLw"}; +static inline constexpr Nid recv{"Ez8xjo9UF4E"}; +static inline constexpr Nid rfork_thread{"bSDxEpGzmUE"}; +static inline constexpr Nid access{"8vE6Z6VEYyk"}; +static inline constexpr Nid sceKernelOpen{"1G3lF1Gg1k8"}; +static inline constexpr Nid mono_aot_get_method{"6t5G5dYpMVg"}; +static inline constexpr Nid sceKernelLoadStartModule{"wzvqT4UqKX8"}; +static inline constexpr Nid sceKernelDebugOutText("9JYNqN6jAKI"); +static inline constexpr Nid sceKernelGetFsSandboxRandomWord("JGfTMBOdUJo"); +static inline constexpr Nid open("wuCroIGjt2g"); + + +static inline constexpr Nid sceKernelSendNotificationRequest("zl7hupSO0C0"); + + +static inline constexpr Nid mono_get_root_domain{"5a8b+s6HtaA"}; +static inline constexpr Nid mono_property_get_get_method{"uzLsJUMjvLY"}; +static inline constexpr Nid mono_property_get_set_method{"BhrEyM1kGW8"}; +static inline constexpr Nid mono_class_get_property_from_name{"KK8Cr0wE81M"}; +static inline constexpr Nid mono_class_from_name{"bzxTZSnnB38"}; +static inline constexpr Nid mono_runtime_invoke{"pre9BjkyDhs"}; +static inline constexpr Nid mono_string_new{"pXv-WCokNsY"}; +static inline constexpr Nid mono_assembly_get_image{"xP4EC4Sp9GI"}; +static inline constexpr Nid mono_domain_assembly_open{"dCeihPtadCM"}; +static inline constexpr Nid mono_thread_attach{"qqUxjwJzc2I"}; + +static inline constexpr Nid mono_class_get_method_from_name{"H5IByLixeaI"}; +static inline constexpr Nid scePadReadState{"YndgXqQVV7c"}; +static inline constexpr Nid strlen{"j4ViWNHEgww"}; +static inline constexpr Nid sceAppInstUtilInstallByPackage{"tDtjgaXYmuo"}; + +static inline constexpr Nid sceKernelIsDevelopmentMode{"UtO0OHMCgmI"}; +} diff --git a/extern/libhijacker/include/notify.hpp b/extern/libhijacker/include/notify.hpp new file mode 100644 index 0000000..7c622b5 --- /dev/null +++ b/extern/libhijacker/include/notify.hpp @@ -0,0 +1,34 @@ +#pragma once + +extern "C" +{ + #include + #include + #include + #include + #include + + typedef struct + { + int32_t type; // 0x00 + int32_t req_id; // 0x04 + int32_t priority; // 0x08 + int32_t msg_id; // 0x0C + int32_t target_id; // 0x10 + int32_t user_id; // 0x14 + int32_t unk1; // 0x18 + int32_t unk2; // 0x1C + int32_t app_id; // 0x20 + int32_t error_num; // 0x24 + int32_t unk3; // 0x28 + char use_icon_image_uri; // 0x2C + char message[1024]; // 0x2D + char uri[1024]; // 0x42D + char unkstr[1024]; // 0x82D + } OrbisNotificationRequest; // Size = 0xC30 + + int32_t sceKernelSendNotificationRequest(int32_t device, OrbisNotificationRequest *req, size_t size, int32_t blocking); + +} + +void printf_notification(const char *fmt, ...); diff --git a/extern/libhijacker/include/offsets.hpp b/extern/libhijacker/include/offsets.hpp new file mode 100644 index 0000000..26fac05 --- /dev/null +++ b/extern/libhijacker/include/offsets.hpp @@ -0,0 +1,14 @@ + +extern "C" { + #include +} + +namespace offsets { + +size_t allproc(); +size_t security_flags(); +size_t qa_flags(); +size_t utoken_flags(); +size_t root_vnode(); + +} // offsets diff --git a/extern/libhijacker/include/util.hpp b/extern/libhijacker/include/util.hpp new file mode 100644 index 0000000..637c3c7 --- /dev/null +++ b/extern/libhijacker/include/util.hpp @@ -0,0 +1,894 @@ +#pragma once + +#include +extern "C" { + #include + #include + #include + #include + void *malloc(size_t length); + void free(void *ptr); + + void *realloc(void *ptr, size_t size); + +} + +// for things we need but can't have... yet... +// these are not identical to the standard ones and will have unexpected behavior + +#ifdef DEBUG +void printBacktrace(); +#ifdef CAN_EXIT +#pragma clang diagnostic ignored "-Wformat-security" +template +[[noreturn]] static void fatalf(const char *msg, Types... values) { + __builtin_printf(msg, values...); + printBacktrace(); + exit(0); +} +#else +#pragma clang diagnostic ignored "-Wformat-security" +template +static void fatalf(const char *msg, Types... values) { + __builtin_printf(msg, values...); + printBacktrace(); +} +#endif +#endif + +template +class UniquePtr { + + T *ptr; + + public: + explicit UniquePtr() : ptr(nullptr) {} + UniquePtr(decltype(nullptr)) : ptr(nullptr) {} + UniquePtr(T *ptr) : ptr(ptr) {} + UniquePtr(const UniquePtr &rhs) = delete; + UniquePtr &operator=(const UniquePtr &rhs) = delete; + UniquePtr(UniquePtr &&rhs) noexcept : ptr(rhs.ptr) { + rhs.ptr = nullptr; + } + UniquePtr &operator=(UniquePtr &&rhs) noexcept { + delete ptr; + ptr = rhs.ptr; + rhs.ptr = nullptr; + return *this; + } + ~UniquePtr() { delete ptr;} + + explicit operator bool() const { + return ptr != nullptr; + } + + T &operator*() { + #ifdef DEBUG + if (ptr == nullptr) [[unlikely]] { + fatalf("nullptr dereference\n"); + } + #endif + return *ptr; + } + + const T &operator*() const { + #ifdef DEBUG + if (ptr == nullptr) [[unlikely]] { + fatalf("nullptr dereference\n"); + } + #endif + return *ptr; + } + + T *operator->() const { + #ifdef DEBUG + if (ptr == nullptr) [[unlikely]] { + fatalf("nullptr dereference\n"); + } + #endif + return ptr; + } + + bool operator==(decltype(nullptr)) const { return ptr == nullptr; } + bool operator!=(decltype(nullptr)) const { return ptr != nullptr; } + + T *get() const { + return ptr; + } + + T *release() { + T *tmp = ptr; + ptr = nullptr; + return tmp; + } +}; + +template +class UniquePtr { + + T *ptr; + + public: + UniquePtr() = delete; + UniquePtr(decltype(nullptr)) : ptr(nullptr) {} + UniquePtr(T *ptr) : ptr(ptr) {} + UniquePtr(const UniquePtr &rhs) = delete; + UniquePtr &operator=(const UniquePtr &rhs) = delete; + UniquePtr(UniquePtr &&rhs) noexcept : ptr(rhs.ptr) { + rhs.ptr = nullptr; + } + UniquePtr &operator=(UniquePtr &&rhs) noexcept { + delete[] ptr; + ptr = rhs.ptr; + rhs.ptr = nullptr; + return *this; + } + ~UniquePtr() { delete[] ptr; } + + explicit operator bool() const { + return ptr; + } + + T &operator[](size_t i ) const { + #ifdef DEBUG + if (ptr == nullptr) [[unlikely]] { + fatalf("nullptr dereference\n"); + } + #endif + return ptr[i]; + } + + bool operator==(decltype(nullptr)) const { return ptr == nullptr; } + bool operator!=(decltype(nullptr)) const { return ptr != nullptr; } + + T *get() const { + #ifdef DEBUG + if (ptr == nullptr) [[unlikely]] { + fatalf("nullptr dereference\n"); + } + #endif + return ptr; + } + + T *release() { + T *tmp = ptr; + ptr = nullptr; + return tmp; + } +}; + + +class StringView { + const char * str; + size_t size; + + public: + constexpr StringView() : str(nullptr), size(0) {} + constexpr StringView(decltype(nullptr)) : str(nullptr), size(0) {} + + constexpr StringView(const char *str) : str(str), size(__builtin_strlen(str)) {} + + constexpr StringView(const char *str, unsigned long length) : str(str), size(length) {} + + constexpr char operator[](size_t i) const { + return str[i]; + } + + explicit operator bool() const { + return str; + } + + constexpr size_t length() const { + return size; + } + + operator const char*() const { + return str; + } + + constexpr const char *c_str() const { + return str; + } + + constexpr bool operator==(decltype(nullptr)) const { + return str == nullptr; + } + + constexpr bool operator==(const StringView &rhs) const { + if (size != rhs.size) { + return false; + } + if (str == rhs.str) { + return true; + } + if (str == nullptr || rhs.str == nullptr) { + return false; + } + return __builtin_memcmp(str, rhs.str, size) == 0; + } + + bool contains(const char *needle) const { + return __builtin_strstr(str, needle); + } + + constexpr bool endswith(const StringView &view) const { + const size_t sz = view.length(); + const char *buf = view.c_str(); + return sz > size ? false : __builtin_memcmp(str + (size - sz), buf, sz) == 0; + } + + constexpr bool startswith(const StringView &view) const { + const size_t sz = view.length(); + const char *buf = view.c_str(); + return sz > size ? false : __builtin_memcmp(str, buf, sz) == 0; + } + + constexpr int32_t operator<=>(const StringView &rhs) const { + return __builtin_strcmp(str, rhs.str); + } +}; + +constexpr StringView operator"" _sv(const char *str, unsigned long len) { return {str, len}; } + +// NOLINTBEGIN(cppcoreguidelines-owning-memory, cppcoreguidelines-pro-type-member-init) + +// +class String { + + static constexpr size_t SSO_MAX = 16; + + union StringBuf { + char *ptr; + char buf[SSO_MAX]; + }; + + StringBuf data; + size_t size; + size_t capacity; + + static constexpr size_t align(size_t x) { + auto r = x % SSO_MAX; + return r ? x + (SSO_MAX - r) : x; + } + + bool is_sso() const { return capacity == SSO_MAX; } + + void grow(size_t target) { + const size_t newCapacity = align(target); + if (capacity >= newCapacity) { + return; + } + if (is_sso()) { + char *tmp = new char[newCapacity]; + __builtin_memcpy(tmp, data.buf, size); + tmp[size] = '\0'; + data.ptr = tmp; + } else { + char *tmp = new char[newCapacity]; + __builtin_memcpy(tmp, data.ptr, size); + tmp[size] = '\0'; + delete[] data.ptr; + data.ptr = tmp; + } + capacity = newCapacity; + } + + char *buffer() { + return is_sso() ? data.buf : data.ptr; + } + + const char *buffer() const { + return is_sso() ? data.buf : data.ptr; + } + + public: + explicit String() noexcept : data(), size(0), capacity(SSO_MAX) { data.buf[0] = '\0'; } + String(int v) noexcept : data(), size(snprintf(data.buf, SSO_MAX, "%d", v)), capacity(SSO_MAX) { + } + String(uint32_t v) noexcept : data(), size(snprintf(data.buf, SSO_MAX, "%u", v)), capacity(SSO_MAX) { + } + String(const StringView &view) noexcept : size(view.length()), capacity(align(view.length()+1)) { + if (is_sso()) { + __builtin_memcpy(data.buf, view.c_str(), size); + *(data.buf + size) = '\0'; + } else { + data.ptr = new char[capacity]; + __builtin_memcpy(data.ptr, view.c_str(), size); + data.ptr[size] = '\0'; + } + } + String(const String &rhs) : size(rhs.size), capacity(rhs.capacity) { + if (is_sso()) { + __builtin_memcpy(data.buf, rhs.c_str(), size+1); + capacity = SSO_MAX; + } else { + data.ptr = new char[rhs.capacity]; + __builtin_memcpy(data.ptr, rhs.c_str(), size+1); + } + } + String &operator=(const String &rhs) { + size = rhs.size; + capacity = rhs.capacity; + if (is_sso()) { + __builtin_memcpy(data.buf, rhs.c_str(), size+1); + capacity = SSO_MAX; + } else { + data.ptr = new char[rhs.capacity]; + __builtin_memcpy(data.ptr, rhs.c_str(), size+1); + } + return *this; + } + String(String &&rhs) noexcept : size(rhs.size), capacity(rhs.capacity) { + if (is_sso()) { + __builtin_memcpy(data.buf, rhs.c_str(), size+1); + capacity = SSO_MAX; + } else { + capacity = rhs.capacity; + data.ptr = rhs.data.ptr; + rhs.data.ptr = nullptr; + } + } + String &operator=(String &&rhs) noexcept { + size = rhs.size; + capacity = rhs.capacity; + if (is_sso()) { + __builtin_memcpy(data.buf, rhs.c_str(), size+1); + capacity = SSO_MAX; + } else { + capacity = rhs.capacity; + data.ptr = rhs.data.ptr; + rhs.data.ptr = nullptr; + } + return *this; + } + ~String() { + if (!is_sso()) { + delete[] data.ptr; + } + } + + String &operator+=(const String &rhs) { + const size_t newsz = size + rhs.length(); + if (newsz >= capacity) { + grow(newsz + 1); // an additional character must be reserved for '\0' + } + char *__restrict ptr = buffer(); + __builtin_memcpy(ptr + size, rhs.c_str(), rhs.length()); + size = newsz; // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + ptr[size] = '\0'; + return *this; // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + } + + String &operator+=(const StringView &rhs) { + const size_t newsz = size + rhs.length(); + if (newsz >= capacity) { + grow(newsz + 1); // an additional character must be reserved for '\0' + } + char *__restrict ptr = buffer(); + __builtin_memcpy(ptr + size, rhs.c_str(), rhs.length()); + size = newsz; // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + ptr[size] = '\0'; + return *this; // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + } + + String operator+(const StringView &rhs) const noexcept { + String res = *this; + res += rhs; + return res; + } + + String &operator+=(const char c) { + const size_t newsz = size + 1; + if (newsz >= capacity) { + grow(newsz + 1); // an additional character must be reserved for '\0' + } + char *__restrict ptr = buffer(); + ptr[size++] = c; + ptr[size] = '\0'; + return *this; // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + } + + bool operator==(const StringView &rhs) const { + if (size != rhs.length()) { + return false; + } + return __builtin_memcmp(buffer(), rhs.c_str(), size) == 0; + } + + const char *c_str() const { + return is_sso() ? data.buf : data.ptr; + } + + size_t length() const { + return size; + } + + bool contains(const char *needle) const { + return __builtin_strstr(buffer(), needle); + } + + bool endswith(const StringView &view) const { + const size_t sz = view.length(); + const char *buf = view.c_str(); + return sz > size ? false : __builtin_memcmp(buffer() + (size - sz), buf, sz) == 0; + } + + bool startswith(const StringView &view) const { + const size_t sz = view.length(); + const char *buf = view.c_str(); + return sz > size ? false : __builtin_memcmp(buffer(), buf, sz) == 0; + } + + operator StringView() const { + return {buffer(), size}; + } + + StringView subview(size_t from) const { + return subview(from, size); + } + + StringView subview(size_t from, size_t to) const { + return {buffer() + from, to - from}; + } + + String substring(size_t from) const { + return substring(from, size); + } + + String substring(size_t from, size_t to) const { + return StringView{buffer() + from, to - from}; + } + + ssize_t rfind(char c) const noexcept { + const char *ptr = strrchr(buffer(), c); + if (ptr == nullptr) { + return -1; + } + return ptr - buffer(); + } + + void reserve(size_t i) { + if (i > 0) [[likely]] { + grow(i); + } + } + + String operator+(const String& rhs) const noexcept { + String cpy{*this}; + cpy += rhs; + return cpy; + } +}; + +inline String operator+(const StringView& lhs, const StringView& rhs) noexcept { + String cpy{lhs}; + cpy += rhs; + return cpy; +} + +// NOLINTEND(cppcoreguidelines-owning-memory, cppcoreguidelines-pro-type-member-init) + +// +template +class Array { + + UniquePtr ptr; + size_t size; + + public: + Array(decltype(nullptr)) : ptr(nullptr), size(0) {} + Array(size_t size) : ptr(new T[size]), size(size) {} + Array(const Array &rhs) = delete; + Array &operator=(const Array &rhs) = delete; + Array(Array &&rhs) noexcept = default; + Array &operator=(Array &&rhs) noexcept = default; + ~Array() noexcept = default; + + size_t length() const { + return size; + } + T &operator[](size_t i) const { + #ifdef DEBUG + if (i >= size) [[unlikely]] { + fatalf("index %llu is out of bounds for length %llu\n", i, size); + } + #endif + return ptr[i]; + } + T *begin() const { + return ptr.get(); + } + T *end() const { + return ptr.get() + size; + } + explicit operator bool() const { + return (bool)ptr && size > 0; + } + bool operator==(decltype(nullptr)) const { + return ptr == nullptr; + } + T *data() { return ptr.get(); } + size_t shrink(size_t newSize) { + if (newSize < size) { + size = newSize; + } + return size; + } +}; + +template +class List; + +template +class _List_iterator; + +template +class _List_node { + friend class List; + friend class _List_iterator; + + UniquePtr<_List_node> next; + // don't need a doubly linked list + T value; + + public: + _List_node(T t) : next(nullptr), value(t) {} + _List_node(_List_node *next, T t) : next(next), value(t) {} + + template + _List_node(_List_node *next, Types ...values) : next(next), value(values...) {} + + T &operator*() { return value; } +}; + +template +class _List_iterator { + _List_node *it; + + public: + _List_iterator(decltype(nullptr)) :it(nullptr) {} + _List_iterator(_List_node *it) : it(it) {} + T &operator*() const { return **it; } + _List_iterator operator++(int) { + _List_iterator tmp = *this; + it = it->next; + return tmp; + } + _List_iterator &operator++() { + it = it->next.get(); + return *this; + } + bool operator!=(const _List_iterator &rhs) const { + // use reference equality of the nodes + return it != rhs.it; + } +}; + +template +class List { + UniquePtr<_List_node> head; + size_t size; + + public: + List() : head(nullptr), size(0) {} + + size_t length() const { return size; } + + template + T &emplace_front(Types ...values) { + // much cheaper to emplace in front + size++; + head = {new _List_node(head.release(), values...)}; + return head->value; + } + + T &push_front(const T &value) { + size++; + head = {new _List_node(head.release(), value)}; + return head->value; + } + + _List_iterator begin() const { + return head.get(); + } + + _List_iterator end() const { + return nullptr; + } + + const T& operator[](size_t i) const { + auto it = begin(); + for (size_t j = 0; j < i; j++) { + it++; + } + return *it; + } +}; + +template +class Vector { + + static constexpr size_t DEFAULT_CAPACITY = 16; + + T *first; + T *last; + T *eos; + + size_t available() const noexcept { + return eos - last; + } + + void grow(size_t cap) noexcept { + if (cap <= capacity()) { + return; + } + first = reallocate(first, cap); + eos = first + cap; + } + + constexpr void setSize(const size_t count, const size_t length, const T &value) noexcept { + if (length > count) { + if constexpr(!__is_trivially_destructible(T)) { + for (size_t i = count; i < length; i++) { + first[i].~T(); + } + } + } else { + grow(count); + for (size_t i = length; i < count; i++) { + first[i] = value; + } + } + last = first + count; + } + + static T *allocate(size_t n) noexcept { + return reinterpret_cast(malloc(n * sizeof(T))); // NOLINT + } + + static T *reallocate(T *ptr, size_t n) noexcept { + // if it fails we're screwed anyway + // no exceptions so we'll just run until fault + return reinterpret_cast(realloc(ptr, n * sizeof(T))); // NOLINT + } + + public: + Vector() noexcept : first(allocate(DEFAULT_CAPACITY)), last(first), eos(first + DEFAULT_CAPACITY) {} // NOLINT + + Vector(const Vector &rhs) noexcept : first(allocate(rhs.capacity())), last(first), eos(first + rhs.capacity()) { // NOLINT + if constexpr(__is_trivially_copyable(T)) { + __builtin_memcpy(first, rhs.first, rhs.size() * sizeof(T)); + } else { + const size_t length = rhs.size(); + for (size_t i = 0; i < length; i++) { + first[i] = rhs[i]; + } + } + } + + Vector &operator=(const Vector &rhs) noexcept { + if (this == &rhs) [[unlikely]] { + return *this; + } + + const size_t length = rhs.size(); + + if (capacity() < length) { + clear(); + } + + first = grow(rhs.capacity()); // NOLINT + last = first + length; + + if constexpr(__is_trivially_copyable(T)) { + __builtin_memcpy(first, rhs.first, length * sizeof(T)); + } else { + for (size_t i = 0; i < length; i++) { + first[i] = rhs[i]; + } + } + + return *this; + } + + Vector(Vector &&rhs) noexcept : first(rhs.first), last(rhs.last), eos(rhs.eos) { + rhs.first = nullptr; + } + + Vector &operator=(Vector &&rhs) noexcept { + free(first); + first = rhs.first; + last = rhs.last; + eos = rhs.eos; + rhs.first = nullptr; + return *this; + } + + ~Vector() noexcept { + free(first); + } + + constexpr T &at(size_t i) noexcept { + return first[i]; + } + + constexpr const T &at(size_t i) const noexcept { + return first[i]; + } + + constexpr T &operator[](size_t i) noexcept { + return first[i]; + } + + constexpr const T &operator[](size_t i) const noexcept { + return first[i]; + } + + constexpr T &front() noexcept { + return *first; + } + + constexpr const T &front() const noexcept { + return *first; + } + + constexpr T &back() noexcept { + return *last; + } + + constexpr const T &back() const noexcept { + return *last; + } + + constexpr T *data() noexcept { + return first; + } + + constexpr const T *data() const noexcept { + return first; + } + + constexpr T *begin() noexcept { + return first; + } + + constexpr const T *begin() const noexcept { + return first; + } + + constexpr const T *cbegin() const noexcept { + return first; + } + + constexpr T *end() noexcept { + return last; + } + + constexpr const T *end() const noexcept { + return last; + } + + constexpr const T *cend() const noexcept { + return last; + } + + constexpr bool empty() const noexcept { + return first == last; + } + + constexpr size_t size() const noexcept { + return last - first; + } + + constexpr void reserve(size_t length) noexcept { + if (length <= capacity()) { + return; + } + grow(length); + } + + constexpr size_t capacity() const noexcept { + return eos - first; + } + + constexpr void shrink_to_fit() noexcept { + if (size() <= capacity()) { + return; + } + first = reallocate(first, size()); + } + + constexpr void clear() noexcept { + if constexpr(!__is_trivially_destructible(T)) { + const size_t length = size(); + for (size_t i = 0; i < length; i++) { + first[i].~T(); + } + } + last = first; + } + + // ok I got lazy + + // + + constexpr T *erase(const T *pos) noexcept { + const size_t i = pos - first; + if constexpr(!__is_trivially_destructible(T)) { + first[i].~T(); + } + if (first + i != --last) { + __builtin_memcpy(last - i, first + i, size() * sizeof(T)); + } + return last; + } + + template + constexpr T &emplace_back(Args&&... args) noexcept { + return *last++ = T{args...}; + } + + constexpr void push_back(const T &value) noexcept { + *last++ = value; + } + + constexpr void push_back(T &&value) noexcept { + *last++ = static_cast(value); + } + + constexpr void pop_back() noexcept { + last--; + if constexpr(!__is_trivially_destructible(T)) { + last->~T(); /* patched: was '*last.~T()' (operator precedence bug) */ + } + } + + constexpr void resize(size_t count) noexcept { + const size_t length = size(); + if (length == count) { + return; + } + setSize(count, length, T()); + } + + constexpr void resize(size_t count, const T &value) noexcept { + const size_t length = size(); + if (length == count) { + return; + } + setSize(count, length, value); + } + + constexpr void swap(Vector &other) noexcept { + swap(*this, other); + } +}; + +// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) + +// +[[maybe_unused]] static void hexdump(const void *ptr, size_t len) { + const uint8_t *buf = reinterpret_cast(ptr); + auto rows = len / 16; + for (size_t i = 0; i < rows; i++) { + auto j = i * 16; + __builtin_printf( + "%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", + buf[j], buf[j+1], buf[j+2], buf[j+3], buf[j+4], buf[j+5], buf[j+6], buf[j+7], + buf[j+8], buf[j+9], buf[j+10], buf[j+11], buf[j+12], buf[j+13], buf[j+14], buf[j+15] + ); + } + buf += rows * 16; + for (size_t i = 0; i < len % 16; i++) { + __builtin_printf("%02X ", buf[i]); + } + puts(""); +} + +#include +#include +#include +#include + + +// NOLINTEND(cppcoreguidelines-avoid-magic-numbers) diff --git a/homebrew.js b/homebrew.js new file mode 100644 index 0000000..e889ca0 --- /dev/null +++ b/homebrew.js @@ -0,0 +1,14 @@ +async function main() { + const PAYLOAD = window.workingDir + '/lapy_jb_daemon.elf'; + + return { + mainText: "lapy Jb daemon", + secondaryText: 'lapy Jb daemon', + onclick: async () => { + return { + path: PAYLOAD, + daemon: true + }; + } + }; +} diff --git a/lapy_jb_daemon.elf b/lapy_jb_daemon.elf new file mode 100644 index 0000000..bccf85e Binary files /dev/null and b/lapy_jb_daemon.elf differ diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..701cb26 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,194 @@ +/* ============================================================================ + * lapy_jb_daemon v1.2 — multi-firmware standalone homebrew daemon + * + * Mimics etaHEN's jailbreak-on-demand daemon API for Sony app PRXes that + * write /download0/etahen_jailbreak from inside their sandbox. + * + * v1.2 = revert to libhijacker-based jailbreak (= proven, multi-fw via + * ps5-payload-sdk runtime KERNEL_ADDRESS_* resolution). v1.1 had a manual + * reimpl with bugs (wrong authid, wrong cr_sceAttr offset, missing sandbox + * escape via fd_rdir/jdir → rootvnode). Reverting fixes all that. + * + * Multi-fw mechanism : + * - ps5-payload-sdk (libkernel_sys) auto-detects fw at boot via + * payload_args->kdata_base_addr and populates KERNEL_ADDRESS_* extern + * globals (allproc, rootvnode, etc.) with the correct per-fw values. + * - The offsets_v940.cpp override (linked before -lhijacker) converts + * KERNEL_ADDRESS_* → relative offsets. Multi-fw transparent. + * - Compatible : every fw covered by ps5-payload-sdk's runtime tables + * (3.00 → 12.00 via kstuff/kstuff-lite + ps5-hen). + * + * Built with ps5-payload-sdk (John Törnblom). + * Credits : libhijacker (illusion + LightingMod), modified offsets by + * Team PHU / Arksama. + * ============================================================================ */ + +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int klog_printf(const char *fmt, ...); +int sceNotificationSend(int userId, bool isLogged, const char *payload); +} + +/* libhijacker references kernel_base as extern "C" — the definition lives + * here as the standalone-payload entry point. Set from payload_args. */ +extern "C" uint64_t kernel_base = 0; + +#define LOG(fmt, ...) klog_printf("[lapy_jb] " fmt "\n", ##__VA_ARGS__) + +#define SANDBOX_BASE "/mnt/sandbox" +#define JB_FILE_RELPATH "/download0/etahen_jailbreak" +#define POLL_INTERVAL_US (250 * 1000) + +/* Modern Sony notification — kstuff-INDEPENDENT, libSceNotification IPC. */ +static void notif(const char *fmt, ...) { + char text[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(text, sizeof(text), fmt, ap); + va_end(ap); + char json[1024]; + snprintf(json, sizeof(json), + "{\"rawData\":{" + "\"viewTemplateType\":\"InteractiveToastTemplateB\"," + "\"channelType\":\"Downloads\"," + "\"useCaseId\":\"IDC\"," + "\"toastOverwriteType\":\"Yes\"," + "\"isImmediate\":true," + "\"priority\":100," + "\"viewData\":{" + "\"icon\":{\"type\":\"Predefined\",\"parameters\":{\"icon\":\"download\"}}," + "\"message\":{\"body\":\"%s\"}," + "\"subMessage\":{\"body\":\"Lapy JB Daemon\"}" + "}," + "\"platformViews\":{" + "\"previewDisabled\":{" + "\"viewData\":{" + "\"icon\":{\"type\":\"Predefined\",\"parameters\":{\"icon\":\"download\"}}," + "\"message\":{\"body\":\"%s\"}" + "}" + "}" + "}" + "}," + "\"localNotificationId\":\"LAPY_JB\"" + "}", + text, text); + sceNotificationSend(0xFE, true, json); +} + +/* ----- File helpers ------------------------------------------------------- */ +static ssize_t slurp_file(const char *path, char *buf, size_t buf_size) { + int fd = open(path, O_RDONLY); + if (fd < 0) return -1; + ssize_t n = read(fd, buf, buf_size - 1); + close(fd); + if (n < 0) return -1; + buf[n] = '\0'; + return n; +} + +/* Parse {"PID":""} JSON. Returns pid or -1. */ +static pid_t parse_pid_from_json(const char *json) { + const char *p = strstr(json, "\"PID\""); + if (!p) return -1; + p = strchr(p, ':'); + if (!p) return -1; + p++; + while (*p && (*p == ' ' || *p == '\t' || *p == '"')) p++; + if (!*p) return -1; + pid_t pid = (pid_t)atoi(p); + return pid > 0 ? pid : -1; +} + +/* Bump target's ucred via libhijacker (= jailbreak() : caps + authid + uid + + * sceAttr + sandbox escape via fd_rdir/jdir → rootvnode). Returns 0 on success. */ +static int do_jailbreak(pid_t target_pid) { + LOG("jailbreak attempt : pid=%d", target_pid); + auto hj = Hijacker::getHijacker(target_pid); + if (!hj) { + LOG("getHijacker(%d) FAIL — process likely exited", target_pid); + return -1; + } + /* libhijacker's jailbreak() does ALL of : + * - cr_uid / cr_ruid / cr_svuid = 0 + * - cr_ngroups = 0, cr_rgid = 0 + * - cr_sceCaps[0..1] = 0xFFFFFFFFFFFFFFFF + * - cr_sceAuthID = 0x4801000000000013 (jailbreak authid) + * - cr_sceAttr[0] = 0x80 at offset 0x83 (= unsandboxed) + * - fd_rdir / fd_jdir = rootvnode (= sandbox escape) + * Multi-fw via ps5-payload-sdk's runtime KERNEL_ADDRESS_*. */ + hj->jailbreak(/*escapeSandbox=*/ true); + LOG("jailbreak OK pid=%d", target_pid); + return 0; +} + +/* ----- Main loop ---------------------------------------------------------- */ +int main(void) { + LOG("======================================"); + LOG(" Lapy JB Daemon v1.2 (multi-fw)"); + LOG(" No etaHEN, no PHU required"); + LOG("======================================"); + + payload_args_t *args = payload_get_args(); + if (!args) { + LOG("FATAL : payload_get_args() returned NULL — must run via elfldr/HBL"); + notif("Lapy JB FATAL : needs elfldr launcher"); + return 1; + } + kernel_base = args->kdata_base_addr; + LOG("kernel_base = 0x%lx", (uintptr_t)kernel_base); + + notif("Lapy JB Daemon active — launch your app"); + LOG("polling %s/_%s every %dms", + SANDBOX_BASE, JB_FILE_RELPATH, POLL_INTERVAL_US / 1000); + + char buf[512]; + char path[512]; + while (true) { + DIR *d = opendir(SANDBOX_BASE); + if (!d) { usleep(POLL_INTERVAL_US); continue; } + struct dirent *de; + while ((de = readdir(d)) != NULL) { + if (de->d_name[0] == '.') continue; + int n = snprintf(path, sizeof(path), "%s/%s%s", + SANDBOX_BASE, de->d_name, JB_FILE_RELPATH); + if (n <= 0 || (size_t)n >= sizeof(path)) continue; + + struct stat st; + if (stat(path, &st) != 0 || st.st_size == 0) continue; + + ssize_t got = slurp_file(path, buf, sizeof(buf)); + if (got > 0) { + pid_t pid = parse_pid_from_json(buf); + if (pid > 0) { + LOG("request from %s : pid=%d", de->d_name, pid); + if (do_jailbreak(pid) == 0) { + notif("%s pid=%d unsandboxed", de->d_name, (int)pid); + } else { + notif("Jailbreak FAIL pid=%d", (int)pid); + } + } else { + LOG("could not parse PID from '%s' (%s)", buf, path); + } + } + unlink(path); /* signal to PRX : bump done */ + } + closedir(d); + usleep(POLL_INTERVAL_US); + } + return 0; +} diff --git a/source/offsets_v940.cpp b/source/offsets_v940.cpp new file mode 100644 index 0000000..7365cb3 --- /dev/null +++ b/source/offsets_v940.cpp @@ -0,0 +1,37 @@ +// Multi-fw libhijacker offsets override. +// +// libhijacker's vanilla offsets.cpp hardcodes fw 9.40 kernel-symbol offsets. +// This file replaces it with a multi-fw resolution path : ps5-payload-sdk +// already exposes the absolute kernel virtual addresses at runtime via the +// KERNEL_ADDRESS_* extern globals (auto-populated per-fw from kern.sdk_version, +// see ps5/kernel.h), so the only job here is converting absolute VA -> offset +// from kdata_base — which is what libhijacker's offsets:: namespace expects. +// +// Compile this .o BEFORE -lhijacker on the link line so the linker resolves +// the offsets:: symbols from here and skips the vanilla offsets.cpp.o that +// would otherwise be pulled from libhijacker.a. +// +// Covers every fw the SDK knows about (3.00 -> 12.00 via kstuff). On an +// unknown fw the SDK returns 0 and libhijacker fails cleanly with NULL +// attach — same graceful failure as the old 9.40-hardcoded path on wrong fw. + +extern "C" { +#include +#include +} + +namespace offsets { + +// Convert absolute kernel VA to offset from kernel_base. Signed-int +// subtraction so the high-half canonical addresses cancel cleanly. +static inline size_t rel(intptr_t absolute_va) { + return (size_t)(absolute_va - KERNEL_ADDRESS_DATA_BASE); +} + +size_t allproc() { return rel(KERNEL_ADDRESS_ALLPROC); } +size_t security_flags() { return rel(KERNEL_ADDRESS_SECURITY_FLAGS); } +size_t qa_flags() { return rel(KERNEL_ADDRESS_QA_FLAGS); } +size_t utoken_flags() { return rel(KERNEL_ADDRESS_UTOKEN_FLAGS); } +size_t root_vnode() { return rel(KERNEL_ADDRESS_ROOTVNODE); } + +}