From 4cc9e0a8f026719e41cc8c12898b0b85a789955a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20T=C3=B6rnblom?= Date: Tue, 8 Oct 2024 19:04:04 +0200 Subject: [PATCH] add experimental api for payload elf loader --- assets/elfldr.html | 23 +++++ host_tools/ps5-elfldr | 45 +++++++++ src/pc/sys.c | 21 +++++ src/ps5/elfldr.c | 120 ++++++++++++++++++++++++ src/ps5/elfldr.h | 14 +++ src/ps5/hbldr.c | 3 +- src/ps5/sys.c | 62 +++++++++++- src/sys.h | 6 +- src/websrv.c | 213 +++++++++++++++++++++++++++++++++++------- 9 files changed, 470 insertions(+), 37 deletions(-) create mode 100644 assets/elfldr.html create mode 100755 host_tools/ps5-elfldr diff --git a/assets/elfldr.html b/assets/elfldr.html new file mode 100644 index 0000000..4f2e62a --- /dev/null +++ b/assets/elfldr.html @@ -0,0 +1,23 @@ + + + + + + ELF Launcher + + + + + +
+ +
+ + +
+ + +
+ + + diff --git a/host_tools/ps5-elfldr b/host_tools/ps5-elfldr new file mode 100755 index 0000000..6368bda --- /dev/null +++ b/host_tools/ps5-elfldr @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Copyright (C) 2024 John Törnblom +# +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not see +# + +PS5_HOST= + + +while getopts "h:" opt; do + case ${opt} in + h) PS5_HOST=$OPTARG;; + esac +done +shift $((OPTIND -1)) + +if [[ -z $PS5_HOST || -z $1 ]]; then + echo "Usage: $(basename $0) -h HOST PAYLOAD [ARGS...]" >&2 + exit 1 +fi + +PAYLOAD_PATH=$1 +PAYLOAD_NAME=$(basename "$PAYLOAD_PATH") +shift + +PAYLOAD_ARGS=${PAYLOAD_NAME// /\\ } +for ARG in "$@"; do + PAYLOAD_ARGS="${PAYLOAD_ARGS} ${ARG// /\\ }" +done + +curl --form "elf=@${PAYLOAD_PATH}" \ + --url-query pipe="1" \ + --url-query args="$PAYLOAD_ARGS" \ + http://$PS5_HOST:8080/elfldr diff --git a/src/pc/sys.c b/src/pc/sys.c index 034006e..c76e60e 100644 --- a/src/pc/sys.c +++ b/src/pc/sys.c @@ -16,6 +16,10 @@ along with this program; see the file COPYING. If not, see #include #include +#include +#include + +#include int @@ -45,3 +49,20 @@ sys_launch_homebrew(const char* cwd, const char* path, const char* args, return fileno(pf); } + + +int +sys_launch_payload(const char* cwd, uint8_t* elf, size_t elf_size, + const char* args, const char* env) { + char filename[] = "/tmp/elfXXXXXX"; + mktemp(filename); + + FILE *f = fopen(filename, "wx"); + fwrite(elf, elf_size, 1, f); + fclose(f); + + chmod(filename, 0777); + + return sys_launch_homebrew(cwd, filename, args, env); +} + diff --git a/src/ps5/elfldr.c b/src/ps5/elfldr.c index 32f0317..fb37f1c 100644 --- a/src/ps5/elfldr.c +++ b/src/ps5/elfldr.c @@ -68,6 +68,15 @@ typedef struct elfldr_ctx { } elfldr_ctx_t; +/** + * Absolute path to the SceSpZeroConf eboot. + **/ +static const char* SceSpZeroConf = "/system/vsh/app/NPXS40112/eboot.bin"; + + +int sceKernelSpawn(int *pid, int dbg, const char *path, char *root, char** argv); + + /** * Parse a R_X86_64_RELATIVE relocatable. **/ @@ -376,6 +385,32 @@ elfldr_payload_args(pid_t pid) { } +int +elfldr_raise_privileges(pid_t pid) { + static const uint8_t caps[16] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}; + intptr_t vnode; + + if(!(vnode=kernel_get_root_vnode())) { + return -1; + } + if(kernel_set_proc_rootdir(pid, vnode)) { + return -1; + } + if(kernel_set_proc_jaildir(pid, 0)) { + return -1; + } + if(kernel_set_ucred_uid(pid, 0)) { + return -1; + } + if(kernel_set_ucred_caps(pid, caps)) { + return -1; + } + + return 0; +} + + /** * Prepare registers of a process for execution of an ELF. **/ @@ -644,3 +679,88 @@ elfldr_exec(pid_t pid, uint8_t* elf) { return 0; } + +/** + * Execute an ELF inside a new process. + **/ +pid_t +elfldr_spawn(const char* cwd, int stdio, uint8_t* elf, char** argv, + char** envp) { + uint8_t int3instr = 0xcc; + intptr_t brkpoint; + uint8_t orginstr; + pid_t pid = -1; + + if(sceKernelSpawn(&pid, 1, SceSpZeroConf, 0, argv)) { + perror("sceKernelSpawn"); + return -1; + } + + elfldr_raise_privileges(pid); + + // The proc is now in the STOP state, with the instruction pointer pointing + // at the libkernel entry. Let the kernel assign process parameters accessed + // via sceKernelGetProcParam() + if(pt_syscall(pid, 599)) { + puts("sys_dynlib_process_needed_and_relocate failed"); + pt_detach(pid, SIGKILL); + return -1; + } + + // Allow libc to allocate arbitrary amount of memory. + elfldr_set_heap_size(pid, -1); + + //Insert a breakpoint at the eboot entry. + if(!(brkpoint=kernel_dynlib_entry_addr(pid, 0))) { + puts("kernel_dynlib_entry_addr failed"); + pt_detach(pid, SIGKILL); + return -1; + } + brkpoint += 58;// offset to invocation of main() + if(mdbg_copyout(pid, brkpoint, &orginstr, sizeof(orginstr))) { + perror("mdbg_copyout"); + pt_detach(pid, SIGKILL); + return -1; + } + if(mdbg_copyin(pid, &int3instr, brkpoint, sizeof(int3instr))) { + perror("mdbg_copyin"); + pt_detach(pid, SIGKILL); + return -1; + } + + // Continue execution until we hit the breakpoint, then remove it. + if(pt_continue(pid, SIGCONT)) { + perror("pt_continue"); + pt_detach(pid, SIGKILL); + return -1; + } + if(waitpid(pid, 0, 0) == -1) { + perror("waitpid"); + pt_detach(pid, SIGKILL); + return -1; + } + if(mdbg_copyin(pid, &orginstr, brkpoint, sizeof(orginstr))) { + perror("mdbg_copyin"); + pt_detach(pid, SIGKILL); + return -1; + } + + if(argv[0]) { + elfldr_set_procname(pid, argv[0]); + } else { + elfldr_set_procname(pid, "payload"); + } + + elfldr_set_environ(pid, envp); + elfldr_set_cwd(pid, cwd); + elfldr_set_stdio(pid, stdio); + + // Execute the ELF + if(elfldr_exec(pid, elf)) { + kill(pid, SIGKILL); + return -1; + } + + return pid; +} + diff --git a/src/ps5/elfldr.h b/src/ps5/elfldr.h index 3022d9a..cebdd2a 100644 --- a/src/ps5/elfldr.h +++ b/src/ps5/elfldr.h @@ -18,6 +18,20 @@ along with this program; see the file COPYING. If not, see #include + +/** + * Escape jail and raise privileges. + **/ +int elfldr_raise_privileges(pid_t pid); + + +/** + * Execute an ELF inside a new process. + **/ +int elfldr_spawn(const char* cwd, int stdio, uint8_t* elf, char** argv, + char** envp); + + /** * Execute an ELF inside the process with the given pid. **/ diff --git a/src/ps5/hbldr.c b/src/ps5/hbldr.c index bc3fbbd..5ca6863 100644 --- a/src/ps5/hbldr.c +++ b/src/ps5/hbldr.c @@ -472,8 +472,7 @@ hbldr_launch(const char*cwd, const char* path, int stdio, char** argv, return -1; } - kernel_set_proc_rootdir(pid, kernel_get_root_vnode()); - kernel_set_proc_jaildir(pid, 0); + elfldr_raise_privileges(pid); if(bigapp_replace(pid, elf, path, stdio, cwd, envp) < 0) { if(pt_detach(pid, SIGKILL)) { diff --git a/src/ps5/sys.c b/src/ps5/sys.c index 9565bab..21b2119 100644 --- a/src/ps5/sys.c +++ b/src/ps5/sys.c @@ -25,6 +25,7 @@ along with this program; see the file COPYING. If not, see #include #include +#include "elfldr.h" #include "hbldr.h" #include "pt.h" #include "sys.h" @@ -171,6 +172,10 @@ sys_launch_homebrew(const char* cwd, const char* path, const char* args, int fds[2]; pid_t pid; + if(!cwd) { + cwd = "/"; + } + if(!args) { args = ""; } @@ -179,7 +184,7 @@ sys_launch_homebrew(const char* cwd, const char* path, const char* args, env = ""; } - printf("launch homebrew: %s %s %s\n", env, path, args); + printf("launch homebrew: CWD=%s %s %s %s\n", cwd, env, path, args); if(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) { perror("socketpair"); @@ -214,6 +219,61 @@ sys_launch_homebrew(const char* cwd, const char* path, const char* args, } +int +sys_launch_payload(const char* cwd, uint8_t* elf, size_t elf_size, + const char* args, const char* env) { + char* argv[255]; + char* envp[255]; + int optval = 1; + int fds[2]; + pid_t pid; + + if(!cwd) { + cwd = "/"; + } + + if(!args) { + args = ""; + } + + if(!env) { + env = ""; + } + + printf("launch payload: CWD=%s %s %s\n", cwd, env, args); + + if(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == -1) { + perror("socketpair"); + return 1; + } + + if(setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)) < 0) { + perror("setsockopt"); + close(fds[0]); + close(fds[1]); + return -11; + } + + args_split(args, argv, 255); + args_split(env, envp, 255); + pid = elfldr_spawn(cwd, fds[1], elf, argv, envp); + + for(int i=0; argv[i]; i++) { + free(argv[i]); + } + for(int i=0; envp[i]; i++) { + free(envp[i]); + } + + close(fds[1]); + if(pid < 0) { + close(fds[0]); + return -1; + } + + return fds[0]; +} + int sys_launch_title(const char* title_id, const char* args) { app_launch_ctx_t ctx = {0}; diff --git a/src/sys.h b/src/sys.h index 6a1d971..69d3f8d 100644 --- a/src/sys.h +++ b/src/sys.h @@ -16,7 +16,11 @@ along with this program; see the file COPYING. If not, see #pragma once +#include + + int sys_launch_title(const char* title_id, const char* args); int sys_launch_homebrew(const char* cwd, const char* path, const char* args, const char* env); - +int sys_launch_payload(const char* cwd, uint8_t* elf, size_t elf_size, + const char* argv, const char* env); diff --git a/src/websrv.c b/src/websrv.c index 08947d8..265d84f 100644 --- a/src/websrv.c +++ b/src/websrv.c @@ -33,6 +33,67 @@ along with this program; see the file COPYING. If not, see #include "websrv.h" +typedef struct post_data { + char *key; + uint8_t *val; + size_t len; + struct post_data *next; +} post_data_t; + + +typedef struct post_request { + struct MHD_PostProcessor* pp; + post_data_t* data; +} post_request_t; + + +static post_data_t* +post_data_get(post_data_t* data, const char* key) { + if(!data) { + return 0; + } + + if(!strcmp(key, data->key)) { + return data; + } + + return post_data_get(data->next, key); +} + + +static const char* +post_data_val(post_data_t* data, const char* key) { + data = post_data_get(data, key); + return data ? (const char*)data->val : 0; +} + + +static enum MHD_Result +post_iterator(void *cls, enum MHD_ValueKind kind, const char *key, + const char *filename, const char *mime, const char *encoding, + const char *value, uint64_t off, size_t size) { + post_request_t *req = cls; + post_data_t *data = post_data_get(req->data, key); + + if(data) { + data->val = realloc(data->val, off+size+1); + } else { + data = malloc(sizeof(post_data_t)); + data->next = req->data; + data->key = strdup(key); + data->val = malloc(off+size+1); + data->len = 0; + req->data = data; + } + + memcpy(data->val+off, value, size); + data->val[off+size] = 0; + data->len += size; + + return MHD_YES; +} + + enum MHD_Result websrv_queue_response(struct MHD_Connection *conn, unsigned int status, struct MHD_Response *resp) { @@ -117,10 +178,6 @@ hbldr_request(struct MHD_Connection *conn) { pipe = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "pipe"); cwd = MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "cwd"); - if(!cwd) { - cwd = "/"; - } - if(!path) { if((resp=MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT))) { ret = websrv_queue_response(conn, MHD_HTTP_BAD_REQUEST, resp); @@ -150,53 +207,142 @@ hbldr_request(struct MHD_Connection *conn) { } + /** - * + * Respond to a ELF payload loading request. **/ static enum MHD_Result -ahc_echo(void *cls, struct MHD_Connection *conn, - const char *url, const char *method, - const char *version, const char *upload_data, - size_t *upload_data_size, void **ptr) { - static int aptr; +elfldr_request(struct MHD_Connection *conn, post_data_t *data) { + enum MHD_Result ret = MHD_NO; + struct MHD_Response *resp; + post_data_t *elf; + const char *args; + const char *pipe; + const char *env; + const char *cwd; + int fd; - if(strcmp(method, MHD_HTTP_METHOD_GET)) { - return MHD_NO; + if(!(args=MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "args"))) { + args = post_data_val(data, "args"); + } + if(!(env=MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "env"))) { + env = post_data_val(data, "env"); + } + if(!(pipe=MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "pipe"))) { + pipe = post_data_val(data, "pipe"); + } + if(!(cwd=MHD_lookup_connection_value(conn, MHD_GET_ARGUMENT_KIND, "cwd"))) { + cwd = post_data_val(data, "cwd"); } - // never respond on first call - if(&aptr != *ptr) { - *ptr = &aptr; - return MHD_YES; + if(!(elf=post_data_get(data, "elf"))) { + if((resp=MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT))) { + ret = websrv_queue_response(conn, MHD_HTTP_BAD_REQUEST, resp); + MHD_destroy_response(resp); + } + + } else if((fd=sys_launch_payload(cwd, elf->val, elf->len, args, env)) < 0) { + if((resp=MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT))) { + ret = websrv_queue_response(conn, MHD_HTTP_SERVICE_UNAVAILABLE, resp); + MHD_destroy_response(resp); + } + + } else if(pipe && strcmp(pipe, "0")) { + if((resp=MHD_create_response_from_pipe(fd))) { + ret = websrv_queue_response(conn, MHD_HTTP_OK, resp); + MHD_destroy_response(resp); + } + } else { + close(fd); + if((resp=MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT))) { + ret = websrv_queue_response(conn, MHD_HTTP_OK, resp); + MHD_destroy_response(resp); + } } - // reset when done - *ptr = NULL; + return ret; +} + - if(!strcmp("/fs", url)) { - return fs_request(conn, url); + +/** + * + **/ +static enum MHD_Result +websrv_on_request(void *cls, struct MHD_Connection *conn, + const char *url, const char *method, + const char *version, const char *upload_data, + size_t *upload_data_size, void **con_cls) { + post_request_t *req = *con_cls; + enum MHD_Result ret; + + if(!strcmp(method, MHD_HTTP_METHOD_GET)) { + if(!strcmp("/fs", url)) { + return fs_request(conn, url); + } + if(!strncmp("/fs/", url, 4)) { + return fs_request(conn, url); + } + if(!strcmp("/launch", url)) { + return launch_request(conn); + } + if(!strcmp("/hbldr", url)) { + return hbldr_request(conn); + } + if(!strcmp("/version", url)) { + return version_request(conn); + } + if(!strcmp("/", url) || !url[0]) { + return asset_request(conn, "/index.html"); + } + return asset_request(conn, url); } - if(!strncmp("/fs/", url, 4)) { - return fs_request(conn, url); + + if(strcmp(method, MHD_HTTP_METHOD_POST)) { + return MHD_NO; + } + + if(!req) { + req = *con_cls = malloc(sizeof(post_request_t)); + req->pp = MHD_create_post_processor(conn, 0x1000, &post_iterator, req); + req->data = 0; + return MHD_YES; } - if(!strcmp("/launch", url)) { - return launch_request(conn); + if(*upload_data_size) { + ret = MHD_post_process(req->pp, upload_data, *upload_data_size); + *upload_data_size = 0; + return ret; } - if(!strcmp("/hbldr", url)) { - return hbldr_request(conn); + if(!strcmp("/elfldr", url)) { + return elfldr_request(conn, req->data); } - if(!strcmp("/version", url)) { - return version_request(conn); + return MHD_NO; +} + + + +static void +websrv_on_completed(void *cls, struct MHD_Connection *connection, + void **con_cls, enum MHD_RequestTerminationCode toe) { + post_request_t *req = *con_cls; + post_data_t *data; + + if(!req) { + return; } - if(!strcmp("/", url) || !url[0]) { - return asset_request(conn, "/index.html"); + while((data=req->data)) { + req->data = data->next; + free(data->key); + free(data->val); + free(data); } - return asset_request(conn, url); + MHD_destroy_post_processor(req->pp); + free(req); } @@ -283,8 +429,9 @@ websrv_listen(unsigned short port) { if(!(httpd=MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_ITC | MHD_USE_NO_LISTEN_SOCKET | MHD_USE_DEBUG | MHD_USE_INTERNAL_POLLING_THREAD, - 0, NULL, NULL, - &ahc_echo, NULL, MHD_OPTION_END))) { + 0, NULL, NULL, &websrv_on_request, NULL, + MHD_OPTION_NOTIFY_COMPLETED, &websrv_on_completed, + NULL, MHD_OPTION_END))) { perror("MHD_start_daemon"); close(srvfd); return -1;