Files
ps5debug-NG/debugger/source/console.c
T
OpenSourcereR-dev 9c649a44dc Initial public release: ps5debug-NG v1.2.2
PS5 debugger payload, inspired by Ctn's ps5debug 1.0b5 and
wire-compatible with it. Runs the wire-protocol server inside
SceShellCore via an umtx-loaded installer that injects the embedded
debugger ELF.

Features: process scan/RPC/ELF-inject, on-console scan, full
debugger (breakpoints, watchpoints, single-step, regs/FP/DR access),
kernel R/W, server-side Zydis disassembly, on-console Keystone
assembler (0xBDAA0024), foreground-app metadata (0xBDDD0006),
server-side RBP stack walk (CMD_PROC_READ_STACK), klog forwarder
(TCP 3232), rest-mode survival and a lot more.

Supports FW families 3.x / 4.0 / 4.x (incl. 4.51) / 5.x / 6.x /
7.x / 7.5x / 8.x via the kpatch routine in installer/source/main.c.

GPL-3.0-only.
2026-05-14 13:44:35 +07:00

407 lines
13 KiB
C

// SPDX-License-Identifier: GPL-3.0-only
#include "protocol.h"
#include "sdk_shim.h"
#include "net.h"
#include "kern_rw_fast.h"
#include <stdarg.h>
#define PROC_NEXT_OFFSET 0x00
#define PROC_TITLE_ID_OFFSET 0x470
#define PROC_CONTENT_ID_OFFSET 0x4C4
#define PROC_SELFINFO_NAME_OFFSET 0x59C
#define PROC_SELFINFO_NAME_SIZE 32
struct cmd_console_notify_packet {
uint32_t messageType;
uint32_t length;
} __attribute__((packed));
struct cmd_console_print_packet {
uint32_t length;
} __attribute__((packed));
typedef struct notify_request {
char useless1[45];
char message[3075];
} notify_request_t;
int console_reboot_handle(int fd, struct cmd_packet *packet) {
(void)fd; (void)packet;
sceKernelReboot();
return 0;
}
int console_end_handle(int fd, struct cmd_packet *packet) {
(void)packet;
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
int console_print_handle(int fd, struct cmd_packet *packet) {
struct cmd_console_print_packet *pp = (struct cmd_console_print_packet *)packet->data;
if (!pp) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
void *data = net_alloc_buffer(pp->length);
if (!data) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
memset(data, 0, pp->length);
net_recv_all(fd, data, (int)pp->length, 1);
klog_puts((const char *)data);
net_send_int32(fd, CMD_SUCCESS);
free(data);
return 0;
}
int console_notify_handle(int fd, struct cmd_packet *packet) {
struct cmd_console_notify_packet *np = (struct cmd_console_notify_packet *)packet->data;
if (!np) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
void *data = net_alloc_buffer(np->length);
if (!data) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
memset(data, 0, np->length);
net_recv_all(fd, data, (int)np->length, 1);
notify_request_t req;
memset(&req, 0, sizeof(req));
uint32_t copy = np->length;
if (copy >= sizeof(req.message)) copy = sizeof(req.message) - 1;
memcpy(req.message, data, copy);
req.message[copy] = 0;
sceKernelSendNotificationRequest(0, &req, sizeof(req), 0);
net_send_int32(fd, CMD_SUCCESS);
free(data);
return 0;
}
int console_info_handle(int fd, struct cmd_packet *packet) {
(void)packet;
net_send_int32(fd, CMD_SUCCESS);
return 0;
}
static int sfo_read_file(const char *path, uint8_t *buf, size_t bufsz, size_t *out_len) {
int fdf = open(path, 0, 0);
if (fdf < 0) return -1;
size_t total = 0;
while (total < bufsz) {
long n = read(fdf, buf + total, bufsz - total);
if (n <= 0) break;
total += (size_t)n;
}
close(fdf);
if (out_len) *out_len = total;
return 0;
}
static int sfo_get_string(const uint8_t *sfo, size_t sfo_len,
const char *want_key, char *out, size_t out_max) {
if (out_max == 0) return -1;
out[0] = '\0';
if (sfo_len < 20) return -1;
if (sfo[0] != 0 || sfo[1] != 'P' || sfo[2] != 'S' || sfo[3] != 'F') return -1;
uint32_t key_table = *(const uint32_t *)(sfo + 8);
uint32_t data_table = *(const uint32_t *)(sfo + 12);
uint32_t nentries = *(const uint32_t *)(sfo + 16);
if (key_table > sfo_len) return -1;
if (data_table > sfo_len) return -1;
size_t want_len = strlen(want_key);
for (uint32_t i = 0; i < nentries; i++) {
size_t eoff = 20 + (size_t)i * 16;
if (eoff + 16 > sfo_len) return -1;
const uint8_t *e = sfo + eoff;
uint16_t key_off = *(const uint16_t *)(e + 0);
uint32_t param_len = *(const uint32_t *)(e + 4);
uint32_t data_off = *(const uint32_t *)(e + 12);
size_t key_abs = (size_t)key_table + key_off;
if (key_abs + want_len + 1 > sfo_len) continue;
if (memcmp(sfo + key_abs, want_key, want_len) != 0) continue;
if (sfo[key_abs + want_len] != '\0') continue;
size_t data_abs = (size_t)data_table + data_off;
if (data_abs + param_len > sfo_len) return -1;
size_t copy = param_len;
if (copy >= out_max) copy = out_max - 1;
memcpy(out, sfo + data_abs, copy);
out[copy] = '\0';
while (copy > 0 && out[copy - 1] == '\0') copy--;
out[copy] = '\0';
return 0;
}
return -2;
}
struct cmd_console_foreground_app_response {
uint32_t pid;
char titleid[16];
char contentid[64];
char name[40];
char app_ver[16];
} __attribute__((packed));
static int json_get_string(const uint8_t *json, size_t json_len,
const char *key, char *out, size_t out_max) {
if (out_max == 0) return -1;
out[0] = '\0';
char needle[64];
int nl = snprintf(needle, sizeof(needle), "\"%s\"", key);
if (nl <= 0 || (size_t)nl >= sizeof(needle)) return -1;
size_t need_len = (size_t)nl;
const uint8_t *found = NULL;
for (size_t i = 0; i + need_len <= json_len; i++) {
if (memcmp(json + i, needle, need_len) == 0) {
found = json + i + need_len;
break;
}
}
if (!found) return -2;
const uint8_t *end = json + json_len;
while (found < end && (*found == ' ' || *found == ':' || *found == '\t'
|| *found == '\n' || *found == '\r')) {
found++;
}
if (found >= end || *found != '"') return -1;
found++;
size_t copy = 0;
while (found < end && *found != '"' && copy < out_max - 1) {
out[copy++] = (char)(*found++);
}
out[copy] = '\0';
return 0;
}
int console_foreground_app_handle(int fd, struct cmd_packet *packet) {
(void)packet;
struct cmd_console_foreground_app_response resp;
memset(&resp, 0, sizeof(resp));
intptr_t allproc_head = (intptr_t)KERNEL_ADDRESS_ALLPROC;
intptr_t kproc = 0;
if (kernel_copyout_fast(allproc_head, &kproc, sizeof(kproc)) != 0) {
net_send_int32(fd, CMD_DATA_NULL);
return 1;
}
intptr_t found_proc = 0;
int32_t found_pid = 0;
intptr_t cur = kproc;
for (uint32_t guard = 0; cur != 0 && guard < 0x1000; guard++) {
char nm[PROC_SELFINFO_NAME_SIZE + 1];
nm[PROC_SELFINFO_NAME_SIZE] = 0;
if (kernel_copyout_fast(cur + PROC_SELFINFO_NAME_OFFSET, nm, PROC_SELFINFO_NAME_SIZE) == 0
&& nm[0] != 0
&& strncmp(nm, "eboot.bin", 9) == 0
&& (nm[9] == '\0' || nm[9] == ' ')) {
int32_t pid_val = 0;
kernel_copyout_fast(cur + KERNEL_OFFSET_PROC_P_PID, &pid_val, sizeof(pid_val));
if (pid_val > 0) {
found_proc = cur;
found_pid = pid_val;
break;
}
}
intptr_t next = 0;
if (kernel_copyout_fast(cur + PROC_NEXT_OFFSET, &next, sizeof(next)) != 0) break;
cur = next;
}
if (found_proc == 0) {
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, &resp, sizeof(resp));
return 0;
}
void *proc_buf = kernel_get_proc_struct_fast((uint32_t)found_pid);
if (!proc_buf) {
resp.pid = (uint32_t)found_pid;
memcpy(resp.name, "eboot.bin", 9);
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, &resp, sizeof(resp));
return 0;
}
const uint8_t *p = (const uint8_t *)proc_buf;
resp.pid = (uint32_t)found_pid;
memcpy(resp.name, p + PROC_SELFINFO_NAME_OFFSET, PROC_SELFINFO_NAME_SIZE);
memcpy(resp.titleid, p + PROC_TITLE_ID_OFFSET, sizeof(resp.titleid));
memcpy(resp.contentid, p + PROC_CONTENT_ID_OFFSET, sizeof(resp.contentid));
char titleid_z[17];
memcpy(titleid_z, resp.titleid, 16);
titleid_z[16] = '\0';
char meta_dir[96];
snprintf(meta_dir, sizeof(meta_dir), "/system_data/priv/appmeta/%s", titleid_z);
char sfo_path[120];
char json_path[120];
snprintf(sfo_path, sizeof(sfo_path), "%s/param.sfo", meta_dir);
snprintf(json_path, sizeof(json_path), "%s/param.json", meta_dir);
uint8_t *meta_buf = (uint8_t *)net_alloc_buffer(8192);
if (meta_buf) {
size_t meta_len = 0;
char ver_a[16] = {0}, ver_b[16] = {0};
const char *pick = NULL;
if (sfo_read_file(sfo_path, meta_buf, 8192, &meta_len) == 0 && meta_len >= 20
&& meta_buf[0] == 0 && meta_buf[1] == 'P'
&& meta_buf[2] == 'S' && meta_buf[3] == 'F') {
sfo_get_string(meta_buf, meta_len, "APP_VER", ver_a, sizeof(ver_a));
sfo_get_string(meta_buf, meta_len, "VERSION", ver_b, sizeof(ver_b));
if (ver_a[0] && ver_b[0]) pick = (strcmp(ver_a, ver_b) >= 0) ? ver_a : ver_b;
else if (ver_a[0]) pick = ver_a;
else if (ver_b[0]) pick = ver_b;
}
if (!pick) {
meta_len = 0;
if (sfo_read_file(json_path, meta_buf, 8192, &meta_len) == 0 && meta_len >= 2
&& meta_buf[0] == '{') {
json_get_string(meta_buf, meta_len, "contentVersion", ver_a, sizeof(ver_a));
json_get_string(meta_buf, meta_len, "originContentVersion", ver_b, sizeof(ver_b));
if (ver_a[0] && ver_b[0]) pick = (strcmp(ver_a, ver_b) >= 0) ? ver_a : ver_b;
else if (ver_a[0]) pick = ver_a;
else if (ver_b[0]) pick = ver_b;
if (!pick) {
json_get_string(meta_buf, meta_len, "masterVersion", ver_a, sizeof(ver_a));
if (ver_a[0]) pick = ver_a;
}
}
}
if (pick) {
size_t plen = strlen(pick);
if (plen >= sizeof(resp.app_ver)) plen = sizeof(resp.app_ver) - 1;
memcpy(resp.app_ver, pick, plen);
resp.app_ver[plen] = '\0';
}
free(meta_buf);
}
free(proc_buf);
net_send_int32(fd, CMD_SUCCESS);
net_send_all(fd, &resp, sizeof(resp));
return 0;
}
int console_handle(int fd, struct cmd_packet *packet) {
switch (packet->cmd) {
case 0xBDDD0001u: return console_reboot_handle(fd, packet);
case 0xBDDD0002u: return console_end_handle(fd, packet);
case 0xBDDD0003u: return console_print_handle(fd, packet);
case 0xBDDD0004u: return console_notify_handle(fd, packet);
case 0xBDDD0005u: return console_info_handle(fd, packet);
case 0xBDDD0006u: return console_foreground_app_handle(fd, packet);
default: return 1;
}
}
__attribute__((noinline))
void klog_format_log(const char *fmt, ...)
{
char formatted[300];
va_list ap;
va_start(ap, fmt);
vsprintf(formatted, fmt, ap);
va_end(ap);
printf("[ps5debug] %s\n", formatted);
}
struct klog_sin {
uint8_t sin_len;
uint8_t sin_family;
uint16_t sin_port;
uint32_t sin_addr;
uint8_t sin_zero[8];
};
int g_klog_listen_fd = -1;
__attribute__((noinline))
void *klog_server_thread(void *arg)
{
(void)arg;
int s = sceNetSocket((const char *)0, 2, 1, 0);
if (s < 0) return (void *)0;
int one = 1;
sceNetSetsockopt(s, 0xFFFF, 0x0004, &one, 4);
sceNetSetsockopt(s, 0xFFFF, 0x0200, &one, 4);
sceNetSetsockopt(s, 0xFFFF, 0x0008, &one, 4);
struct klog_sin sin;
memset(&sin, 0, 16);
sin.sin_family = 2;
sin.sin_port = 0xA00C;
sin.sin_addr = 0;
if (sceNetBind(s, &sin, 16) < 0) return (void *)0;
if (sceNetListen(s, 5) < 0) return (void *)0;
g_klog_listen_fd = s;
char rbuf[0x1000];
struct timespec ts;
for (;;) {
scePthreadYield();
int connfd = sceNetAccept(s, (void *)0, (void *)0);
if (connfd < 0) {
int err = *sceNetErrnoLoc();
if (err == 0xA3 || err == EBADF) {
sceNetSocketClose(s);
g_klog_listen_fd = -1;
return (void *)0;
}
continue;
}
if (connfd == 0) continue;
int klog_fd = open("/dev/klog", 0, 0);
if (klog_fd == -1) {
write(connfd, "Cannot open /dev/klog\n", 0x17);
return (void *)0;
}
write(connfd, "Successfully opened klog!\n", 0x0A);
for (;;) {
ts.tv_sec = 0;
ts.tv_nsec = 100000000L;
nanosleep(&ts, (struct timespec *)0);
int err_val = 0;
unsigned int err_len = 4;
int gso = sceNetGetsockopt(connfd, 0xFFFF, 0x1007, &err_val, &err_len);
if ((gso | err_val) != 0) break;
long n = read(klog_fd, rbuf, 0xFFF);
if (n > 0) {
rbuf[n] = 0;
long expect = n + 1;
if (write(connfd, rbuf, (unsigned long)expect) != expect)
break;
}
}
klog_format_log("klog client disconnected");
close(klog_fd);
close(connfd);
}
}