Skip to content

Instantly share code, notes, and snippets.

@Al-Azif
Last active March 2, 2025 06:21
Show Gist options
  • Save Al-Azif/71c4bed7c64ae225755ded2c5de6671e to your computer and use it in GitHub Desktop.
Save Al-Azif/71c4bed7c64ae225755ded2c5de6671e to your computer and use it in GitHub Desktop.
// Author: Al Azif
// Version: 1.0.0
// Published: 2025-03-01
// GitHub: https://github.com/Al-Azif/
// Contact: https://x.com/_alazif
// Changelog:
// 0.9.0 (2025-02-28)
// - Initial Release
// 1.0.0b (2025-03-01)
// - Cleaned up code
// - Added comments
// - Published source
// - Added PS5 support
// 1.0.0 (2025-03-01)
// - Add Makefile for both PS4 and PS5
// - Get Model for PS5 without sflash0 access
// Compiling for PS4 or PS5?
#if !defined(COMPILE_FOR_PS4) && !defined(COMPILE_FOR_PS5)
#error "Define either `COMPILE_FOR_PS4` or `COMPILE_FOR_PS5`"
#endif
#if defined(COMPILE_FOR_PS4) && defined(COMPILE_FOR_PS5)
#error "Define either `COMPILE_FOR_PS4` or `COMPILE_FOR_PS5` not both"
#endif
// Set headers and constants based on which console we're building for
#if defined(COMPILE_FOR_PS4)
// https://github.com/Scene-Collective/ps4-payload-sdk
#include "ps4.h"
#define SFLASH0_SIZE 0x2000000
#define BLOCK_SIZE 0x200
#define SERIAL_OFFSET 0x1C80C0
#define MODEL_OFFSET 0x1C8041
#define NOTIFICATION(...) printf_notification(__VA_ARGS__)
// Included in the PS4 Payload SDK:
// - printf_notification()
// - jailbreak()
#elif defined(COMPILE_FOR_PS5)
// https://github.com/ps5-payload-dev/sdk
#include <fcntl.h> // open
#include <ps5/kernel.h> // kernel_get_root_vnode, kernel_set_proc_jaildir,
// kernel_set_proc_rootdir, kernel_set_ucred_caps,
// kernel_set_ucred_uid
#include <stddef.h> // size_t
#include <stdint.h> // intptr_t, uint8_t
#include <stdio.h> // snprintf, sprintf
#include <stdlib.h> // free, malloc
#include <string.h> // memcpy, memset, strcpy, strlen
#include <sys/sysctl.h> // sysctlbyname
#include <sys/types.h> // off_t
#include <unistd.h> // close, getpid, lseek, read, usleep, write
#define SFLASH0_SIZE 0x200000
#define BLOCK_SIZE 0x200
#define SERIAL_OFFSET 0x1C7250
#define MODEL_OFFSET 0x1C7230
// Notification setup taken from PS4 Payload SDK, same as printf_notification
typedef struct {
int type; //0x00
int req_id; //0x04
int priority; //0x08
int msg_id; //0x0C
int target_id; //0x10
int user_id; //0x14
int unk1; //0x18
int unk2; //0x1C
int app_id; //0x20
int error_num; //0x24
int unk3; //0x28
char use_icon_image_uri; //0x2C
char message[1024]; //0x2D
char uri[1024]; //0x42D
char unkstr[1024]; //0x82D
} SceNotificationRequest; //Size = 0xC30
int sceKernelSendNotificationRequest(int device, SceNotificationRequest *req, size_t size, int blocking);
#define NOTIFICATION(...) \
do { \
SceNotificationRequest noti_buffer; \
char icon_uri[38] = "cxml://psnotification/tex_icon_system"; \
noti_buffer.type = 0; \
noti_buffer.unk3 = 0; \
noti_buffer.use_icon_image_uri = 1; \
noti_buffer.target_id = -1; \
snprintf(noti_buffer.uri, sizeof(noti_buffer.uri), icon_uri); \
snprintf(noti_buffer.message, sizeof(noti_buffer.message), ##__VA_ARGS__); \
sceKernelSendNotificationRequest(0, (SceNotificationRequest *)&noti_buffer, sizeof(noti_buffer), 0); \
} while (0)
int jailbreak() {
pid_t pid = getpid();
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;
}
#endif
inline off_t max(off_t a, off_t b) {
return a > b ? a : b;
}
inline off_t align_up(off_t x, off_t align) {
off_t y = max(x, align);
return (y + (align - 1)) & ~(align - 1);
}
inline off_t align_down(off_t x, off_t align) {
off_t y = max(x, align);
return y & ~(align - 1);
}
// Finds the first space char ('\x20') in a string and changes it, as well as
// any following chars, to a null terminator
void fix_space_termination(char *output, size_t length) {
int found = 0;
for (size_t i = 0; i < length; i++) {
if (found) {
output[i] = '\0';
continue;
}
if (output[i] == '\x20') {
output[i] = '\0';
found = 1;
}
}
}
// Checks if `/dev/sflash0` can be accessed
int sflash0_accessible() {
int fd = open("/dev/sflash0", O_RDONLY | O_NONBLOCK, 0);
if (fd < 0) {
return 0;
}
close(fd);
return 1;
}
// Read values from sflash0... it's not as straightforward as you may think
void read_sflash0(char *output, off_t offset, size_t length) {
// Open `/dev/sflash0` as read only, non-blocking
int fd = open("/dev/sflash0", O_RDONLY | O_NONBLOCK, 0);
if (fd < 0) {
NOTIFICATION("Error opening `/dev/sflash0`");
return;
}
// This should never trigger, if it does something is seriously wrong
if (lseek(fd, 0, SEEK_END) != SFLASH0_SIZE) {
NOTIFICATION("Error, incorrect sflash0 size");
close(fd);
return;
}
// Check the size of the input offset and length vs the size of sflash0 to
// prevent reading out of bounds
if (offset + length > SFLASH0_SIZE) {
NOTIFICATION("Error, set offset and length will go read of bounds");
close(fd);
return;
}
/////////////////////////////////////////////////////////////////////////////
// The following is done in a weird way, for a reason
/////////////////////////////////////////////////////////////////////////////
// Seek to our offset within sflash0, aligned down to the nearest block size
// We know this will succeed because of the previous checks
lseek(fd, align_down(offset, BLOCK_SIZE), SEEK_SET);
// Create a buffer the size of our length, aligned up to the nearest block
// size
char *buffer = malloc(align_up(length, BLOCK_SIZE));
if (!buffer) {
NOTIFICATION("Error allocating read buffer");
close(fd);
return;
}
// Read sflash0, from the current location, into the created buffer, read
// length aligned up to the nearest block size
if (read(fd, buffer, align_up(length, BLOCK_SIZE)) != align_up(length, BLOCK_SIZE)) {
NOTIFICATION("Error reading `/dev/sflash0`");
memset(output, '\0', length);
free(buffer);
close(fd);
return;
}
close(fd);
// Copy the buffer from the actual offset we input, minus the aligned offset,
// that we actually read from.
// Ex.
// We wanted 0x208, the block size is 0x200, we read from 0x200 as it is
// 0x208 aligned down, 0x200 became 0x0 for the buffer. Meaning we want
// to copy length starting from 0x8
memcpy(output, &buffer[offset - align_down(offset, BLOCK_SIZE)], length);
free(buffer);
}
// String returned is 27 chars in length, including the null terminator
void get_idps(char *output) {
unsigned char idps[16] = {0};
// The two lines below are `sceKernelGetIdPs(idps);`
size_t idps_len = sizeof(idps);
sysctlbyname("machdep.idps", (char *)idps, &idps_len, 0, 0);
for (int i = 0; i < 9; i++) {
sprintf(&output[i * 3], "%02X ", idps[i]);
}
output[26] = '\0'; // Replacing the space at the end of the string
}
// String returned is 33 chars in length (max), including the null terminator
void get_model(char *output) {
char model[33] = {0};
size_t model_len = sizeof(model) - 1;
sysctlbyname("machdep.icc.hw_model", (char *)model, &model_len, 0, 0);
fix_space_termination(model, sizeof(model));
memcpy(output, model, model_len);
}
// A loop to look for USB drives, if one is found it returns it's name and path
// This is the same as the `wait_for_usb` function in the PS4 Payload SDK
int await_usb(char *usb_name, char *usb_path) {
int row = 0;
char probefile[19] = {0};
int fd = -1;
while (fd == -1) {
usleep(100 * 1000);
if (row >= 80) { // 10 attempts at each USB #, reaching 8 resets to 0
row = 0;
} else {
row += 1;
}
snprintf(probefile, sizeof(probefile), "/mnt/usb%i/.probe", row / 10);
fd = open(probefile, O_WRONLY | O_CREAT | O_TRUNC, 0777);
}
close(fd);
unlink(probefile);
sprintf(usb_name, "USB%i", row / 10);
sprintf(usb_path, "/mnt/usb%i", row / 10);
return 1;
}
// Write output to path, formatting the data for the wiki before doing so
// https://www.psdevwiki.com/ps4/Serial_Database
void write_output(char *path, char *serial, char *idps, char *model) {
// Open file creating if it doesn't exist and overwriting if it does
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0777);
if (fd < 0) {
NOTIFICATION("Unabled to create data file");
return;
}
// serial(13) + idps(26) + model(32) + format(13) + null-terminator(1) = 85
char output[85] = {0};
sprintf(output, "| %s || %s || %s ||", serial, idps, model);
if (write(fd, output, strlen(output)) < 0) {
NOTIFICATION("Error writing data file");
close(fd);
return;
}
close(fd);
NOTIFICATION("Data file saved successfully");
}
#if defined(COMPILE_FOR_PS4)
int _main(struct thread *td) {
UNUSED(td);
// Initialize Kernel and Libc functions
initKernel();
initLibc();
#elif defined(COMPILE_FOR_PS5)
int main() {
#endif
// Need access to `/dev/sflash0` and USB devices
jailbreak();
// Partial IDPS
char idps[27] = {0};
get_idps(idps);
if (strlen(idps) == 0) {
strcpy(idps, "ERROR");
}
if (sflash0_accessible()) {
// Partial Serial
char serial[14] = {0};
read_sflash0(serial, SERIAL_OFFSET, sizeof(serial) - 1);
if (strlen(serial) == 0) {
strcpy(serial, "ERROR");
}
// Model
char model[33] = {0};
read_sflash0(model, MODEL_OFFSET, sizeof(model) - 1);
// The model string is terminated by a space... let's fix that
fix_space_termination(model, sizeof(model));
if (strlen(model) == 0) {
strcpy(model, "ERROR");
}
NOTIFICATION("Partial Serial:\n%s", serial);
NOTIFICATION("Partial IDPS:\n%s", idps);
NOTIFICATION("Model Number:\n%s", model);
// We can save the data anywhere by just calling the `write_output` function
// with a custom path rather than looking for a USB device. However, we are
// going to look for a USB device this time.
// Save the output to a USB drive, if it exists
char usb_name[5] = {0};
char usb_path[10] = {0};
if (await_usb(usb_name, usb_path)) {
NOTIFICATION("USB device detected\n\nSaving data to %s", usb_name);
char output_path[19] = {0};
sprintf(output_path, "%s/data.txt", usb_path);
write_output(output_path, serial, idps, model);
}
} else {
NOTIFICATION("Partial IDPS:\n%s", idps);
#if defined(COMPILE_FOR_PS5)
char model[33] = {0};
get_model(model);
NOTIFICATION("Model Number:\n%s", model);
#endif
}
return 0;
}
LIBPS4 := $(PS4SDK)/libPS4
CC := gcc
OBJCOPY := objcopy
ODIR := build
SDIR := source
IDIRS := -I$(LIBPS4)/include -Iinclude
LDIRS := -L$(LIBPS4)
MAPFILE := $(shell basename "$(CURDIR)")-ps4.map
CFLAGS := $(IDIRS) -Os -std=c11 -ffunction-sections -fdata-sections -fno-builtin -nostartfiles -nostdlib -Wall -Wextra -Werror -masm=intel -march=btver2 -mtune=btver2 -m64 -mabi=sysv -mcmodel=small -fpie -fPIC -DCOMPILE_FOR_PS4
LFLAGS := $(LDIRS) -Xlinker -T $(LIBPS4)/linker.x -Xlinker -Map="$(MAPFILE)" -Wl,--build-id=none -Wl,--gc-sections
CFILES := $(wildcard $(SDIR)/*.c)
SFILES := $(wildcard $(SDIR)/*.s)
OBJS := $(patsubst $(SDIR)/%.c, $(ODIR)/%.o, $(CFILES)) $(patsubst $(SDIR)/%.s, $(ODIR)/%.o, $(SFILES))
LIBS := -lPS4
TARGET = $(shell basename "$(CURDIR)")-ps4.bin
$(TARGET): $(ODIR) $(OBJS)
$(CC) $(LIBPS4)/crt0.s $(ODIR)/*.o -o temp.t $(CFLAGS) $(LFLAGS) $(LIBS)
$(OBJCOPY) -O binary temp.t "$(TARGET)"
rm -f temp.t
$(ODIR)/%.o: $(SDIR)/%.c
$(CC) -c -o $@ $< $(CFLAGS)
$(ODIR)/%.o: $(SDIR)/%.s
$(CC) -c -o $@ $< $(CFLAGS)
$(ODIR):
@mkdir $@
.PHONY: clean
clean:
rm -rf "$(TARGET)" "$(MAPFILE)" $(ODIR)
include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
ELF := $(shell basename "$(CURDIR)")-ps5.elf
CFLAGS := -Os -std=c11 -Wall -Wextra -Wno-format-security -Werror -fpie -fPIC -DCOMPILE_FOR_PS5
LIBS := -lkernel_sys
all: $(ELF)
$(ELF): source/main.c
$(CC) $(CFLAGS) $(LIBS) -o $@ $^
clean:
rm -f $(ELF)
@Al-Azif
Copy link
Author

Al-Azif commented Mar 1, 2025

About

Shows Partial Serial , Partial IDPS, and Model as a notification, it will also save the data to a USB device, if one is available, at /data.txt. The data.txt file is preformatted for pasting here: PS4 and PS5

Function Table

Partial Serial Partial IDPS Model Save to USB Notes
PS4 (Access to /dev/sflash0) X X X X
PS4 (Only Userland) Untested
PS5 (Access to /dev/sflash0) X X X X
PS5 (Only Userland) X X

Building

PS4

Uses the PS4 Payload SDK
make -f Makefile.ps4

PS5

Uses the PS5 Payload SDK
make -f Makefile.ps5

Notes

  • If you get 00 00 00 01 02 [82] 00 XX XX then the exploit you are using is spoofing your consoles target ID to be that of a testkit, 82 and lower is developer hardware, if you have retail hardware you should not be getting developer target IDs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment