├── .clang-tidy ├── .gitattributes ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE.txt ├── README.md ├── build.bat ├── build.sh ├── crt ├── CMakeLists.txt └── crt0.c ├── daemon ├── CMakeLists.txt ├── linker.x └── source │ ├── commands.cpp │ ├── crt0.c │ ├── elfserver.cpp │ ├── fd.hpp │ ├── game_patch.cpp │ ├── game_patch.hpp │ ├── game_patch_memory.cpp │ ├── game_patch_memory.hpp │ ├── game_patch_thread.cpp │ ├── game_patch_thread.hpp │ ├── game_patch_xml.cpp │ ├── game_patch_xml.hpp │ ├── game_patch_xml_cfg.hpp │ ├── klogger.cpp │ ├── launcher.hpp │ ├── main.cpp │ ├── pad.hpp │ ├── server.cpp │ ├── server.hpp │ ├── servers.hpp │ ├── thread.hpp │ └── titleid_map.hpp ├── daemon_log.py ├── daemon_shim ├── CMakeLists.txt ├── linker.x └── source │ └── main.cpp ├── data └── game_patch_fliprate_list.xml ├── ftp ├── cmd.c ├── cmd.h └── ftp.c ├── include ├── backtrace.hpp ├── build.h ├── dbg.hpp ├── dbg │ ├── args.hpp │ └── dbg.hpp ├── elf │ ├── elf.hpp │ ├── loader.hpp │ └── nid │ │ ├── b64.hpp │ │ ├── nid.hpp │ │ └── sha1.hpp ├── elfldr.hpp ├── hijacker.hpp ├── hijacker │ ├── allocator.hpp │ ├── hijacker.hpp │ ├── memory.hpp │ └── spawner.hpp ├── kernel.hpp ├── kernel │ ├── frame.hpp │ ├── kernel.hpp │ ├── kthread.hpp │ ├── proc.hpp │ └── rtld.hpp ├── kernel_helpers.h ├── nid.hpp ├── notify.hpp ├── offsets.hpp ├── print.hpp └── util.hpp ├── kill_daemon.py ├── launch.py ├── libhijacker ├── CMakeLists.txt └── source │ ├── backtrace.cpp │ ├── dbg.cpp │ ├── elf │ ├── elf.cpp │ └── sysmodules.hpp │ ├── hijacker.cpp │ ├── kernel.cpp │ ├── notify.cpp │ ├── offsets.cpp │ ├── print.cpp │ └── spawner.cpp ├── linker.x ├── send_elf.py ├── shellcode ├── build_cmd.txt ├── format_shellcode.py └── rfork_thread.c ├── spawner ├── CMakeLists.txt ├── linker.x └── source │ ├── app.cpp │ ├── daemon.c │ ├── kernel_helpers.c │ ├── main.cpp │ └── start.c ├── stubber ├── elf.go ├── go.mod ├── main.go ├── root.cmake └── toolchain.cmake └── test_elf └── CMakeLists.txt /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'bugprone-*,-bugprone-reserved-identifier,-bugprone-easily-swappable-parameters,clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-non-private-member-variables-in-classes,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-pro-bounds-constant-array-index,performance*,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling' 3 | WarningsAsErrors: true 4 | HeaderFilterRegex: '.*' 5 | AnalyzeTemporaryDtors: false 6 | FormatStyle: none 7 | ... 8 | 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**/*.md' 7 | pull_request: 8 | paths-ignore: 9 | - '**/*.md' 10 | workflow_dispatch: 11 | 12 | concurrency: 13 | group: ${{ github.ref }}-${{ github.event_name }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write 21 | steps: 22 | 23 | - name: Setup environment 24 | run: | 25 | echo "PS5SDK=${{ github.workspace }}/ps5sdk" >> $GITHUB_ENV 26 | sudo apt install ninja-build 27 | 28 | - name: Show Clang version 29 | run: clang -v 30 | 31 | - name: Checkout 32 | uses: actions/checkout@main 33 | with: 34 | submodules: recursive 35 | fetch-depth: 0 36 | 37 | - name: Checkout PS5SDK 38 | uses: actions/checkout@main 39 | with: 40 | repository: illusion0001/PS5SDK 41 | ref: nand-clang-16 42 | path: ${{ env.PS5SDK }} 43 | 44 | - name: Set commit version 45 | run: | 46 | echo "commit_ver=1.$(git rev-list HEAD --count)" >> $GITHUB_ENV 47 | echo "commit_hash=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_ENV 48 | 49 | - name: Build SDK 50 | working-directory: ${{ env.PS5SDK }} 51 | run: bash ./build.sh 52 | 53 | - name: Build stubs 54 | if: false 55 | working-directory: stubber 56 | run: | 57 | curl -sL https://github.com/illusion0001/libhijacker/releases/download/stubber-lib/lib1.tar.gz | tar xz -C ./ 58 | cd PS5_Root 59 | mkdir build 60 | cd build 61 | cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .. 62 | ninja 63 | cp -rv ${{ github.workspace }}/stubber/PS5_Root/build/system/common/lib/ ${{ github.workspace }} 64 | 65 | - name: Install stubs 66 | run: curl -sL https://github.com/illusion0001/libhijacker/releases/download/stubber-lib/lib2.tar.gz | tar xz -C ./ 67 | 68 | - name: Build elf 69 | run: bash ./build.sh 70 | 71 | - name: Upload artifacts 72 | # if: github.event_name != 'workflow_dispatch' 73 | if: false 74 | uses: actions/upload-artifact@main 75 | with: 76 | name: libhijacker_${{ env.commit_ver }}-${{ env.commit_hash }} 77 | path: | 78 | bin/spawner.elf 79 | README.md 80 | 81 | - name: Create Release 82 | if: github.event_name == 'workflow_dispatch' 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | run: | 86 | zip libhijacker_${{ env.commit_ver }}-${{ env.commit_hash }}.zip bin/spawner.elf README.md 87 | gh release create ${{ env.commit_ver }}-${{ env.commit_hash }} libhijacker_${{ env.commit_ver }}-${{ env.commit_hash }}.zip --target ${{ GITHUB.SHA }} -t "${{ env.commit_ver }}" -F README.md 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .vscode 3 | .ninja_deps 4 | .ninja_log 5 | cmake_install.cmake 6 | compile_commands.json 7 | build.ninja 8 | CMakeCache.txt 9 | **/CMakeFiles 10 | **/bin 11 | **/lib 12 | *.a 13 | *.o 14 | *.elf 15 | *.so 16 | *.txt 17 | !**/CMakeLists.txt 18 | /BREW00000 19 | aerolib.csv 20 | stubber/main.exe 21 | stubber/out/ 22 | **/*.ninja 23 | klog.txt 24 | log.txt 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "extern/pfd_sfo_tools"] 2 | path = extern/pfd_sfo_tools 3 | url = https://github.com/illusion0001/pfd_sfo_tools.git 4 | [submodule "extern/tiny-json"] 5 | path = extern/tiny-json 6 | url = https://github.com/rafagafe/tiny-json.git 7 | [submodule "extern/mxml"] 8 | path = extern/mxml 9 | url = https://github.com/illusion0001/mxml.git 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.20) 2 | 3 | project("hijacker") 4 | 5 | set(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") 6 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_ROOT}/bin) 7 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_ROOT}/lib) 8 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_ROOT}/lib) # static libs are archive 9 | 10 | add_subdirectory(crt) 11 | add_subdirectory(daemon) 12 | add_subdirectory(daemon_shim) 13 | add_subdirectory(libhijacker) 14 | add_subdirectory(spawner) 15 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildPresets": [ 3 | { 4 | "hidden": false, 5 | "verbose": true, 6 | "name": "default-build-windows", 7 | "displayName": "DefaultBuild", 8 | "configurePreset": "ps5-base", 9 | "description": "default build" 10 | }, 11 | { 12 | "hidden": false, 13 | "verbose": true, 14 | "name": "default-build-nix", 15 | "displayName": "DefaultBuild", 16 | "configurePreset": "nix-base", 17 | "description": "default build" 18 | } 19 | ], 20 | "configurePresets": [ 21 | { 22 | "name": "ps5-base", 23 | "hidden": true, 24 | "generator": "Ninja", 25 | "binaryDir": "${sourceDir}/build/${presetName}", 26 | "installDir": "${sourceDir}/build/install/${presetName}", 27 | "toolchainFile": "${env:PS5SDK}/cmake/toolchain-ps5.cmake", 28 | "cacheVariables": { 29 | "CMAKE_C_COMPILER": "clang.exe", 30 | "CMAKE_CXX_COMPILER": "clang++.exe" 31 | }, 32 | "condition": { 33 | "type": "equals", 34 | "lhs": "${hostSystemName}", 35 | "rhs": "Windows" 36 | } 37 | }, 38 | { 39 | "name": "nix-base", 40 | "hidden": true, 41 | "generator": "Ninja", 42 | "binaryDir": "${sourceDir}/build/${presetName}", 43 | "installDir": "${sourceDir}/build/install/${presetName}", 44 | "toolchainFile": "${env:PS5SDK}/cmake/toolchain-ps5.cmake", 45 | "cacheVariables": { 46 | "CMAKE_C_COMPILER": "clang", 47 | "CMAKE_CXX_COMPILER": "clang++" 48 | }, 49 | "condition": { 50 | "type": "notEquals", 51 | "lhs": "${hostSystemName}", 52 | "rhs": "Windows" 53 | } 54 | }, 55 | { 56 | "name": "ps5-debug", 57 | "displayName": "PS5 Debug", 58 | "inherits": "ps5-base", 59 | "architecture": { 60 | "value": "x64", 61 | "strategy": "external" 62 | }, 63 | "cacheVariables": { 64 | "CMAKE_BUILD_TYPE": "Debug" 65 | } 66 | }, 67 | { 68 | "name": "ps5-release", 69 | "displayName": "PS5 Release", 70 | "inherits": "ps5-debug", 71 | "cacheVariables": { 72 | "CMAKE_BUILD_TYPE": "Release" 73 | } 74 | }, 75 | { 76 | "name": "linux-debug", 77 | "displayName": "Linux Debug", 78 | "inherits": "nix-base", 79 | "cacheVariables": { 80 | "CMAKE_BUILD_TYPE": "Debug" 81 | }, 82 | "vendor": { 83 | "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { 84 | "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" 85 | } 86 | } 87 | }, 88 | { 89 | "name": "macos-debug", 90 | "displayName": "macOS Debug", 91 | "inherits": "nix-base", 92 | "cacheVariables": { 93 | "CMAKE_BUILD_TYPE": "Debug" 94 | }, 95 | "vendor": { 96 | "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { 97 | "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" 98 | } 99 | } 100 | } 101 | ], 102 | "version": 3 103 | } 104 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_TOOLCHAIN_FILE=%PS5SDK%/cmake/toolchain-ps5.cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 . 2 | ninja 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_TOOLCHAIN_FILE=$PS5SDK/cmake/toolchain-ps5.cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 . 3 | ninja 4 | -------------------------------------------------------------------------------- /crt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################################### 2 | # PS5SDK - CRT build script 3 | # @author Znullptr 4 | ################################################################################################### 5 | 6 | cmake_minimum_required (VERSION 3.20) 7 | 8 | set(basename "ps5sdk_crt") 9 | project(${basename} C) 10 | 11 | # Language Standard Defaults 12 | set(CMAKE_C_STANDARD 11) 13 | set(CMAKE_C_STANDARD_REQUIRED ON) 14 | 15 | set(CMAKE_VERBOSE_MAKEFILE ON) 16 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 17 | 18 | if ("${CMAKE_C_COMPILER_ID}" MATCHES "[Cc]lang") 19 | set(IS_CLANG 1) 20 | else() 21 | message(FATAL_ERROR "${PROJECT_NAME} is meant to be built with clang! CompilerID: ${CMAKE_C_COMPILER_ID}") 22 | endif() 23 | 24 | if (NOT DEFINED D_PS5SDK) 25 | set(D_PS5SDK ".") 26 | endif() 27 | 28 | include_directories (SYSTEM "${D_PS5SDK}") 29 | include_directories (SYSTEM "${D_PS5SDK}/include") 30 | 31 | ########## finalize main target sources ########## 32 | 33 | file(GLOB SrcFiles *.c) 34 | 35 | add_library(${PROJECT_NAME} STATIC ${SrcFiles}) 36 | -------------------------------------------------------------------------------- /crt/crt0.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define STDOUT 1 10 | #define STDERR 2 11 | #define SYSCALL_OFFSET 7 12 | #define LIBKERNEL_HANDLE 0x2001 13 | 14 | // NOLINTBEGIN(*) c sucks so screw linting it 15 | 16 | // Store necessary sockets/pipe for corruption. 17 | int _rw_pipe[2]; 18 | int _master_sock; 19 | int _victim_sock; 20 | uint64_t _pipe_addr; 21 | uintptr_t kernel_base; 22 | uintptr_t syscall_addr = 0; 23 | 24 | extern int puts(const char*); 25 | extern int printf(const char*, ...); 26 | 27 | extern size_t _write(int fd, const void *buf, size_t nbyte); 28 | extern size_t _read(int fd, void *buf, size_t nbyte); 29 | 30 | // Arguments passed by way of entrypoint arguments. 31 | static void kernel_init_rw(struct payload_args *__restrict args) { 32 | _rw_pipe[0] = args->rwpipe[0]; 33 | _rw_pipe[1] = args->rwpipe[1]; 34 | _master_sock = args->rwpair[0]; 35 | _victim_sock = args->rwpair[1]; 36 | _pipe_addr = args->kpipe_addr; 37 | kernel_base = args->kdata_base_addr; 38 | } 39 | 40 | // Internal kwrite function - not friendly, only for setting up better primitives. 41 | static void kwrite(uint64_t addr, uint64_t *data) { 42 | setsockopt(_master_sock, IPPROTO_IPV6, IPV6_PKTINFO, (uint64_t[3]){addr, 0, 0}, 0x14); 43 | setsockopt(_victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, data, 0x14); 44 | } 45 | 46 | // Public API function to write kernel data. 47 | void kernel_copyin(const void *src, uint64_t kdest, size_t length) { 48 | // Set pipe flags 49 | kwrite(_pipe_addr, (uint64_t[3]){0, 0x4000000000000000, 0}); 50 | 51 | // Set pipe data addr 52 | kwrite(_pipe_addr + 0x10, (uint64_t[3]){kdest, 0, 0}); 53 | 54 | // Perform write across pipe 55 | _write(_rw_pipe[1], src, length); 56 | } 57 | 58 | // Public API function to read kernel data. 59 | void kernel_copyout(uint64_t ksrc, void *dest, size_t length) { 60 | // Set pipe flags 61 | kwrite(_pipe_addr, (uint64_t[3]){0x4000000040000000, 0x4000000000000000, 0}); 62 | 63 | // Set pipe data addr 64 | kwrite(_pipe_addr + 0x10, (uint64_t[3]){ksrc, 0, 0}); 65 | 66 | // Perform read across pipe 67 | _read(_rw_pipe[0], dest, length); 68 | } 69 | 70 | extern int main(int argc, const char **argv); 71 | 72 | extern void (*__preinit_array_start[])(void) __attribute__((weak)); 73 | extern void (*__preinit_array_end[])(void) __attribute__((weak)); 74 | extern void (*__init_array_start[])(void) __attribute__((weak)); 75 | extern void (*__init_array_end[])(void) __attribute__((weak)); 76 | extern void (*__fini_array_start[])(void) __attribute__((weak)); 77 | extern void (*__fini_array_end[])(void) __attribute__((weak)); 78 | extern uint8_t __bss_start __attribute__((weak)); 79 | extern uint8_t __bss_end __attribute__((weak)); 80 | 81 | static void _preinit(void) { 82 | const size_t length = __preinit_array_end - __preinit_array_start; 83 | for (size_t i = 0; i < length; i++) { 84 | __preinit_array_start[i](); 85 | } 86 | } 87 | 88 | static void _init(void) { 89 | const size_t length = __init_array_end - __init_array_start; 90 | for (size_t i = 0; i < length; i++) { 91 | __init_array_start[i](); 92 | } 93 | } 94 | 95 | static void _fini(void) { 96 | const size_t length = __fini_array_end - __fini_array_start; 97 | for (size_t i = 0; i < length; i++) { 98 | __fini_array_start[i](); 99 | } 100 | } 101 | 102 | void __attribute__((noreturn)) _start(struct payload_args *__restrict args) { 103 | 104 | // zero .bss 105 | __builtin_memset(&__bss_start, 0, &__bss_end - &__bss_start); 106 | 107 | // init stdout, stderr and kernelrw first 108 | int fd = open("/dev/console", O_WRONLY); 109 | dup2(fd, STDOUT); 110 | dup2(STDOUT, STDERR); 111 | kernel_init_rw(args); 112 | 113 | // preinit and then init 114 | _preinit(); 115 | _init(); 116 | 117 | // register _fini 118 | atexit(_fini); 119 | 120 | uintptr_t get_authinfo = 0; 121 | args->dlsym(LIBKERNEL_HANDLE, "get_authinfo", &get_authinfo); 122 | syscall_addr = get_authinfo + SYSCALL_OFFSET; 123 | 124 | // run main 125 | exit(main(0, NULL)); 126 | } 127 | 128 | int __attribute__ ((naked, weak, noinline)) mdbg_call() { 129 | __asm__ volatile( 130 | "mov $573, %rax\n" 131 | "jmp *syscall_addr(%rip)\n" 132 | ); 133 | } 134 | 135 | int __attribute__ ((naked, weak, noinline)) ptrace() { 136 | __asm__ volatile( 137 | "mov $26, %rax\n" 138 | "jmp *syscall_addr(%rip)\n" 139 | ); 140 | } 141 | 142 | int __attribute__ ((naked, weak, noinline)) nmount() { 143 | __asm__ volatile( 144 | "mov $378, %rax\n" 145 | "jmp *syscall_addr(%rip)\n" 146 | ); 147 | } 148 | 149 | // NOLINTEND(*) 150 | -------------------------------------------------------------------------------- /daemon/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################################### 2 | # PS5SDK - Example: pipe pirate 3 | # Uses the read/write primitive to read and write some kernel data. 4 | # @authors ChendoChap, Specter, Znullptr 5 | ################################################################################################### 6 | 7 | cmake_minimum_required (VERSION 3.20) 8 | 9 | set(basename "daemon") 10 | project(${basename} C CXX ASM) 11 | 12 | # Language Standard Defaults 13 | set(CMAKE_C_STANDARD 11) 14 | set(CMAKE_C_STANDARD_REQUIRED ON) 15 | 16 | set(CMAKE_CXX_EXTENSIONS ON) 17 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 18 | set(CMAKE_CXX_STANDARD 20) 19 | 20 | # Check for sub-project as part of main build or external build 21 | if (NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 22 | set(IS_SUBPROJECT TRUE) 23 | else() 24 | set(IS_SUBPROJECT FALSE) 25 | endif() 26 | 27 | message("IS_SUBPROJECT: ${IS_SUBPROJECT}") 28 | 29 | set(D_CWD "${CMAKE_CURRENT_SOURCE_DIR}") 30 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${D_CWD}/../bin) 31 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${D_CWD}/bin) 32 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${D_CWD}/bin) # static libs are archive 33 | 34 | # Headers 35 | include_directories (SYSTEM "${D_PS5SDK}") 36 | include_directories (SYSTEM "${D_PS5SDK}/include") 37 | 38 | add_executable(${PROJECT_NAME}) 39 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}.elf") 40 | 41 | # Must build with clang 42 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc]lang") 43 | set(IS_CLANG 1) 44 | else() 45 | message(FATAL_ERROR "${PROJECT_NAME} is meant to be built with clang! CompilerID: ${CMAKE_CXX_COMPILER_ID}") 46 | endif() 47 | 48 | # Finalize main target sources 49 | target_compile_options(${PROJECT_NAME} PUBLIC 50 | $<$:${C_DEFS} ${C_FLAGS}> 51 | $<$:${CXX_DEFS} ${CXX_FLAGS}> 52 | $<$:${ASM_FLAGS}> 53 | ) 54 | 55 | message("========== build: ${PROJECT_NAME} ==========") 56 | 57 | set(D_SRC ${D_CWD}/source) 58 | 59 | file(GLOB SrcFiles ${D_SRC}/*.c ${D_SRC}/*.cpp ${D_SRC}/*.h ${D_SRC}/*.hpp ${D_SRC}/*.s ${D_SRC}/../../ftp/*.c ${D_SRC}/../../ftp/*.h ${D_SRC}/../../extern/pfd_sfo_tools/sfopatcher/src/*.c ${D_SRC}/../../extern/pfd_sfo_tools/sfopatcher/src/*.h ${D_SRC}/../../extern/tiny-json/*.c ${D_SRC}/../../extern/tiny-json/*.h ${D_SRC}/../../extern/mxml/*.c ${D_SRC}/../../extern/mxml/*.h) 60 | 61 | set(CMAKE_C_FLAGS "--target=x86_64-freebsd-pc-elf -DPPR -DPS5 -DPS5_FW_VERSION=${V_FW} ") 62 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200112 -D__BSD_VISIBLE=1 -D__XSI_VISIBLE=500") 63 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fshort-wchar -fno-builtin -nostdlib -Wall") # -nostartfiles 64 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -fPIE -march=znver2 -Wall -Werror") 65 | set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -DDEBUG -gfull -gdwarf-2 -O0 -pedantic -pedantic-errors") 66 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -gfull -gdwarf-2 -O0") 67 | 68 | target_sources(${PROJECT_NAME} PRIVATE ${SrcFiles}) 69 | target_include_directories(${PROJECT_NAME} PRIVATE "${D_CWD}/../include") 70 | target_link_directories (${PROJECT_NAME} PUBLIC "${PROJECT_ROOT}/lib") 71 | target_link_libraries (${PROJECT_NAME} PUBLIC hijacker SceLibcInternal SceSystemService SceUserService SceSysmodule SceSysCore ScePad SceVideoOut kernel_sys) 72 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 73 | set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=lld -Xlinker -pie -Xlinker -zmax-page-size=16384 -Xlinker -zcommon-page-size=16384 -Xlinker -T ${D_CWD}/linker.x -Wl,--build-id=none -Wl,-z,norelro") 74 | -------------------------------------------------------------------------------- /daemon/linker.x: -------------------------------------------------------------------------------- 1 | PHDRS { 2 | /* 3 | * PF_X = 0x1 4 | * PF_W = 0x2 5 | * PF_R = 0x4 6 | */ 7 | phdr_text PT_LOAD FLAGS(0x5); 8 | phdr_data PT_LOAD FLAGS(0x6); 9 | phdr_rodata PT_LOAD FLAGS(0x4); 10 | phdr_relro PT_LOAD FLAGS(0x4); 11 | phdr_eh_frame PT_GNU_EH_FRAME FLAGS(0x4); 12 | phdr_dynamic PT_DYNAMIC FLAGS(0x0); 13 | } 14 | 15 | SECTIONS { 16 | 17 | PROVIDE (__payload_start = .); 18 | 19 | .text : { 20 | PROVIDE_HIDDEN(__text_start = .); 21 | *(.text .text.*); 22 | PROVIDE_HIDDEN(__text_stop = .); 23 | } : phdr_text 24 | 25 | .init : { 26 | *(.init) 27 | } : phdr_text 28 | 29 | .fini : { 30 | *(.fini) 31 | } : phdr_text 32 | 33 | .plt : { 34 | *(.plt) 35 | } : phdr_text 36 | 37 | . = ALIGN(0x4000); /* move to a new page in memory */ 38 | 39 | .data : { 40 | *(.data); 41 | *(.data.*); 42 | } : phdr_data 43 | 44 | .bss (NOLOAD) : { 45 | PROVIDE_HIDDEN (__bss_start = .); 46 | *(.bss .bss.*); 47 | *(COMMON) 48 | PROVIDE_HIDDEN (__bss_end = .); 49 | } : phdr_data 50 | 51 | . = ALIGN(0x4000); /* move to a new page in memory */ 52 | 53 | .rodata : { 54 | *(.rodata .rodata.*); 55 | } : phdr_rodata 56 | 57 | .gcc_except_table : { 58 | *(.gcc_except_table*) 59 | } : phdr_rodata 60 | 61 | .hash : { 62 | *(.hash); 63 | } : phdr_rodata 64 | 65 | . = ALIGN(0x4000); /* move to a new page in memory */ 66 | 67 | .eh_frame_hdr : ALIGN(0x4000) { 68 | *(.eh_frame_hdr) 69 | } : phdr_eh_frame 70 | 71 | .eh_frame : ALIGN(0x10) { 72 | *(.eh_frame) 73 | } : phdr_eh_frame 74 | 75 | . = ALIGN(0x4000); /* move to a new page in memory */ 76 | 77 | .data.rel.ro : { 78 | *(.data.rel.ro .data.rel.ro.*); 79 | } : phdr_relro 80 | 81 | .preinit_array : { 82 | PROVIDE_HIDDEN (__preinit_array_start = .); 83 | KEEP (*(.preinit_array*)) 84 | PROVIDE_HIDDEN (__preinit_array_end = .); 85 | } : phdr_relro 86 | 87 | .init_array : { 88 | PROVIDE_HIDDEN(__init_array_start = .); 89 | KEEP (*(.init_array .init_array.*)); 90 | PROVIDE_HIDDEN(__init_array_stop = .); 91 | } : phdr_relro 92 | 93 | .fini_array : { 94 | PROVIDE_HIDDEN(__fini_array_start = .); 95 | KEEP (*(.fini_array .fini_array.*)); 96 | PROVIDE_HIDDEN(__fini_array_stop = .); 97 | } : phdr_relro 98 | 99 | .got : { 100 | *(.got); 101 | } : phdr_relro 102 | 103 | .got.plt : { 104 | *(.got.plt); 105 | } : phdr_relro 106 | 107 | .rela.dyn : { 108 | *(.rela.dyn) *(.rela); 109 | } : phdr_relro 110 | 111 | .rela.plt : { 112 | *(rela.plt); 113 | } : phdr_relro 114 | 115 | PROVIDE (__payload_end = .); 116 | 117 | /* this needs to be forced aligned to 0x4000 */ 118 | .dynamic : ALIGN(0x4000) { 119 | PROVIDE_HIDDEN (_DYNAMIC = .); 120 | *(.dynamic); 121 | } : phdr_dynamic 122 | 123 | .dynsym : { 124 | *(.dynsym); 125 | } : phdr_dynamic 126 | 127 | .dynstr : { 128 | *(.dynstr); 129 | } : phdr_dynamic 130 | } 131 | -------------------------------------------------------------------------------- /daemon/source/commands.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "dbg/dbg.hpp" 8 | #include "elf/elf.hpp" 9 | #include "fd.hpp" 10 | #include "hijacker/hijacker.hpp" 11 | #include "launcher.hpp" 12 | #include "servers.hpp" 13 | 14 | enum Command : int8_t { 15 | INVALID_CMD = -1, 16 | ACTIVE_CMD = 0, 17 | LAUNCH_CMD, 18 | PROCLIST_CMD, 19 | KILL_CMD, 20 | KILL_APP_CMD 21 | }; 22 | 23 | static bool launchApp(const char *titleId); 24 | static bool killApp(uint32_t appId); 25 | 26 | static void replyError(TcpSocket &sock) { 27 | const Command cmd = INVALID_CMD; 28 | if (!sock.isClosed()) { 29 | sock.write(&cmd, sizeof(cmd)); 30 | } 31 | } 32 | 33 | static void replyOk(TcpSocket &sock) { 34 | const Command cmd = ACTIVE_CMD; 35 | sock.write(&cmd, sizeof(cmd)); 36 | } 37 | 38 | static void listProcs() { 39 | for (const auto &p : dbg::getProcesses()) { 40 | printf("%d %s\n", p.pid(), p.name().c_str()); 41 | } 42 | } 43 | 44 | void CommandServer::run(TcpSocket &sock) { 45 | Command cmd = INVALID_CMD; 46 | if (!sock.read(&cmd, sizeof(cmd))) { 47 | // failure to read is catastrophic 48 | sock.close(); 49 | return; 50 | } 51 | 52 | 53 | printf("cmd: %u\n", cmd); 54 | 55 | switch (cmd) { 56 | case ACTIVE_CMD: 57 | replyOk(sock); 58 | break; 59 | case LAUNCH_CMD: 60 | // NPXS40000 61 | static constexpr auto APP_ID_LENGTH = 10; 62 | char appId[APP_ID_LENGTH]; 63 | if (!sock.read(appId, sizeof(appId) - 1)) { 64 | replyError(sock); 65 | break; 66 | } 67 | appId[sizeof(appId) - 1] = '\0'; 68 | if (launchApp(appId)) { 69 | replyOk(sock); 70 | } else { 71 | replyError(sock); 72 | } 73 | break; 74 | case PROCLIST_CMD: 75 | listProcs(); 76 | break; 77 | /*case KILL_CMD: 78 | replyOk(sock); 79 | sock.close(); 80 | break;*/ 81 | case KILL_APP_CMD: { 82 | uint32_t id{}; 83 | if (!sock.read(&id, sizeof(id))) { 84 | replyError(sock); 85 | break; 86 | } 87 | printf("id: 0x%lx\n", id); 88 | if (!killApp(id)) { 89 | replyError(sock); 90 | break; 91 | } 92 | replyOk(sock); 93 | break; 94 | } 95 | case INVALID_CMD: 96 | [[fallthrough]]; 97 | default: 98 | replyError(sock); 99 | break; 100 | } 101 | } 102 | 103 | static void __attribute__((constructor)) initUserService() { 104 | static constexpr auto DEFAULT_PRIORITY = 256; 105 | int priority = DEFAULT_PRIORITY; 106 | sceUserServiceInitialize(&priority); 107 | } 108 | 109 | static bool killApp(uint32_t appId) { 110 | uint32_t res = sceLncUtilKillApp(appId); 111 | printf("sceApplicationKill returned 0x%llx\n", res); 112 | return true; 113 | } 114 | 115 | struct LaunchArgs { 116 | const char *titleId; 117 | uint32_t id; 118 | int *appId; 119 | }; 120 | 121 | static void *doLaunchApp(void *ptr) { 122 | UniquePtr args = reinterpret_cast(ptr); 123 | Flag flag = Flag_None; 124 | LncAppParam param{sizeof(LncAppParam), args->id, 0, 0, flag}; 125 | 126 | int err = sceLncUtilLaunchApp(args->titleId, nullptr, ¶m); 127 | printf("sceLncUtilLaunchApp returned 0x%llx\n", (uint32_t)err); 128 | if (err >= 0) { 129 | return nullptr; 130 | } 131 | switch ((uint32_t) err) { 132 | case SCE_LNC_UTIL_ERROR_ALREADY_RUNNING: 133 | printf("app %s is already running\n", args->titleId); 134 | break; 135 | case SCE_LNC_ERROR_APP_NOT_FOUND: 136 | printf("app %s not found\n", args->titleId); 137 | break; 138 | default: 139 | printf("unknown error 0x%llx\n", (uint32_t) err); 140 | break; 141 | } 142 | return nullptr; 143 | } 144 | 145 | static pthread_t launchAppThread(const char *titleId, int *appId) { 146 | puts("launching app"); 147 | uint32_t id = -1; 148 | uint32_t res = sceUserServiceGetForegroundUser(&id); 149 | if (res != 0) { 150 | printf("sceUserServiceGetForegroundUser failed: 0x%llx\n", res); 151 | return nullptr; 152 | } 153 | printf("user id %u\n", id); 154 | 155 | // the thread will clean this up 156 | LaunchArgs *args = new LaunchArgs{titleId, id, appId}; // NOLINT(*) 157 | pthread_t td = nullptr; 158 | pthread_create(&td, nullptr, doLaunchApp, args); 159 | return td; 160 | } 161 | 162 | static int getNextPid(const int lastPid) { 163 | // get the pid of the new process as soon as it is created 164 | int pid = lastPid; 165 | while (pid == lastPid) { 166 | usleep(1000); // NOLINT(*) 167 | pid = dbg::getAllPids()[0]; 168 | } 169 | if (dbg::ProcessInfo{pid}.name() == "ScePfs"_sv) { 170 | puts("Skipping ScePfs"); 171 | return getNextPid(pid); 172 | } 173 | return pid; 174 | } 175 | 176 | class GameServer : public TcpServer { 177 | 178 | static constexpr uint16_t PORT = 9050; 179 | int pid; 180 | 181 | void run(TcpSocket &sock) override; 182 | 183 | void start() noexcept override { 184 | TcpSocket sock = accept(); 185 | if (sock) { 186 | run(sock); 187 | } 188 | } 189 | 190 | public: 191 | GameServer(int pid) noexcept : TcpServer(PORT), pid(pid) { 192 | printf("waiting for game elf on port %d\n", PORT); 193 | } 194 | 195 | GameServer(const GameServer&) = delete; 196 | 197 | GameServer &operator=(const GameServer&) = delete; 198 | 199 | GameServer(GameServer &&rhs) noexcept : TcpServer((TcpServer&&)rhs), pid(rhs.pid) { 200 | rhs.pid = 0; 201 | } 202 | 203 | GameServer &operator=(GameServer &&rhs) = delete; 204 | 205 | ~GameServer() noexcept override = default; 206 | }; 207 | 208 | void GameServer::run(TcpSocket &sock) { 209 | size_t elfSize = 0; 210 | if (!sock.read(&elfSize, sizeof(elfSize))) { 211 | return; 212 | } 213 | 214 | UniquePtr buf = new uint8_t[elfSize]; 215 | if (!sock.read(buf.get(), sizeof(elfSize))) { 216 | return; 217 | } 218 | 219 | auto hijacker = Hijacker::getHijacker(pid); 220 | if (hijacker == nullptr) { 221 | sock.println("failed to get hijacker for game process"); 222 | return; 223 | } 224 | 225 | Elf elf{hijacker.get(), buf.get()}; 226 | if (elf.launch()) { 227 | puts("launch succeeded"); 228 | } else { 229 | puts("launch failed"); 230 | } 231 | } 232 | 233 | static bool launchApp(const char *titleId) { 234 | //static constexpr size_t NANOSLEEP_OFFSET = 0x28D0; 235 | //LoopBuilder loop = SLEEP_LOOP; 236 | 237 | const int lastPid = dbg::getAllPids()[0]; 238 | 239 | int appId = 0; 240 | pthread_t td = launchAppThread(titleId, &appId); 241 | if (td == nullptr) { 242 | puts("failed to start thread"); 243 | return false; 244 | } 245 | 246 | puts("waiting for new process to spawn"); 247 | 248 | // get the pid of the new process as soon as it is created 249 | int pid = getNextPid(lastPid); 250 | 251 | printf("found new pid %d\n", pid); 252 | 253 | UniquePtr spawned = nullptr; 254 | { 255 | // attach to the new process 256 | dbg::Tracer tracer{pid}; 257 | 258 | // run until execve finishes and sends the signal 259 | tracer.run(); 260 | 261 | while (spawned == nullptr) { 262 | // this should grab it first try but I haven't confirmed yet 263 | spawned = Hijacker::getHijacker(pid); 264 | } 265 | 266 | printf("libkernel imagebase: 0x%08llx\n", spawned->getLibKernelBase()); 267 | 268 | puts("spawned process obtained"); 269 | 270 | puts("success"); 271 | 272 | uintptr_t base = 0; 273 | while (base == 0) { 274 | // this should also work first try but not confirmed 275 | base = spawned->getLibKernelBase(); 276 | } 277 | 278 | puts("joining"); 279 | pthread_join(td, nullptr); 280 | 281 | puts("finished"); 282 | printf("spawned imagebase 0x%08llx\n", base); 283 | 284 | tracer.run(); 285 | } 286 | 287 | GameServer gs{pid}; 288 | gs.TcpServer::run(); 289 | return true; 290 | //sceLncUtilKillApp 291 | } 292 | -------------------------------------------------------------------------------- /daemon/source/crt0.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define STDOUT 1 10 | #define STDERR 2 11 | 12 | // NOLINTBEGIN(*) c sucks so screw linting it 13 | 14 | extern int main(int argc, const char **argv); 15 | 16 | // Store necessary sockets/pipe for corruption. 17 | int _rw_pipe[2]; 18 | int _master_sock; 19 | int _victim_sock; 20 | uint64_t _pipe_addr; 21 | uintptr_t kernel_base; 22 | 23 | extern int puts(const char*); 24 | extern int printf(const char*, ...); 25 | 26 | extern size_t _write(int fd, const void *buf, size_t nbyte); 27 | extern size_t _read(int fd, void *buf, size_t nbyte); 28 | 29 | // Arguments passed by way of entrypoint arguments. 30 | static void kernel_init_rw(struct payload_args *__restrict args) { 31 | _rw_pipe[0] = args->rwpipe[0]; 32 | _rw_pipe[1] = args->rwpipe[1]; 33 | _master_sock = args->rwpair[0]; 34 | _victim_sock = args->rwpair[1]; 35 | _pipe_addr = args->kpipe_addr; 36 | kernel_base = args->kdata_base_addr; 37 | } 38 | 39 | // Internal kwrite function - not friendly, only for setting up better primitives. 40 | static void kwrite(uint64_t addr, uint64_t *data) { 41 | setsockopt(_master_sock, IPPROTO_IPV6, IPV6_PKTINFO, (uint64_t[3]){addr, 0, 0}, 0x14); 42 | setsockopt(_victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, data, 0x14); 43 | } 44 | 45 | // Public API function to write kernel data. 46 | void kernel_copyin(const void *src, uint64_t kdest, size_t length) { 47 | // Set pipe flags 48 | kwrite(_pipe_addr, (uint64_t[3]){0, 0x4000000000000000, 0}); 49 | 50 | // Set pipe data addr 51 | kwrite(_pipe_addr + 0x10, (uint64_t[3]){kdest, 0, 0}); 52 | 53 | // Perform write across pipe 54 | _write(_rw_pipe[1], src, length); 55 | } 56 | 57 | // Public API function to read kernel data. 58 | void kernel_copyout(uint64_t ksrc, void *dest, size_t length) { 59 | // Set pipe flags 60 | kwrite(_pipe_addr, (uint64_t[3]){0x4000000040000000, 0x4000000000000000, 0}); 61 | 62 | // Set pipe data addr 63 | kwrite(_pipe_addr + 0x10, (uint64_t[3]){ksrc, 0, 0}); 64 | 65 | // Perform read across pipe 66 | _read(_rw_pipe[0], dest, length); 67 | } 68 | 69 | extern int main(int argc, const char **argv); 70 | 71 | extern void (*__preinit_array_start[])(void) __attribute__((weak)); 72 | extern void (*__preinit_array_end[])(void) __attribute__((weak)); 73 | extern void (*__init_array_start[])(void) __attribute__((weak)); 74 | extern void (*__init_array_end[])(void) __attribute__((weak)); 75 | extern void (*__fini_array_start[])(void) __attribute__((weak)); 76 | extern void (*__fini_array_end[])(void) __attribute__((weak)); 77 | extern uint8_t __bss_start __attribute__((weak)); 78 | extern uint8_t __bss_end __attribute__((weak)); 79 | 80 | static void _preinit(void) { 81 | const size_t length = __preinit_array_end - __preinit_array_start; 82 | for (size_t i = 0; i < length; i++) { 83 | __preinit_array_start[i](); 84 | } 85 | } 86 | 87 | static void _init(void) { 88 | const size_t length = __init_array_end - __init_array_start; 89 | for (size_t i = 0; i < length; i++) { 90 | __init_array_start[i](); 91 | } 92 | } 93 | 94 | static void _fini(void) { 95 | const size_t length = __fini_array_end - __fini_array_start; 96 | for (size_t i = 0; i < length; i++) { 97 | __fini_array_start[i](); 98 | } 99 | } 100 | 101 | int _start(struct payload_args *__restrict args) { 102 | 103 | // zero .bss 104 | __builtin_memset(&__bss_start, 0, &__bss_end - &__bss_start); 105 | 106 | // init stdout, stderr and kernelrw first 107 | int fd = open("/dev/console", O_WRONLY); 108 | dup2(fd, STDOUT); 109 | dup2(STDOUT, STDERR); 110 | kernel_init_rw(args); 111 | 112 | // preinit and then init 113 | _preinit(); 114 | _init(); 115 | 116 | // run main 117 | int res = main(0, NULL); 118 | _fini(); 119 | return res; 120 | } 121 | 122 | // NOLINTEND(*) 123 | -------------------------------------------------------------------------------- /daemon/source/fd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class FileDescriptor { 9 | static constexpr int STUPID_C_ERROR_VALUE = -1; 10 | 11 | protected: 12 | int fd; 13 | 14 | explicit FileDescriptor() : fd(-1) {} 15 | 16 | public: 17 | FileDescriptor(int fd) : fd(fd) {} 18 | FileDescriptor(const FileDescriptor&) = delete; 19 | FileDescriptor& operator=(const FileDescriptor &) = delete; 20 | FileDescriptor(FileDescriptor &&rhs) noexcept : fd(rhs.fd) { rhs.fd = -1; } 21 | FileDescriptor& operator=(FileDescriptor &&rhs) noexcept { 22 | if (fd != -1) { 23 | close(fd); 24 | } 25 | fd = rhs.fd; 26 | rhs.fd = -1; 27 | return *this; 28 | } 29 | ~FileDescriptor() { 30 | if (fd != -1) { 31 | close(fd); 32 | } 33 | } 34 | 35 | operator int() const { return fd; } 36 | 37 | bool read(void *buf, size_t size) const { 38 | if (::read(fd, buf, size) == STUPID_C_ERROR_VALUE) [[unlikely]] { 39 | int err = errno; 40 | printf("read failed %d %s\n", err, strerror(err)); 41 | return false; 42 | } 43 | return true; 44 | } 45 | 46 | bool write(const void *buf, size_t size) const { 47 | if (::write(fd, buf, size) == STUPID_C_ERROR_VALUE) [[unlikely]] { 48 | int err = errno; 49 | printf("write failed %d %s\n", err, strerror(err)); 50 | return false; 51 | } 52 | return true; 53 | } 54 | 55 | template 56 | bool write(const char (&buf)[N]) const { 57 | return write(buf, N-1); 58 | } 59 | 60 | template 61 | bool println(const char (&buf)[N]) const { 62 | if (!write(buf)) [[unlikely]] { 63 | return false; 64 | } 65 | return write("\n"); 66 | } 67 | 68 | template 69 | int puts(const char (&buf)[N]) const { 70 | if (println(buf)) { 71 | return N; 72 | } 73 | return 0; 74 | } 75 | 76 | bool writeAll(const void *buf, size_t size) const { 77 | while (size > 0) { 78 | auto wrote = ::write(fd, buf, size); 79 | if (wrote == STUPID_C_ERROR_VALUE) [[unlikely]] { 80 | return false; 81 | } 82 | size -= wrote; 83 | } 84 | return true; 85 | } 86 | }; 87 | -------------------------------------------------------------------------------- /daemon/source/game_patch.hpp: -------------------------------------------------------------------------------- 1 | #include "game_patch_memory.hpp" 2 | 3 | extern uint32_t FlipRate_ConfigureOutput_Ptr; 4 | extern uint32_t FlipRate_isVideoModeSupported_Ptr; 5 | extern int32_t g_isPatch120Hz; 6 | 7 | void DoPatch_Bloodborne109(pid_t app_pid, uint64_t text_base); 8 | void DoPatch_GravityDaze2_111(pid_t app_pid, uint64_t text_base); 9 | void DoPatch_SOTC_100(pid_t app_pid, uint64_t text_base); 10 | void DoPatch_BigCollection(pid_t app_pid, uint64_t text_base, uint32_t idx); 11 | void DoPatch_TheOrder1886_102(pid_t app_pid, uint64_t text_base); 12 | void DoPatch_Big4R_100(pid_t app_pid, uint64_t text_base, uint32_t idx); 13 | void DoPatch_DemonSouls(pid_t app_pid, uint64_t text_base, uint32_t idx); 14 | void DoPatch_DriveClub_128(pid_t app_pid, uint64_t text_base); 15 | void DoPatch_TheLastGuardian_103(pid_t app_pid, uint64_t text_base); 16 | void DoPatch_CTR_121(pid_t app_pid, uint64_t text_base, uint32_t idx); 17 | void DoPatch_t1ps4_111(pid_t app_pid, uint64_t text_base); 18 | void DoPatch_t2ps4(pid_t app_pid, uint64_t text_base, uint32_t idx); 19 | void DoPatch_ACEZioCollection_102(pid_t app_pid, uint64_t text_base, uint32_t idx); 20 | void DoPatchBF4_124(pid_t app_pid, uint64_t text_base); 21 | void DoPatchKillzone_181(pid_t app_pid, uint64_t text_base); 22 | void DoPatchGravityDaze_101(pid_t app_pid, uint64_t text_base); 23 | void DoPatchMEC_102(pid_t app_pid, uint64_t text_base); 24 | void DoPatchNier103(pid_t app_pid, uint64_t text_base); 25 | void DoPatchDoom_112(pid_t app_pid, uint64_t text_base); 26 | void DoPatchBPR_103(pid_t app_pid, uint64_t text_base); 27 | -------------------------------------------------------------------------------- /daemon/source/game_patch_memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" 4 | { 5 | #include 6 | #include 7 | #include 8 | #include 9 | void *malloc(size_t size); 10 | void free(void *ptr); 11 | } 12 | 13 | enum write_flag : uint32_t 14 | { 15 | no_flag = 0, 16 | isOffsetConfigureOutput = 1 << 1, 17 | isOffsetVideoModeSupported = 1 << 2, 18 | }; 19 | 20 | void dump_bytes_vm(pid_t pid, uint64_t addr, size_t bytes_size); 21 | 22 | void write_bytes(pid_t pid, uint64_t addr, const char *hexString, enum write_flag special_flag = no_flag); 23 | void write_bytes32(pid_t pid, uint64_t addr, const uint32_t val); 24 | void write_bytes64(pid_t pid, uint64_t addr, const size_t val); 25 | void write_float32(pid_t pid, uint64_t addr, const float val); 26 | void write_float64(pid_t pid, uint64_t addr, const double val); 27 | void write_string(pid_t pid, uint64_t addr, const char *string); 28 | void write_wstring(pid_t pid, uint64_t addr, const wchar_t *string); 29 | uint8_t *PatternScan(const uint64_t module_base, const uint64_t module_size, const char *signature); 30 | 31 | bool patchShellCore(const pid_t app_pid, const uint64_t shellcore_base, const uint64_t shellcore_size); 32 | bool UnPatchShellCore(const pid_t app_pid); 33 | 34 | #define NO_ASLR_BASE 0x00400000 35 | #define NO_ASLR(addr) (text_base + (addr - NO_ASLR_BASE)) 36 | #define BASE_ASLR(addr) (text_base + addr) 37 | #define BASE_ASLR_OFFSET(base_addr, addr) (text_base + (addr - base_addr)) 38 | 39 | #define startsWith(str1, str2) (strncmp(str1, str2, __builtin_strlen(str2)) == 0) 40 | -------------------------------------------------------------------------------- /daemon/source/game_patch_thread.hpp: -------------------------------------------------------------------------------- 1 | void *GamePatch_InputThread(void *unused); 2 | void* GamePatch_Thread(void* unused); 3 | 4 | extern int32_t g_game_patch_thread_running; 5 | -------------------------------------------------------------------------------- /daemon/source/game_patch_xml.cpp: -------------------------------------------------------------------------------- 1 | #include "../extern/mxml/mxml.h" 2 | #include "game_patch_xml_cfg.hpp" 3 | #include 4 | 5 | #include "game_patch_memory.hpp" 6 | #include "notify.hpp" 7 | 8 | // Include the `game_patch_fliprate_list.xml` as a symbol 9 | __asm__( 10 | ".intel_syntax noprefix\n" 11 | ".section .data\n" 12 | "DefaultXml_FliprateList:\n" 13 | ".incbin \".." XML_PATH_LIST "\"\n"); 14 | 15 | extern "C" const char DefaultXml_FliprateList[]; 16 | 17 | int makeDefaultXml_List() 18 | { 19 | FILE *f = fopen(XML_PATH_LIST, "rb"); 20 | if (!f) 21 | { 22 | FILE *new_f = fopen(XML_PATH_LIST, "w"); 23 | // Print default data to TTY 24 | printf("%s\n", DefaultXml_FliprateList); 25 | fputs(DefaultXml_FliprateList, new_f); 26 | fflush(new_f); 27 | fclose(new_f); 28 | printf_notification("Created default config file:\n" XML_PATH_LIST); 29 | } 30 | return 0; 31 | } 32 | 33 | extern "C" void *malloc(size_t); 34 | extern "C" void free(void *); 35 | 36 | int Xml_parseTitleID(const char *titleId) 37 | { 38 | makeDefaultXml_List(); 39 | char *buffer = NULL; 40 | uint64_t length = 0; 41 | FILE *f = fopen(XML_PATH_LIST, "rb"); 42 | printf("File: " XML_PATH_LIST " exist at 0x%p\n", f); 43 | if (f) 44 | { 45 | fseek(f, 0, SEEK_END); 46 | length = ftell(f); 47 | fseek(f, 0, SEEK_SET); 48 | buffer = (char *)malloc(length); 49 | if (buffer) 50 | { 51 | fread(buffer, 1, length, f); 52 | printf("Memory at 0x%p\n", buffer); 53 | } 54 | fclose(f); 55 | } 56 | mxml_node_t *tree = mxmlLoadString(NULL, buffer, MXML_OPAQUE_CALLBACK); 57 | 58 | if (tree == NULL) 59 | { 60 | return 0; 61 | } 62 | 63 | mxml_node_t *titleIDNode = mxmlFindElement(tree, tree, "TitleID", NULL, NULL, MXML_DESCEND); 64 | 65 | int found_id = 0; 66 | if (titleIDNode != NULL) 67 | { 68 | mxml_node_t *idNode = mxmlFindElement(titleIDNode, tree, "ID", NULL, NULL, MXML_DESCEND); 69 | 70 | while (idNode != NULL) 71 | { 72 | const char *idValue = mxmlGetOpaque(idNode); 73 | if (idValue != NULL) 74 | { 75 | // printf("TitleID: %s\n", idValue); 76 | if (strncmp(titleId, idValue, __builtin_strlen("CUSAxxxxx")) == 0) 77 | { 78 | found_id = 1; 79 | printf("%s match !! found_id=0x%08x\n", titleId, found_id); 80 | break; 81 | } 82 | } 83 | idNode = mxmlFindElement(idNode, titleIDNode, "ID", NULL, NULL, MXML_NO_DESCEND); 84 | } 85 | } 86 | 87 | if (buffer) 88 | { 89 | free(buffer); 90 | } 91 | if (tree) 92 | { 93 | mxmlDelete(tree); 94 | } 95 | return found_id; 96 | } 97 | 98 | int simple_get_bool(const char *val) 99 | { 100 | if (val == NULL || val[0] == 0) 101 | { 102 | return 0; 103 | } 104 | if ( 105 | val[0] == '1' || 106 | startsWith(val, "on") || 107 | startsWith(val, "true") || 108 | startsWith(val, "On") || 109 | startsWith(val, "True")) 110 | { 111 | return 1; 112 | } 113 | else if ( 114 | val[0] == '0' || 115 | startsWith(val, "off") || 116 | startsWith(val, "false") || 117 | startsWith(val, "Off") || 118 | startsWith(val, "False")) 119 | { 120 | return 0; 121 | } 122 | return 0; 123 | } 124 | 125 | #undef startsWith 126 | 127 | // This is so bad but we want to generate a default config with the same key 128 | // that is used for comparisons 129 | #define SET_XML_KEY(key, value) " <" key ">" #value "\n" 130 | const char DefaultCfgData[] = "" 131 | "\n" 132 | " \n" 133 | SET_XML_KEY(GR1_EN, 0) 134 | SET_XML_KEY(GR2_60HzKey, 0) 135 | SET_XML_KEY(BB_60FPSKey, 1) 136 | SET_XML_KEY(BB_MBKey, 0) 137 | SET_XML_KEY(BB_CAKey, 0) 138 | SET_XML_KEY(BB_DebugCameraKey, 0) 139 | SET_XML_KEY(BB_ColorBorder, 0) 140 | SET_XML_KEY(BB_Vsync, 0) 141 | SET_XML_KEY(BB_1280_720, 0) 142 | SET_XML_KEY(SOTC_DebugMenu, 0) 143 | SET_XML_KEY(TO1886_60FPS, 1) 144 | SET_XML_KEY(TO1886_16_9, 0) 145 | SET_XML_KEY(Big4R_MainDebugMenu, 0) 146 | SET_XML_KEY(Big4R_TLLDebugMenu, 0) 147 | SET_XML_KEY(DemonSouls_UnlockFPS, 0) 148 | SET_XML_KEY(DemonSouls_DebugMenu, 0) 149 | SET_XML_KEY(DemonSouls104_DebugMenu, 0) 150 | SET_XML_KEY(Driveclub_60FPS, 1) 151 | SET_XML_KEY(Driveclub_DLC_Unlock, 1) 152 | SET_XML_KEY(TLG_60FPS, 0) 153 | SET_XML_KEY(t2ps4_109_1080p_in120Hz, 0) 154 | SET_XML_KEY(BPR_103_120Hz, 0) 155 | SET_XML_KEY(BPR_103_1080p, 1) 156 | SET_XML_KEY(BPR_103_SkipLogos, 1) 157 | SET_XML_KEY(BPR_103_SkipIntroVideo, 0) 158 | SET_XML_KEY(UCC_DebugMenu, 0) 159 | "\n"; 160 | #undef SET_XML_KEY 161 | 162 | int makeDefaultXml_Cfg() 163 | { 164 | FILE *f = fopen(XML_PATH, "rb"); 165 | if (!f) 166 | { 167 | FILE *new_f = fopen(XML_PATH, "w"); 168 | // Print the default data to TTY 169 | printf("%s\n", DefaultCfgData); 170 | fputs(DefaultCfgData, new_f); 171 | fflush(new_f); 172 | fclose(new_f); 173 | printf_notification("Created default config file:\n" XML_PATH); 174 | } 175 | return 0; 176 | } 177 | 178 | int parseXML(const char *xml_key) 179 | { 180 | makeDefaultXml_Cfg(); 181 | char *buffer = NULL; 182 | uint64_t length = 0; 183 | FILE *f = fopen(XML_PATH, "rb"); 184 | int ret = 0; 185 | 186 | printf("File " XML_PATH " exist at 0x%p\n", f); 187 | if (f) 188 | { 189 | fseek(f, 0, SEEK_END); 190 | length = ftell(f); 191 | fseek(f, 0, SEEK_SET); 192 | buffer = (char *)malloc(length); 193 | if (buffer) 194 | { 195 | fread(buffer, 1, length, f); 196 | printf("Memory at 0x%p\n", buffer); 197 | } 198 | fclose(f); 199 | } 200 | else 201 | { 202 | return -2; 203 | } 204 | mxml_node_t *tree = mxmlLoadString(NULL, buffer, MXML_OPAQUE_CALLBACK); 205 | if (tree == NULL) 206 | { 207 | printf("Failed to parse XML.\n"); 208 | return 0; 209 | } 210 | 211 | mxml_node_t *xml_element = mxmlFindElement(tree, tree, xml_key, NULL, NULL, MXML_DESCEND); 212 | if (xml_element) 213 | { 214 | const char *value = mxmlGetOpaque(xml_element); 215 | if (value) 216 | { 217 | ret = simple_get_bool(value); 218 | printf("%s: (%s) 0x%08x\n", xml_key, value, ret); 219 | } 220 | } 221 | if (buffer) 222 | { 223 | free(buffer); 224 | } 225 | if (tree) 226 | { 227 | mxmlDelete(tree); 228 | } 229 | return ret; 230 | } 231 | -------------------------------------------------------------------------------- /daemon/source/game_patch_xml.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int makeDefaultXml_List(); 4 | int Xml_parseTitleID(const char* titleId); 5 | int makeDefaultXml_Cfg(); 6 | int parseXML(const char* xml_key); 7 | -------------------------------------------------------------------------------- /daemon/source/game_patch_xml_cfg.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // default xml path 4 | #define XML_PATH "/data/game_patch_cfg.xml" 5 | #define XML_PATH_LIST "/data/game_patch_fliprate_list.xml" 6 | // Xml Keys 7 | #define GR1_EN "GravityRush1_English" 8 | #define GR2_60HzKey "GravityRush2_60FPS" 9 | #define BB_60FPSKey "Bloodborne_60FPS" 10 | #define BB_MBKey "Bloodborne_MotionBlur" 11 | #define BB_CAKey "Bloodborne_ChromaticAberration" 12 | #define BB_DebugCameraKey "Bloodborne_DebugCamera" 13 | #define BB_ColorBorder "Bloodborne_ColorBorderDiag" 14 | #define BB_Vsync "Bloodborne_Vsync" 15 | #define BB_1280_720 "Bloodborne_1280x720" 16 | #define SOTC_DebugMenu "SOTC_DebugMenu" 17 | #define TO1886_60FPS "TheOrder1886_60FPS" 18 | #define TO1886_16_9 "TheOrder1886_16_9" 19 | #define Big4R_MainDebugMenu "Uncharted_4_Remaster_DebugMenu" 20 | #define Big4R_TLLDebugMenu "Uncharted_TheLostLegacy_Remaster_DebugMenu" 21 | #define DemonSouls_UnlockFPS "DemonSouls_UnlockFPS" 22 | #define DemonSouls_DebugMenu "DemonSouls_DebugMenu" 23 | #define DemonSouls104_DebugMenu "DemonSouls104_DebugMenu" 24 | #define Driveclub_60FPS "Driveclub_60FPS" 25 | #define Driveclub_DLC_Unlock "Driveclub_UnlockAllCarsBikes" 26 | #define TLG_60FPS "TheLastGurdian_60FPS" 27 | #define t2ps4_109_1080p_in120Hz "TheLastOfUs2_109_Force1080p_in_120Hz" 28 | #define BPR_103_120Hz "BurnoutParadise_103_120hz" 29 | #define BPR_103_1080p "BurnoutParadise_103_1920x1080" 30 | #define BPR_103_SkipLogos "BurnoutParadise_103_SkipLogos" 31 | #define BPR_103_SkipIntroVideo "BurnoutParadise_103_SkipIntroVideo" 32 | #define UCC_DebugMenu "UCC_DebugMenu" 33 | -------------------------------------------------------------------------------- /daemon/source/klogger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "fd.hpp" 13 | #include "servers.hpp" 14 | #include "util.hpp" 15 | 16 | void KlogServer::run(TcpSocket &sock) { 17 | static constexpr size_t KLOG_BUF_SIZE = 256; 18 | static char klogbuf[KLOG_BUF_SIZE]; 19 | FileDescriptor fd = open("/dev/klog", O_NONBLOCK, 0); 20 | pollfd readfds[] = { 21 | {.fd = fd, .events = POLLRDNORM, .revents = 0}, 22 | {.fd = sock.getFd(), .events = POLLHUP, .revents = 0} 23 | }; 24 | while (!sock.isClosed()) { 25 | int res = poll(readfds, sizeof(readfds) / sizeof(pollfd), INFTIM); 26 | if (res == STUPID_C_ERROR_VALUE || res == 0) { 27 | // error occured 28 | return; 29 | } 30 | 31 | if (readfds[1].revents & POLLHUP) { 32 | // connection was closed 33 | return; 34 | } 35 | 36 | klogbuf[0] = '\0'; 37 | auto nread = read(fd, klogbuf, sizeof(klogbuf)); 38 | if (nread == STUPID_C_ERROR_VALUE) { 39 | // error occured 40 | return; 41 | } 42 | if (!sock.isClosed()) { 43 | sock.write(klogbuf, nread); 44 | } 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /daemon/source/launcher.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define SCE_LNC_UTIL_ERROR_ALREADY_RUNNING 0x8094000c 6 | #define SCE_LNC_ERROR_APP_NOT_FOUND 0x80940031 7 | 8 | enum Flag : uint64_t { 9 | Flag_None = 0, 10 | SkipLaunchCheck = 1, 11 | SkipResumeCheck = 1, 12 | SkipSystemUpdateCheck = 2, 13 | RebootPatchInstall = 4, 14 | VRMode = 8, 15 | NonVRMode = 16, 16 | Pft = 32UL, 17 | RaIsConfirmed = 64UL, 18 | ShellUICheck = 128UL 19 | }; 20 | 21 | 22 | struct LncAppParam { 23 | uint32_t sz; 24 | uint32_t user_id; 25 | uint32_t app_opt; 26 | uint64_t crash_report; 27 | Flag check_flag; 28 | }; 29 | 30 | extern "C" int sceUserServiceGetForegroundUser(uint32_t *userId); 31 | extern "C" int sceLncUtilLaunchApp(const char* tid, const char* argv[], LncAppParam* param); 32 | extern "C" int sceUserServiceInitialize(const int *); 33 | extern "C" uint32_t sceLncUtilKillApp(uint32_t appId); 34 | -------------------------------------------------------------------------------- /daemon/source/pad.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | extern "C" 7 | { 8 | // https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/master/include/orbis/_types/pad.h 9 | #define ORBIS_PAD_PORT_TYPE_STANDARD 0 10 | #define ORBIS_PAD_PORT_TYPE_SPECIAL 2 11 | 12 | #define ORBIS_PAD_DEVICE_CLASS_PAD 0 13 | #define ORBIS_PAD_DEVICE_CLASS_GUITAR 1 14 | #define ORBIS_PAD_DEVICE_CLASS_DRUMS 2 15 | 16 | #define ORBIS_PAD_CONNECTION_TYPE_STANDARD 0 17 | #define ORBIS_PAD_CONNECTION_TYPE_REMOTE 2 18 | 19 | enum OrbisPadButton 20 | { 21 | ORBIS_PAD_BUTTON_L3 = 0x0002, 22 | ORBIS_PAD_BUTTON_R3 = 0x0004, 23 | ORBIS_PAD_BUTTON_OPTIONS = 0x0008, 24 | ORBIS_PAD_BUTTON_UP = 0x0010, 25 | ORBIS_PAD_BUTTON_RIGHT = 0x0020, 26 | ORBIS_PAD_BUTTON_DOWN = 0x0040, 27 | ORBIS_PAD_BUTTON_LEFT = 0x0080, 28 | 29 | ORBIS_PAD_BUTTON_L2 = 0x0100, 30 | ORBIS_PAD_BUTTON_R2 = 0x0200, 31 | ORBIS_PAD_BUTTON_L1 = 0x0400, 32 | ORBIS_PAD_BUTTON_R1 = 0x0800, 33 | 34 | ORBIS_PAD_BUTTON_TRIANGLE = 0x1000, 35 | ORBIS_PAD_BUTTON_CIRCLE = 0x2000, 36 | ORBIS_PAD_BUTTON_CROSS = 0x4000, 37 | ORBIS_PAD_BUTTON_SQUARE = 0x8000, 38 | 39 | ORBIS_PAD_BUTTON_TOUCH_PAD = 0x100000 40 | }; 41 | 42 | #define ORBIS_PAD_MAX_TOUCH_NUM 2 43 | #define ORBIS_PAD_MAX_DATA_NUM 0x40 44 | 45 | typedef struct vec_float3 46 | { 47 | float x; 48 | float y; 49 | float z; 50 | } vec_float3; 51 | 52 | typedef struct vec_float4 53 | { 54 | float x; 55 | float y; 56 | float z; 57 | float w; 58 | } vec_float4; 59 | 60 | typedef struct stick 61 | { 62 | uint8_t x; 63 | uint8_t y; 64 | } stick; 65 | 66 | typedef struct analog 67 | { 68 | uint8_t l2; 69 | uint8_t r2; 70 | } analog; 71 | 72 | typedef struct OrbisPadTouch 73 | { 74 | uint16_t x, y; 75 | uint8_t finger; 76 | uint8_t pad[3]; 77 | } OrbisPadTouch; 78 | 79 | typedef struct OrbisPadTouchData 80 | { 81 | uint8_t fingers; 82 | uint8_t pad1[3]; 83 | uint32_t pad2; 84 | OrbisPadTouch touch[ORBIS_PAD_MAX_TOUCH_NUM]; 85 | } OrbisPadTouchData; 86 | 87 | // The ScePadData Structure contains data polled from the DS4 controller. This includes button states, analogue 88 | // positional data, and touchpad related data. 89 | typedef struct OrbisPadData 90 | { 91 | uint32_t buttons; 92 | stick leftStick; 93 | stick rightStick; 94 | analog analogButtons; 95 | uint16_t padding; 96 | vec_float4 quat; 97 | vec_float3 vel; 98 | vec_float3 acell; 99 | OrbisPadTouchData touch; 100 | uint8_t connected; 101 | uint64_t timestamp; 102 | uint8_t ext[16]; 103 | uint8_t count; 104 | uint8_t unknown[15]; 105 | } OrbisPadData; 106 | 107 | // The PadColor structure contains RGBA for the DS4 controller lightbar. 108 | typedef struct OrbisPadColor 109 | { 110 | uint8_t r; 111 | uint8_t g; 112 | uint8_t b; 113 | uint8_t a; 114 | } OrbisPadColor; 115 | 116 | typedef struct OrbisPadVibeParam 117 | { 118 | uint8_t lgMotor; 119 | uint8_t smMotor; 120 | } OrbisPadVibeParam; 121 | 122 | // Vendor information about which controller to open for scePadOpenExt 123 | typedef struct _OrbisPadExtParam 124 | { 125 | uint16_t vendorId; 126 | uint16_t productId; 127 | uint16_t productId_2; // this is in here twice? 128 | uint8_t unknown[10]; 129 | } OrbisPadExtParam; 130 | 131 | typedef struct _OrbisPadInformation 132 | { 133 | float touchpadDensity; 134 | uint16_t touchResolutionX; 135 | uint16_t touchResolutionY; 136 | uint8_t stickDeadzoneL; 137 | uint8_t stickDeadzoneR; 138 | uint8_t connectionType; 139 | uint8_t count; 140 | int32_t connected; 141 | int32_t deviceClass; 142 | uint8_t unknown[8]; 143 | } OrbisPadInformation; 144 | 145 | int32_t scePadInit(void); 146 | int32_t scePadOpen(uint32_t userId, int32_t type, int32_t index, void *); 147 | int32_t scePadClose(int32_t handle); 148 | int32_t scePadReadState(int32_t handle, OrbisPadData *pData); 149 | int32_t scePadRead(int32_t handle, OrbisPadData *pData, int32_t num); 150 | int32_t scePadSetProcessPrivilege(int32_t num); 151 | } 152 | -------------------------------------------------------------------------------- /daemon/source/server.cpp: -------------------------------------------------------------------------------- 1 | #include "server.hpp" 2 | 3 | bool TcpSocket::read(void *buf, size_t buflen) noexcept { 4 | ssize_t nread = 0; 5 | while (!isClosed() && nread < buflen) { 6 | pollfd pfd[] = { 7 | {.fd = fd, .events = POLLHUP | POLLRDNORM, .revents = 0}, 8 | {.fd = server, .events = POLLHUP, .revents = 0} 9 | }; 10 | int res = poll(pfd, sizeof(pfd) / sizeof(pollfd), INFTIM); 11 | if (res == STUPID_C_ERROR_VALUE || res == 0) { 12 | // error occured 13 | return false; 14 | } 15 | 16 | if ((pfd[0].revents | pfd[1].revents) & (POLLHUP | POLLERR | POLLNVAL)) { 17 | // connection closed 18 | close(); 19 | return false; 20 | } 21 | 22 | // we are ready to read 23 | auto result = ::read(fd, static_cast(buf) + nread, buflen - nread); 24 | if (result == STUPID_C_ERROR_VALUE) [[unlikely]] { 25 | int err = errno; 26 | printf("read failed %d %s\n", err, strerror(err)); 27 | return false; 28 | } 29 | nread += result; 30 | } 31 | return !isClosed(); 32 | } 33 | 34 | bool TcpSocket::write(const void *buf, size_t buflen) noexcept { 35 | ssize_t wrote = 0; 36 | while (!isClosed() && wrote < buflen) { 37 | pollfd pfd[] = { 38 | {.fd = fd, .events = POLLHUP | POLLWRNORM, .revents = 0}, 39 | {.fd = server, .events = POLLHUP, .revents = 0} 40 | }; 41 | int res = poll(pfd, sizeof(pfd) / sizeof(pollfd), INFTIM); 42 | if (res == STUPID_C_ERROR_VALUE || res == 0) { 43 | // error occured 44 | return false; 45 | } 46 | 47 | if ((pfd[0].revents | pfd[1].revents) & (POLLHUP | POLLERR | POLLNVAL)) { 48 | // connection closed 49 | close(); 50 | return false; 51 | } 52 | 53 | // we are ready to write 54 | auto result = ::write(fd, static_cast(buf) + wrote, buflen - wrote); 55 | if (result == STUPID_C_ERROR_VALUE) [[unlikely]] { 56 | int err = errno; 57 | printf("write failed %d %s\n", err, strerror(err)); 58 | return false; 59 | } 60 | wrote += result; 61 | } 62 | return !isClosed(); 63 | } 64 | -------------------------------------------------------------------------------- /daemon/source/server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "thread.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef SOCK_NONBLOCK 16 | #define SOCK_NONBLOCK 0x20000000 17 | #endif 18 | 19 | namespace { 20 | 21 | static constexpr int STUPID_C_ERROR_VALUE = -1; 22 | struct BaseTcpServer; 23 | 24 | } 25 | 26 | class TcpSocket { 27 | 28 | int fd; 29 | int server; 30 | 31 | public: 32 | explicit TcpSocket() noexcept : fd(-1), server(-1) {} 33 | TcpSocket(int sock, int server) noexcept : fd(sock), server(server) {} 34 | TcpSocket(const TcpSocket&) = delete; 35 | TcpSocket &operator=(const TcpSocket&) = delete; 36 | TcpSocket(TcpSocket &&rhs) noexcept : fd(rhs.fd), server(rhs.server) { 37 | rhs.fd = -1; 38 | rhs.server = -1; 39 | } 40 | TcpSocket &operator=(TcpSocket &&rhs) noexcept { 41 | close(); 42 | fd = rhs.fd; 43 | server = rhs.server; 44 | rhs.fd = -1; 45 | rhs.server = -1; 46 | return *this; 47 | } 48 | ~TcpSocket() noexcept { 49 | close(); 50 | } 51 | 52 | bool isClosed() const noexcept { 53 | return fd == -1; 54 | } 55 | 56 | explicit operator bool() const noexcept { 57 | return fd != -1; 58 | } 59 | 60 | void close() noexcept { 61 | if (fd != -1) { 62 | ::close(fd); 63 | fd = -1; 64 | } 65 | } 66 | 67 | int getFd() const noexcept { 68 | return fd; 69 | } 70 | 71 | bool read(void *buf, size_t buflen) noexcept; 72 | 73 | bool write(const void *buf, size_t buflen) noexcept; 74 | 75 | template 76 | bool write(const char (&buf)[N]) noexcept { 77 | return write(buf, N-1); 78 | } 79 | 80 | template 81 | bool println(const char (&buf)[N]) noexcept { 82 | if (!write(buf)) [[unlikely]] { 83 | return false; 84 | } 85 | return write("\n"); 86 | } 87 | 88 | template 89 | int puts(const char (&buf)[N]) noexcept { 90 | if (println(buf)) { 91 | return N; 92 | } 93 | return 0; 94 | } 95 | }; 96 | 97 | class ServerSocket { 98 | 99 | int fd; 100 | 101 | protected: 102 | friend struct BaseTcpServer; 103 | 104 | TcpSocket accept() const noexcept { 105 | pollfd pfd = {.fd = fd, .events = POLLHUP | POLLRDNORM, .revents = 0}; 106 | int res = poll(&pfd, 1, INFTIM); 107 | if (res == STUPID_C_ERROR_VALUE || res == 0 || pfd.revents & POLLHUP) { 108 | // error occured 109 | return TcpSocket{}; 110 | } 111 | 112 | // we are ready to accept 113 | 114 | struct sockaddr client_addr{}; 115 | socklen_t addr_len = sizeof(client_addr); 116 | int conn = ::accept(fd, &client_addr, &addr_len); 117 | if (conn == STUPID_C_ERROR_VALUE) { 118 | int err = errno; 119 | if (err != EBADF) { 120 | printf("accept failed %d %s\n", err, strerror(err)); 121 | } 122 | } 123 | return {conn, fd}; 124 | } 125 | 126 | bool bind(struct sockaddr_in *server_addr) noexcept { 127 | if (::bind(fd, reinterpret_cast(server_addr), sizeof(sockaddr_in)) == STUPID_C_ERROR_VALUE) { 128 | int err = errno; 129 | if (err != EBADF) { 130 | printf("bind failed %d %s\n", err, strerror(err)); 131 | } 132 | return false; 133 | } 134 | return true; 135 | } 136 | 137 | bool listen(int backlog) noexcept { 138 | if (::listen(fd, backlog) == STUPID_C_ERROR_VALUE) { 139 | int err = errno; 140 | if (err != EBADF) { 141 | printf("listen failed %d %s\n", err, strerror(err)); 142 | } 143 | return false; 144 | } 145 | return true; 146 | } 147 | 148 | bool setsockopt(int level, int optname, const void *optval, socklen_t optlen) noexcept { 149 | auto result = ::setsockopt(fd, level, optname, optval, optlen); 150 | if (result == STUPID_C_ERROR_VALUE) [[unlikely]] { 151 | int err = errno; 152 | if (err != EBADF) { 153 | printf("setsockopt failed %d %s\n", err, strerror(err)); 154 | } 155 | return false; 156 | } 157 | return true; 158 | } 159 | 160 | public: 161 | ServerSocket(int fd) noexcept : fd(fd) {} 162 | 163 | ServerSocket(const ServerSocket&) = delete; 164 | 165 | ServerSocket &operator=(const ServerSocket&) = delete; 166 | 167 | ServerSocket(ServerSocket &&rhs) noexcept : fd(rhs.fd) { 168 | rhs.fd = -1; 169 | } 170 | 171 | ServerSocket &operator=(ServerSocket &&rhs) noexcept { 172 | if (fd != -1) { 173 | ::close(fd); 174 | } 175 | fd = rhs.fd; 176 | rhs.fd = -1; 177 | return *this; 178 | } 179 | 180 | ~ServerSocket() noexcept { 181 | if (fd != -1) { 182 | ::close(fd); 183 | } 184 | } 185 | 186 | void close() noexcept { 187 | if (fd != -1) { 188 | ::close(fd); 189 | fd = -1; 190 | } 191 | } 192 | 193 | bool isClosed() const noexcept { 194 | return fd == -1; 195 | } 196 | 197 | explicit operator bool() const noexcept { 198 | return fd != -1; 199 | } 200 | }; 201 | 202 | namespace { 203 | 204 | struct BaseTcpServer { 205 | 206 | ServerSocket serverSock; 207 | 208 | BaseTcpServer(uint16_t port) noexcept : serverSock(socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) { 209 | if (!serverSock) { 210 | return; 211 | } 212 | 213 | int value = 1; 214 | if (!serverSock.setsockopt(SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int))) { 215 | return; 216 | } 217 | 218 | struct sockaddr_in server_addr{0, AF_INET, htons(port), {}, {}}; 219 | 220 | if (!serverSock.bind(&server_addr)) { 221 | return; 222 | } 223 | 224 | if (!serverSock.listen(1)) { 225 | return; 226 | } 227 | } 228 | 229 | explicit operator bool() const noexcept { 230 | return (bool)serverSock; 231 | } 232 | 233 | TcpSocket accept() const noexcept { 234 | return serverSock.accept(); 235 | } 236 | 237 | bool isClosed() const noexcept { 238 | return serverSock.isClosed(); 239 | } 240 | 241 | }; 242 | 243 | } 244 | 245 | class TcpServer : protected BaseTcpServer { 246 | 247 | JThread thread; 248 | 249 | static int start(void *self) noexcept { 250 | static_cast(self)->start(); 251 | return 0; 252 | } 253 | 254 | virtual void start() noexcept { 255 | while (!isClosed()) { 256 | TcpSocket sock = accept(); 257 | if (sock) { 258 | run(sock); 259 | } 260 | } 261 | } 262 | 263 | void cancel() noexcept { 264 | close(); 265 | } 266 | 267 | virtual void run(TcpSocket &sock) = 0; 268 | 269 | protected: 270 | static constexpr int STUPID_C_ERROR_VALUE = -1; 271 | 272 | public: 273 | TcpServer(uint16_t port) noexcept : BaseTcpServer(port), thread() {} 274 | 275 | TcpServer(const TcpServer&) = delete; 276 | 277 | TcpServer &operator=(const TcpServer&) = delete; 278 | 279 | TcpServer(TcpServer &&rhs) noexcept = default; 280 | 281 | TcpServer &operator=(TcpServer &&rhs) noexcept = default; 282 | 283 | virtual ~TcpServer() noexcept { 284 | close(); 285 | } 286 | 287 | void stop() noexcept { 288 | close(); 289 | } 290 | 291 | void close() noexcept { 292 | serverSock.close(); 293 | } 294 | 295 | void run() noexcept { 296 | thread = JThread(start, this); 297 | } 298 | 299 | void join() noexcept { 300 | thread.join(); 301 | } 302 | }; 303 | -------------------------------------------------------------------------------- /daemon/source/servers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "server.hpp" 4 | 5 | class ElfServer : public TcpServer { 6 | 7 | static constexpr uint16_t PORT = 9029; 8 | 9 | void run(TcpSocket &sock) override; 10 | 11 | public: 12 | ElfServer() noexcept : TcpServer(PORT) {} 13 | 14 | ElfServer(const ElfServer&) = delete; 15 | 16 | ElfServer &operator=(const ElfServer&) = delete; 17 | 18 | ElfServer(ElfServer &&rhs) noexcept = default; 19 | 20 | ElfServer &operator=(ElfServer &&rhs) noexcept = default; 21 | 22 | ~ElfServer() noexcept override = default; 23 | }; 24 | 25 | class KlogServer : public TcpServer { 26 | 27 | static constexpr uint16_t PORT = 9081; 28 | 29 | void run(TcpSocket &sock) override; 30 | 31 | public: 32 | KlogServer() noexcept : TcpServer(PORT) {} 33 | 34 | KlogServer(const KlogServer&) = delete; 35 | 36 | KlogServer &operator=(const KlogServer&) = delete; 37 | 38 | KlogServer(KlogServer &&rhs) noexcept = default; 39 | 40 | KlogServer &operator=(KlogServer &&rhs) noexcept = default; 41 | 42 | ~KlogServer() noexcept override = default; 43 | }; 44 | 45 | class CommandServer : public TcpServer { 46 | 47 | static constexpr uint16_t PORT = 9028; 48 | 49 | void run(TcpSocket &sock) override; 50 | 51 | public: 52 | CommandServer() noexcept : TcpServer(PORT) {} 53 | 54 | CommandServer(const CommandServer&) = delete; 55 | 56 | CommandServer &operator=(const CommandServer&) = delete; 57 | 58 | CommandServer(CommandServer &&rhs) noexcept = default; 59 | 60 | CommandServer &operator=(CommandServer &&rhs) noexcept = default; 61 | 62 | ~CommandServer() noexcept override = default; 63 | }; 64 | 65 | class AbortServer : public TcpServer { 66 | 67 | static constexpr uint16_t PORT = 9048; 68 | 69 | void run(TcpSocket &sock) override; 70 | 71 | void start() noexcept override { 72 | TcpSocket sock = accept(); 73 | if (sock) { 74 | run(sock); 75 | } 76 | } 77 | 78 | public: 79 | AbortServer() noexcept : TcpServer(PORT) {} 80 | 81 | AbortServer(const AbortServer&) = delete; 82 | 83 | AbortServer &operator=(const AbortServer&) = delete; 84 | 85 | AbortServer(AbortServer &&rhs) noexcept = default; 86 | 87 | AbortServer &operator=(AbortServer &&rhs) noexcept = default; 88 | 89 | ~AbortServer() noexcept override = default; 90 | }; 91 | -------------------------------------------------------------------------------- /daemon/source/thread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | 5 | #include 6 | 7 | typedef pthread_t thrd_t; 8 | typedef int (*thrd_start_t)(void*); 9 | 10 | extern int _Thrd_create(thrd_t *thr, thrd_start_t func, void *arg); 11 | extern thrd_t _Thrd_current(void); 12 | extern int _Thrd_equal(thrd_t lhs, thrd_t rhs); 13 | [[noreturn]] extern void _Thrd_exit(int res); 14 | extern thrd_t _Thrd_current(void); 15 | extern int _Thrd_join(thrd_t thr, int *res); 16 | extern void _Thrd_yield(void); 17 | extern int _Thrd_detach(thrd_t thr); 18 | 19 | } 20 | 21 | namespace std { 22 | [[noreturn]] extern void terminate(); 23 | } 24 | 25 | class JThread; 26 | 27 | class Thread { 28 | 29 | friend class JThread; 30 | thrd_t id; 31 | 32 | public: 33 | explicit Thread() = default; 34 | 35 | Thread(thrd_start_t fun, void *args) : id{0} { 36 | _Thrd_create(&id, fun, args); 37 | } 38 | 39 | Thread(const Thread&) = delete; 40 | Thread &operator=(const Thread&) = delete; 41 | Thread(Thread &&rhs) noexcept : id(rhs.id) { 42 | rhs.id = 0; 43 | } 44 | Thread &operator=(Thread &&rhs) noexcept { 45 | if (id != 0) { 46 | std::terminate(); 47 | } 48 | id = rhs.id; 49 | rhs.id = 0; 50 | return *this; 51 | } 52 | ~Thread() { 53 | if (id != 0) { 54 | std::terminate(); 55 | } 56 | } 57 | 58 | thrd_t get_id() const { return id; } 59 | bool joinable() const { return id != 0; } 60 | void join() { 61 | if (joinable()) { 62 | _Thrd_join(id, nullptr); 63 | id = 0; 64 | } 65 | } 66 | void detach() { 67 | if (joinable()) { 68 | thrd_t tmp = id; 69 | id = 0; 70 | _Thrd_detach(tmp); 71 | } 72 | } 73 | }; 74 | 75 | 76 | class JThread : public Thread { 77 | 78 | public: 79 | explicit JThread() noexcept : Thread() {} 80 | JThread(thrd_start_t fun, void *args = nullptr) : Thread{fun, args} {} 81 | JThread(const JThread&) = delete; 82 | JThread &operator=(const JThread&) = delete; 83 | JThread(JThread&&) noexcept = default; 84 | JThread &operator=(JThread&&) noexcept = default; 85 | ~JThread() { 86 | join(); 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /daemon/source/titleid_map.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef void (*GameHandler)(void *args); 7 | 8 | struct TitleId { 9 | static constexpr auto TITLEID_LENGTH = 10; 10 | char id[TITLEID_LENGTH]{}; 11 | 12 | constexpr int_fast64_t operator<=>(const TitleId& rhs) const { 13 | const auto a = __builtin_bit_cast(Helper, id); 14 | const auto b = __builtin_bit_cast(Helper, rhs.id); 15 | return a <=> b; 16 | } 17 | 18 | private: 19 | struct __attribute__((packed)) Helper { 20 | int_fast64_t low; 21 | uint16_t hi; 22 | constexpr int_fast64_t operator<=>(const Helper& rhs) const { 23 | const auto i = low - rhs.low; 24 | if (i == 0) [[unlikely]] { 25 | return hi - rhs.hi; 26 | } 27 | return i; 28 | } 29 | }; 30 | 31 | static_assert(sizeof(Helper) == TITLEID_LENGTH); 32 | }; 33 | 34 | constexpr TitleId operator"" _tid(const char *str, unsigned long len) { 35 | TitleId tmp{}; 36 | // this will cause an error at compile time if the string is too long 37 | // include the null terminator 38 | __builtin_memcpy(tmp.id, str, len + 1); 39 | return tmp; 40 | } 41 | 42 | struct TitleIdKeyValue { 43 | GameHandler handler = nullptr; 44 | TitleId id{}; 45 | 46 | constexpr int_fast64_t operator<=>(const TitleIdKeyValue &rhs) const { 47 | return id <=> rhs.id; 48 | } 49 | }; 50 | 51 | template 52 | struct TitleIdKeyValueArray { 53 | // aggregate for compile time sorting 54 | TitleIdKeyValue array[N]; 55 | 56 | constexpr TitleIdKeyValueArray(const TitleIdKeyValue (&array)[N]) { 57 | __builtin_memcpy(this->array, array, sizeof(array)); 58 | } 59 | 60 | constexpr TitleIdKeyValue &operator[](size_t i) { 61 | return array[i]; 62 | } 63 | 64 | constexpr const TitleIdKeyValue &operator[](size_t i) const { 65 | return array[i]; 66 | } 67 | }; 68 | 69 | constexpr void swap(TitleIdKeyValue& lhs, TitleIdKeyValue& rhs) { 70 | TitleIdKeyValue tmp = rhs; 71 | rhs = lhs; 72 | lhs = tmp; 73 | } 74 | 75 | template 76 | constexpr void doSort(TitleIdKeyValueArray &array, size_t left, size_t right) { 77 | if (left < right) { 78 | size_t m = left; 79 | 80 | for (size_t i = left + 1; i < right; i++) { 81 | if (array[i] < array[left]) { 82 | swap(array[++m], array[i]); 83 | } 84 | } 85 | 86 | swap(array[left], array[m]); 87 | 88 | doSort(array, left, m); 89 | doSort(array, m + 1, right); 90 | } 91 | } 92 | 93 | template 94 | constexpr TitleIdKeyValueArray sort(const TitleIdKeyValue (&array)[N]) { 95 | TitleIdKeyValueArray sorted = array; 96 | doSort(sorted, 0, N); 97 | return sorted; 98 | } 99 | 100 | template 101 | class TitleIdMap { 102 | friend struct SymbolLookupTable; 103 | 104 | TitleIdKeyValueArray entries; 105 | 106 | constexpr int_fast64_t binarySearch(const TitleId &key) const { 107 | int_fast64_t lo = 0; 108 | int_fast64_t hi = static_cast(N) - 1; 109 | 110 | while (lo <= hi) { 111 | const auto m = (lo + hi) >> 1; 112 | const auto n = entries[m].id <=> key; 113 | 114 | if (n == 0) [[unlikely]] { 115 | return m; 116 | } 117 | 118 | if (n < 0) { 119 | lo = m + 1; 120 | } else { 121 | hi = m - 1; 122 | } 123 | } 124 | return -(lo + 1); 125 | } 126 | 127 | constexpr static int_fast64_t toIndex(int_fast64_t i) { 128 | return -(i + 1); 129 | } 130 | 131 | public: 132 | constexpr TitleIdMap(const TitleIdKeyValue (&array)[N]) : entries{sort(array)} {} 133 | static constexpr auto length() { return N; } 134 | 135 | constexpr const TitleIdKeyValue *operator[](const TitleId &key) const { 136 | auto index = binarySearch(key); 137 | if (index < 0) { 138 | return nullptr; 139 | } 140 | return entries.array + index; 141 | } 142 | }; 143 | -------------------------------------------------------------------------------- /daemon_log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import asyncio 3 | import re 4 | import sys 5 | from contextlib import asynccontextmanager 6 | from pathlib import Path 7 | from typing import Union 8 | import aiofiles 9 | 10 | SEM = asyncio.Semaphore(60) 11 | LOGGER_PORT = 9071 12 | 13 | 14 | class LineBuffer(bytearray): 15 | 16 | # hack to deal with StreamReader not allowing a regex pattern 17 | SEP = re.compile(b'(?:\r\n)|\r|\n') 18 | 19 | def find(self, _, offset): 20 | match = self.SEP.search(self, offset) 21 | return match.start() if match else -1 22 | 23 | 24 | class DummyLogger: 25 | 26 | async def __aenter__(self): 27 | await asyncio.sleep(0) 28 | return self 29 | 30 | async def __aexit__(self, *args): 31 | await asyncio.sleep(0) 32 | 33 | async def write(self, _): 34 | await asyncio.sleep(0) 35 | 36 | 37 | def decode(data: bytes) -> str: 38 | return data.decode('latin-1') 39 | 40 | def encode(data: str) -> bytes: 41 | return data.encode('latin-1') 42 | 43 | 44 | @asynccontextmanager 45 | async def get_logger(log: Union[Path, None]): 46 | if log is None: 47 | logger = DummyLogger() 48 | else: 49 | logger = aiofiles.open(log, 'w+', encoding='utf-8') 50 | async with logger as log: 51 | yield log 52 | 53 | 54 | @asynccontextmanager 55 | async def open_connection(host: str, port: int): 56 | while True: 57 | try: 58 | reader, writer = await asyncio.open_connection(host, port) 59 | except OSError: 60 | await asyncio.sleep(1) 61 | continue 62 | try: 63 | yield reader, writer 64 | break 65 | finally: 66 | await writer.drain() 67 | writer.close() 68 | 69 | 70 | async def log_task(reader: asyncio.StreamReader, logger: Union[DummyLogger, aiofiles.threadpool.AsyncTextIOWrapper]): 71 | line = b'' 72 | while True: 73 | if reader.at_eof(): 74 | if '\r' in line: 75 | await logger.write(line.replace('\r', '\n')) 76 | break 77 | try: 78 | line = await reader.readline() 79 | except OSError: 80 | await asyncio.sleep(1) 81 | continue 82 | 83 | line = line.decode('latin-1') 84 | if '\r' not in line: 85 | await logger.write(line) 86 | print(line, end='') 87 | 88 | 89 | async def logger_client(host: str): 90 | async with get_logger(Path('daemon_log.txt')) as logger: 91 | while True: 92 | async with SEM: 93 | async with open_connection(host, LOGGER_PORT) as (reader, _): 94 | reader._buffer = LineBuffer(reader._buffer) 95 | await log_task(reader, logger) 96 | 97 | 98 | 99 | def main(): 100 | if len(sys.argv) != 2: 101 | print(f'usage: {__file__} ps5ip') 102 | sys.exit() 103 | try: 104 | asyncio.run(logger_client(sys.argv[1])) 105 | except KeyboardInterrupt: 106 | pass 107 | 108 | 109 | if __name__ == '__main__': 110 | main() 111 | -------------------------------------------------------------------------------- /daemon_shim/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################################### 2 | # PS5SDK - Example: pipe pirate 3 | # Uses the read/write primitive to read and write some kernel data. 4 | # @authors ChendoChap, Specter, Znullptr 5 | ################################################################################################### 6 | 7 | cmake_minimum_required (VERSION 3.20) 8 | 9 | set(basename "daemon_shim") 10 | project(${basename} C CXX ASM) 11 | 12 | # Language Standard Defaults 13 | set(CMAKE_C_STANDARD 11) 14 | set(CMAKE_C_STANDARD_REQUIRED ON) 15 | 16 | set(CMAKE_CXX_EXTENSIONS ON) 17 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 18 | set(CMAKE_CXX_STANDARD 20) 19 | 20 | # Check for sub-project as part of main build or external build 21 | if (NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 22 | set(IS_SUBPROJECT TRUE) 23 | else() 24 | set(IS_SUBPROJECT FALSE) 25 | endif() 26 | 27 | message("IS_SUBPROJECT: ${IS_SUBPROJECT}") 28 | 29 | set(D_CWD "${CMAKE_CURRENT_SOURCE_DIR}") 30 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${D_CWD}/../bin) 31 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${D_CWD}/bin) 32 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${D_CWD}/bin) # static libs are archive 33 | 34 | # Headers 35 | include_directories (SYSTEM "${D_PS5SDK}") 36 | include_directories (SYSTEM "${D_PS5SDK}/include") 37 | 38 | add_executable(${PROJECT_NAME}) 39 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}.elf") 40 | 41 | # Must build with clang 42 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc]lang") 43 | set(IS_CLANG 1) 44 | else() 45 | message(FATAL_ERROR "${PROJECT_NAME} is meant to be built with clang! CompilerID: ${CMAKE_CXX_COMPILER_ID}") 46 | endif() 47 | 48 | # Finalize main target sources 49 | target_compile_options(${PROJECT_NAME} PUBLIC 50 | $<$:${C_DEFS} ${C_FLAGS}> 51 | $<$:${CXX_DEFS} ${CXX_FLAGS}> 52 | $<$:${ASM_FLAGS}> 53 | ) 54 | 55 | message("========== build: ${PROJECT_NAME} ==========") 56 | 57 | set(D_SRC ${D_CWD}/source) 58 | 59 | file(GLOB SrcFiles ${D_SRC}/*.c ${D_SRC}/*.cpp ${D_SRC}/*.h ${D_SRC}/*.hpp ${D_SRC}/*.s ${D_SRC}/../../ftp/*.c ${D_SRC}/../../ftp/*.h) 60 | 61 | set(CMAKE_C_FLAGS "--target=x86_64-freebsd-pc-elf -DPPR -DPS5 -DPS5_FW_VERSION=${V_FW} ") 62 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200112 -D__BSD_VISIBLE=1 -D__XSI_VISIBLE=500") 63 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-builtin -nostdlib -Wall") # -nostartfiles 64 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -fPIE -march=znver2 -Wall -Werror -DMTRW_COMMAND") 65 | set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -gfull -gdwarf-2 -O0 -pedantic -pedantic-errors") 66 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -gfull -gdwarf-2 -O0") 67 | 68 | target_sources(${PROJECT_NAME} PRIVATE ${SrcFiles}) 69 | target_include_directories(${PROJECT_NAME} PRIVATE "${D_CWD}/../include") 70 | target_link_directories (${PROJECT_NAME} PUBLIC "${PROJECT_ROOT}/lib") 71 | target_link_libraries (${PROJECT_NAME} PUBLIC ps5sdk_crt hijacker SceLibcInternal SceSystemService SceUserService SceSysmodule kernel_sys) 72 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 73 | set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=lld -Xlinker -pie -Xlinker -zmax-page-size=16384 -Xlinker -zcommon-page-size=16384 -Xlinker -T ${D_CWD}/linker.x -Wl,--build-id=none -Wl,-z,norelro") 74 | -------------------------------------------------------------------------------- /daemon_shim/linker.x: -------------------------------------------------------------------------------- 1 | PHDRS { 2 | /* 3 | * PF_X = 0x1 4 | * PF_W = 0x2 5 | * PF_R = 0x4 6 | */ 7 | phdr_text PT_LOAD FLAGS(0x5); 8 | phdr_data PT_LOAD FLAGS(0x6); 9 | phdr_rodata PT_LOAD FLAGS(0x4); 10 | phdr_relro PT_LOAD FLAGS(0x4); 11 | phdr_eh_frame PT_GNU_EH_FRAME FLAGS(0x4); 12 | phdr_dynamic PT_DYNAMIC FLAGS(0x0); 13 | } 14 | 15 | SECTIONS { 16 | 17 | PROVIDE (__payload_start = .); 18 | 19 | .text : { 20 | PROVIDE_HIDDEN(__text_start = .); 21 | *(.text .text.*); 22 | PROVIDE_HIDDEN(__text_stop = .); 23 | } : phdr_text 24 | 25 | .init : { 26 | *(.init) 27 | } : phdr_text 28 | 29 | .fini : { 30 | *(.fini) 31 | } : phdr_text 32 | 33 | .plt : { 34 | *(.plt) 35 | } : phdr_text 36 | 37 | . = ALIGN(0x4000); /* move to a new page in memory */ 38 | 39 | .data : { 40 | *(.data); 41 | *(.data.*); 42 | } : phdr_data 43 | 44 | .bss (NOLOAD) : { 45 | PROVIDE_HIDDEN (__bss_start = .); 46 | *(.bss .bss.*); 47 | *(COMMON) 48 | PROVIDE_HIDDEN (__bss_end = .); 49 | } : phdr_data 50 | 51 | . = ALIGN(0x4000); /* move to a new page in memory */ 52 | 53 | .rodata : { 54 | *(.rodata .rodata.*); 55 | } : phdr_rodata 56 | 57 | .gcc_except_table : { 58 | *(.gcc_except_table*) 59 | } : phdr_rodata 60 | 61 | .hash : { 62 | *(.hash); 63 | } : phdr_rodata 64 | 65 | . = ALIGN(0x4000); /* move to a new page in memory */ 66 | 67 | .eh_frame_hdr : ALIGN(0x4000) { 68 | *(.eh_frame_hdr) 69 | } : phdr_eh_frame 70 | 71 | .eh_frame : ALIGN(0x10) { 72 | *(.eh_frame) 73 | } : phdr_eh_frame 74 | 75 | . = ALIGN(0x4000); /* move to a new page in memory */ 76 | 77 | .data.rel.ro : { 78 | *(.data.rel.ro .data.rel.ro.*); 79 | } : phdr_relro 80 | 81 | .preinit_array : { 82 | PROVIDE_HIDDEN (__preinit_array_start = .); 83 | KEEP (*(.preinit_array*)) 84 | PROVIDE_HIDDEN (__preinit_array_end = .); 85 | } : phdr_relro 86 | 87 | .init_array : { 88 | PROVIDE_HIDDEN(__init_array_start = .); 89 | KEEP (*(.init_array .init_array.*)); 90 | PROVIDE_HIDDEN(__init_array_stop = .); 91 | } : phdr_relro 92 | 93 | .fini_array : { 94 | PROVIDE_HIDDEN(__fini_array_start = .); 95 | KEEP (*(.fini_array .fini_array.*)); 96 | PROVIDE_HIDDEN(__fini_array_stop = .); 97 | } : phdr_relro 98 | 99 | .got : { 100 | *(.got); 101 | } : phdr_relro 102 | 103 | .got.plt : { 104 | *(.got.plt); 105 | } : phdr_relro 106 | 107 | .rela.dyn : { 108 | *(.rela.dyn) *(.rela); 109 | } : phdr_relro 110 | 111 | .rela.plt : { 112 | *(rela.plt); 113 | } : phdr_relro 114 | 115 | PROVIDE (__payload_end = .); 116 | 117 | /* this needs to be forced aligned to 0x4000 */ 118 | .dynamic : ALIGN(0x4000) { 119 | PROVIDE_HIDDEN (_DYNAMIC = .); 120 | *(.dynamic); 121 | } : phdr_dynamic 122 | 123 | .dynsym : { 124 | *(.dynsym); 125 | } : phdr_dynamic 126 | 127 | .dynstr : { 128 | *(.dynstr); 129 | } : phdr_dynamic 130 | } 131 | -------------------------------------------------------------------------------- /daemon_shim/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "elf/elf.hpp" 13 | #include "hijacker/hijacker.hpp" 14 | #include "util.hpp" 15 | 16 | extern int runKlogger(void *unused); 17 | extern int runElfServer(void *unused); 18 | extern int runCommandProcessor(void *unused); 19 | 20 | static constexpr int SUSPENDING_ERRNO = 0xa3; 21 | static constexpr int ELF_PORT = 9027; 22 | static constexpr int KERNELRW_PORT = 9030; 23 | static constexpr int STUPID_C_ERROR_VALUE = -1; 24 | 25 | extern "C" ssize_t _read(int, void *, size_t); 26 | extern void printBacktrace(); 27 | 28 | class Socket { 29 | int fd = -1; 30 | 31 | void close() { 32 | if (fd != -1) { 33 | ::close(fd); 34 | fd = -1; 35 | } 36 | } 37 | 38 | public: 39 | Socket() = default; 40 | Socket(int fd) : fd(fd) {} 41 | Socket(const Socket&) = delete; 42 | Socket(Socket &&rhs) noexcept : fd(rhs.fd) { rhs.fd = -1; } 43 | Socket &operator=(int fd) { 44 | close(); 45 | this->fd = fd; 46 | return *this; 47 | } 48 | Socket &operator=(const Socket &rhs) = delete; 49 | Socket &operator=(Socket &&rhs) noexcept { 50 | close(); 51 | fd = rhs.fd; 52 | rhs.fd = -1; 53 | return *this; 54 | } 55 | ~Socket() { 56 | close(); 57 | } 58 | 59 | int getFd() const { return fd; } 60 | explicit operator bool() const { return fd != -1; } 61 | bool read(uint8_t *buf, size_t size) const { 62 | while (size > 0) { 63 | auto read = _read(fd, buf, size); 64 | if (read == -1) { 65 | return false; 66 | } 67 | size -= read; 68 | buf += read; 69 | } 70 | return true; 71 | } 72 | }; 73 | 74 | bool runElf(Hijacker *hijacker, uint16_t port) { 75 | UniquePtr buf = nullptr; 76 | { 77 | Socket sock = socket(AF_INET, SOCK_STREAM, 0); 78 | 79 | if (!sock) { 80 | __builtin_printf("socket: %s", strerror(errno)); 81 | return false; 82 | } 83 | 84 | int value = 1; 85 | if (setsockopt(sock.getFd(), SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int)) < 0) { 86 | __builtin_printf("setsockopt: %s", strerror(errno)); 87 | return false; 88 | } 89 | 90 | //__builtin_memset(&server_addr, 0, sizeof(server_addr)); 91 | struct sockaddr_in server_addr{0, AF_INET, htons(port), {}, {}}; 92 | 93 | if (bind(sock.getFd(), reinterpret_cast(&server_addr), sizeof(server_addr)) != 0) { 94 | __builtin_printf("bind: %s", strerror(errno)); 95 | return false; 96 | } 97 | 98 | if (listen(sock.getFd(), 1) != 0) { 99 | __builtin_printf("listen: %s", strerror(errno)); 100 | return false; 101 | } 102 | 103 | struct sockaddr client_addr{}; 104 | socklen_t addr_len = sizeof(client_addr); 105 | Socket conn = accept(sock.getFd(), &client_addr, &addr_len); 106 | if (!conn) { 107 | if (errno != SUSPENDING_ERRNO) { 108 | __builtin_printf("accept: %s", strerror(errno)); 109 | } 110 | return false; 111 | } 112 | 113 | ssize_t size = 0; 114 | if (_read(conn.getFd(), &size, sizeof(size)) == -1) { 115 | if (errno != SUSPENDING_ERRNO) { 116 | __builtin_printf("read size: %s", strerror(errno)); 117 | } 118 | return false; 119 | } 120 | 121 | 122 | buf = {new uint8_t[size]}; 123 | 124 | if (!conn.read(buf.get(), size)) { 125 | return false; 126 | } 127 | } 128 | 129 | Elf elf{hijacker, buf.release()}; 130 | 131 | if (!elf.launch()) { 132 | return false; 133 | } 134 | 135 | return true; 136 | } 137 | 138 | class FileDescriptor { 139 | int fd = -1; 140 | 141 | void close() { 142 | if (fd != -1) { 143 | ::close(fd); 144 | fd = -1; 145 | } 146 | } 147 | 148 | public: 149 | FileDescriptor() = default; 150 | FileDescriptor(int fd) : fd(fd) {} 151 | FileDescriptor(const FileDescriptor&) = delete; 152 | FileDescriptor(FileDescriptor &&rhs) noexcept : fd(rhs.fd) { rhs.fd = -1; } 153 | FileDescriptor &operator=(int fd) { 154 | close(); 155 | this->fd = fd; 156 | return *this; 157 | } 158 | FileDescriptor &operator=(const FileDescriptor &rhs) = delete; 159 | FileDescriptor &operator=(FileDescriptor &&rhs) noexcept { 160 | close(); 161 | fd = rhs.fd; 162 | rhs.fd = -1; 163 | return *this; 164 | } 165 | ~FileDescriptor() { 166 | close(); 167 | } 168 | 169 | operator int() const { return fd; } 170 | explicit operator bool() const { return fd != -1; } 171 | bool read(uint8_t *buf, size_t size) const { 172 | while (size > 0) { 173 | auto read = _read(fd, buf, size); 174 | if (read == STUPID_C_ERROR_VALUE) { 175 | if (errno != SUSPENDING_ERRNO) { 176 | __builtin_printf("read failed error %s\n", strerror(errno)); 177 | printBacktrace(); 178 | } 179 | return false; 180 | } 181 | size -= read; 182 | buf += read; 183 | } 184 | return true; 185 | } 186 | 187 | void release() { fd = -1; } 188 | }; 189 | 190 | extern "C" void *start_ftp(void*); 191 | 192 | static void *kernelRWHandler(void *unused) noexcept { 193 | (void)unused; 194 | FileDescriptor server = socket(AF_INET, SOCK_STREAM, 0); 195 | if (server == STUPID_C_ERROR_VALUE) { 196 | perror("kernelRWHandler socket"); 197 | return nullptr; 198 | } 199 | int value = 1; 200 | if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(int)) == STUPID_C_ERROR_VALUE) { 201 | perror("kernelRWHandler setsockopt"); 202 | return nullptr; 203 | } 204 | 205 | struct sockaddr_in server_addr{0, AF_INET, htons(KERNELRW_PORT), {inet_addr("127.0.0.1")}, {}}; 206 | 207 | if (bind(server, reinterpret_cast(&server_addr), sizeof(server_addr)) == STUPID_C_ERROR_VALUE) { 208 | perror("kernelRWHandler bind"); 209 | return nullptr; 210 | } 211 | 212 | if (listen(server, 1) == STUPID_C_ERROR_VALUE) { 213 | perror("kernelRWHandler listen"); 214 | return nullptr; 215 | } 216 | 217 | while (true) { 218 | struct sockaddr client_addr{}; 219 | socklen_t addr_len = sizeof(client_addr); 220 | FileDescriptor conn = ::accept(server, &client_addr, &addr_len); 221 | if (conn == STUPID_C_ERROR_VALUE) { 222 | perror("kernelRWHandler accept"); 223 | continue; 224 | } 225 | 226 | int pid = 0; 227 | int sockets[2]{}; 228 | ssize_t n = read(conn, &pid, sizeof(pid)); 229 | if (n == STUPID_C_ERROR_VALUE) { 230 | if (errno == SUSPENDING_ERRNO) { 231 | return nullptr; 232 | } 233 | perror("kernelRWHandler read"); 234 | continue; 235 | } 236 | if (n != sizeof(pid)) { 237 | puts("kernelRWHandler didn't read enough data for pid"); 238 | continue; 239 | } 240 | n = read(conn, sockets, sizeof(sockets)); 241 | if (n == STUPID_C_ERROR_VALUE) { 242 | if (errno == SUSPENDING_ERRNO) { 243 | return nullptr; 244 | } 245 | perror("kernelRWHandler read"); 246 | continue; 247 | } 248 | if (n != sizeof(sockets)) { 249 | puts("kernelRWHandler didn't read enough data for sockets"); 250 | continue; 251 | } 252 | if (!createReadWriteSockets(pid, sockets)) { 253 | printf("failed to create kernelrw sockets for pid %d\n", pid); 254 | continue; 255 | } 256 | n = write(conn, &kernel_base, sizeof(kernel_base)); 257 | if (n == STUPID_C_ERROR_VALUE) { 258 | if (errno == SUSPENDING_ERRNO) { 259 | return nullptr; 260 | } 261 | perror("kernelRWHandler write"); 262 | continue; 263 | } 264 | if (n != sizeof(sockets)) { 265 | puts("kernelRWHandler didn't write enough data for response"); 266 | continue; 267 | } 268 | } 269 | return nullptr; 270 | } 271 | 272 | static void *elfLoader(void *unused) noexcept { 273 | (void) unused; 274 | puts("starting elfLoader"); 275 | while (true) { 276 | auto hijacker = Hijacker::getHijacker(getpid()); 277 | if (!runElf(hijacker.get(), ELF_PORT)) { 278 | if (errno == SUSPENDING_ERRNO) { 279 | return nullptr; 280 | } 281 | perror("elfLoader"); 282 | } 283 | } 284 | } 285 | 286 | int main() { 287 | static constexpr int ONE_SECOND = 1000000; 288 | while (true) { 289 | pthread_t ftp = nullptr; 290 | pthread_t krw = nullptr; 291 | pthread_t elfldr = nullptr; 292 | pthread_create(&ftp, nullptr, start_ftp, nullptr); 293 | pthread_create(&krw, nullptr, kernelRWHandler, nullptr); 294 | pthread_create(&elfldr, nullptr, elfLoader, nullptr); 295 | pthread_join(ftp, nullptr); 296 | pthread_join(krw, nullptr); 297 | pthread_join(elfldr, nullptr); 298 | usleep(ONE_SECOND); 299 | } 300 | return 0; 301 | } 302 | -------------------------------------------------------------------------------- /data/game_patch_fliprate_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CUSA00411 5 | CUSA00419 6 | CUSA00880 7 | CUSA29862 8 | 9 | CUSA03041 10 | CUSA08519 11 | CUSA08568 12 | CUSA15698 13 | 14 | CUSA00035 15 | CUSA00070 16 | CUSA00076 17 | CUSA00100 18 | CUSA00670 19 | CUSA00670 20 | 21 | CUSA13795 22 | CUSA14876 23 | CUSA15979 24 | 25 | CUSA07399 26 | CUSA07402 27 | CUSA08120 28 | 29 | CUSA07820 30 | CUSA10249 31 | CUSA13986 32 | CUSA14006 33 | 34 | CUSA01127 35 | CUSA01114 36 | CUSA01098 37 | 38 | CUSA00663 39 | CUSA00605 40 | CUSA00476 41 | 42 | CUSA04893 43 | CUSA05008 44 | CUSA05943 45 | 46 | CUSA10326 47 | CUSA10357 48 | CUSA11724 49 | CUSA10327 50 | 51 | CUSA00496 52 | CUSA00462 53 | CUSA00477 54 | 55 | CUSA05847 56 | CUSA05904 57 | CUSA05848 58 | CUSA05849 59 | CUSA05863 60 | 61 | CUSA00016 62 | CUSA00021 63 | CUSA00022 64 | CUSA00812 65 | 66 | CUSA04294 67 | CUSA04295 68 | CUSA04459 69 | CUSA04782 70 | CUSA06328 71 | 72 | CUSA03372 73 | CUSA03397 74 | CUSA04122 75 | 76 | CUSA00054 77 | CUSA00103 78 | CUSA02151 79 | CUSA02703 80 | 81 | CUSA01836 82 | CUSA01799 83 | CUSA07218 84 | 85 | CUSA00009 86 | CUSA00010 87 | CUSA00206 88 | 89 | CUSA32708 90 | CUSA32709 91 | CUSA32710 92 | CUSA32711 93 | CUSA11456 94 | CUSA13323 95 | CUSA16972 96 | CUSA16981 97 | 98 | CUSA24652 99 | CUSA24653 100 | 101 | CUSA09493 102 | CUSA04733 103 | CUSA04849 104 | 105 | CUSA00362 106 | CUSA00363 107 | CUSA00689 108 | CUSA01485 109 | 110 | 111 | -------------------------------------------------------------------------------- /ftp/cmd.h: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 2023 John Törnblom 2 | 3 | This program is free software; you can redistribute it and/or modify it 4 | under the terms of the GNU General Public License as published by the 5 | Free Software Foundation; either version 3, or (at your option) any 6 | later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program; see the file COPYING. If not, see 15 | . */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | /** 26 | * Data structure that captures the current state of a client. 27 | **/ 28 | typedef struct ftp_env { 29 | int data_fd; 30 | int active_fd; 31 | int passive_fd; 32 | char cwd[PATH_MAX]; 33 | atomic_bool *srv_running; 34 | 35 | char type; 36 | off_t data_offset; 37 | char rename_path[PATH_MAX]; 38 | struct sockaddr_in data_addr; 39 | } ftp_env_t; 40 | 41 | 42 | /** 43 | * Callback function prototype for ftp commands. 44 | **/ 45 | typedef int (ftp_command_fn_t)(ftp_env_t* env, const char* arg); 46 | 47 | 48 | /** 49 | * Standard FTP commands. 50 | **/ 51 | int ftp_cmd_CDUP(ftp_env_t *env, const char* arg); 52 | int ftp_cmd_CWD (ftp_env_t *env, const char* arg); 53 | int ftp_cmd_DELE(ftp_env_t *env, const char* arg); 54 | int ftp_cmd_LIST(ftp_env_t *env, const char* arg); 55 | int ftp_cmd_MKD (ftp_env_t *env, const char* arg); 56 | int ftp_cmd_NOOP(ftp_env_t *env, const char* arg); 57 | int ftp_cmd_PASV(ftp_env_t *env, const char* arg); 58 | int ftp_cmd_PORT(ftp_env_t *env, const char* arg); 59 | int ftp_cmd_PWD (ftp_env_t *env, const char* arg); 60 | int ftp_cmd_QUIT(ftp_env_t *env, const char* arg); 61 | int ftp_cmd_REST(ftp_env_t *env, const char* arg); 62 | int ftp_cmd_RETR(ftp_env_t *env, const char* arg); 63 | int ftp_cmd_RMD (ftp_env_t *env, const char* arg); 64 | int ftp_cmd_RNFR(ftp_env_t *env, const char* arg); 65 | int ftp_cmd_RNTO(ftp_env_t *env, const char* arg); 66 | int ftp_cmd_SIZE(ftp_env_t *env, const char* arg); 67 | int ftp_cmd_STOR(ftp_env_t *env, const char* arg); 68 | int ftp_cmd_SYST(ftp_env_t *env, const char* arg); 69 | int ftp_cmd_TYPE(ftp_env_t *env, const char* arg); 70 | int ftp_cmd_USER(ftp_env_t *env, const char* arg); 71 | 72 | 73 | /** 74 | * Custom FTP commands. 75 | **/ 76 | int ftp_cmd_KILL(ftp_env_t *env, const char* arg); 77 | int ftp_cmd_MTRW(ftp_env_t *env, const char* arg); 78 | 79 | 80 | /** 81 | * Error responses to unknown/unavailable FTP commands. 82 | **/ 83 | int ftp_cmd_unavailable(ftp_env_t *env, const char* arg); 84 | int ftp_cmd_unknown(ftp_env_t *env, const char* arg); 85 | -------------------------------------------------------------------------------- /include/backtrace.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include 5 | } 6 | 7 | struct Frame { 8 | Frame *next; 9 | uintptr_t addr; 10 | }; 11 | 12 | const Frame *getFramePointer(); 13 | -------------------------------------------------------------------------------- /include/build.h: -------------------------------------------------------------------------------- 1 | #define STANDALONE // sendable using nc (no host features, scripts will not work in this mode) 2 | #define RESTMODE // able to enter sleep mode (no host features, scripts will not work in this mode) 3 | -------------------------------------------------------------------------------- /include/dbg.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dbg/dbg.hpp" 4 | -------------------------------------------------------------------------------- /include/dbg/args.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include 5 | } 6 | 7 | enum DbgCommand : uint64_t { 8 | CREATE_SCRATCH_CMD = 0x3, 9 | READ_CMD = 0x12, 10 | WRITE_CMD = 0x13, 11 | PROCESS_LIST_CMD = 0x14, 12 | THREAD_LIST_CMD = 0x15, 13 | PROCESS_INFO_CMD = 0x18, 14 | THREAD_INFO_CMD = 0x19, 15 | ARG2_CMD = 0x1e 16 | }; 17 | 18 | // NOLINTBEGIN(*) 19 | 20 | // aggressively zero all dbg args 21 | template 22 | struct DbgArg { 23 | DbgArg() { 24 | // because of the stupid wrong pure attribute in the sdk headers 25 | // __builtin_memset must be used 26 | __builtin_memset(this, 0, size); 27 | } 28 | }; 29 | 30 | struct DbgArg1 : DbgArg<0x20> { 31 | uint32_t type; 32 | uint32_t pad1; 33 | DbgCommand cmd; 34 | 35 | DbgArg1(uint32_t type, DbgCommand cmd) : DbgArg(), type(type), cmd(cmd){} 36 | private: 37 | uint8_t __attribute__((unused)) pad[0x10]; 38 | }; 39 | 40 | static_assert(sizeof(DbgArg1) == 0x20, "sizeof(DbgArg1) != 0x20"); 41 | 42 | struct DbgArg2 : DbgArg<0x40> { 43 | }; 44 | 45 | struct DbgReadArg : DbgArg2 { 46 | int pid; 47 | uintptr_t src; 48 | void *dst; 49 | uint64_t length; 50 | DbgReadArg(int pid, uintptr_t src, void *dst, uint64_t length) 51 | : pid(pid), src(src), dst(dst), length(length) {} 52 | private: 53 | unsigned char __attribute__((unused)) pad[0x20]; 54 | }; 55 | 56 | static_assert(sizeof(DbgReadArg) == 0x40, "sizeof(DbgReadArg) != 0x40"); 57 | 58 | struct DbgKickProcessArg : DbgArg2 { 59 | uint32_t pid; 60 | uint32_t __attribute__((unused)) pad1; 61 | const uint32_t cmd = 16; 62 | uint32_t __attribute__((unused)) pad2; 63 | const uint32_t value = 3; 64 | DbgKickProcessArg(int pid) : pid(pid) {} 65 | private: 66 | unsigned char __attribute__((unused)) pad[0x2c]; 67 | }; 68 | 69 | static_assert(sizeof(DbgKickProcessArg) == 0x40, "sizeof(DbgKickProcessArg) != 0x40"); 70 | 71 | struct DbgGetPidsArg : DbgArg2 { 72 | uint64_t buf; 73 | uint64_t length; 74 | 75 | template 76 | DbgGetPidsArg(int(&buf)[size]) : buf((uint64_t) &buf), length(size) {} 77 | DbgGetPidsArg(int *buf, unsigned long size) : buf((uint64_t) buf), length(size) {} 78 | private: 79 | unsigned char __attribute__((unused)) pad[0x30]; 80 | }; 81 | 82 | static_assert(sizeof(DbgGetPidsArg) == 0x40, "sizeof(DbgGetPidsArg) != 0x40"); 83 | 84 | struct DbgGetTidsArg : DbgArg2 { 85 | uint32_t pid; 86 | uint64_t buf; 87 | uint64_t length; 88 | 89 | template 90 | DbgGetTidsArg(int pid, int(&buf)[size]) : pid(pid), buf((uint64_t) &buf), length(size) {} 91 | DbgGetTidsArg(int pid, int *buf, unsigned long size) : pid(pid), buf((uint64_t) buf), length(size) {} 92 | private: 93 | unsigned char __attribute__((unused)) pad[0x28]; 94 | }; 95 | 96 | static_assert(sizeof(DbgGetTidsArg) == 0x40, "sizeof(DbgGetTidsArg) != 0x40"); 97 | 98 | struct DbgGetProcInfoArg : DbgArg2 { 99 | int pid; 100 | uintptr_t buf; 101 | uint64_t length; 102 | 103 | DbgGetProcInfoArg(int pid, void *buf, uint64_t length) 104 | : pid(pid), buf((uintptr_t) buf), length(length) {} 105 | private: 106 | unsigned char __attribute__((unused)) pad[0x28]; 107 | }; 108 | 109 | static_assert(sizeof(DbgGetProcInfoArg) == 0x40, "sizeof(DbgGetProcInfoArg) != 0x40"); 110 | 111 | 112 | struct DbgGetThreadInfoArg : DbgArg2 { 113 | uint32_t pid; 114 | uint32_t __attribute__((unused)) pad1; 115 | uint32_t tid; 116 | uint64_t buf; 117 | uint64_t length; 118 | 119 | DbgGetThreadInfoArg(int pid, int tid, void *buf, uint64_t length) 120 | : pid(pid), tid(tid), buf((uintptr_t) buf), length(length) {} 121 | private: 122 | unsigned char __attribute__((unused)) pad[0x20]; 123 | }; 124 | 125 | static_assert(sizeof(DbgGetThreadInfoArg) == 0x40, "sizeof(DbgGetThreadInfoArg) != 0x40"); 126 | 127 | struct DbgArg3 : DbgArg<0x20> { 128 | int64_t err; 129 | uint64_t length; 130 | 131 | explicit DbgArg3() : length() {} 132 | private: 133 | unsigned char __attribute__((unused)) pad[0x10]; 134 | }; 135 | 136 | static_assert(sizeof(DbgArg3) == 0x20, "sizeof(DbgArg3) != 0x20"); 137 | 138 | // NOLINTEND(*) 139 | -------------------------------------------------------------------------------- /include/elf/elf.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include 5 | #include 6 | #include 7 | } 8 | 9 | #include "hijacker.hpp" 10 | #include 11 | 12 | struct SymbolLookupTable; 13 | 14 | constexpr auto i = sizeof(Elf64_Dyn); 15 | 16 | class Elf : Elf64_Ehdr { 17 | 18 | public: 19 | struct MappedMemory { 20 | uintptr_t mem; 21 | size_t len; 22 | }; 23 | 24 | private: 25 | dbg::Tracer tracer; 26 | const Elf64_Phdr *__restrict phdrs; 27 | const char *__restrict strtab; 28 | size_t strtabLength; 29 | const Elf64_Sym *__restrict symtab; 30 | size_t symtabLength; 31 | const Elf64_Rela *__restrict relatbl; 32 | size_t relaLength; 33 | const Elf64_Rela *__restrict plt; 34 | size_t pltLength; 35 | Hijacker *__restrict hijacker; 36 | size_t textOffset; 37 | uintptr_t imagebase; 38 | uint8_t *data; 39 | Array libs; 40 | Array mappedMemory; 41 | int jitFd; 42 | 43 | bool processProgramHeaders() noexcept; 44 | bool parseDynamicTable() noexcept; 45 | bool processRelocations() noexcept; 46 | bool processPltRelocations() noexcept; 47 | bool load() noexcept; 48 | bool start(uintptr_t args) noexcept; 49 | uintptr_t setupKernelRW() noexcept; 50 | uintptr_t getSymbolAddress(const Elf64_Rela *__restrict rel) const noexcept; 51 | 52 | public: 53 | Elf(Hijacker *hijacker, uint8_t *data) noexcept; 54 | Elf(const Elf&) = delete; 55 | Elf &operator=(const Elf&) = delete; 56 | Elf(Elf&&) noexcept = default; 57 | Elf &operator=(Elf&&) noexcept = default; 58 | ~Elf() noexcept; // external linkage to prevent undefined behavior 59 | 60 | bool launch() noexcept; 61 | explicit operator bool() const noexcept { 62 | return static_cast(tracer); 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /include/elf/loader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../thread.hpp" 4 | 5 | struct ElfLoaderBase { 6 | void run(); 7 | }; 8 | 9 | class ElfLoader : public ElfLoaderBase, public JThread { 10 | friend struct ElfLoaderBase; 11 | 12 | public: 13 | void run(); 14 | }; 15 | -------------------------------------------------------------------------------- /include/elf/nid/b64.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include 5 | } 6 | 7 | #include "../../nid.hpp" 8 | 9 | // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,cppcoreguidelines-pro-bounds-constant-array-index) 10 | namespace { 11 | 12 | static inline constexpr char encoder[]{"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-"}; 13 | 14 | } 15 | 16 | static inline constexpr void b64encode(char (&dest)[NID_LENGTH+1], const unsigned char *src) { 17 | for (unsigned int i = 0, j = 0; j < NID_LENGTH;) { 18 | uint32_t a = src[i++]; 19 | uint32_t b = src[i++]; 20 | uint32_t c = src[i++]; 21 | 22 | uint32_t abc = (a << 16) | (b << 8) | c; 23 | 24 | dest[j++] = encoder[(abc >> 18) & 0x3F]; 25 | dest[j++] = encoder[(abc >> 12) & 0x3F]; 26 | dest[j++] = encoder[(abc >> 6) & 0x3F]; 27 | dest[j++] = encoder[abc & 0x3F]; 28 | } 29 | dest[NID_LENGTH] = 0; 30 | } 31 | 32 | // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,cppcoreguidelines-pro-bounds-constant-array-index) 33 | -------------------------------------------------------------------------------- /include/elf/nid/nid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "b64.hpp" 4 | #include "../../nid.hpp" 5 | #include "sha1.hpp" 6 | #include "util.hpp" 7 | #include 8 | 9 | static inline constexpr void fillNid(Nid &buf, const StringView &sym) { 10 | constexpr size_t NID_DIGEST_LENGTH = 8; 11 | constexpr size_t NID_SHA_LENGTH = 20; 12 | uint8_t encodedDigest[NID_DIGEST_LENGTH + 1]{}; 13 | uint8_t sha1[NID_SHA_LENGTH]{}; 14 | genSha1(sha1, sym); 15 | for (size_t i = 0; i < NID_DIGEST_LENGTH; i++) { 16 | encodedDigest[i] = sha1[NID_DIGEST_LENGTH - 1 - i]; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index) 17 | } 18 | b64encode(buf.str, encodedDigest); 19 | } 20 | -------------------------------------------------------------------------------- /include/elfldr.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "elf/elf.hpp" 4 | #include "elf/nid/nid.hpp" 5 | -------------------------------------------------------------------------------- /include/hijacker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hijacker/hijacker.hpp" 4 | #include "hijacker/spawner.hpp" 5 | 6 | class ScopedSuspender { 7 | Hijacker *hijacker; 8 | 9 | public: 10 | ScopedSuspender(Hijacker *hijacker) : hijacker(hijacker) { hijacker->suspend(); } 11 | ScopedSuspender(const ScopedSuspender&) = delete; 12 | ScopedSuspender(ScopedSuspender&&) = delete; 13 | ScopedSuspender &operator=(const ScopedSuspender&) = delete; 14 | ScopedSuspender &operator=(ScopedSuspender&&) = delete; 15 | ~ScopedSuspender() { hijacker->resume(); } 16 | }; 17 | -------------------------------------------------------------------------------- /include/hijacker/allocator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "kernel.hpp" 3 | 4 | // "allocates" memory from another process out of EXISTING process memory 5 | class ProcessMemoryAllocator { 6 | static constexpr size_t ALIGNMENT = 0x10; 7 | static constexpr size_t ALIGNMENT_MASK = 0xf; 8 | const SharedLibSection *section; 9 | size_t allocated; 10 | 11 | public: 12 | ProcessMemoryAllocator(decltype(nullptr)) : section(nullptr), allocated() {} 13 | ProcessMemoryAllocator(const SharedLibSection *section) : section(section), allocated() {} 14 | 15 | /** 16 | * "Releases" all the allocated memory 17 | */ 18 | void release() { 19 | allocated = 0; 20 | } 21 | 22 | /** 23 | * Allocated virtual memory from the end of the existing section. 24 | * It is allocated from the end because this portion of memory is likely unused. 25 | * @param size the size of memory to "allocate" 26 | * @return the virtual address for the requested memory 27 | */ 28 | uintptr_t allocate(size_t size) { 29 | if ((size & ALIGNMENT_MASK) != 0) { 30 | size = (size & ~ALIGNMENT_MASK) + ALIGNMENT; 31 | } 32 | allocated += size; 33 | return section->end() - allocated; 34 | } 35 | 36 | operator bool() const { 37 | return section != nullptr; 38 | } 39 | 40 | bool operator==(decltype(nullptr)) const { 41 | return section == nullptr; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /include/hijacker/hijacker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dbg.hpp" 4 | #include "hijacker/allocator.hpp" 5 | #include "memory.hpp" 6 | #include "kernel.hpp" 7 | #include "kernel/rtld.hpp" 8 | #include "util.hpp" 9 | #include "allocator.hpp" 10 | #include 11 | 12 | class Hijacker { 13 | 14 | static constexpr int LIBKERNEL_HANDLE = 0x2001; 15 | 16 | UniquePtr obj; 17 | 18 | protected: 19 | friend class Spawner; 20 | ProcessMemoryAllocator textAllocator; 21 | ProcessMemoryAllocator dataAllocator; 22 | 23 | private: 24 | mutable UniquePtr libkernel; 25 | mutable int mainThreadId = -1; 26 | bool isMainThreadRunning = true; 27 | 28 | Hijacker(SharedObject *obj) : obj(obj), textAllocator(nullptr), dataAllocator(nullptr), libkernel(nullptr) { 29 | auto eboot = this->obj->getEboot(); 30 | while (eboot == nullptr) { 31 | // this can happen when it is still loading 32 | eboot = this->obj->getEboot(); 33 | } 34 | while (textAllocator == nullptr) { 35 | textAllocator = ProcessMemoryAllocator(eboot->getTextSection()); 36 | } 37 | while (dataAllocator == nullptr) { 38 | dataAllocator = ProcessMemoryAllocator(eboot->getDataSection()); 39 | } 40 | } 41 | 42 | RtldMeta *getLibKernelMetaData() const { 43 | auto *meta = getLibKernel(); 44 | return meta ? meta->getMetaData() : nullptr; 45 | } 46 | 47 | public: 48 | uintptr_t getLibKernelBase() const { 49 | RtldMeta *meta = getLibKernelMetaData(); 50 | return meta ? meta->imageBase : 0; 51 | } 52 | private: 53 | int getMainThreadId() const; 54 | 55 | public: 56 | static UniquePtr getHijacker(const StringView &processName); 57 | static UniquePtr getHijacker(int pid) { 58 | auto p = ::getProc(pid); 59 | if (p == nullptr) [[unlikely]] { 60 | return nullptr; 61 | } 62 | 63 | auto obj = p->getSharedObject(); 64 | 65 | // obj may be a nullptr when racing process creation 66 | return obj != nullptr ? new Hijacker{obj.release()} : nullptr; 67 | } 68 | 69 | UniquePtr getProc() const { 70 | return ::getProc(getPid()); 71 | } 72 | 73 | void suspend() { 74 | if (isMainThreadRunning) { 75 | dbg::suspend(obj->pid); 76 | isMainThreadRunning = false; 77 | } 78 | } 79 | 80 | void resume() { 81 | if (!isMainThreadRunning) { 82 | dbg::resume(obj->pid); 83 | isMainThreadRunning = true; 84 | } 85 | } 86 | 87 | int getPid() const { 88 | return obj->pid; 89 | } 90 | 91 | SharedLib *getEboot() const { 92 | return obj->getEboot(); 93 | } 94 | 95 | uintptr_t imagebase() const { 96 | auto eboot = obj->getEboot(); 97 | return eboot ? eboot->imagebase() : 0; 98 | } 99 | 100 | SharedLib *getLibKernel() const { 101 | if (libkernel == nullptr) [[unlikely]] { 102 | libkernel = obj->getLib(LIBKERNEL_HANDLE); 103 | } 104 | return libkernel.get(); 105 | } 106 | 107 | UniquePtr getLib(int handle) const { 108 | return obj->getLib(handle); 109 | } 110 | 111 | UniquePtr getLib(const StringView &name) const { 112 | return obj->getLib(name); 113 | } 114 | 115 | SharedLibIterator getLibs() const { 116 | return obj->getLibs(); 117 | } 118 | 119 | UniquePtr getTrapFrame() const; 120 | void jailbreak(bool escapeSandbox=true) const; 121 | uintptr_t getFunctionAddress(const SharedLib *lib, const Nid &fname) const noexcept; 122 | 123 | uintptr_t getLibKernelFunctionAddress(const Nid &fname) const { 124 | return getFunctionAddress(getLibKernel(), fname); 125 | } 126 | 127 | uintptr_t getLibKernelAddress(const Nid &fname) const { 128 | return getFunctionAddress(getLibKernel(), fname); 129 | } 130 | 131 | ProcessMemoryAllocator &getDataAllocator() { 132 | return dataAllocator; 133 | } 134 | 135 | ProcessMemoryAllocator &getTextAllocator() { 136 | return textAllocator; 137 | } 138 | 139 | UniquePtr read(uintptr_t vaddr, size_t size) { 140 | return dbg::read(getPid(), vaddr, size).release(); 141 | } 142 | 143 | void read(uintptr_t vaddr, void *buf, size_t size) { 144 | dbg::read(getPid(), vaddr, buf, size); 145 | } 146 | 147 | template 148 | T read(uintptr_t vaddr) { 149 | T t; 150 | read(vaddr, &t, sizeof(t)); 151 | return t; 152 | } 153 | 154 | template 155 | bool write(uintptr_t vaddr, const uint8_t(&buf)[size]) { 156 | return dbg::write(getPid(), vaddr, buf, size); 157 | } 158 | 159 | bool write(uintptr_t vaddr, const void *buf, size_t size) { 160 | return dbg::write(getPid(), vaddr, buf, size); 161 | } 162 | 163 | template 164 | bool write(uintptr_t vaddr, const T &value) { 165 | return write(vaddr, &value, sizeof(value)); 166 | } 167 | 168 | template 169 | ProcessPointer getPointer(uintptr_t addr) const { 170 | return {getPid(), addr}; 171 | } 172 | 173 | void hexdump(uintptr_t addr, size_t size) { 174 | auto buf = read(addr, size); 175 | ::hexdump(buf.get(), size); 176 | } 177 | }; 178 | -------------------------------------------------------------------------------- /include/hijacker/memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include 5 | } 6 | 7 | #include "dbg.hpp" 8 | 9 | template 10 | class ProcessReference { 11 | uintptr_t addr; 12 | int pid; 13 | 14 | public: 15 | ProcessReference() = default; 16 | ProcessReference(int pid, uintptr_t addr) : addr(addr), pid(pid) {} 17 | ProcessReference(const ProcessReference &rhs) : addr(rhs.addr), pid(rhs.pid) {} 18 | ProcessReference(ProcessReference&&) noexcept = delete; 19 | ProcessReference &operator=(const ProcessReference &rhs) { 20 | addr = rhs.addr; 21 | pid = rhs.pid; 22 | return *this; 23 | } 24 | ProcessReference &operator=(ProcessReference&&) noexcept = delete; 25 | ProcessReference &operator=(T value) { 26 | dbg::write(pid, addr, &value, sizeof(value)); 27 | return *this; 28 | } 29 | ~ProcessReference() noexcept = default; 30 | operator T() const { 31 | T value; 32 | dbg::read(pid, addr, &value, sizeof(T)); 33 | return value; 34 | } 35 | }; 36 | 37 | template <> 38 | class ProcessReference { 39 | uintptr_t addr; 40 | int pid; 41 | 42 | public: 43 | ProcessReference(int pid, uintptr_t addr) : addr(addr), pid(pid) {} 44 | ProcessReference(const ProcessReference &rhs) : addr(rhs.addr), pid(rhs.pid) {} 45 | ProcessReference(ProcessReference&&) noexcept = delete; 46 | ProcessReference &operator=(const ProcessReference &rhs) { 47 | addr = rhs.addr; 48 | pid = rhs.pid; 49 | return *this; 50 | } 51 | ProcessReference &operator=(bool value) { 52 | dbg::write(pid, addr, &value, sizeof(value)); 53 | return *this; 54 | } 55 | ProcessReference &operator=(ProcessReference&&) noexcept = delete; 56 | ~ProcessReference() noexcept = default; 57 | explicit operator bool() const { 58 | bool value; // NOLINT(cppcoreguidelines-init-variables) 59 | dbg::read(pid, addr, &value, sizeof(bool)); 60 | return value; 61 | } 62 | }; 63 | 64 | template 65 | class ProcessPointer { 66 | uintptr_t addr; 67 | int pid; 68 | 69 | public: 70 | ProcessPointer() : addr(), pid() {} 71 | ProcessPointer(int pid, uintptr_t addr) : addr(addr), pid(pid) {} 72 | ProcessReference operator*() const { return {pid, addr}; } 73 | T get() const { 74 | T value{}; 75 | dbg::read(pid, addr, &value, sizeof(T)); 76 | return value; 77 | } 78 | uintptr_t address() const { return addr; } 79 | }; 80 | -------------------------------------------------------------------------------- /include/hijacker/spawner.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hijacker.hpp" 4 | #include "hijacker/hijacker.hpp" 5 | #include "hijacker/spawner.hpp" 6 | #include "memory.hpp" 7 | #include "util.hpp" 8 | #include 9 | 10 | extern "C" { 11 | #include 12 | #include 13 | } 14 | 15 | class Spawner { 16 | dbg::IdArray pids; 17 | uintptr_t nanosleepOffset; 18 | int pid; 19 | 20 | static inline constexpr Nid _nanosleep{"NhpspxdjEKU"}; 21 | 22 | static uintptr_t getNanosleepOffset(const Hijacker &hijacker) { 23 | uintptr_t addr = hijacker.getLibKernelFunctionAddress(_nanosleep); 24 | return addr - hijacker.getLibKernelBase(); 25 | } 26 | 27 | static uintptr_t getNanosleepOffset() { 28 | auto hijacker = Hijacker::getHijacker(getpid()); 29 | if (hijacker == nullptr) [[unlikely]] { 30 | return 0; 31 | } 32 | return getNanosleepOffset(*hijacker); 33 | } 34 | 35 | public: 36 | explicit Spawner() : pids(dbg::getAllPids()), nanosleepOffset(getNanosleepOffset()), pid(getpid()) {} 37 | Spawner(const Hijacker &ptr) : pids(dbg::getAllPids()), nanosleepOffset(getNanosleepOffset(ptr)), pid(ptr.getPid()) {} 38 | UniquePtr bootstrap(Hijacker &hijacker); 39 | UniquePtr spawn(); 40 | }; 41 | -------------------------------------------------------------------------------- /include/kernel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel.hpp" 4 | #include "kernel/kernel.hpp" 5 | #include "kernel/rtld.hpp" 6 | #include "kernel/proc.hpp" 7 | #include "kernel/frame.hpp" 8 | #include "util.hpp" 9 | 10 | bool createReadWriteSockets(const UniquePtr &proc, const int *sockets) noexcept; 11 | 12 | inline bool createReadWriteSockets(int pid, const int *sockets) noexcept { 13 | auto proc = getProc(pid); 14 | if (proc == nullptr) { 15 | puts("createReadWriteSockets proc == nullptr"); 16 | return false; 17 | } 18 | return createReadWriteSockets(proc, sockets); 19 | } 20 | -------------------------------------------------------------------------------- /include/kernel/frame.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel.hpp" 4 | #include "kernel/kernel.hpp" 5 | 6 | namespace { 7 | 8 | static constexpr size_t TRAPFRAME_SIZE = 0x110; 9 | 10 | } 11 | 12 | class TrapFrame : public KernelObject { 13 | 14 | static constexpr size_t RAX_OFFSET = 0x30; 15 | static constexpr size_t RBX_OFFSET = 0x38; 16 | static constexpr size_t RCX_OFFSET = 0x18; 17 | static constexpr size_t RDX_OFFSET = 0x10; 18 | static constexpr size_t RSI_OFFSET = 0x08; 19 | static constexpr size_t RDI_OFFSET = 0x00; 20 | static constexpr size_t R8_OFFSET = 0x20; 21 | static constexpr size_t R9_OFFSET = 0x28; 22 | static constexpr size_t R10_OFFSET = 0x48; 23 | static constexpr size_t R11_OFFSET = 0x50; 24 | static constexpr size_t R12_OFFSET = 0x58; 25 | static constexpr size_t R13_OFFSET = 0x60; 26 | static constexpr size_t R14_OFFSET = 0x68; 27 | static constexpr size_t R15_OFFSET = 0x70; 28 | static constexpr size_t RBP_OFFSET = 0x40; 29 | static constexpr size_t RSP_OFFSET = 0x100; 30 | static constexpr size_t RIP_OFFSET = 0xe8; 31 | 32 | 33 | 34 | public: 35 | TrapFrame(uintptr_t addr) : KernelObject(addr) {} 36 | 37 | TrapFrame &setFrame(const TrapFrame *frame) { 38 | memcpy(buf, frame->buf, sizeof(buf)); 39 | return *this; 40 | } 41 | 42 | uint64_t getRax() { 43 | return get(); 44 | } 45 | 46 | TrapFrame &setRax(uint64_t rax) { 47 | set(rax); 48 | return *this; 49 | } 50 | 51 | uint64_t getRbx() { 52 | return get(); 53 | } 54 | 55 | TrapFrame &setRbx(uint64_t rbx) { 56 | set(rbx); 57 | return *this; 58 | } 59 | 60 | uint64_t getRcx() { 61 | return get(); 62 | } 63 | 64 | 65 | TrapFrame &setRcx(uint64_t rcx) { 66 | set(rcx); 67 | return *this; 68 | } 69 | 70 | uint64_t getRdx() { 71 | return get(); 72 | } 73 | 74 | TrapFrame &setRdx(uint64_t rdx) { 75 | set(rdx); 76 | return *this; 77 | } 78 | 79 | uint64_t getRsi() { 80 | return get(); 81 | } 82 | 83 | TrapFrame &setRsi(uint64_t rsi) { 84 | set(rsi); 85 | return *this; 86 | } 87 | 88 | uint64_t getRdi() { 89 | return get(); 90 | } 91 | 92 | TrapFrame &setRdi(uint64_t rdi) { 93 | set(rdi); 94 | return *this; 95 | } 96 | 97 | uint64_t getR8() { 98 | return get(); 99 | } 100 | 101 | TrapFrame &setR8(uint64_t r8) { 102 | set(r8); 103 | return *this; 104 | } 105 | 106 | uint64_t getR9() { 107 | return get(); 108 | } 109 | 110 | TrapFrame &setR9(uint64_t r9) { 111 | set(r9); 112 | return *this; 113 | } 114 | 115 | uint64_t getR10() { 116 | return get(); 117 | } 118 | 119 | TrapFrame &setR10(uint64_t r10) { 120 | set(r10); 121 | return *this; 122 | } 123 | 124 | uint64_t getR11() { 125 | return get(); 126 | } 127 | 128 | TrapFrame &setR11(uint64_t r11) { 129 | set(r11); 130 | return *this; 131 | } 132 | 133 | uint64_t getR12() { 134 | return get(); 135 | } 136 | TrapFrame &setR12(uint64_t r12) { 137 | set(r12); 138 | return *this; 139 | } 140 | 141 | uint64_t getR13() { 142 | return get(); 143 | } 144 | 145 | TrapFrame &setR13(uint64_t r13) { 146 | set(r13); 147 | return *this; 148 | } 149 | 150 | uint64_t getR14() { 151 | return get(); 152 | } 153 | 154 | TrapFrame &setR14(uint64_t r14) { 155 | set(r14); 156 | return *this; 157 | } 158 | 159 | uint64_t getR15() { 160 | return get(); 161 | } 162 | 163 | TrapFrame &setR15(uint64_t r15) { 164 | set(r15); 165 | return *this; 166 | } 167 | 168 | uint64_t getRbp() { 169 | return get(); 170 | } 171 | 172 | TrapFrame &setRbp(uint64_t rbp) { 173 | set(rbp); 174 | return *this; 175 | } 176 | 177 | uint64_t getRsp() { 178 | return get(); 179 | } 180 | 181 | TrapFrame &setRsp(uint64_t rsp) { 182 | set(rsp); 183 | return *this; 184 | } 185 | 186 | uint64_t getRip() { 187 | return get(); 188 | } 189 | 190 | TrapFrame &setRip(uint64_t rip) { 191 | set(rip); 192 | return *this; 193 | } 194 | }; 195 | -------------------------------------------------------------------------------- /include/kernel/kernel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "util.hpp" 4 | #include 5 | 6 | extern "C" { 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | } 14 | 15 | namespace { 16 | 17 | static constexpr size_t UCRED_SIZE = 0x168; 18 | 19 | } 20 | 21 | template 22 | void kread(uintptr_t addr, T *dst) { 23 | kernel_copyout(addr, dst, sizeof(T)); 24 | } 25 | 26 | template 27 | T kread(uintptr_t addr) { 28 | T dst{}; 29 | kernel_copyout(addr, &dst, sizeof(T)); 30 | return dst; 31 | } 32 | 33 | template 34 | void kread(uintptr_t addr, uint8_t(&buf)[length]) { 35 | kernel_copyout(addr, buf, length); 36 | } 37 | 38 | template 39 | void kwrite(uintptr_t addr, const T& value) { 40 | kernel_copyin(const_cast(&value), addr, sizeof(T)); // NOLINT(cppcoreguidelines-pro-type-const-cast) 41 | } 42 | 43 | template 44 | void kwrite(uintptr_t addr, const uint8_t(&buf)[length]) { 45 | kernel_copyin(const_cast(buf), addr, length); // NOLINT(cppcoreguidelines-pro-type-const-cast) 46 | } 47 | 48 | inline void kwrite(uintptr_t addr, const void *src, size_t length) { 49 | kernel_copyin(const_cast(src), addr, length); // NOLINT(cppcoreguidelines-pro-type-const-cast) 50 | } 51 | 52 | template 53 | class KernelObject; 54 | 55 | template 56 | concept SizedKernelObject = requires { T::length; }; 57 | 58 | template 59 | concept DerivedKernelObject = requires (T t) { 60 | static_cast>(t); 61 | }; 62 | 63 | template 64 | concept KernelObjectBase = SizedKernelObject && DerivedKernelObject; 65 | 66 | template 67 | class KIterable; 68 | 69 | String getKernelString(uintptr_t addr); 70 | 71 | template 72 | class KernelObject { 73 | 74 | uintptr_t addr; 75 | 76 | protected: 77 | template 78 | friend class KIterable; 79 | uint8_t buf[__restrict size]; 80 | explicit KernelObject() = default; 81 | KernelObject(uintptr_t addr) : addr(addr) { // NOLINT(cppcoreguidelines-pro-type-member-init) 82 | #ifdef DEBUG 83 | if (addr == 0) [[unlikely]] { 84 | fatalf("kernel nullpointer dereference attempted\n"); 85 | volatile void **tmp = nullptr; 86 | *tmp = nullptr; // NOLINT(clang-analyzer-core.NullDereference) 87 | } 88 | #endif 89 | kernel_copyout(addr, buf, size); 90 | } 91 | 92 | void reload() { 93 | kernel_copyout(addr, buf, size); 94 | } 95 | 96 | template 97 | T get() const { 98 | static_assert(offset < size, "offset >= size"); 99 | return *(T *)(buf + offset); 100 | } 101 | 102 | template 103 | void set(T value) { 104 | static_assert(offset < size, "offset >= size"); 105 | *(T *)(buf + offset) = value; 106 | } 107 | 108 | template 109 | String getString() const { 110 | uintptr_t addr = get(); 111 | return getKernelString(addr); 112 | } 113 | 114 | public: 115 | static constexpr size_t length = size; 116 | KernelObject(KernelObject &&rhs) noexcept = default; 117 | KernelObject &operator=(KernelObject &&rhs) noexcept = default; 118 | KernelObject(const KernelObject &rhs) = default; 119 | KernelObject &operator=(const KernelObject &rhs) = default; 120 | ~KernelObject() noexcept = default; 121 | 122 | explicit operator bool() const { 123 | return addr; 124 | } 125 | 126 | const void *data() const { 127 | return buf; 128 | } 129 | 130 | uintptr_t address() const { 131 | return addr; 132 | } 133 | 134 | void flush() const { 135 | #ifdef DEBUG 136 | if (!addr) [[unlikely]] { 137 | fatalf("nullptr dereference\n"); 138 | } 139 | #endif 140 | kernel_copyin(const_cast(buf), addr, size); // NOLINT(cppcoreguidelines-pro-type-const-cast) 141 | } 142 | }; 143 | 144 | 145 | template 146 | class KPointer { 147 | 148 | protected: 149 | uintptr_t addr; 150 | 151 | public: 152 | KPointer(decltype(nullptr)) noexcept : addr(0) {} 153 | KPointer(uintptr_t addr) noexcept : addr(addr) {} 154 | T operator*() const { 155 | T t; 156 | kernel_copyout(addr, &t, sizeof(T)); 157 | return t; 158 | } 159 | 160 | explicit operator bool() const { 161 | return addr; 162 | } 163 | 164 | bool operator==(const KPointer &rhs) const { return addr == rhs.addr; } 165 | }; 166 | 167 | template 168 | struct KIterator; 169 | 170 | template 171 | class KIterable { 172 | 173 | friend struct KIterator; 174 | 175 | protected: 176 | 177 | uintptr_t addr; 178 | 179 | KIterable(decltype(nullptr)) : addr() {} 180 | 181 | public: 182 | KIterable(uintptr_t addr) : addr(addr) {} 183 | 184 | UniquePtr operator*() { 185 | return {new T{addr}}; 186 | } 187 | 188 | bool operator!=(const KIterable &rhs) const { return addr != rhs.addr; } 189 | 190 | KIterable &operator++() { 191 | #ifdef DEBUG 192 | if (addr == 0) [[unlikely]] { 193 | return *this; 194 | } 195 | #endif 196 | uintptr_t ptr = 0; 197 | kernel_copyout(addr, &ptr, sizeof(ptr)); 198 | addr = ptr; 199 | return *this; 200 | } 201 | 202 | explicit operator bool() const { return addr; } 203 | }; 204 | 205 | template 206 | struct KIterator { 207 | 208 | uintptr_t addr; 209 | 210 | KIterator(uintptr_t addr) : addr(addr) {} 211 | KIterable begin() const { 212 | return addr; 213 | } 214 | KIterable end() const { 215 | return nullptr; 216 | } 217 | 218 | }; 219 | 220 | class KUcred : public KernelObject { 221 | 222 | static constexpr size_t AUTHID_OFFSET = 0x58; 223 | 224 | public: 225 | KUcred(uintptr_t addr) : KernelObject(addr) {} 226 | 227 | uint64_t authid() const { 228 | return get(); 229 | } 230 | 231 | void authid(uint64_t value) { 232 | set(value); 233 | } 234 | }; 235 | -------------------------------------------------------------------------------- /include/kernel/kthread.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "kernel.hpp" 3 | #include "frame.hpp" 4 | 5 | namespace { 6 | 7 | static constexpr size_t THREAD_SIZE = 0x680; 8 | 9 | } 10 | 11 | class KThread : public KernelObject { 12 | 13 | static constexpr size_t TID_OFFSET = 0x9c; 14 | static constexpr size_t FRAME_OFFSET = 0x460; 15 | 16 | public: 17 | KThread(uintptr_t addr) : KernelObject(addr) {} 18 | 19 | UniquePtr getFrame() { 20 | uintptr_t ptr = get(); 21 | return ptr ? new TrapFrame(ptr) : nullptr; 22 | } 23 | 24 | int tid() const { 25 | return get(); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /include/kernel/proc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kernel.hpp" 4 | #include "kernel/kernel.hpp" 5 | #include "rtld.hpp" 6 | #include "kthread.hpp" 7 | #include "../offsets.hpp" 8 | #include "util.hpp" 9 | 10 | extern "C" { 11 | #include 12 | extern int getpid(); 13 | extern const uintptr_t kernel_base; 14 | } 15 | 16 | namespace { 17 | 18 | static constexpr size_t SELFINFO_SIZE = 0x400; 19 | static constexpr size_t SELFINFO_NAME_SIZE = 32; 20 | static constexpr size_t SELFINFO_PATH_SIZE = 972; 21 | 22 | static constexpr size_t FILEDESCENT_SIZE = 0x30; 23 | static constexpr size_t PROC_SIZE = 0xc90; 24 | 25 | } 26 | 27 | struct SelfInfo { 28 | uint32_t type; 29 | uint32_t ps4_sdk_version; 30 | uint32_t ps5_sdk_version; 31 | uint32_t beef_face; 32 | uint32_t dbbcc; 33 | char name[SELFINFO_NAME_SIZE]; 34 | char path[SELFINFO_PATH_SIZE]; 35 | }; 36 | 37 | static_assert(sizeof(SelfInfo) == SELFINFO_SIZE, "sizeof(SelfInfo) != 0x400"); 38 | 39 | class Filedescent : public KernelObject { 40 | 41 | static constexpr size_t FILE_OFFSET = 0; 42 | static constexpr size_t FLAGS_OFFSET = 0x28; 43 | static constexpr size_t SEQ_OFFSET = 0x2c; 44 | 45 | public: 46 | Filedescent(uintptr_t addr) : KernelObject(addr) {} 47 | uintptr_t file() const { 48 | return get(); 49 | } 50 | 51 | uint8_t flags() const { 52 | return get(); 53 | } 54 | 55 | uint32_t seq() const { 56 | return get(); 57 | } 58 | }; 59 | 60 | class FdTbl { 61 | uintptr_t addr; 62 | size_t ntables; 63 | 64 | size_t checkIndex(size_t i) const { 65 | #ifdef DEBUG 66 | if (i >= ntables) [[unlikely]] { 67 | fatalf("index %llu is out of bounds for length %llu\n", i, ntables); 68 | } 69 | #endif 70 | return i; 71 | } 72 | 73 | public: 74 | FdTbl(uintptr_t addr) : addr(addr), ntables((uint32_t)kread(addr)) {} 75 | 76 | Filedescent operator[](size_t i) const { 77 | return {addr + (checkIndex(i) * Filedescent::length) + sizeof(ntables)}; 78 | } 79 | 80 | uintptr_t getFile(size_t i) const { 81 | uintptr_t fp = addr + (checkIndex(i) * Filedescent::length) + sizeof(ntables); 82 | return kread(fp); 83 | } 84 | 85 | void setFile(size_t i, uintptr_t file) const { 86 | uintptr_t fp = addr + (checkIndex(i) * Filedescent::length) + sizeof(ntables); 87 | kwrite(fp, file); 88 | } 89 | 90 | uintptr_t getFileData(size_t i) const { 91 | return kread(getFile(i)); 92 | } 93 | 94 | void setFileData(size_t i, uintptr_t data) const { 95 | kwrite(getFile(i), data); 96 | } 97 | 98 | size_t length() const { 99 | return ntables; 100 | } 101 | }; 102 | 103 | class KProc : public KernelObject { 104 | 105 | static constexpr size_t UCRED_OFFSET = 0x40; 106 | static constexpr size_t PATH_OFFSET = 0x3d8; 107 | static constexpr size_t SHARED_OBJECT_OFFSET = 0x3e8; 108 | static constexpr size_t PID_OFFSET = 0xbc; 109 | static constexpr size_t THREADS_OFFSET = 0x10; 110 | static constexpr size_t FD_OFFSET = 0x48; 111 | static constexpr size_t TITLEID_OFFSET = 0x470; 112 | static constexpr size_t SELFINFO_OFFSET = 0x588; 113 | static constexpr size_t SELFINFO_NAME_OFFSET = 0x59C; 114 | 115 | public: 116 | 117 | KProc(uintptr_t addr) : KernelObject(addr) {} 118 | uintptr_t p_ucred() const { 119 | return get(); 120 | } 121 | 122 | UniquePtr ucred() const { 123 | return new KUcred{p_ucred()}; 124 | } 125 | 126 | int p_pid() const { 127 | return get(); 128 | } 129 | 130 | int pid() const { 131 | return p_pid(); 132 | } 133 | 134 | UniquePtr getSharedObject() const { 135 | auto obj = get(); 136 | return obj != 0 ? new SharedObject{obj, pid()} : nullptr; 137 | } 138 | 139 | String getPath() const { 140 | return getString(); 141 | } 142 | 143 | KIterator p_threads() const { 144 | return address() + THREADS_OFFSET; 145 | } 146 | 147 | KIterator getThreads() const { 148 | return p_threads(); 149 | } 150 | 151 | UniquePtr getThread(int tid) const { 152 | for (auto td : getThreads()) { 153 | if (tid == td->tid()) { 154 | return td.release(); 155 | } 156 | } 157 | return nullptr; 158 | } 159 | 160 | uintptr_t p_fd() const { 161 | return get(); 162 | } 163 | 164 | FdTbl getFdTbl() const { 165 | return {kread(p_fd())}; 166 | } 167 | 168 | const SelfInfo *getSelfInfo() const { 169 | return reinterpret_cast(buf + SELFINFO_OFFSET); 170 | } 171 | 172 | // no flush required 173 | void setName(const StringView &name, bool reload=false) { 174 | const size_t length = name.length() < (SELFINFO_NAME_SIZE-1) ? name.length() : SELFINFO_NAME_SIZE; 175 | kwrite(address() + SELFINFO_NAME_OFFSET, name.c_str(), length + 1); 176 | if (reload) { 177 | this->reload(); 178 | } 179 | } 180 | 181 | String titleId() const 182 | { 183 | return getKernelString(address() + TITLEID_OFFSET); 184 | } 185 | }; 186 | 187 | inline KIterator getAllProcs() { 188 | return {kernel_base + offsets::allproc()}; 189 | } 190 | 191 | UniquePtr getProc(int pid); 192 | 193 | inline UniquePtr getProc() { 194 | return getProc(getpid()); 195 | } 196 | -------------------------------------------------------------------------------- /include/kernel_helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | #include 9 | 10 | void kernel_init_rw(int master_sock, int victim_sock, int *rw_pipe, uint64_t pipe_addr); 11 | void kernel_copyin(void *src, uint64_t kdest, size_t length); 12 | void kernel_copyout(uint64_t ksrc, void *dest, size_t length); 13 | 14 | #ifdef __cplusplus 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /include/nid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include 5 | #include 6 | } 7 | 8 | namespace { 9 | 10 | static constexpr size_t NID_LENGTH = 11; 11 | 12 | } 13 | 14 | union Nid { 15 | 16 | char str[NID_LENGTH + 1]; // 12th character is for NULL terminator to allow constexpr constructor 17 | struct __attribute__((packed)) data_t { 18 | int_fast64_t low; 19 | int_fast32_t hi; 20 | } data; 21 | 22 | Nid() noexcept = default; 23 | 24 | explicit constexpr Nid(const char *nid) noexcept : str{} { 25 | __builtin_memcpy(str, nid, NID_LENGTH); 26 | str[NID_LENGTH] = '\0'; 27 | } 28 | 29 | constexpr int_fast64_t operator<=>(const Nid& rhs) const { 30 | auto i = data.low - rhs.data.low; 31 | if (i == 0) [[unlikely]] { 32 | return data.hi - rhs.data.hi; 33 | } 34 | return i; 35 | } 36 | 37 | constexpr bool operator==(const Nid &rhs) const { 38 | return data.low == rhs.data.low && data.hi == rhs.data.hi; 39 | } 40 | 41 | constexpr void setNullTerminator() { 42 | str[NID_LENGTH] = '\0'; 43 | } 44 | }; 45 | 46 | namespace nid { 47 | 48 | static inline constexpr Nid get_authinfo{"igMefp4SAv0"}; 49 | static inline constexpr Nid sceSystemServiceGetAppStatus{"t5ShV0jWEFE"}; 50 | static inline constexpr Nid sceSystemServiceAddLocalProcess{"0cl8SuwosPQ"}; 51 | static inline constexpr Nid socketpair{"MZb0GKT3mo8"}; 52 | static inline constexpr Nid usleep{"QcteRwbsnV0"}; 53 | static inline constexpr Nid _errno{"9BcDykPmo1I"}; 54 | static inline constexpr Nid sceSysmoduleLoadModuleInternal{"39iV5E1HoCk"}; 55 | static inline constexpr Nid sceSysmoduleLoadModuleByNameInternal{"CU8m+Qs+HN4"}; 56 | static inline constexpr Nid mmap{"BPE9s9vQQXo"}; 57 | static inline constexpr Nid munmap{"UqDGjXA5yUM"}; 58 | static inline constexpr Nid sceKernelJitCreateSharedMemory{"avvJ3J0H0EY"}; 59 | static inline constexpr Nid socket{"TU-d9PfIHPM"}; 60 | static inline constexpr Nid pipe{"-Jp7F+pXxNg"}; 61 | static inline constexpr Nid sceKernelDlsym{"LwG8g3niqwA"}; 62 | static inline constexpr Nid setsockopt{"fFxGkxF2bVo"}; 63 | static inline constexpr Nid execve{"kdguLiAheLI"}; 64 | static inline constexpr Nid _nanosleep{"NhpspxdjEKU"}; 65 | static inline constexpr Nid close{"bY-PO6JhzhQ"}; 66 | static inline constexpr Nid connect{"XVL8So3QJUk"}; 67 | static inline constexpr Nid send{"fZOeZIOEmLw"}; 68 | static inline constexpr Nid recv{"Ez8xjo9UF4E"}; 69 | static inline constexpr Nid rfork_thread{"bSDxEpGzmUE"}; 70 | static inline constexpr Nid access{"8vE6Z6VEYyk"}; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /include/notify.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" 4 | { 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | typedef struct 12 | { 13 | int32_t type; // 0x00 14 | int32_t req_id; // 0x04 15 | int32_t priority; // 0x08 16 | int32_t msg_id; // 0x0C 17 | int32_t target_id; // 0x10 18 | int32_t user_id; // 0x14 19 | int32_t unk1; // 0x18 20 | int32_t unk2; // 0x1C 21 | int32_t app_id; // 0x20 22 | int32_t error_num; // 0x24 23 | int32_t unk3; // 0x28 24 | char use_icon_image_uri; // 0x2C 25 | char message[1024]; // 0x2D 26 | char uri[1024]; // 0x42D 27 | char unkstr[1024]; // 0x82D 28 | } OrbisNotificationRequest; // Size = 0xC30 29 | 30 | int32_t sceKernelSendNotificationRequest(int32_t device, OrbisNotificationRequest *req, size_t size, int32_t blocking); 31 | } 32 | 33 | void printf_notification(const char *fmt, ...); 34 | -------------------------------------------------------------------------------- /include/offsets.hpp: -------------------------------------------------------------------------------- 1 | 2 | extern "C" { 3 | #include 4 | } 5 | 6 | namespace offsets { 7 | 8 | size_t allproc(); 9 | size_t security_flags(); 10 | size_t qa_flags(); 11 | size_t utoken_flags(); 12 | size_t root_vnode(); 13 | 14 | } // offsets 15 | -------------------------------------------------------------------------------- /include/print.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include 5 | size_t print_null(...); 6 | } 7 | 8 | #ifdef DEBUG 9 | #define _puts(x) printf("%s:%d: %s\n", __PRETTY_FUNCTION__, __LINE__, x) 10 | #define _printf(...) { \ 11 | printf("%s:%d ", __PRETTY_FUNCTION__, __LINE__); \ 12 | printf(__VA_ARGS__); \ 13 | } 14 | #define print_ret(func) printf("%s:%d: " #func ": 0x%08x\n", __PRETTY_FUNCTION__, __LINE__, func) 15 | #else 16 | #define _puts(x) print_null("%s:%d: %s\n", __PRETTY_FUNCTION__, __LINE__, x) 17 | #define _printf(...) { \ 18 | print_null("%s:%d ", __PRETTY_FUNCTION__, __LINE__); \ 19 | print_null(__VA_ARGS__); \ 20 | } 21 | #define print_ret(func) print_null("%s:%d: " #func ": 0x%08x\n", __PRETTY_FUNCTION__, __LINE__, func) 22 | #endif 23 | -------------------------------------------------------------------------------- /kill_daemon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import asyncio 3 | import sys 4 | from contextlib import asynccontextmanager 5 | 6 | COMMAND_PORT = 9048 7 | KILL_CMD = b'\x03' 8 | RESPONSE_OK = 0 9 | RESPONSE_ERROR = 255 10 | 11 | @asynccontextmanager 12 | async def open_connection(host: str, port: int): 13 | while True: 14 | try: 15 | reader, writer = await asyncio.open_connection(host, port) 16 | except OSError: 17 | await asyncio.sleep(1) 18 | continue 19 | try: 20 | yield reader, writer 21 | break 22 | finally: 23 | await writer.drain() 24 | writer.close() 25 | 26 | 27 | async def kill(host: str): 28 | async with open_connection(host, COMMAND_PORT) as (_, _): 29 | pass 30 | 31 | 32 | if __name__ == '__main__': 33 | if len(sys.argv) > 2: 34 | print(f'usage: {__file__} ps5ip') 35 | sys.exit() 36 | 37 | if len(sys.argv) == 2: 38 | try: 39 | asyncio.run(kill(sys.argv[1])) 40 | except KeyboardInterrupt: 41 | pass 42 | sys.exit() 43 | -------------------------------------------------------------------------------- /launch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import asyncio 3 | import sys 4 | from contextlib import asynccontextmanager 5 | 6 | COMMAND_PORT = 9028 7 | LAUNCH_CMD = b'\x01' 8 | KILL_APP_CMD = b'\x04' 9 | PROC_LIST_CMD = b'\x02' 10 | RESPONSE_OK = b'\x00' 11 | RESPONSE_ERROR = 255 12 | 13 | @asynccontextmanager 14 | async def open_connection(host: str, port: int): 15 | while True: 16 | try: 17 | reader, writer = await asyncio.open_connection(host, port) 18 | except OSError: 19 | await asyncio.sleep(1) 20 | continue 21 | try: 22 | yield reader, writer 23 | break 24 | finally: 25 | await writer.drain() 26 | writer.close() 27 | 28 | 29 | async def launch(host: str, appId: str): 30 | async with open_connection(host, COMMAND_PORT) as (reader, writer): 31 | writer.write(LAUNCH_CMD) 32 | writer.write(appId.encode('latin-1')) 33 | await writer.drain() 34 | reply = await reader.read() 35 | if not reply: 36 | print('no response') 37 | return 38 | err = reply[0] 39 | reply = reply[1:] 40 | print(err) 41 | if err == RESPONSE_ERROR: 42 | print(f'launch failed: {reply.decode("latin-1")}') 43 | else: 44 | print('launch successful') 45 | 46 | 47 | async def kill(host: str, appId: int): 48 | async with open_connection(host, COMMAND_PORT) as (reader, writer): 49 | writer.write(KILL_APP_CMD) 50 | writer.write(appId.to_bytes(length=4, byteorder='little')) 51 | await writer.drain() 52 | reply = await reader.read() 53 | if not reply: 54 | print('no response') 55 | return 56 | err = reply[0] 57 | reply = reply[1:] 58 | print(err) 59 | if err == RESPONSE_ERROR: 60 | print(f'launch failed: {reply.decode("latin-1")}') 61 | else: 62 | print('launch successful') 63 | 64 | 65 | async def list_procs(host: str): 66 | async with open_connection(host, COMMAND_PORT) as (_, writer): 67 | writer.write(PROC_LIST_CMD) 68 | await writer.drain() 69 | 70 | 71 | if __name__ == '__main__': 72 | if len(sys.argv) > 4: 73 | print(f'usage: {__file__} ps5ip titleId') 74 | sys.exit() 75 | 76 | if len(sys.argv) == 2: 77 | try: 78 | asyncio.run(list_procs(sys.argv[1])) 79 | except KeyboardInterrupt: 80 | pass 81 | sys.exit() 82 | if sys.argv[2] == 'kill': 83 | try: 84 | try: 85 | id = int(sys.argv[3]) 86 | except ValueError: 87 | id = int(sys.argv[3], 16) 88 | asyncio.run(kill(sys.argv[1], id)) 89 | except KeyboardInterrupt: 90 | pass 91 | if len(sys.argv[2]) > 9: 92 | print('invalid title id') 93 | sys.exit() 94 | 95 | try: 96 | asyncio.run(launch(sys.argv[1], sys.argv[2])) 97 | except KeyboardInterrupt: 98 | pass 99 | -------------------------------------------------------------------------------- /libhijacker/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################################### 2 | # PS5SDK - Example: pipe pirate 3 | # Uses the read/write primitive to read and write some kernel data. 4 | # @authors ChendoChap, Specter, Znullptr 5 | ################################################################################################### 6 | 7 | cmake_minimum_required (VERSION 3.20) 8 | 9 | set(basename "hijacker") 10 | project(${basename} C CXX ASM) 11 | 12 | # Language Standard Defaults 13 | set(CMAKE_C_STANDARD 11) 14 | set(CMAKE_C_STANDARD_REQUIRED ON) 15 | 16 | set(CMAKE_CXX_EXTENSIONS ON) 17 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 18 | 19 | if ("${CMAKE_CXX_STANDARD}" STREQUAL "") 20 | set(CMAKE_CXX_STANDARD 20) 21 | endif() 22 | 23 | # Check for sub-project as part of main build or external build 24 | if (NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 25 | set(IS_SUBPROJECT TRUE) 26 | else() 27 | set(IS_SUBPROJECT FALSE) 28 | endif() 29 | 30 | message("IS_SUBPROJECT: ${IS_SUBPROJECT}") 31 | 32 | set(D_CWD "${PROJECT_SOURCE_DIR}") 33 | 34 | 35 | # Headers 36 | include_directories (SYSTEM "${D_PS5SDK}") 37 | include_directories (SYSTEM "${D_PS5SDK}/include") 38 | 39 | set(D_SRC ${D_CWD}/source) 40 | 41 | file(GLOB SrcFiles ${D_SRC}/*.c ${D_SRC}/*.cpp ${D_SRC}/**/*.cpp ${D_SRC}/*.h ${D_SRC}/*.hpp ${D_SRC}/**/*.hpp ${D_SRC}/*.s) 42 | 43 | add_library(${PROJECT_NAME} STATIC ${SrcFiles}) 44 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") 45 | 46 | # Must build with clang 47 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc]lang") 48 | set(IS_CLANG 1) 49 | else() 50 | message(FATAL_ERROR "${PROJECT_NAME} is meant to be built with clang! CompilerID: ${CMAKE_CXX_COMPILER_ID}") 51 | endif() 52 | 53 | # Finalize main target sources 54 | target_compile_options(${PROJECT_NAME} PUBLIC 55 | $<$:${C_DEFS} ${C_FLAGS}> 56 | $<$:${CXX_DEFS} ${CXX_FLAGS}> 57 | $<$:${ASM_FLAGS}> 58 | ) 59 | 60 | message("========== build: ${PROJECT_NAME} ==========") 61 | 62 | set(CMAKE_C_FLAGS "--target=x86_64-freebsd-pc-elf -march=znver2 -mavx2 -DPPR -DPS5 -DPS5_FW_VERSION=${V_FW} ") 63 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200112 -D__BSD_VISIBLE=1 -D__XSI_VISIBLE=500") 64 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-builtin -nostdlib -Wall") # -nostartfiles 65 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") 66 | set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -O0 -march=znver2 -mavx2 -Wall -Wextra -Wmove -Wmost -Werror -pedantic -pedantic-errors -fno-exceptions") 67 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g0 -O0") 68 | target_include_directories(${PROJECT_NAME} PRIVATE "${D_CWD}/../include") 69 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 70 | -------------------------------------------------------------------------------- /libhijacker/source/backtrace.cpp: -------------------------------------------------------------------------------- 1 | #include "backtrace.hpp" 2 | 3 | const Frame * __attribute__((naked)) getFramePointer() { 4 | __asm__ volatile( 5 | "push %rbp\n" 6 | "pop %rax\n" 7 | "ret\n" 8 | ); 9 | } 10 | 11 | extern "C" int puts(const char*); 12 | 13 | static uintptr_t __attribute__((naked, noinline)) getTextStart() { 14 | asm volatile( 15 | "lea __text_start(%rip), %rax\n" 16 | "ret\n" 17 | ); 18 | } 19 | 20 | static uintptr_t __attribute__((naked, noinline)) getTextEnd() { 21 | asm volatile( 22 | "lea __text_stop(%rip), %rax\n" 23 | "ret\n" 24 | ); 25 | } 26 | 27 | void printBacktrace() { 28 | const uintptr_t start = getTextStart(); 29 | const uintptr_t stop = getTextEnd(); 30 | __builtin_printf(".text: 0x%08llx\n", (unsigned long long)start); 31 | puts("---backtrace start---"); 32 | for (const Frame *__restrict frame = getFramePointer(); frame != nullptr; frame = frame->next) { 33 | if (frame->addr != 0) [[likely]] { 34 | if (frame->addr >= start && frame->addr <= stop) { 35 | __builtin_printf("0x%llx\n", (unsigned long long)frame->addr - start); 36 | } 37 | } 38 | } 39 | puts("---backtrace end---"); 40 | } 41 | -------------------------------------------------------------------------------- /libhijacker/source/dbg.cpp: -------------------------------------------------------------------------------- 1 | #include "dbg.hpp" 2 | #include "dbg/dbg.hpp" 3 | #include "hijacker/hijacker.hpp" 4 | #include "kernel.hpp" 5 | #include "nid.hpp" 6 | #include "util.hpp" 7 | #include 8 | 9 | extern "C" { 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | } 18 | 19 | static constexpr int SYSCALL_OFFSET = 10; 20 | 21 | extern "C" int mdbg_call(void *, void *, void *); 22 | extern "C" int sceKernelDlsym(int handle, const char* symbol, void** addrp); 23 | extern "C" int *__error(); 24 | 25 | static constexpr uintptr_t PID_OFFSET = 0xBC; 26 | static constexpr uintptr_t UCRED_OFFSET = 0x40; 27 | 28 | static uintptr_t getCurrentProc() { 29 | 30 | const pid_t pid = getpid(); 31 | uintptr_t proc = kread(kernel_base + offsets::allproc()); 32 | while (proc != 0) { 33 | int cid = kread(proc + PID_OFFSET); 34 | if (cid == pid) { 35 | return proc; 36 | } 37 | proc = kread(proc); 38 | } 39 | return 0; 40 | } 41 | 42 | namespace dbg { 43 | 44 | int __attribute__((noinline)) mdbg_call(DbgArg1 &arg1, DbgArg2 &arg2, DbgArg3 &arg3) { 45 | AuthidSwapper swapper{DEBUGGER_AUTHID}; 46 | return ::mdbg_call(&arg1, &arg2, &arg3); 47 | } 48 | 49 | IdArray getAllPids() { 50 | static constexpr size_t LENGTH = 10000; 51 | DbgArg1 arg1{1, DbgCommand::PROCESS_LIST_CMD}; 52 | UniquePtr buf{new int[LENGTH]}; 53 | DbgGetPidsArg arg2{buf.get(), LENGTH}; 54 | DbgArg3 arg3{}; 55 | mdbg_call(arg1, arg2, arg3); 56 | return {buf.get(), arg3.length}; 57 | } 58 | 59 | IdArray getAllTids(int pid) { 60 | static constexpr size_t LENGTH = 0x2000; 61 | DbgArg1 arg1{1, DbgCommand::THREAD_LIST_CMD}; 62 | UniquePtr buf{new int[LENGTH]}; 63 | DbgGetTidsArg arg2{pid, buf.get(), LENGTH}; 64 | DbgArg3 arg3{}; 65 | mdbg_call(arg1, arg2, arg3); 66 | return {buf.get(), arg3.length}; 67 | } 68 | 69 | void ProcessInfo::fillInfo() { 70 | DbgArg1 arg1{1, DbgCommand::PROCESS_INFO_CMD}; 71 | DbgGetProcInfoArg arg2{_pid, buf.get(), BUF_LENGTH}; 72 | DbgArg3 arg3{}; 73 | mdbg_call(arg1, arg2, arg3); 74 | 75 | } 76 | 77 | void ThreadInfo::fillInfo() { 78 | DbgArg1 arg1{1, DbgCommand::THREAD_INFO_CMD}; 79 | DbgGetThreadInfoArg arg2{_pid, _tid, buf.get(), BUF_LENGTH}; 80 | DbgArg3 arg3{}; 81 | mdbg_call(arg1, arg2, arg3); 82 | } 83 | 84 | static void logState(const DbgArg3 &arg) { 85 | // NOLINTBEGIN(*) 86 | uint64_t state = -1; 87 | if (arg.err == 0) { 88 | state = arg.length & 7; 89 | if ((arg.length & 0x10) != 0) { 90 | state |= 8; 91 | } 92 | } 93 | if (state == 7 || state == 0) { 94 | puts("idk what this means other than we're screwed"); 95 | } 96 | printf("state: 0x%08llx\n", state); 97 | // NOLINTEND(*) 98 | } 99 | 100 | void suspend(int pid) { 101 | puts("suspending"); 102 | DbgArg1 arg1{1, DbgCommand::ARG2_CMD}; 103 | DbgKickProcessArg arg2{pid}; 104 | DbgArg3 arg3{}; 105 | mdbg_call(arg1, arg2, arg3); 106 | logState(arg3); 107 | } 108 | 109 | void resume(int pid) { 110 | puts("resuming"); 111 | // this is the same as suspend but is separate for easier debugging 112 | DbgArg1 arg1{1, DbgCommand::ARG2_CMD}; 113 | DbgKickProcessArg arg2{pid}; 114 | DbgArg3 arg3{}; 115 | mdbg_call(arg1, arg2, arg3); 116 | logState(arg3); 117 | } 118 | 119 | bool read(int pid, uintptr_t src, void *dst, size_t length) { 120 | DbgArg1 arg1{1, DbgCommand::READ_CMD}; 121 | DbgReadArg arg2{pid, src, dst, length}; 122 | DbgArg3 arg3{}; 123 | mdbg_call(arg1, arg2, arg3); 124 | if (arg3.length != length) { 125 | int err = arg3.err != -1 ? (int) arg3.err : errno; 126 | printf("read failed %d: %s\n", err, strerror(err)); 127 | return false; 128 | } 129 | return true; 130 | } 131 | 132 | bool write(int pid, uintptr_t dst, const void *src, size_t length) { 133 | DbgArg1 arg1{1, DbgCommand::WRITE_CMD}; 134 | DbgReadArg arg2{pid, dst, const_cast(src), length}; // NOLINT(*) 135 | DbgArg3 arg3{}; 136 | mdbg_call(arg1, arg2, arg3); 137 | if (arg3.length != length) { 138 | int err = arg3.err != -1 ? (int) arg3.err : errno; 139 | printf("write failed %d: %s\n", err, strerror(err)); 140 | return false; 141 | } 142 | return true; 143 | } 144 | 145 | uint64_t setAuthId(uint64_t authid) { 146 | static constexpr int AUTHID_OFFSET = 0x58; 147 | uintptr_t proc = getCurrentProc(); 148 | uintptr_t ucred = kread(proc + UCRED_OFFSET); 149 | uint64_t id = kread(ucred + AUTHID_OFFSET); 150 | kwrite(ucred + AUTHID_OFFSET, authid); 151 | return id; 152 | } 153 | 154 | uintptr_t Tracer::call(const Registers &backup, Registers &jmp) const noexcept { 155 | if (libkernel_base == 0) [[unlikely]] { 156 | auto hijacker = Hijacker::getHijacker(pid); 157 | libkernel_base = hijacker->getLibKernelBase(); 158 | if (libkernel_base == 0) [[unlikely]] { 159 | puts("failed to get libkernel base"); 160 | return -1; 161 | } 162 | } 163 | 164 | jmp.rsp(jmp.rsp() - sizeof(uintptr_t)); 165 | 166 | if (!setRegisters(jmp)) { 167 | return -1; 168 | } 169 | 170 | // set the return address to the `INT3` at the start of libkernel 171 | dbg::write(pid, jmp.rsp(), &libkernel_base, sizeof(libkernel_base)); 172 | 173 | // call the function 174 | run(); 175 | 176 | if (!getRegisters(jmp)) { 177 | return -1; 178 | } 179 | 180 | // restore registers 181 | if (!setRegisters(backup)) { 182 | return -1; 183 | } 184 | 185 | return jmp.rax(); 186 | } 187 | 188 | uintptr_t Tracer::syscall(const Registers &backup, Registers &jmp) const noexcept { 189 | if (syscall_addr == 0) [[unlikely]] { 190 | auto hijacker = Hijacker::getHijacker(pid); 191 | auto addr = hijacker->getLibKernelFunctionAddress(nid::get_authinfo); 192 | if (addr != 0) { 193 | addr += SYSCALL_OFFSET; 194 | } 195 | syscall_addr = addr; 196 | } 197 | 198 | jmp.rip(syscall_addr); 199 | 200 | if (!setRegisters(jmp)) { 201 | return -1; 202 | } 203 | 204 | // execute the syscall instruction 205 | step(); 206 | if (!getRegisters(jmp)) { 207 | setRegisters(backup); 208 | return -1; 209 | } 210 | 211 | // restore registers 212 | if (!setRegisters(backup)) { 213 | return -1; 214 | } 215 | 216 | return jmp.rax(); 217 | } 218 | 219 | void Tracer::perror(const char *msg) const noexcept { 220 | if (errno_addr == 0) [[unlikely]] { 221 | auto hijacker = Hijacker::getHijacker(pid); 222 | errno_addr = hijacker->getLibKernelAddress(nid::_errno); 223 | if (errno_addr == 0) [[unlikely]] { 224 | puts("failed to get errno address"); 225 | return; 226 | } 227 | } 228 | int err = 0; 229 | read(pid, errno_addr, &err, sizeof(err)); 230 | printf("%s: %s\n", msg, strerror(err)); 231 | } 232 | 233 | int Tracer::pipe(int *fildes) const noexcept { 234 | fildes[0] = -1; 235 | fildes[1] = -1; 236 | const Registers backup = getRegisters(); 237 | Registers jmp = backup; 238 | const auto rsp = jmp.rsp() - sizeof(long[2]); 239 | dbg::write(pid, rsp, fildes, sizeof(int[2])); 240 | jmp.rax(PIPE2); 241 | jmp.rdi(rsp); 242 | jmp.rsi(0); 243 | int err = static_cast(syscall(backup, jmp)); 244 | if (err < 0) { 245 | return err; 246 | } 247 | dbg::read(pid, rsp, fildes, sizeof(int[2])); 248 | return 0; 249 | } 250 | 251 | int Tracer::setsockopt(int s, int level, int optname, const void *optval, unsigned int optlen) const noexcept { 252 | const Registers backup = getRegisters(); 253 | Registers jmp = backup; 254 | const auto rsp = jmp.rsp() - optlen; 255 | jmp.rax(SETSOCKOPT); 256 | jmp.rsp(rsp); 257 | jmp.rdi(s); jmp.rsi(level); jmp.rdx(optname); jmp.r10(rsp); jmp.r8(optlen); 258 | dbg::write(pid, rsp, optval, optlen); 259 | int err = static_cast(syscall(backup, jmp)); 260 | if (err < 0) { 261 | return err; 262 | } 263 | return 0; 264 | } 265 | 266 | } // dbg 267 | -------------------------------------------------------------------------------- /libhijacker/source/hijacker.cpp: -------------------------------------------------------------------------------- 1 | #include "hijacker.hpp" 2 | #include "offsets.hpp" 3 | #include "util.hpp" 4 | #include 5 | 6 | extern "C" { 7 | #include 8 | #include 9 | } 10 | 11 | UniquePtr Hijacker::getHijacker(const StringView &processName) { 12 | UniquePtr obj = nullptr; 13 | for (dbg::ProcessInfo info : dbg::getProcesses()) { 14 | if (info.name() == processName) { 15 | auto p = ::getProc(info.pid()); 16 | obj = p->getSharedObject(); 17 | } 18 | } 19 | return obj ? new Hijacker(obj.release()) : nullptr; 20 | } 21 | 22 | int Hijacker::getMainThreadId() const { 23 | if (mainThreadId == -1) { 24 | for (dbg::ThreadInfo info : dbg::getThreads(obj->pid)) { 25 | StringView name = info.name(); 26 | if (name.contains("Main") || name.contains(".")) { 27 | // this works for most of them 28 | mainThreadId = info.tid(); 29 | break; 30 | } 31 | } 32 | if (mainThreadId == -1) [[unlikely]] { 33 | puts("main thread id not found"); 34 | } 35 | } 36 | return mainThreadId; 37 | } 38 | 39 | UniquePtr Hijacker::getTrapFrame() const { 40 | // do not cache this 41 | int tid = getMainThreadId(); 42 | if (tid == -1) [[unlikely]] { 43 | return nullptr; 44 | } 45 | auto p = ::getProc(obj->pid); 46 | if (p == nullptr) { 47 | return nullptr; 48 | } 49 | 50 | auto td = p->getThread(tid); 51 | if (td == nullptr) { 52 | return nullptr; 53 | } 54 | return td->getFrame(); 55 | } 56 | 57 | // NOLINTBEGIN 58 | 59 | // 60 | static inline void copyin(uintptr_t kdst, const void *src, size_t length) { 61 | kernel_copyin(const_cast(src), kdst, length); 62 | } 63 | 64 | void Hijacker::jailbreak(bool escapeSandbox) const { 65 | auto p = getProc(); 66 | uintptr_t ucred = p->p_ucred(); 67 | uintptr_t fd = p->p_fd(); 68 | UniquePtr rootvnode_area_store{new uint8_t[0x100]}; 69 | kernel_copyout(kernel_base + offsets::root_vnode(), rootvnode_area_store.get(), 0x100); 70 | uint32_t uid_store = 0; 71 | uint32_t ngroups_store = 0; 72 | uint64_t authid_store = 0x4801000000000013l; 73 | int64_t caps_store = -1; 74 | uint8_t attr_store[] = {0x80, 0, 0, 0, 0, 0, 0, 0}; 75 | 76 | copyin(ucred + 0x04, &uid_store, 0x4); // cr_uid 77 | copyin(ucred + 0x08, &uid_store, 0x4); // cr_ruid 78 | copyin(ucred + 0x0C, &uid_store, 0x4); // cr_svuid 79 | copyin(ucred + 0x10, &ngroups_store, 0x4); // cr_ngroups 80 | copyin(ucred + 0x14, &uid_store, 0x4); // cr_rgid 81 | 82 | if (escapeSandbox) { 83 | // Escape sandbox 84 | copyin(fd + 0x10, rootvnode_area_store.get(), 0x8); // fd_rdir 85 | copyin(fd + 0x18, rootvnode_area_store.get(), 0x8); // fd_jdir 86 | } 87 | 88 | // Escalate sony privileges 89 | copyin(ucred + 0x58, &authid_store, 0x8); // cr_sceAuthID 90 | copyin(ucred + 0x60, &caps_store, 0x8); // cr_sceCaps[0] 91 | copyin(ucred + 0x68, &caps_store, 0x8); // cr_sceCaps[1] 92 | copyin(ucred + 0x83, attr_store, 0x1); // cr_sceAttr[0] 93 | } 94 | 95 | uintptr_t Hijacker::getFunctionAddress(const SharedLib *lib, const Nid &fname) const noexcept { 96 | RtldMeta *meta = lib->getMetaData(); 97 | rtld::ElfSymbol sym = meta->getSymbolTable()[fname]; 98 | #ifdef DEBUG 99 | if (!sym) [[unlikely]] { 100 | fatalf("failed to get symbol for %s %s\n", lib->getPath().c_str(), fname.str); 101 | } 102 | #endif 103 | return sym ? sym.vaddr() : 0; 104 | } 105 | // NOLINTEND 106 | -------------------------------------------------------------------------------- /libhijacker/source/kernel.cpp: -------------------------------------------------------------------------------- 1 | #include "kernel.hpp" 2 | #include "util.hpp" 3 | 4 | extern "C" { 5 | #include 6 | } 7 | 8 | static constexpr size_t BUF_SIZE = 0x10; 9 | 10 | String getKernelString(uintptr_t addr) { 11 | String res{}; 12 | char buf[BUF_SIZE]; 13 | while (true) { 14 | kernel_copyout(addr + res.length(), buf, sizeof(buf)); 15 | size_t read = strnlen(buf, sizeof(buf)); 16 | res += StringView{buf, read}; 17 | if (read < sizeof(buf)) { 18 | return res; 19 | } 20 | } 21 | } 22 | 23 | UniquePtr getProc(int pid) { 24 | for (auto p : getAllProcs()) { 25 | if (pid == p->pid()) { 26 | return p.release(); 27 | } 28 | } 29 | return nullptr; 30 | } 31 | 32 | bool createReadWriteSockets(const UniquePtr &proc, const int *sockets) noexcept { 33 | // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) 34 | auto newtbl = proc->getFdTbl(); 35 | auto sock = newtbl.getFileData(sockets[0]); 36 | if (sock == 0) [[unlikely]] { 37 | puts("createReadWriteSockets sock == 0"); 38 | return false; 39 | } 40 | kwrite(sock, 0x100); 41 | auto pcb = kread(sock + 0x18); 42 | if (pcb == 0) [[unlikely]] { 43 | puts("createReadWriteSockets master pcb == 0"); 44 | return false; 45 | } 46 | auto master_inp6_outputopts = kread(pcb + 0x120); 47 | if (master_inp6_outputopts == 0) [[unlikely]] { 48 | puts("createReadWriteSockets master_inp6_outputopts == 0"); 49 | return false; 50 | } 51 | sock = newtbl.getFileData(sockets[1]); 52 | kwrite(sock, 0x100); 53 | pcb = kread(sock + 0x18); 54 | if (pcb == 0) [[unlikely]] { 55 | puts("createReadWriteSockets victim pcb == 0"); 56 | return false; 57 | } 58 | auto victim_inp6_outputopts = kread(pcb + 0x120); 59 | if (victim_inp6_outputopts == 0) [[unlikely]] { 60 | puts("createReadWriteSockets victim_inp6_outputopts == 0"); 61 | return false; 62 | } 63 | kwrite(master_inp6_outputopts + 0x10, victim_inp6_outputopts + 0x10); 64 | kwrite(master_inp6_outputopts + 0xc0, 0x13370000); 65 | return true; 66 | // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) 67 | } 68 | -------------------------------------------------------------------------------- /libhijacker/source/notify.cpp: -------------------------------------------------------------------------------- 1 | #include "notify.hpp" 2 | 3 | void printf_notification(const char *fmt, ...) { 4 | OrbisNotificationRequest noti_buffer{}; 5 | 6 | va_list args{}; 7 | va_start(args, fmt); 8 | vsnprintf(noti_buffer.message, sizeof(noti_buffer.message), fmt, args); 9 | va_end(args); 10 | 11 | // these dont do anything currently 12 | // that or the structure has changed 13 | // lets just copy messages for now 14 | /* 15 | noti_buffer.type = 0; 16 | noti_buffer.unk3 = 0; 17 | noti_buffer.use_icon_image_uri = 0; 18 | noti_buffer.target_id = -1; 19 | */ 20 | printf("Sent notification: 0x%08x with message:\n%s\n", sceKernelSendNotificationRequest(0, (OrbisNotificationRequest * ) & noti_buffer, sizeof(noti_buffer), 0), noti_buffer.message); 21 | } 22 | -------------------------------------------------------------------------------- /libhijacker/source/offsets.cpp: -------------------------------------------------------------------------------- 1 | 2 | extern "C" { 3 | #include 4 | #include 5 | #include 6 | } 7 | 8 | static constexpr uint32_t VERSION_MASK = 0xffff0000; 9 | 10 | static constexpr uint32_t V300 = 0x3000000; 11 | static constexpr uint32_t V310 = 0x3100000; 12 | static constexpr uint32_t V320 = 0x3200000; 13 | static constexpr uint32_t V321 = 0x3210000; 14 | static constexpr uint32_t V400 = 0x4000000; 15 | static constexpr uint32_t V402 = 0x4020000; 16 | static constexpr uint32_t V403 = 0x4030000; 17 | static constexpr uint32_t V450 = 0x4500000; 18 | static constexpr uint32_t V451 = 0x4510000; 19 | 20 | static uint32_t getSystemSwVersion() { 21 | static uint32_t version; 22 | if (version != 0) [[likely]] { 23 | return version; 24 | } 25 | size_t size = 4; 26 | sysctlbyname("kern.sdk_version", &version, &size, nullptr, 0); 27 | return version; 28 | } 29 | 30 | namespace offsets { 31 | 32 | // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) 33 | 34 | size_t allproc() { 35 | static size_t allprocOffset; 36 | if (allprocOffset != 0) [[likely]] { 37 | return allprocOffset; 38 | } 39 | switch(getSystemSwVersion() & VERSION_MASK) { 40 | case V300: 41 | case V310: 42 | case V320: 43 | case V321: 44 | allprocOffset = 0x276DC58; 45 | break; 46 | case V400: 47 | case V402: 48 | case V403: 49 | case V450: 50 | case V451: 51 | allprocOffset = 0x27EDCB8; 52 | break; 53 | default: 54 | allprocOffset = -1; 55 | break; 56 | } 57 | return allprocOffset; 58 | } 59 | 60 | size_t security_flags() { 61 | switch(getSystemSwVersion() & VERSION_MASK) { 62 | case V300: 63 | case V310: 64 | case V320: 65 | case V321: 66 | return 0x6466474; 67 | case V400: 68 | return 0x6506474; 69 | case V402: 70 | case V403: 71 | case V450: 72 | case V451: 73 | return 0x6505474; 74 | default: 75 | return -1; 76 | } 77 | } 78 | 79 | size_t qa_flags() { 80 | switch(getSystemSwVersion() & VERSION_MASK) { 81 | case V300: 82 | case V310: 83 | case V320: 84 | case V321: 85 | return 0x6466498; 86 | case V400: 87 | case V402: 88 | case V403: 89 | case V450: 90 | case V451: 91 | return 0x6506498; 92 | default: 93 | return -1; 94 | } 95 | } 96 | 97 | size_t utoken_flags() { 98 | switch(getSystemSwVersion() & VERSION_MASK) { 99 | case V300: 100 | case V310: 101 | case V320: 102 | case V321: 103 | return 0x6466500; 104 | case V400: 105 | case V402: 106 | case V403: 107 | case V450: 108 | case V451: 109 | return 0x6506500; 110 | default: 111 | return -1; 112 | } 113 | } 114 | 115 | size_t root_vnode() { 116 | switch(getSystemSwVersion() & VERSION_MASK) { 117 | case V300: 118 | case V310: 119 | case V320: 120 | case V321: 121 | return 0x67AB4C0; 122 | case V400: 123 | case V402: 124 | case V403: 125 | case V450: 126 | case V451: 127 | return 0x66E74C0; 128 | default: 129 | return -1; 130 | } 131 | } 132 | 133 | // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) 134 | 135 | } // offsets 136 | -------------------------------------------------------------------------------- /libhijacker/source/print.cpp: -------------------------------------------------------------------------------- 1 | #include "print.hpp" 2 | 3 | size_t print_null(...) 4 | { 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /libhijacker/source/spawner.cpp: -------------------------------------------------------------------------------- 1 | #include "dbg/dbg.hpp" 2 | #include "hijacker.hpp" 3 | #include "hijacker/hijacker.hpp" 4 | #include "util.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | // this always seems to be the case 10 | static constexpr uintptr_t ENTRYPOINT_OFFSET = 0x70; 11 | static constexpr int SYSTEM_SERVICE_HANDLE = 38; 12 | 13 | struct LoopBuilder { 14 | static constexpr size_t LOOB_BUILDER_SIZE = 39; 15 | static constexpr size_t LOOP_BUILDER_TARGET_OFFSET = 2; 16 | uint8_t data[LOOB_BUILDER_SIZE]; 17 | 18 | void setTarget(uintptr_t addr) { 19 | *reinterpret_cast(data + LOOP_BUILDER_TARGET_OFFSET) = addr; 20 | } 21 | }; 22 | 23 | static inline constexpr LoopBuilder SLEEP_LOOP{ 24 | // // 48 b8 xx xx xx xx xx xx xx xx 48 c7 c7 40 42 0f 00 ff d0 eb eb 25 | //loop: 26 | // MOV RAX, _nanosleep 27 | 0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | // MOV RDI, 1000000000 // 1 second 29 | 0x48, 0xc7, 0xc7, 0x00, 0xca, 0x9a, 0x3b, 30 | // MOV RSI, 0 31 | 0x48, 0xc7, 0xc6, 0x00, 0x00, 0x00, 0x00, 32 | // PUSH RDI 33 | 0x57, 34 | // PUSH RSI 35 | 0x56, 36 | // CALL RAX 37 | 0xff, 0xd0, 38 | // JMP loop 39 | 0xeb, 0xe2 40 | }; 41 | 42 | namespace { 43 | // shellcode to hack into your ps5 and steal your nudes 44 | extern const uint8_t SHELLCODE[282]; 45 | } 46 | 47 | struct Args { 48 | struct Result { 49 | int32_t state; 50 | int32_t err; 51 | } result; 52 | uintptr_t sceSystemServiceGetAppStatus; 53 | uintptr_t sceSystemServiceAddLocalProcess; 54 | uintptr_t socketpair; 55 | uintptr_t usleep; 56 | uintptr_t errno; 57 | 58 | Args(Hijacker &hijacker) : 59 | result({0, 0}), socketpair(hijacker.getLibKernelFunctionAddress(nid::socketpair)), 60 | usleep(hijacker.getLibKernelFunctionAddress(nid::usleep)), errno(hijacker.getLibKernelFunctionAddress(nid::_errno)) { 61 | UniquePtr libSceSystemService = hijacker.getLib(SYSTEM_SERVICE_HANDLE); 62 | SharedLib *lib = libSceSystemService.get(); 63 | sceSystemServiceGetAppStatus = hijacker.getFunctionAddress(lib, nid::sceSystemServiceGetAppStatus); 64 | sceSystemServiceAddLocalProcess = hijacker.getFunctionAddress(lib, nid::sceSystemServiceAddLocalProcess); 65 | } 66 | }; 67 | 68 | static int32_t getResult(const Hijacker &hijacker, ProcessPointer &state, dbg::IdArray &pids, int pid) { 69 | 70 | // we are done when a new process has spawned or the hijacked process has died 71 | 72 | auto frame = hijacker.getTrapFrame(); 73 | if (frame == nullptr) { 74 | return -2; 75 | } 76 | 77 | int32_t res = *state; 78 | if (res != 0) { 79 | return res; 80 | } 81 | 82 | auto ids = dbg::getAllPids(); 83 | 84 | return ids.contains(pid) && ids.length() > pids.length(); 85 | } 86 | 87 | UniquePtr Spawner::spawn() { 88 | LoopBuilder loop = SLEEP_LOOP; 89 | 90 | const int lastPid = pids[0]; 91 | 92 | // this should never miss because the pid lock is currently 93 | // held by the kernel when creating the new process 94 | // if spawning fails it's because the redis server can only do this once 95 | // we'll end up obtaining the previously spawned one 96 | // may be a good idea to rename the process 97 | int id = dbg::getAllPids()[0]; 98 | 99 | if (id == lastPid) { 100 | // catastrophic failure 101 | puts("catastrophic failure\n"); 102 | printf("lastPid %d id %d\n", lastPid, id); 103 | return nullptr; 104 | } 105 | 106 | UniquePtr info = new dbg::ProcessInfo(id); 107 | 108 | printf("spawned %s\n", info->name().c_str()); 109 | // TODO: time from here until the stack pointer is set 110 | UniquePtr spawned = nullptr; 111 | while (spawned == nullptr) { 112 | spawned = Hijacker::getHijacker(id); 113 | usleep(10); // NOLINT(*) 114 | } 115 | 116 | uintptr_t base = 0; 117 | while (base == 0) { 118 | base = spawned->getLibKernelBase(); 119 | } 120 | 121 | loop.setTarget(base + nanosleepOffset); 122 | base = spawned->imagebase(); 123 | 124 | // force the entrypoint to an infinite loop so that it doesn't start until we're ready 125 | dbg::write(id, base + ENTRYPOINT_OFFSET, loop.data, sizeof(loop.data)); 126 | 127 | return spawned; 128 | } 129 | 130 | UniquePtr Spawner::bootstrap(Hijacker &hijacker) { 131 | 132 | uintptr_t entry = hijacker.textAllocator.allocate(sizeof(SHELLCODE)); 133 | hijacker.write(entry, SHELLCODE); 134 | 135 | uintptr_t argbuf = hijacker.dataAllocator.allocate(sizeof(Args)); 136 | Args args{hijacker}; 137 | dbg::write(pid, argbuf, &args, sizeof(args)); 138 | 139 | ProcessPointer pstate = {pid, argbuf}; 140 | 141 | ScopedSuspender suspender{&hijacker}; 142 | auto frame = hijacker.getTrapFrame(); 143 | if (frame == nullptr) { 144 | return nullptr; 145 | } 146 | 147 | UniquePtr backup{new TrapFrame(*frame.get())}; 148 | 149 | frame->setRdi(argbuf) 150 | .setRip(entry) 151 | .setRsp(frame->getRsp()) 152 | .flush(); 153 | 154 | // wait for the new process to spawn 155 | hijacker.resume(); 156 | int32_t state = 0; 157 | do { // NOLINT(cppcoreguidelines-avoid-do-while) 158 | state = getResult(hijacker, pstate, pids, pid); 159 | } while (state == 0); 160 | 161 | if (state != 1) [[unlikely]] { 162 | if (state == -2) { 163 | // process died 164 | return nullptr; 165 | } 166 | ProcessPointer pres{pid, argbuf}; 167 | Args::Result res = *pres; 168 | if (res.state == -1) { 169 | printf("spawn failed err: %s\n", strerror(res.err)); 170 | } else if (res.state > 1) { 171 | printf("spawn failed %lld\n", res.state); 172 | } else { 173 | printf("spawn failed state: %llx err: 0x%llx\n", (unsigned long long) res.state, (unsigned long long) res.err); 174 | } 175 | return nullptr; 176 | } 177 | 178 | hijacker.suspend(); 179 | hijacker.getTrapFrame()->setFrame(backup.get()) 180 | .flush(); 181 | 182 | return spawn(); 183 | } 184 | 185 | // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) 186 | 187 | namespace { 188 | 189 | // see shellcode/spawner.c for source 190 | const uint8_t SHELLCODE[]{ 191 | 0x53, 0x48, 0x83, 0xec, 0x60, 0x48, 0x89, 0xfb, 0x48, 0x8d, 0x7c, 0x24, 0x40, 0xff, 0x53, 0x08, 192 | 0x85, 0xc0, 0x0f, 0x88, 0xa2, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x4c, 0x24, 0x10, 0xbf, 0x01, 0x00, 193 | 0x00, 0x00, 0xbe, 0x01, 0x00, 0x00, 0x00, 0x31, 0xd2, 0xc7, 0x44, 0x24, 0x10, 0x00, 0x00, 0x00, 194 | 0x00, 0xc7, 0x44, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x44, 0x24, 0x18, 0x01, 0x00, 0x00, 195 | 0x00, 0xc7, 0x44, 0x24, 0x1c, 0xff, 0xff, 0xff, 0xff, 0x48, 0xc7, 0x44, 0x24, 0x20, 0x00, 0x00, 196 | 0x00, 0x00, 0x48, 0xc7, 0x44, 0x24, 0x28, 0x00, 0x00, 0x00, 0x00, 0xff, 0x53, 0x18, 0x83, 0xf8, 197 | 0xff, 0x74, 0x77, 0x48, 0xb8, 0x2f, 0x61, 0x70, 0x70, 0x30, 0x2f, 0x65, 0x62, 0x48, 0x8d, 0x74, 198 | 0x24, 0x30, 0x48, 0x8d, 0x54, 0x24, 0x08, 0x48, 0x8d, 0x4c, 0x24, 0x10, 0x48, 0x89, 0x44, 0x24, 199 | 0x30, 0x48, 0xb8, 0x6f, 0x6f, 0x74, 0x2e, 0x62, 0x69, 0x6e, 0x00, 0x48, 0x89, 0x44, 0x24, 0x38, 200 | 0x48, 0xc7, 0x44, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x7c, 0x24, 0x40, 0xff, 0x53, 0x10, 201 | 0x85, 0xc0, 0x78, 0x56, 0xc7, 0x03, 0x01, 0x00, 0x00, 0x00, 0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, 202 | 0xbf, 0x40, 0x42, 0x0f, 0x00, 0xff, 0x53, 0x20, 0xeb, 0xf6, 0xc7, 0x03, 0x02, 0x00, 0x00, 0x00, 203 | 0x89, 0x43, 0x04, 0x66, 0x66, 0x66, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 204 | 0xbf, 0x40, 0x42, 0x0f, 0x00, 0xff, 0x53, 0x20, 0xeb, 0xf6, 0xff, 0x53, 0x28, 0x8b, 0x00, 0xc7, 205 | 0x03, 0x03, 0x00, 0x00, 0x00, 0x89, 0x43, 0x04, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 206 | 0xbf, 0x40, 0x42, 0x0f, 0x00, 0xff, 0x53, 0x20, 0xeb, 0xf6, 0xc7, 0x03, 0x02, 0x00, 0x00, 0x00, 207 | 0x89, 0x43, 0x04, 0x66, 0x66, 0x66, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 208 | 0xbf, 0x40, 0x42, 0x0f, 0x00, 0xff, 0x53, 0x20, 0xeb, 0xf6 209 | }; 210 | 211 | } // anonymous namespace 212 | 213 | // NOLINTEND(cppcoreguidelines-avoid-magic-numbers) 214 | -------------------------------------------------------------------------------- /linker.x: -------------------------------------------------------------------------------- 1 | PHDRS { 2 | /* 3 | * PF_X = 0x1 4 | * PF_W = 0x2 5 | * PF_R = 0x4 6 | */ 7 | phdr_text PT_LOAD FLAGS(0x5); 8 | phdr_data PT_LOAD FLAGS(0x6); 9 | phdr_rodata PT_LOAD FLAGS(0x4); 10 | phdr_relro PT_LOAD FLAGS(0x4); 11 | phdr_eh_frame PT_GNU_EH_FRAME FLAGS(0x4); 12 | phdr_dynamic PT_DYNAMIC FLAGS(0x0); 13 | } 14 | 15 | SECTIONS { 16 | 17 | PROVIDE (__payload_start = .); 18 | 19 | .text : { 20 | PROVIDE_HIDDEN(__text_start = .); 21 | *(.text .text.*); 22 | PROVIDE_HIDDEN(__text_stop = .); 23 | } : phdr_text 24 | 25 | .init : { 26 | *(.init) 27 | } : phdr_text 28 | 29 | .fini : { 30 | *(.fini) 31 | } : phdr_text 32 | 33 | .plt : { 34 | *(.plt) 35 | } : phdr_text 36 | 37 | . = ALIGN(0x4000); /* move to a new page in memory */ 38 | 39 | .data : { 40 | *(.data); 41 | *(.data.*); 42 | } : phdr_data 43 | 44 | .bss (NOLOAD) : { 45 | PROVIDE_HIDDEN (__bss_start = .); 46 | *(.bss .bss.*); 47 | *(COMMON) 48 | PROVIDE_HIDDEN (__bss_end = .); 49 | } : phdr_data 50 | 51 | . = ALIGN(0x4000); /* move to a new page in memory */ 52 | 53 | .rodata : { 54 | *(.rodata .rodata.*); 55 | } : phdr_rodata 56 | 57 | .gcc_except_table : { 58 | *(.gcc_except_table*) 59 | } : phdr_rodata 60 | 61 | .hash : { 62 | *(.hash); 63 | } : phdr_rodata 64 | 65 | . = ALIGN(0x4000); /* move to a new page in memory */ 66 | 67 | .eh_frame_hdr : ALIGN(0x4000) { 68 | *(.eh_frame_hdr) 69 | } : phdr_eh_frame 70 | 71 | .eh_frame : ALIGN(0x10) { 72 | *(.eh_frame) 73 | } : phdr_eh_frame 74 | 75 | . = ALIGN(0x4000); /* move to a new page in memory */ 76 | 77 | .data.rel.ro : { 78 | *(.data.rel.ro .data.rel.ro.*); 79 | } : phdr_relro 80 | 81 | .preinit_array : { 82 | PROVIDE_HIDDEN (__preinit_array_start = .); 83 | KEEP (*(.preinit_array*)) 84 | PROVIDE_HIDDEN (__preinit_array_end = .); 85 | } : phdr_relro 86 | 87 | .init_array : { 88 | PROVIDE_HIDDEN(__init_array_start = .); 89 | KEEP (*(.init_array .init_array.*)); 90 | PROVIDE_HIDDEN(__init_array_stop = .); 91 | } : phdr_relro 92 | 93 | .fini_array : { 94 | PROVIDE_HIDDEN(__fini_array_start = .); 95 | KEEP (*(.fini_array .fini_array.*)); 96 | PROVIDE_HIDDEN(__fini_array_stop = .); 97 | } : phdr_relro 98 | 99 | .got : { 100 | *(.got); 101 | } : phdr_relro 102 | 103 | .got.plt : { 104 | *(.got.plt); 105 | } : phdr_relro 106 | 107 | .rela.dyn : { 108 | *(.rela.dyn) *(.rela); 109 | } : phdr_relro 110 | 111 | .rela.plt : { 112 | *(rela.plt); 113 | } : phdr_relro 114 | 115 | PROVIDE (__payload_end = .); 116 | 117 | /* this needs to be forced aligned to 0x4000 */ 118 | .dynamic : ALIGN(0x4000) { 119 | PROVIDE_HIDDEN (_DYNAMIC = .); 120 | *(.dynamic); 121 | } : phdr_dynamic 122 | 123 | .dynsym : { 124 | *(.dynsym); 125 | } : phdr_dynamic 126 | 127 | .dynstr : { 128 | *(.dynstr); 129 | } : phdr_dynamic 130 | } 131 | -------------------------------------------------------------------------------- /shellcode/build_cmd.txt: -------------------------------------------------------------------------------- 1 | clang --target=x86_64-freebsd-pc-elf -fPIC -fPIE -fomit-frame-pointer -Wall -Werror -gfull -gdwarf-2 -O3 -march=znver2 -mavx2 -c 2 | -------------------------------------------------------------------------------- /shellcode/format_shellcode.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | FSTRING = ' 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x}, 0x{:02x},' 5 | 6 | def iter_rows(data: bytes): 7 | length = len(data) 8 | rows = length // 16 9 | for i in range(rows): 10 | pos = i*16 11 | yield FSTRING.format(*data[pos:pos+16]) 12 | 13 | 14 | def get_rem(data: bytes): 15 | length = len(data) 16 | rem = length % 16 17 | 18 | if rem == 0: 19 | return None 20 | 21 | end = length - rem 22 | res = [] 23 | for i in range(rem): 24 | res.append(f'0x{data[end+i]:02x}') 25 | return ' ' + ', '.join(res) 26 | 27 | 28 | def print_shellcode(shellcode: str): 29 | data = bytes.fromhex(shellcode) 30 | print(f'length: {len(data)}') 31 | print('{') 32 | for row in iter_rows(data): 33 | print(row) 34 | rem = get_rem(data) 35 | if rem: 36 | print(rem) 37 | print('};') 38 | 39 | 40 | if __name__ == '__main__': 41 | if len(sys.argv) == 1: 42 | print(f'usage: {__file__} shellcode bytes...') 43 | else: 44 | print_shellcode(''.join(sys.argv[1:])) 45 | -------------------------------------------------------------------------------- /shellcode/rfork_thread.c: -------------------------------------------------------------------------------- 1 | #define TITLEID_LENGTH 10 2 | #define BREW 0x57455242 3 | #define F_OK 0 4 | #define NULL 0 5 | 6 | // AF_UNIX address family 7 | #define AF_UNIX 1 8 | 9 | // Maximum length of the sun_path field 10 | #define UNIX_PATH_MAX 104 11 | 12 | #define SIN_ZERO_SIZE 8 13 | 14 | #define PING 0 15 | #define PONG 1 16 | #define PROCESS_LAUNCHED 1 17 | #define MSG_NOSIGNAL 0x20000 /* do not generate SIGPIPE on EOF */ 18 | 19 | // NOLINTBEGIN(*) 20 | 21 | typedef struct { 22 | const char *arg0; // /app0/eboot.bin 23 | const char *path; // /system_ex/app/BREW00000/eboot.bin 24 | const char *sandboxPath; // /mnt/sandbox/BREW00000_000 25 | // remaining unknown 26 | } procSpawnArgs; 27 | 28 | typedef int (*func_t)(void*); 29 | typedef int (*rfork_thread_t)(int flags, void *stack, func_t func, void *arg); 30 | 31 | struct sockaddr_un { 32 | unsigned char sun_len; 33 | unsigned char sun_family; // AF_UNIX 34 | char sun_path[104]; // Path name 35 | }; 36 | 37 | typedef struct { 38 | int sock; 39 | const func_t inf_loop; // haha open prospero go brrrrrrr 40 | func_t func; 41 | int (*socket)(int domain, int type, int protocol); 42 | int (*close)(int fd); 43 | int (*connect)(int s, void *name, unsigned int namelen); 44 | long (*send)(int sockfd, const void *buf, int len, int flags); 45 | long (*recv)(int sockfd, void * buf, int len, int flags); 46 | int (*access)(const char *path, int flags); 47 | } ExtraStuff; 48 | 49 | struct result { 50 | int cmd; 51 | int pid; 52 | func_t func; 53 | }; 54 | 55 | // INFINITE_LOOP: 0xeb 0xfe 56 | 57 | // insert a "MOV rfork_thread, R8" at the beginning of the hook shellcode 58 | // insert a "MOV &stuff, R9" at the beginning of the hook shellcode 59 | /* 60 | 49 b8 xx xx xx xx xx xx xx xx 61 | 49 b9 xx xx xx xx xx xx xx xx 62 | */ 63 | 64 | #define SOCK_STREAM 1 65 | #define SERVER_SIZE 15 + 2 66 | #define IPC_PATH_LENGTH 16 67 | 68 | 69 | static inline int __attribute__((always_inline)) reconnect(const char *restrict path, ExtraStuff *restrict stuff) { 70 | volatile struct sockaddr_un server; 71 | stuff->sock = stuff->socket(AF_UNIX, SOCK_STREAM, 0); 72 | if (stuff->sock == -1) { 73 | return -1; 74 | } 75 | 76 | server.sun_len = 0; 77 | server.sun_family = AF_UNIX; 78 | __builtin_memcpy((char*)server.sun_path, path, IPC_PATH_LENGTH); 79 | 80 | if (stuff->connect(stuff->sock, (void*)&server, SERVER_SIZE) == -1){ 81 | stuff->close(stuff->sock); 82 | stuff->sock = -1; 83 | return -1; 84 | } 85 | return 0; 86 | } 87 | 88 | 89 | static inline int __attribute__((always_inline)) exit_fail(int flags, void *stack, func_t func, procSpawnArgs *restrict arg, rfork_thread_t orig, ExtraStuff *restrict stuff) { 90 | if (stuff->sock != -1) { 91 | stuff->close(stuff->sock); 92 | } 93 | stuff->sock = -1; 94 | return orig(flags, stack, func, arg); 95 | } 96 | 97 | typedef union { 98 | const char *str; 99 | volatile unsigned char *u8; 100 | volatile unsigned short *u16; 101 | volatile unsigned int *u32; 102 | volatile unsigned long *u64; 103 | } string_pointer_t; 104 | 105 | #define HOMEBREW_DAEMON_PREFIX_LENGTH 24 106 | 107 | static inline int __attribute__((always_inline)) isEqual(const char *restrict src, const char *restrict dst) { 108 | return __builtin_memcmp(src, dst, HOMEBREW_DAEMON_PREFIX_LENGTH) == 0; 109 | } 110 | 111 | static inline int __attribute__((always_inline)) isHomebrewDaemon(const char *path) { 112 | // /system_ex/app/BREW00000 113 | volatile unsigned long src[3]; 114 | src[0] = 0x5F6D65747379732F; 115 | src[1] = 0x422F7070612F7865; 116 | src[2] = 0x3030303030574552; 117 | return isEqual((char*)src, path); 118 | } 119 | 120 | // /mnt/sandbox/xxxxyyyyy_000/app0/homebrew.elf 121 | #define SANDBOX_PATH_LENGTH 26 122 | #define HOMEBREW_PATH_LENGTH 48 123 | 124 | static inline char *restrict __attribute__((always_inline)) copySandboxPath(char *restrict dst, const char *restrict src) { 125 | return __builtin_memcpy(dst, src, SANDBOX_PATH_LENGTH) + SANDBOX_PATH_LENGTH; 126 | } 127 | 128 | static inline int __attribute__((always_inline)) isHomebrew(ExtraStuff *restrict stuff, procSpawnArgs *restrict arg) { 129 | if (arg == NULL || arg->path == NULL || arg->sandboxPath == NULL) { 130 | // some safety checks 131 | return 0; 132 | } 133 | 134 | if (isHomebrewDaemon(arg->path) == 1) { 135 | // the homebrew.elf file won't be present for the daemon so we'll explicitly check 136 | return 1; 137 | } 138 | 139 | char path[HOMEBREW_PATH_LENGTH]; 140 | volatile unsigned long *dst = (unsigned long *) copySandboxPath(path, arg->sandboxPath); 141 | dst[0] = 0x6F682F307070612F; 142 | dst[1] = 0x652E77657262656D; 143 | dst[2] = 0x666C; 144 | return stuff->access(path, F_OK) == 0; 145 | } 146 | 147 | static int __attribute__((used)) rfork_thread_hook(int flags, void *stack, func_t func, procSpawnArgs *restrict arg, rfork_thread_t orig, ExtraStuff *restrict stuff) { 148 | if (!isHomebrew(stuff, arg)) { 149 | return orig(flags, stack, func, arg); 150 | } 151 | 152 | volatile unsigned long ipc[2]; 153 | ipc[0] = 0x5f6d65747379732f; 154 | // tmp/IPC 155 | ipc[1] = 0x004350492f706d74; // Little-endian 156 | 157 | if (stuff->sock == -1) { 158 | if (reconnect((char*)ipc, stuff) == -1) { 159 | return orig(flags, stack, func, arg); 160 | } 161 | } else { 162 | volatile struct result res; 163 | res.cmd = PING; 164 | res.func = 0; 165 | res.pid = 0; 166 | if (stuff->send(stuff->sock, (void*)&res, sizeof(res), MSG_NOSIGNAL) == -1) { 167 | if (reconnect((char*)ipc, stuff) == -1) { 168 | return exit_fail(flags, stack, func, arg, orig, stuff); 169 | } 170 | } else { 171 | int reply = 0; 172 | if (stuff->recv(stuff->sock, &reply, sizeof(reply), MSG_NOSIGNAL) == -1) { 173 | return exit_fail(flags, stack, func, arg, orig, stuff); 174 | } 175 | if (reply != PONG) { 176 | return exit_fail(flags, stack, func, arg, orig, stuff); 177 | } 178 | } 179 | } 180 | 181 | const int pid = orig(flags, stack, stuff->inf_loop, arg); 182 | struct result res = { 183 | .cmd = PROCESS_LAUNCHED, 184 | .pid = pid, 185 | .func = func 186 | }; 187 | 188 | // we must always write a response so the daemon doesn't get stuck 189 | stuff->send(stuff->sock, (void *)&res, sizeof(res), MSG_NOSIGNAL); 190 | if (pid == -1) { 191 | return exit_fail(flags, stack, func, arg, orig, stuff); 192 | } 193 | 194 | return pid; 195 | } 196 | 197 | // NOLINTEND(*) 198 | -------------------------------------------------------------------------------- /spawner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################################### 2 | # PS5SDK - Example: pipe pirate 3 | # Uses the read/write primitive to read and write some kernel data. 4 | # @authors ChendoChap, Specter, Znullptr 5 | ################################################################################################### 6 | 7 | cmake_minimum_required (VERSION 3.20) 8 | 9 | set(basename "spawner") 10 | project(${basename} C CXX ASM) 11 | 12 | # Language Standard Defaults 13 | set(CMAKE_C_STANDARD 11) 14 | set(CMAKE_C_STANDARD_REQUIRED ON) 15 | 16 | set(CMAKE_CXX_EXTENSIONS ON) 17 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 18 | 19 | if ("${CMAKE_CXX_STANDARD}" STREQUAL "") 20 | set(CMAKE_CXX_STANDARD 20) 21 | endif() 22 | 23 | # Check for sub-project as part of main build or external build 24 | if (NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 25 | set(IS_SUBPROJECT TRUE) 26 | else() 27 | set(IS_SUBPROJECT FALSE) 28 | endif() 29 | 30 | message("IS_SUBPROJECT: ${IS_SUBPROJECT}") 31 | 32 | set(D_CWD "${PROJECT_SOURCE_DIR}") 33 | 34 | # Headers 35 | include_directories (SYSTEM "${D_PS5SDK}") 36 | include_directories (SYSTEM "${D_PS5SDK}/include") 37 | 38 | add_executable(${PROJECT_NAME}) 39 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}.elf") 40 | 41 | # Must build with clang 42 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc]lang") 43 | set(IS_CLANG 1) 44 | else() 45 | message(FATAL_ERROR "${PROJECT_NAME} is meant to be built with clang! CompilerID: ${CMAKE_CXX_COMPILER_ID}") 46 | endif() 47 | 48 | # Finalize main target sources 49 | target_compile_options(${PROJECT_NAME} PUBLIC 50 | $<$:${C_DEFS} ${C_FLAGS}> 51 | $<$:${CXX_DEFS} ${CXX_FLAGS}> 52 | $<$:${ASM_FLAGS}> 53 | ) 54 | 55 | message("========== build: ${PROJECT_NAME} ==========") 56 | 57 | set(D_SRC ${D_CWD}/source) 58 | 59 | file(GLOB SrcFiles ${D_SRC}/*.c ${D_SRC}/*.cpp ${D_SRC}/**/*.cpp ${D_SRC}/*.h ${D_SRC}/*.hpp ${D_SRC}/**/*.hpp ${D_SRC}/*.s) 60 | 61 | set(CMAKE_C_FLAGS "--target=x86_64-freebsd-pc-elf -O0 -march=znver2 -mavx2 -DPPR -DPS5 -DPS5_FW_VERSION=${V_FW} ") 62 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200112 -D__BSD_VISIBLE=1 -D__XSI_VISIBLE=500") 63 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-builtin -nostdlib -Wall") # -nostartfiles 64 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") 65 | set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -DDEBUG -gfull -gdwarf-2 -O0 -march=znver2 -mavx2 -Wall -Wextra -Wmove -Wmost -Werror -pedantic -pedantic-errors -fno-exceptions -Wno-unused-function") 66 | set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -Wno-unused-command-line-argument") 67 | 68 | target_sources(${PROJECT_NAME} PRIVATE ${SrcFiles}) 69 | target_include_directories(${PROJECT_NAME} PRIVATE "${D_CWD}/../include") 70 | target_link_directories (${PROJECT_NAME} PUBLIC "${D_PS5SDK}/lib" "${PROJECT_ROOT}/lib") 71 | target_link_libraries (${PROJECT_NAME} PUBLIC hijacker) 72 | add_dependencies(${PROJECT_NAME} daemon_shim) 73 | add_dependencies(${PROJECT_NAME} daemon) 74 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 75 | set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=lld -Xlinker -pie -Xlinker -zmax-page-size=16384 -Xlinker -zcommon-page-size=16384 -Xlinker -T ${D_CWD}/linker.x -Wl,--build-id=none -Wl,-z,norelro") 76 | -------------------------------------------------------------------------------- /spawner/linker.x: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf64-x86-64") 2 | OUTPUT_ARCH(i386:x86-64) 3 | 4 | ENTRY(_start) 5 | 6 | /* this linker scrip is only for spawner.elf */ 7 | 8 | PHDRS 9 | { 10 | /* 11 | * PF_X = 0x1 12 | * PF_W = 0x2 13 | * PF_R = 0x4 14 | */ 15 | 16 | ph_text PT_LOAD FLAGS (0x1); 17 | ph_data PT_LOAD FLAGS (0x2 | 0x4); 18 | } 19 | 20 | SECTIONS 21 | { 22 | __payload_base = .; 23 | .text : ALIGN(0x4000) 24 | { 25 | PROVIDE_HIDDEN(__text_start = .); 26 | KEEP (*(.init)) 27 | KEEP (*(.fini)) 28 | 29 | *(.text .text.*) 30 | 31 | PROVIDE_HIDDEN(__text_stop = .); 32 | } : ph_text = 0x90909090 33 | 34 | .rodata : ALIGN(0x4000) 35 | { 36 | *(.rodata .rodata.*) 37 | } : ph_data 38 | 39 | .data.rel.ro : ALIGN(0x4000) 40 | { 41 | *(.data.rel.ro .data.rel.ro.*) 42 | } 43 | 44 | .rela : ALIGN(0x10) 45 | { 46 | PROVIDE_HIDDEN(__rela_start = .); 47 | *(.rela *.rela.*) 48 | PROVIDE_HIDDEN(__rela_stop = .); 49 | } 50 | 51 | .data : 52 | { 53 | *(.data .data.*) 54 | 55 | . = ALIGN(0x10); 56 | 57 | __imports_start = .; 58 | KEEP(*(.imports .imports.*)) 59 | __imports_end = .; 60 | 61 | __patches_start = .; 62 | KEEP(*(.patches .patches.*)) 63 | QUAD(0); BYTE(0); BYTE(0); 64 | __patches_end = .; 65 | 66 | __bss_start = .; 67 | *(.bss .bss.*) *(COMMON) 68 | __bss_end = .; 69 | 70 | . = . + 4; 71 | . = ALIGN(4); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /spawner/source/app.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "util.hpp" 12 | 13 | static constexpr auto json = R"({ 14 | "applicationCategoryType": 33554432, 15 | "localizedParameters": { 16 | "defaultLanguage": "en-US", 17 | "en-US": { 18 | "titleName": "HomebrewDaemon" 19 | } 20 | }, 21 | "titleId": "BREW00000" 22 | } 23 | )"_sv; 24 | 25 | struct NonStupidIovec { 26 | const void *iov_base; 27 | size_t iov_length; 28 | 29 | constexpr NonStupidIovec(const char *str) : iov_base(str), iov_length(__builtin_strlen(str)+1) {} 30 | constexpr NonStupidIovec(const char *str, size_t length) : iov_base(str), iov_length(length) {} 31 | }; 32 | 33 | constexpr NonStupidIovec operator"" _iov(const char *str, unsigned long len) { return {str, len+1}; } 34 | 35 | static bool remount(const char *dev, const char *path) { 36 | NonStupidIovec iov[]{ 37 | "fstype"_iov, "exfatfs"_iov, 38 | "fspath"_iov, {path}, 39 | "from"_iov, {dev}, 40 | "large"_iov, "yes"_iov, 41 | "timezone"_iov, "static"_iov, 42 | "async"_iov, {nullptr, 0}, 43 | "ignoreacl"_iov, {nullptr, 0} 44 | }; 45 | constexpr size_t iovlen = sizeof(iov) / sizeof(iov[0]); 46 | return nmount(reinterpret_cast(iov), iovlen, MNT_UPDATE) == 0; 47 | } 48 | 49 | static constexpr int STUPID_C_ERROR = -1; 50 | static constexpr int MKDIR_FLAGS = 0666; 51 | 52 | // NOLINTBEGIN(cppcoreguidelines-owning-memory) 53 | 54 | static bool copyfile(const char *from, const char *to) { 55 | struct stat st{}; 56 | if (stat(from, &st) == STUPID_C_ERROR) { 57 | puts(strerror(errno)); 58 | return false; 59 | } 60 | UniquePtr buf = new uint8_t[st.st_size]; 61 | FILE *fp = fopen(from, "rb"); 62 | if (fp == nullptr) { 63 | puts("open failed"); 64 | puts(strerror(errno)); 65 | return false; 66 | } 67 | fread(buf.get(), 1, st.st_size, fp); 68 | fclose(fp); 69 | fp = fopen(to, "wb+"); 70 | if (fp == nullptr) { 71 | puts("open failed"); 72 | puts(strerror(errno)); 73 | return false; 74 | } 75 | fwrite(buf.get(), 1, st.st_size, fp); 76 | fclose(fp); 77 | return true; 78 | } 79 | 80 | static bool mkdir(const char *path) { 81 | if (::mkdir(path, MKDIR_FLAGS) == STUPID_C_ERROR) { 82 | const int err = errno; 83 | if (err != EEXIST) { 84 | puts(strerror(errno)); 85 | return false; 86 | } 87 | } 88 | return true; 89 | } 90 | 91 | bool makeHomebrewApp() { 92 | // REDIS -> NPXS40028 93 | if (!remount("/dev/ssd0.system_ex", "/system_ex")) { 94 | perror("makenewapp remount"); 95 | return false; 96 | } 97 | if (mkdir("/system_ex/app/BREW00000", MKDIR_FLAGS) == STUPID_C_ERROR) { 98 | const int err = errno; 99 | if (err != EEXIST) { 100 | perror("makenewapp mkdir /system_ex/app/BREW00000"); 101 | return false; 102 | } 103 | puts("BREW00000 already exists, assuming proper installation"); 104 | return true; 105 | } 106 | if (!copyfile("/system_ex/app/NPXS40028/eboot.bin", "/system_ex/app/BREW00000/eboot.bin")) { 107 | puts("failed to copy redis eboot.bin"); 108 | return false; 109 | } 110 | if (!mkdir("/system_ex/app/BREW00000/sce_sys")) { 111 | return false; 112 | } 113 | FILE *fp = fopen("/system_ex/app/BREW00000/sce_sys/param.json", "w+"); 114 | if (fp == nullptr) { 115 | puts("open failed"); 116 | puts(strerror(errno)); 117 | return false; 118 | } 119 | fwrite(json.c_str(), 1, json.length(), fp); 120 | fclose(fp); 121 | return true; 122 | } 123 | 124 | // NOLINTEND(cppcoreguidelines-owning-memory) 125 | -------------------------------------------------------------------------------- /spawner/source/daemon.c: -------------------------------------------------------------------------------- 1 | #include "build.h" 2 | 3 | #ifdef STANDALONE 4 | #define BUILD_EXE "daemon.elf" 5 | #pragma message("Including daemon.elf") 6 | #else 7 | #define BUILD_EXE "daemon_shim.elf" 8 | #pragma message("Including daemon_shim.elf") 9 | #endif 10 | 11 | __asm__( 12 | ".intel_syntax noprefix\n" 13 | ".section .data\n" 14 | ".global daemon_start\n" 15 | ".type daemon_start, @object\n" 16 | ".align 4\n" 17 | "daemon_start:\n" 18 | ".incbin \"../bin/" BUILD_EXE "\"\n" 19 | "daemon_end:\n" 20 | ".global daemon_size\n" 21 | ".type daemon_size, @object\n" 22 | ".align 4\n" 23 | "daemon_size:\n" 24 | ".int daemon_end - daemon_start\n" 25 | ); 26 | -------------------------------------------------------------------------------- /spawner/source/kernel_helpers.c: -------------------------------------------------------------------------------- 1 | /***************************************************** 2 | * PS5 SDK - Kernel helpers 3 | * Implements kernel hacking API for doing kernel read/ 4 | * write. 5 | ****************************************************/ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | // NOLINTBEGIN(*) 14 | 15 | uintptr_t kernel_base; 16 | 17 | // Store necessary sockets/pipe for corruption. 18 | int _master_sock; 19 | int _victim_sock; 20 | int _rw_pipe[2]; 21 | uint64_t _pipe_addr; 22 | 23 | extern size_t _write(int fd, const void *buf, size_t nbyte); 24 | extern size_t _read(int fd, void *buf, size_t nbyte); 25 | 26 | // Arguments passed by way of entrypoint arguments. 27 | void kernel_init_rw(int master_sock, int victim_sock, int *rw_pipe, uint64_t pipe_addr) 28 | { 29 | _master_sock = master_sock; 30 | _victim_sock = victim_sock; 31 | _rw_pipe[0] = rw_pipe[0]; 32 | _rw_pipe[1] = rw_pipe[1]; 33 | _pipe_addr = pipe_addr; 34 | } 35 | 36 | // Internal kwrite function - not friendly, only for setting up better primitives. 37 | void kwrite(uint64_t addr, uint64_t *data) { 38 | uint64_t victim_buf[3]; 39 | 40 | victim_buf[0] = addr; 41 | victim_buf[1] = 0; 42 | victim_buf[2] = 0; 43 | 44 | setsockopt(_master_sock, IPPROTO_IPV6, IPV6_PKTINFO, victim_buf, 0x14); 45 | setsockopt(_victim_sock, IPPROTO_IPV6, IPV6_PKTINFO, data, 0x14); 46 | } 47 | 48 | // Public API function to write kernel data. 49 | void kernel_copyin(void *src, uint64_t kdest, size_t length) 50 | { 51 | uint64_t write_buf[3]; 52 | 53 | // Set pipe flags 54 | write_buf[0] = 0; 55 | write_buf[1] = 0x4000000000000000; 56 | write_buf[2] = 0; 57 | kwrite(_pipe_addr, (uint64_t *) &write_buf); 58 | 59 | // Set pipe data addr 60 | write_buf[0] = kdest; 61 | write_buf[1] = 0; 62 | write_buf[2] = 0; 63 | kwrite(_pipe_addr + 0x10, (uint64_t *) &write_buf); 64 | 65 | // Perform write across pipe 66 | _write(_rw_pipe[1], src, length); 67 | } 68 | 69 | // Public API function to read kernel data. 70 | void kernel_copyout(uint64_t ksrc, void *dest, size_t length) 71 | { 72 | uint64_t write_buf[3]; 73 | 74 | // Set pipe flags 75 | write_buf[0] = 0x4000000040000000; 76 | write_buf[1] = 0x4000000000000000; 77 | write_buf[2] = 0; 78 | kwrite(_pipe_addr, (uint64_t *) &write_buf); 79 | 80 | // Set pipe data addr 81 | write_buf[0] = ksrc; 82 | write_buf[1] = 0; 83 | write_buf[2] = 0; 84 | kwrite(_pipe_addr + 0x10, (uint64_t *) &write_buf); 85 | 86 | // Perform read across pipe 87 | _read(_rw_pipe[0], dest, length); 88 | } 89 | 90 | // NOLINTEND(*) 91 | -------------------------------------------------------------------------------- /spawner/source/start.c: -------------------------------------------------------------------------------- 1 | // Required header 2 | 3 | #ifndef _MMAP_DECLARED 4 | #define _MMAP_DECLARED 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // NOLINTBEGIN(*) 13 | 14 | #define libkernel 0x2001 15 | #define nullptr 0 16 | 17 | extern uintptr_t kernel_base; 18 | void *f_get_authinfo = nullptr; 19 | uintptr_t syscall_addr = 0; 20 | 21 | extern int main(int argc, const char **argv); 22 | 23 | static __attribute__ ((used)) void *f_sceKernelDlsym = nullptr; 24 | int __attribute__ ((naked)) sceKernelDlsym(int lib, const char *name, void **fun) { 25 | __asm__ volatile("jmp *f_sceKernelDlsym(%rip)"); 26 | } 27 | 28 | static __attribute__ ((used)) void *f_sceKernelLoadStartModule = nullptr; 29 | int __attribute__ ((naked)) sceKernelLoadStartModule(const char *name, size_t argc, const void *argv, uint32_t flags, void *unknown, int *result) { 30 | __asm__ volatile("jmp *f_sceKernelLoadStartModule(%rip)"); 31 | } 32 | 33 | #define STUB(fname) \ 34 | static __attribute__ ((used)) void *f_##fname = nullptr;\ 35 | int __attribute__ ((naked)) fname() {\ 36 | __asm__ volatile("jmp *f_"#fname"(%rip)");\ 37 | } 38 | 39 | // int sceKernelLoadStartModule(char *name, size_t argc, void *argv, uint32_t flags, void *unknown, int *result) 40 | 41 | static __attribute__ ((used)) void *f_usleep = nullptr; 42 | int __attribute__ ((naked)) usleep(unsigned int useconds) { 43 | __asm__ volatile("jmp *f_usleep(%rip)"); 44 | } 45 | 46 | static __attribute__ ((used)) void *f_close = nullptr; 47 | int __attribute__ ((naked)) close(int fd) { 48 | __asm__ volatile("jmp *f_close(%rip)"); 49 | } 50 | 51 | static __attribute__ ((used)) void *f_puts = nullptr; 52 | int __attribute__ ((naked)) puts(const char *msg) { 53 | __asm__ volatile("jmp *f_puts(%rip)"); 54 | } 55 | 56 | static __attribute__ ((used)) void *f_free = nullptr; 57 | void __attribute__ ((naked)) free(void *msg) { 58 | __asm__ volatile("jmp *f_free(%rip)"); 59 | } 60 | 61 | static __attribute__ ((used)) void *f_malloc = nullptr; 62 | void *__attribute__ ((naked)) malloc(size_t len) { 63 | __asm__ volatile("jmp *f_malloc(%rip)"); 64 | } 65 | 66 | static __attribute__ ((used)) void *f_dup = nullptr; 67 | int __attribute__ ((naked)) dup(int oldd) { 68 | __asm__ volatile("jmp *f_dup(%rip)"); 69 | } 70 | 71 | static __attribute__ ((used)) void *f_dup2 = nullptr; 72 | int __attribute__ ((naked)) dup2(int oldd, int newd) { 73 | __asm__ volatile("jmp *f_dup2(%rip)"); 74 | } 75 | 76 | static __attribute__ ((used)) void *f_kill = nullptr; 77 | int __attribute__ ((naked)) kill(__pid_t pid, int n) { 78 | __asm__ volatile("jmp *f_kill(%rip)"); 79 | } 80 | 81 | int __attribute__ ((naked, noinline)) mdbg_call() { 82 | __asm__ volatile( 83 | "mov $573, %rax\n" 84 | "jmp *syscall_addr(%rip)\n" 85 | ); 86 | } 87 | 88 | int __attribute__ ((naked, noinline)) ptrace() { 89 | __asm__ volatile( 90 | "mov $26, %rax\n" 91 | "jmp *syscall_addr(%rip)\n" 92 | ); 93 | } 94 | 95 | int __attribute__ ((naked, noinline)) nmount() { 96 | __asm__ volatile( 97 | "mov $378, %rax\n" 98 | "jmp *syscall_addr(%rip)\n" 99 | ); 100 | } 101 | 102 | static __attribute__ ((used)) void *f_unlink = nullptr; 103 | int __attribute__ ((naked)) unlink(const char *path) { 104 | __asm__ volatile("jmp *f_unlink(%rip)"); 105 | } 106 | 107 | STUB(sceUserServiceGetForegroundUser) 108 | STUB(getpid) 109 | STUB(getppid) 110 | STUB(memset) 111 | STUB(putchar) 112 | STUB(memcpy) 113 | STUB(memcmp) 114 | STUB(strcmp) 115 | STUB(socket) 116 | STUB(bind) 117 | STUB(listen) 118 | STUB(accept) 119 | STUB(setsockopt) 120 | STUB(_write) 121 | STUB(_read) 122 | STUB(open) 123 | STUB(mkdir) 124 | STUB(stat) 125 | STUB(printf) 126 | STUB(_ZdaPv) 127 | STUB(_Znam) 128 | STUB(_Znwm) 129 | STUB(_ZdlPv) 130 | STUB(strstr) 131 | STUB(strlen) 132 | STUB(strnlen) 133 | STUB(sysctlbyname) 134 | STUB(strncpy) 135 | STUB(strncmp) 136 | STUB(__error) 137 | STUB(strerror) 138 | STUB(vsnprintf) 139 | STUB(sceKernelPrintBacktraceWithModuleInfo) 140 | STUB(sceKernelSendNotificationRequest) 141 | STUB(waitpid) 142 | STUB(perror) 143 | STUB(pthread_create) 144 | STUB(pthread_join) 145 | 146 | STUB(sceSysmoduleLoadModuleInternal) 147 | 148 | STUB(fopen) 149 | STUB(fwrite) 150 | STUB(fclose) 151 | STUB(fread) 152 | 153 | // these are unused 154 | STUB(sceSysmoduleLoadModuleByNameInternal) 155 | STUB(mmap) 156 | STUB(munmap) 157 | STUB(sceKernelJitCreateSharedMemory) 158 | 159 | #define LINK(lib, fname) sceKernelDlsym(lib, #fname, &f_##fname) 160 | #define LIBKERNEL_LINK(fname) LINK(libkernel, fname) 161 | #define LIBC_LINK(fname) LINK(libc, fname) 162 | 163 | #define STDOUT 1 164 | #define STDERR 2 165 | #define SYSCALL_OFFSET 7 166 | 167 | void _start(struct payload_args *args) { 168 | 169 | f_sceKernelDlsym = (void*)args->dlsym; 170 | LIBKERNEL_LINK(sceKernelLoadStartModule); 171 | int libc = sceKernelLoadStartModule("libSceLibcInternal.sprx", 0, 0, 0, 0, 0); 172 | LIBC_LINK(puts); 173 | LIBKERNEL_LINK(dup); 174 | LIBKERNEL_LINK(dup2); 175 | LIBKERNEL_LINK(socket); 176 | LIBKERNEL_LINK(setsockopt); 177 | LIBKERNEL_LINK(bind); 178 | LIBKERNEL_LINK(listen); 179 | LIBKERNEL_LINK(accept); 180 | LIBKERNEL_LINK(usleep); 181 | LIBKERNEL_LINK(getpid); 182 | LIBKERNEL_LINK(getppid); 183 | LIBKERNEL_LINK(get_authinfo); 184 | syscall_addr = (uintptr_t)f_get_authinfo + SYSCALL_OFFSET; 185 | 186 | LIBKERNEL_LINK(_write); 187 | LIBKERNEL_LINK(_read); 188 | LIBKERNEL_LINK(open); 189 | LIBKERNEL_LINK(close); 190 | LIBKERNEL_LINK(mkdir); 191 | LIBKERNEL_LINK(stat); 192 | LIBKERNEL_LINK(unlink); 193 | LIBKERNEL_LINK(sysctlbyname); 194 | LIBKERNEL_LINK(__error); 195 | LIBKERNEL_LINK(sceKernelPrintBacktraceWithModuleInfo); 196 | LIBKERNEL_LINK(waitpid); 197 | LIBKERNEL_LINK(pthread_create); 198 | LIBKERNEL_LINK(pthread_join); 199 | LIBKERNEL_LINK(kill); 200 | LIBKERNEL_LINK(sceKernelSendNotificationRequest); 201 | 202 | 203 | LIBC_LINK(_Znwm); 204 | LIBC_LINK(_Znam); 205 | LIBC_LINK(_ZdlPv); 206 | LIBC_LINK(_ZdaPv); 207 | LIBC_LINK(memset); 208 | LIBC_LINK(putchar); 209 | LIBC_LINK(malloc); 210 | LIBC_LINK(free); 211 | LIBC_LINK(memcpy); 212 | LIBC_LINK(memcmp); 213 | LIBC_LINK(strcmp); 214 | LIBC_LINK(printf); 215 | LIBC_LINK(perror); 216 | 217 | LIBC_LINK(strstr); 218 | LIBC_LINK(strlen); 219 | LIBC_LINK(strnlen); 220 | LIBC_LINK(strncpy); 221 | LIBC_LINK(strncmp); 222 | LIBC_LINK(strerror); 223 | LIBC_LINK(vsnprintf); 224 | 225 | LIBC_LINK(fopen); 226 | LIBC_LINK(fwrite); 227 | LIBC_LINK(fclose); 228 | LIBC_LINK(fread); 229 | 230 | int libSceSysmodule = sceKernelLoadStartModule("libSceSysmodule.sprx", 0, 0, 0, 0, 0); 231 | 232 | LINK(libSceSysmodule, sceSysmoduleLoadModuleInternal); 233 | LINK(libSceSysmodule, sceSysmoduleLoadModuleByNameInternal); 234 | 235 | kernel_base = args->kdata_base_addr; 236 | kernel_init_rw(args->rwpair[0], args->rwpair[1], args->rwpipe, args->kpipe_addr); 237 | 238 | int origStdout = dup(STDOUT); 239 | int origStderr = dup(STDERR); 240 | 241 | // stdout/stderr is set in main 242 | *args->payloadout = main(0, NULL); 243 | dup2(origStdout, STDOUT); 244 | dup2(origStderr, STDERR); 245 | } 246 | 247 | // NOLINTEND(*) 248 | -------------------------------------------------------------------------------- /stubber/elf.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "debug/elf" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "sync" 12 | "sync/atomic" 13 | ) 14 | 15 | type Elf struct { 16 | *elf.File 17 | path string 18 | dest string 19 | } 20 | 21 | type ElfProcessor struct { 22 | db map[string]string 23 | files chan string 24 | projects chan string 25 | entries map[string]*os.File 26 | filewg sync.WaitGroup 27 | projectwg sync.WaitGroup 28 | nsymbols atomic.Uint64 29 | nfiles atomic.Uint64 30 | rootCmake *os.File 31 | } 32 | 33 | const DYN_SIZE = 0x10 34 | const SYM_SIZE = 0x18 35 | const NID_LENGTH = 11 36 | 37 | type Phdr struct { 38 | *elf.Prog 39 | } 40 | 41 | type ElfTable struct { 42 | Phdr 43 | addr uint64 44 | size uint64 45 | } 46 | 47 | type DynamicInfo struct { 48 | fp *elf.File 49 | strtab ElfTable 50 | symtab ElfTable 51 | } 52 | 53 | type Symbol struct { 54 | elf.Sym64 55 | Name string 56 | } 57 | 58 | type Dyn struct { 59 | buf []byte 60 | ByteOrder binary.ByteOrder 61 | reader io.Reader 62 | } 63 | 64 | func (sym *Symbol) Type() elf.SymType { 65 | return elf.SymType(sym.Info & 0xf) 66 | } 67 | 68 | func (sym *Symbol) Bind() elf.SymBind { 69 | return elf.SymBind(sym.Info >> 4) 70 | } 71 | 72 | func (phdr *Phdr) ReadAt(p []byte, off int64) (n int, err error) { 73 | return phdr.Prog.ReadAt(p, off-int64(phdr.Paddr)) 74 | } 75 | 76 | func (tbl *ElfTable) ReadAt(p []byte, off int64) (n int, err error) { 77 | return tbl.Phdr.ReadAt(p, int64(tbl.addr)+off) 78 | } 79 | 80 | func (tbl *ElfTable) Read(p []byte) (n int, err error) { 81 | return tbl.ReadAt(p, 0) 82 | } 83 | 84 | func getProgContaining(fp *Elf, addr uint64) Phdr { 85 | for i := range fp.Progs { 86 | p := fp.Progs[i] 87 | if p.Paddr <= addr && (p.Paddr+p.Filesz) > addr { 88 | return Phdr{p} 89 | } 90 | } 91 | panic(fmt.Errorf("phdr containing %d not found", addr)) 92 | } 93 | 94 | func getSymtabSize(fp *Elf, hash uint64) uint64 { 95 | p := getProgContaining(fp, uint64(hash)) 96 | buf := make([]byte, 4) 97 | _, err := p.ReadAt(buf, int64(hash)) 98 | if err != nil { 99 | panic(err) 100 | } 101 | return uint64(fp.ByteOrder.Uint32(buf)) 102 | } 103 | 104 | func (info *DynamicInfo) getString(n int) string { 105 | buf := make([]byte, NID_LENGTH) 106 | _, err := info.strtab.ReadAt(buf, int64(n)) 107 | if err != nil { 108 | panic(err) 109 | } 110 | return string(buf) 111 | } 112 | 113 | func (info *DynamicInfo) getSymbol(n int) Symbol { 114 | var sym Symbol 115 | buf := make([]byte, SYM_SIZE) 116 | _, err := info.symtab.ReadAt(buf, int64(n*SYM_SIZE)) 117 | if err != nil { 118 | panic(err) 119 | } 120 | sym.Sym64.Name = info.fp.ByteOrder.Uint32(buf) 121 | sym.Name = info.getString(int(sym.Sym64.Name)) 122 | sym.Info = buf[4] 123 | sym.Other = buf[5] 124 | sym.Shndx = info.fp.ByteOrder.Uint16(buf[6:]) 125 | sym.Value = info.fp.ByteOrder.Uint64(buf[8:]) 126 | sym.Size = info.fp.ByteOrder.Uint64(buf[0x10:]) 127 | return sym 128 | } 129 | 130 | func (d *Dyn) next() { 131 | _, err := d.reader.Read(d.buf) 132 | if err != nil { 133 | panic(err) 134 | } 135 | } 136 | 137 | func (d *Dyn) Tag() elf.DynTag { 138 | return elf.DynTag(d.ByteOrder.Uint64(d.buf)) 139 | } 140 | 141 | func (d *Dyn) Value() uint64 { 142 | return d.ByteOrder.Uint64(d.buf[8:]) 143 | } 144 | 145 | func newDyn(fp *Elf, r io.Reader) *Dyn { 146 | d := Dyn{make([]byte, DYN_SIZE), fp.ByteOrder, r} 147 | d.next() 148 | return &d 149 | } 150 | 151 | func (sym *Symbol) IsExported() bool { 152 | return sym.Value != 0 153 | } 154 | 155 | func getDynamicInfo(fp *Elf) *DynamicInfo { 156 | info := DynamicInfo{fp.File, ElfTable{}, ElfTable{}} 157 | for i := range fp.Progs { 158 | p := fp.Progs[i] 159 | if p.Type == elf.PT_DYNAMIC { 160 | for dyn := newDyn(fp, p.Open()); dyn.Tag() != elf.DT_NULL; dyn.next() { 161 | switch dyn.Tag() { 162 | case elf.DT_SYMTAB: 163 | addr := dyn.Value() 164 | info.symtab.addr = addr 165 | info.symtab.Prog = getProgContaining(fp, addr).Prog 166 | case elf.DT_HASH: 167 | addr := dyn.Value() 168 | info.symtab.size = getSymtabSize(fp, addr) 169 | case elf.DT_STRTAB: 170 | addr := dyn.Value() 171 | info.strtab.addr = addr 172 | info.strtab.Prog = getProgContaining(fp, addr).Prog 173 | case elf.DT_STRSZ: 174 | info.strtab.size = dyn.Value() 175 | } 176 | } 177 | } 178 | } 179 | return &info 180 | } 181 | 182 | func newElfProcessor() *ElfProcessor { 183 | aerolib := readAerolib() 184 | files := make(chan string, 16) 185 | projects := make(chan string, 16) 186 | entries := make(map[string]*os.File, 4) 187 | rootCmake := createFile(filepath.Join(getOutputPath(), CMAKE_FILENAME)) 188 | return &ElfProcessor{ 189 | db: aerolib, 190 | files: files, 191 | projects: projects, 192 | entries: entries, 193 | filewg: sync.WaitGroup{}, 194 | projectwg: sync.WaitGroup{}, 195 | nsymbols: atomic.Uint64{}, 196 | nfiles: atomic.Uint64{}, 197 | rootCmake: rootCmake, 198 | } 199 | } 200 | 201 | func (p *ElfProcessor) Close() { 202 | p.rootCmake.Close() 203 | } 204 | 205 | func (p *ElfProcessor) CloseChannels() { 206 | close(p.files) 207 | close(p.projects) 208 | } 209 | 210 | func isBlacklistedFunction(fn string) bool { 211 | if strings.HasPrefix(fn, "__atomic") { 212 | return true 213 | } 214 | if strings.HasPrefix(fn, "__sync") { 215 | return true 216 | } 217 | return false 218 | } 219 | 220 | func (p *ElfProcessor) processElf(fp *Elf) { 221 | defer fp.Close() 222 | p.nfiles.Add(1) 223 | 224 | out := createFile(fp.dest) 225 | defer out.Close() 226 | 227 | info := getDynamicInfo(fp) 228 | names := make(map[string]bool, info.symtab.size) 229 | for i := 1; i < int(info.symtab.size); i++ { 230 | sym := info.getSymbol(i) 231 | if !sym.IsExported() { 232 | continue 233 | } 234 | 235 | switch sym.Type() { 236 | case elf.STT_FUNC: 237 | p.nsymbols.Add(1) 238 | 239 | if names[sym.Name] { 240 | continue 241 | } 242 | names[sym.Name] = true 243 | 244 | name := p.db[sym.Name] 245 | if name == "" || isBlacklistedFunction(name) { 246 | out.WriteString(fmt.Sprintf("// void %s(void) {}\n", sym.Name)) 247 | } else { 248 | out.WriteString(fmt.Sprintf("void %s(void) {}\n", name)) 249 | } 250 | case elf.STT_OBJECT: 251 | p.nsymbols.Add(1) 252 | name := p.db[sym.Name] 253 | if name == "" { 254 | out.WriteString(fmt.Sprintf("//unsigned char %s[0x%x];\n", sym.Name, sym.Size)) 255 | } else if sym.Size == 0 { 256 | out.WriteString(fmt.Sprintf("//unsigned char %s[0x%x];\n", name, sym.Size)) 257 | } else { 258 | out.WriteString(fmt.Sprintf("unsigned char %s[0x%x];\n", name, sym.Size)) 259 | } 260 | default: 261 | continue 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /stubber/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.20 4 | -------------------------------------------------------------------------------- /stubber/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "debug/elf" 5 | _ "embed" 6 | "encoding/csv" 7 | "fmt" 8 | "io" 9 | "io/fs" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | //go:embed toolchain.cmake 17 | var toolchainCmake string 18 | 19 | //go:embed root.cmake 20 | var rootCmake string 21 | 22 | const AEROLIB_ENTRIES = 94275 23 | 24 | var CMAKE_FILENAME = "CMakeLists.txt" 25 | 26 | func createFile(path string) *os.File { 27 | err := os.MkdirAll(filepath.Dir(path), 0) 28 | if err != nil { 29 | panic(err) 30 | } 31 | fp, err := os.Create(path) 32 | if err != nil { 33 | panic(err) 34 | } 35 | return fp 36 | } 37 | 38 | func getOutputLibraryName(path string) string { 39 | out := filepath.Base(path) 40 | out = strings.TrimSuffix(out, ".sprx") 41 | out = strings.TrimSuffix(out, ".c") 42 | out = strings.TrimPrefix(out, "lib") 43 | return out 44 | } 45 | 46 | func getOutputFile(path string) string { 47 | path, err := filepath.Rel(getInputPath(), path) 48 | if err != nil { 49 | panic(err) 50 | } 51 | out := strings.TrimSuffix(filepath.Base(path), ".sprx") + ".c" 52 | return filepath.Join(filepath.Join(getOutputPath(), path), out) 53 | } 54 | 55 | func getOutputFolder(path string) string { 56 | path, err := filepath.Rel(getInputPath(), path) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return path 61 | } 62 | 63 | func (p *ElfProcessor) process() { 64 | p.filewg.Add(1) 65 | defer p.filewg.Done() 66 | for f := range p.files { 67 | fp, err := elf.Open(f) 68 | if err != nil { 69 | if err.Error() == "EOF" { 70 | continue 71 | } 72 | panic(err) 73 | } 74 | 75 | p.processElf(&Elf{fp, f, getOutputFile(f)}) 76 | } 77 | } 78 | 79 | func (p *ElfProcessor) getProjectKey(path string) string { 80 | if strings.HasSuffix(path, ".c") { 81 | path = filepath.Dir(path) 82 | } 83 | path, err := filepath.Rel(getOutputPath(), path) 84 | if err != nil { 85 | panic(err) 86 | } 87 | return path 88 | } 89 | 90 | func (p *ElfProcessor) makeCmake(path string) *os.File { 91 | key := p.getProjectKey(path) 92 | path = filepath.Join(getOutputPath(), filepath.Join(path, CMAKE_FILENAME)) 93 | out := createFile(path) 94 | p.entries[key] = out 95 | out.WriteString("set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -g0 -nodefaultlibs -nostdlib -fno-omit-frame-pointer ") 96 | out.WriteString("-fno-builtin-function -fno-builtin -Wno-builtin-requires-header -Wno-incompatible-library-redeclaration\")\n") 97 | return out 98 | } 99 | 100 | func (p *ElfProcessor) getFiles(path string, d fs.DirEntry, err error) error { 101 | if err != nil { 102 | panic(err) 103 | } 104 | 105 | name := d.Name() 106 | 107 | if d.IsDir() { 108 | return nil 109 | } 110 | 111 | if strings.HasSuffix(name, ".dll.sprx") { 112 | return nil 113 | } 114 | 115 | if strings.HasSuffix(name, "exe.sprx") { 116 | return nil 117 | } 118 | 119 | if !strings.HasSuffix(name, ".sprx") { 120 | return nil 121 | } 122 | 123 | if !strings.HasPrefix(name, "lib") { 124 | return nil 125 | } 126 | 127 | if strings.Contains(path, "NPXS") { 128 | // skip so we don't have to deal with empty libs 129 | return nil 130 | } 131 | 132 | name = getOutputLibraryName(name) 133 | 134 | output := getOutputFolder(path) 135 | 136 | cmake, ok := p.entries[output] 137 | 138 | if !ok { 139 | cmake = p.makeCmake(output) 140 | } 141 | 142 | cmake.WriteString(fmt.Sprintf("add_library(%s SHARED lib%s.c)\n", name, name)) 143 | p.files <- path 144 | return nil 145 | } 146 | 147 | func readAerolib() map[string]string { 148 | aerolib := make(map[string]string, AEROLIB_ENTRIES) 149 | fp, e := os.OpenFile(getAerolibPath(), os.O_RDONLY, 0) 150 | if e != nil { 151 | panic(e) 152 | } 153 | defer fp.Close() 154 | 155 | r := csv.NewReader(fp) 156 | r.Comma = ' ' 157 | 158 | for { 159 | record, err := r.Read() 160 | if record == nil && err == io.EOF { 161 | break 162 | } 163 | aerolib[record[0]] = record[1] 164 | } 165 | return aerolib 166 | } 167 | 168 | func getInputPath() string { 169 | return os.Args[2] 170 | } 171 | 172 | func getOutputPath() string { 173 | return os.Args[3] 174 | } 175 | 176 | func getAerolibPath() string { 177 | return os.Args[1] 178 | } 179 | 180 | func globCmake(root string) []string { 181 | var files []string 182 | filepath.WalkDir(root, func(s string, d fs.DirEntry, e error) error { 183 | if filepath.Base(s) == CMAKE_FILENAME { 184 | files = append(files, s) 185 | } 186 | return nil 187 | }) 188 | return files 189 | } 190 | 191 | func makeRootCmake(p *ElfProcessor) { 192 | // system and system_ex 193 | 194 | func() { 195 | path := filepath.Join(getOutputPath(), "toolchain.cmake") 196 | out, err := os.Create(path) 197 | if err != nil { 198 | panic(err) 199 | } 200 | defer out.Close() 201 | 202 | out.WriteString(toolchainCmake) 203 | }() 204 | 205 | func() { 206 | f := filepath.Join(getOutputPath(), CMAKE_FILENAME) 207 | out := createFile(f) 208 | defer out.Close() 209 | 210 | out.WriteString(rootCmake) 211 | 212 | dir := getOutputPath() 213 | matches := globCmake(dir) 214 | for i := range matches { 215 | match := matches[i] 216 | project, err := filepath.Rel(dir, filepath.Dir(match)) 217 | if err != nil { 218 | panic(err) 219 | } 220 | if project == "." { 221 | continue 222 | } 223 | project = strings.ReplaceAll(project, "\\", "/") 224 | out.WriteString(fmt.Sprintf("add_subdirectory(%s)\n", project)) 225 | } 226 | }() 227 | } 228 | 229 | func main() { 230 | if len(os.Args) != 4 { 231 | println("usage: " + os.Args[0] + " ") 232 | os.Exit(0) 233 | } 234 | 235 | p := newElfProcessor() 236 | defer p.Close() 237 | 238 | start := time.Now() 239 | 240 | for i := 0; i < 16; i++ { 241 | go p.process() 242 | } 243 | 244 | filepath.WalkDir(getInputPath(), p.getFiles) 245 | 246 | p.CloseChannels() 247 | p.filewg.Wait() 248 | 249 | for key := range p.entries { 250 | p.entries[key].Close() 251 | } 252 | p.projectwg.Wait() 253 | makeRootCmake(p) 254 | end := time.Now() 255 | elapsed := end.Sub(start) 256 | fmt.Printf("processed %d ELF files and %d symbols in %s\n\n", p.nfiles.Load(), p.nsymbols.Load(), elapsed) 257 | println("run the following to build the libraries:") 258 | fmt.Printf("cd %s && mkdir build && cd build\n", getOutputPath()) 259 | println("cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ..") 260 | println("ninja") 261 | } 262 | -------------------------------------------------------------------------------- /stubber/root.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_ASM_COMPILER_WORKS 1) 2 | set(CMAKE_C_COMPILER_WORKS 1) 3 | set(CMAKE_CXX_COMPILER_WORKS 1) 4 | 5 | set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/toolchain.cmake") 6 | cmake_minimum_required(VERSION 3.20) 7 | project(PS5SDK C) 8 | 9 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib") 10 | -------------------------------------------------------------------------------- /stubber/toolchain.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | if(DEFINED CMAKE_CROSSCOMPILING) 4 | set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) 5 | return() 6 | endif() 7 | 8 | set(TOOLCHAIN_PATH "${CMAKE_CURRENT_LIST_DIR}") 9 | #set(CMAKE_MODULE_PATH "${TOOLCHAIN_PATH}/cmake/Modules") 10 | 11 | 12 | set(CMAKE_SYSTEM_NAME FreeBSD) 13 | set(CMAKE_SYSTEM_VERSION 11) 14 | set(CMAKE_SYSTEM_PROCESSOR x86_64) 15 | 16 | 17 | #set(UNIX 1) 18 | set(PS5 1) 19 | set(CMAKE_SYSROOT "${TOOLCHAIN_PATH}") 20 | 21 | set(CMAKE_C_STANDARD_DEFAULT 17) 22 | set(CMAKE_CXX_STANDARD_DEFAULT 20) 23 | 24 | 25 | set(TOOLCHAIN_TRIPLE x86_64-pc-freebsd12-elf) 26 | 27 | set(CMAKE_ASM_COMPILER clang) 28 | set(CMAKE_ASM_COMPILER_TARGET ${TOOLCHAIN_TRIPLE}) 29 | set(CMAKE_C_COMPILER clang) 30 | set(CMAKE_C_COMPILER_TARGET ${TOOLCHAIN_TRIPLE}) 31 | set(CMAKE_CXX_COMPILER clang++) 32 | set(CMAKE_CXX_COMPILER_TARGET ${TOOLCHAIN_TRIPLE}) 33 | 34 | set(CMAKE_ASM_FLAGS_INIT "-fno-exceptions") 35 | set(CMAKE_C_FLAGS_INIT "-fno-exceptions") 36 | set(CMAKE_CXX_FLAGS_INIT "-fno-exceptions") 37 | 38 | set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=lld -fPIC -nodefaultlibs -T${CMAKE_CURRENT_SOURCE_DIR}/linker.x") 39 | set(CMAKE_SHARED_LINKER_FLAGS "-fuse-ld=lld -nostdlib") 40 | add_link_options("LINKER:SHELL:-shared --build-id=none -zmax-page-size=16384 -zcommon-page-size=16384 --hash-style=sysv") 41 | 42 | set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) 43 | set (CMAKE_C_LINKER_WRAPPER_FLAG "-Xlinker" " ") 44 | -------------------------------------------------------------------------------- /test_elf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################################### 2 | # PS5SDK - Example: pipe pirate 3 | # Uses the read/write primitive to read and write some kernel data. 4 | # @authors ChendoChap, Specter, Znullptr 5 | ################################################################################################### 6 | 7 | cmake_minimum_required (VERSION 3.20) 8 | 9 | set(basename "test_elf") 10 | project(${basename} C CXX ASM) 11 | 12 | # Language Standard Defaults 13 | set(CMAKE_C_STANDARD 11) 14 | set(CMAKE_C_STANDARD_REQUIRED ON) 15 | 16 | set(CMAKE_CXX_EXTENSIONS ON) 17 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 18 | 19 | if ("${CMAKE_CXX_STANDARD}" STREQUAL "") 20 | set(CMAKE_CXX_STANDARD 20) 21 | endif() 22 | 23 | # Check for sub-project as part of main build or external build 24 | if (NOT CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) 25 | set(IS_SUBPROJECT TRUE) 26 | else() 27 | set(IS_SUBPROJECT FALSE) 28 | endif() 29 | 30 | message("IS_SUBPROJECT: ${IS_SUBPROJECT}") 31 | 32 | set(D_CWD "${CMAKE_CURRENT_SOURCE_DIR}") 33 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${D_CWD}/bin) 34 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${D_CWD}/bin) 35 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${D_CWD}/bin) # static libs are archive 36 | 37 | # Headers 38 | include_directories (SYSTEM "${D_PS5SDK}") 39 | include_directories (SYSTEM "${D_PS5SDK}/include") 40 | 41 | add_executable(${PROJECT_NAME}) 42 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}.elf") 43 | 44 | # Must build with clang 45 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "[Cc]lang") 46 | set(IS_CLANG 1) 47 | else() 48 | message(FATAL_ERROR "${PROJECT_NAME} is meant to be built with clang! CompilerID: ${CMAKE_CXX_COMPILER_ID}") 49 | endif() 50 | 51 | # Finalize main target sources 52 | target_compile_options(${PROJECT_NAME} PUBLIC 53 | $<$:${C_DEFS} ${C_FLAGS}> 54 | $<$:${CXX_DEFS} ${CXX_FLAGS}> 55 | $<$:${ASM_FLAGS}> 56 | ) 57 | 58 | message("========== build: ${PROJECT_NAME} ==========") 59 | 60 | set(D_SRC ${D_CWD}/source) 61 | 62 | file(GLOB SrcFiles ${D_SRC}/*.c ${D_SRC}/*.cpp ${D_SRC}/*.h ${D_SRC}/*.hpp ${D_SRC}/*.s) 63 | 64 | set(CMAKE_C_FLAGS "--target=x86_64-freebsd-pc-elf -DPPR -DPS5 -DPS5_FW_VERSION=${V_FW} ") 65 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200112 -D__BSD_VISIBLE=1 -D__XSI_VISIBLE=500") 66 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-builtin -nostdlib -Wall") # -nostartfiles 67 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC -fPIE -march=znver2 -Wall -Werror") 68 | set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -O3 -pedantic -pedantic-errors") 69 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") 70 | 71 | target_sources(${PROJECT_NAME} PRIVATE ${SrcFiles}) 72 | target_link_directories (${PROJECT_NAME} PUBLIC ${D_CWD} ${D_CWD}/../lib) 73 | 74 | target_link_libraries (${PROJECT_NAME} PUBLIC ps5sdk_crt SceLibcInternal kernel_sys) 75 | set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=lld -Xlinker -pie -Xlinker --gc-sections -Xlinker -zmax-page-size=16384 -Xlinker -zcommon-page-size=16384 -Xlinker -T ${CMAKE_CURRENT_SOURCE_DIR}/linker.x -Wl,--build-id=none -Wl,-z,norelro") 76 | set (CMAKE_C_LINKER_WRAPPER_FLAG "-Xlinker" " ") 77 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 78 | --------------------------------------------------------------------------------