mirror of
https://github.com/ps5-payload-dev/elfldr.git
synced 2026-10-19 02:19:52 -05:00
369 lines
7.4 KiB
C
369 lines
7.4 KiB
C
/* Copyright (C) 2024 John Törnblom
|
|
|
|
This program 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, 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
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include <arpa/inet.h>
|
|
#include <ifaddrs.h>
|
|
#include <limits.h>
|
|
#include <netinet/in.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/syscall.h>
|
|
|
|
#include <ps5/kernel.h>
|
|
|
|
#include "elfldr.h"
|
|
#include "log.h"
|
|
#include "notify.h"
|
|
#include "selfldr.h"
|
|
#include "uri.h"
|
|
|
|
|
|
/**
|
|
* Magic number that socket input starts with (little endian).
|
|
**/
|
|
#define PAYLOAD_MAGIC_ELF 0x464C457F // ELF payload
|
|
#define PAYLOAD_MAGIC_FILE 0x656C6966 // file:// URI
|
|
#define PAYLOAD_MAGIC_HTTP 0x70747468 // http:// or https:// URI
|
|
#define PAYLOAD_MAGIC_PS4_SELF 0x1D3D154F // PS4 SELF payload
|
|
#define PAYLOAD_MAGIC_PS5_SELF 0xEEF51454 // PS5 SELF payload
|
|
|
|
|
|
/**
|
|
* Decode an escaped argument.
|
|
**/
|
|
static char*
|
|
args_decode(const char* s) {
|
|
size_t length = strlen(s);
|
|
char *arg = malloc(length+1);
|
|
size_t off = 0;
|
|
int escape = 0;
|
|
|
|
for(size_t i=0; i<length; i++) {
|
|
if(s[i] == '\\' && !escape) {
|
|
escape = 1;
|
|
} else {
|
|
arg[off++] = s[i];
|
|
escape = 0;
|
|
}
|
|
}
|
|
|
|
arg[off] = 0;
|
|
return arg;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
**/
|
|
static int
|
|
args_split(const char* args, char** argv, size_t size) {
|
|
char* buf = strdup(args);
|
|
size_t len = strlen(buf);
|
|
int escape = 0;
|
|
int argc = 0;
|
|
|
|
memset(argv, 0, size*sizeof(char*));
|
|
for(int i=0; i<len && argc<size; i++) {
|
|
if(escape) {
|
|
escape = 0;
|
|
continue;
|
|
}
|
|
|
|
if(buf[i] == '\\') {
|
|
escape = 1;
|
|
continue;
|
|
}
|
|
|
|
if(buf[i] == ' ') {
|
|
buf[i] = 0;
|
|
continue;
|
|
}
|
|
|
|
if(buf[i] && !i) {
|
|
argv[argc++] = buf+i;
|
|
continue;
|
|
}
|
|
|
|
if(buf[i] && !buf[i-1]) {
|
|
argv[argc++] = buf+i;
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<argc; i++) {
|
|
argv[i] = args_decode(argv[i]);
|
|
}
|
|
|
|
free(buf);
|
|
|
|
return argc;
|
|
}
|
|
|
|
|
|
/**
|
|
* Spawn an ELF or a SELF payload.
|
|
**/
|
|
static pid_t
|
|
payload_spawn(char* filename, char* args, int fd,
|
|
uint8_t* payload, size_t payload_size) {
|
|
int magic = *((int*)payload);
|
|
char* argv[255+2] = {0};
|
|
pid_t pid = -1;
|
|
|
|
argv[0] = filename;
|
|
args_split(args, argv+1, 255);
|
|
|
|
if(magic == PAYLOAD_MAGIC_ELF) {
|
|
pid = elfldr_spawn(fd, argv, payload, payload_size);
|
|
|
|
} else if(magic == PAYLOAD_MAGIC_PS4_SELF || magic == PAYLOAD_MAGIC_PS5_SELF) {
|
|
pid = selfldr_spawn(fd, argv, payload, payload_size);
|
|
}
|
|
|
|
for(int i=1; argv[i]; i++) {
|
|
free(argv[i]);
|
|
}
|
|
|
|
return pid;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
**/
|
|
static int
|
|
payload_readuri(int fd, char* uri, size_t size) {
|
|
char c;
|
|
int n;
|
|
|
|
for(int i=0; i<size; i++) {
|
|
if((n=read(fd, &c, 1)) < 0) {
|
|
return -1;
|
|
}
|
|
if(n == 0) {
|
|
uri[i] = 0;
|
|
return 0;
|
|
}
|
|
if(c == '\r') {
|
|
continue;
|
|
}
|
|
if(c == '\n') {
|
|
uri[i] = 0;
|
|
return 0;
|
|
}
|
|
|
|
uri[i] = c;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Process connection input.
|
|
**/
|
|
static void
|
|
on_connection(int fd) {
|
|
char* filename = "payload.elf";
|
|
char uri[PATH_MAX+1] = {0};
|
|
uint8_t* buf = 0;
|
|
size_t len = 0;
|
|
int optval = 1;
|
|
char* args = "";
|
|
int magic = 0;
|
|
|
|
if(setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)) < 0) {
|
|
LOG_PERROR("setsockopt");
|
|
return;
|
|
}
|
|
|
|
if(recv(fd, &magic, sizeof(magic), MSG_PEEK | MSG_WAITALL) != sizeof(magic)) {
|
|
LOG_PERROR("recv");
|
|
write(fd, "[elfldr.elf] Unknown payload format\n\r\0", 38);
|
|
return;
|
|
}
|
|
|
|
if(magic == PAYLOAD_MAGIC_FILE || magic == PAYLOAD_MAGIC_HTTP) {
|
|
if(payload_readuri(fd, uri, PATH_MAX) || uri_get_content(uri, &buf, &len)) {
|
|
LOG_PERROR("read_uri");
|
|
write(fd, "[elfldr.elf] Error reading URI payload\n\r\0", 41);
|
|
}
|
|
|
|
} else if(magic == PAYLOAD_MAGIC_ELF) {
|
|
if(elfldr_read(fd, &buf, &len)) {
|
|
LOG_PERROR("elfldr_read");
|
|
write(fd, "[elfldr.elf] Error reading ELF payload\n\r\0", 41);
|
|
}
|
|
|
|
} else if(magic == PAYLOAD_MAGIC_PS4_SELF || magic == PAYLOAD_MAGIC_PS5_SELF) {
|
|
if(selfldr_read(fd, &buf, &len)) {
|
|
LOG_PERROR("selfldr_read");
|
|
write(fd, "[elfldr.elf] Error reading SELF payload\n\r\0", 42);
|
|
}
|
|
} else {
|
|
write(fd, "[elfldr.elf] Unknown payload format\n\r\0", 38);
|
|
}
|
|
|
|
if(buf) {
|
|
if(!(filename=uri_get_filename(uri))) {
|
|
filename = strdup("payload.elf");
|
|
}
|
|
if(!(args=uri_get_param(uri, "args"))) {
|
|
args = strdup("");
|
|
}
|
|
if(payload_spawn(filename, args, fd, buf, len) < 0) {
|
|
write(fd, "[elfldr.elf] Error spawning payload\n\r\0", 38);
|
|
}
|
|
free(filename);
|
|
free(args);
|
|
free(buf);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Serve ELF loader via a socket.
|
|
**/
|
|
static int
|
|
serve_elfldr(uint16_t port) {
|
|
struct sockaddr_in srvaddr;
|
|
struct sockaddr_in cliaddr;
|
|
socklen_t socklen;
|
|
int connfd;
|
|
int srvfd;
|
|
|
|
if((srvfd=socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
|
LOG_PERROR("socket");
|
|
return -1;
|
|
}
|
|
|
|
if(setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0) {
|
|
LOG_PERROR("setsockopt");
|
|
return -1;
|
|
}
|
|
|
|
memset(&srvaddr, 0, sizeof(srvaddr));
|
|
srvaddr.sin_family = AF_INET;
|
|
srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
srvaddr.sin_port = htons(port);
|
|
|
|
if(bind(srvfd, (struct sockaddr*)&srvaddr, sizeof(srvaddr)) != 0) {
|
|
LOG_PERROR("bind");
|
|
return -1;
|
|
}
|
|
|
|
if(listen(srvfd, 5) != 0) {
|
|
LOG_PERROR("listen");
|
|
return -1;
|
|
}
|
|
|
|
while(1) {
|
|
socklen = sizeof(cliaddr);
|
|
if((connfd=accept(srvfd, (struct sockaddr*)&cliaddr, &socklen)) < 0) {
|
|
LOG_PERROR("accept");
|
|
break;
|
|
}
|
|
|
|
on_connection(connfd);
|
|
close(connfd);
|
|
}
|
|
|
|
return close(srvfd);
|
|
}
|
|
|
|
|
|
static int
|
|
notify_address(const char* prefix, int port) {
|
|
char ip[INET_ADDRSTRLEN] = "127.0.0.1";
|
|
struct ifaddrs *ifaddr;
|
|
|
|
if(getifaddrs(&ifaddr) == -1) {
|
|
LOG_PERROR("getifaddrs");
|
|
return -1;
|
|
}
|
|
|
|
// Enumerate all AF_INET IPs
|
|
for(struct ifaddrs *ifa=ifaddr; ifa!=NULL; ifa=ifa->ifa_next) {
|
|
if(ifa->ifa_addr == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if(ifa->ifa_addr->sa_family != AF_INET) {
|
|
continue;
|
|
}
|
|
|
|
// skip localhost
|
|
if(!strncmp("lo", ifa->ifa_name, 2)) {
|
|
continue;
|
|
}
|
|
|
|
struct sockaddr_in *in = (struct sockaddr_in*)ifa->ifa_addr;
|
|
inet_ntop(AF_INET, &(in->sin_addr), ip, sizeof(ip));
|
|
|
|
// skip interfaces without an ip
|
|
if(!strncmp("0.", ip, 2)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
freeifaddrs(ifaddr);
|
|
|
|
notify("%s %s:%d", prefix, ip, port);
|
|
LOG_PRINTF("%s %s:%d\n", prefix, ip, port);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
**/
|
|
int main() {
|
|
int port = 9021;
|
|
pid_t pid;
|
|
|
|
LOG_PRINTF("Socket server was compiled at %s %s\n", __DATE__, __TIME__);
|
|
|
|
if(chdir("/")) {
|
|
LOG_PERROR("chdir");
|
|
return -1;
|
|
}
|
|
|
|
syscall(SYS_setsid);
|
|
while((pid=elfldr_find_pid("elfldr.elf")) > 0) {
|
|
if(kill(pid, SIGKILL)) {
|
|
LOG_PERROR("kill");
|
|
_exit(-1);
|
|
}
|
|
sleep(1);
|
|
}
|
|
|
|
syscall(SYS_thr_set_name, -1, "elfldr.elf");
|
|
signal(SIGCHLD, SIG_IGN);
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
notify_address("Serving ELF loader on", port);
|
|
while(1) {
|
|
serve_elfldr(port);
|
|
sleep(3);
|
|
}
|
|
|
|
return 0;
|
|
}
|