Il developer earthonion ci invita a scoprire un nuovo exploit userland chiamato Prosper Together, progettato per funzionare su PS4 e PS5 sfruttando la versione Console Edition di Don’t Starve Together.

Questo progetto si distingue nella scena homebrew perché utilizza un approccio accessibile e ingegnoso: invece di basarsi su vulnerabilità di sistema complesse, sfrutta il sistema di salvataggi del gioco per ottenere l’esecuzione di codice arbitrario direttamente in ambiente userland.
Don't Starve Together userland exploit by earthonion. Does not work with NetCtrl since it's a normal game, it does not have dup. https://t.co/IlvItz7pYi
— Dr.Yenyen (@calmboy2019) April 14, 2026
Il cuore dell’exploit risiede nella modifica dei file di salvataggio attraverso uno script Python, che consente di iniettare codice Lua appositamente costruito all’interno dei metadati del gioco.
Una volta importato il salvataggio sulla console e avviata una partita, il codice viene eseguito e sfrutta alcune caratteristiche di LuaJIT per uscire dal contesto sandbox previsto dagli sviluppatori, accedendo così all’ambiente globale del gioco.
Da qui si apre una fase molto più avanzata, in cui vengono costruite primitive di memoria come addrof e fakeobj. Queste tecniche permettono di leggere e scrivere direttamente nella memoria della console, offrendo un controllo significativo sul comportamento del sistema.
Grazie a questo livello di accesso, è possibile creare catene ROP e invocare syscall del sistema FreeBSD, trasformando di fatto il gioco in un punto di ingresso per operazioni ben più profonde rispetto a quelle normalmente consentite.
Un ulteriore elemento interessante è la componente di rete: una volta attivo, l’exploit apre una connessione TCP sulla porta 9066, consentendo l’invio di payload Lua in tempo reale da un PC collegato alla stessa rete locale.
Questo rende possibile eseguire comandi remoti, avviare servizi come un server FTP, oppure sviluppare strumenti di debug e automazione direttamente sulla console.
Dal punto di vista pratico, l’utilizzo richiede pochi elementi essenziali: una copia compatibile del gioco, Python per generare il salvataggio modificato, una connessione di rete locale e un salvataggio di partenza da alterare.
È fondamentale sottolineare che Prosper Together non rappresenta un jailbreak completo, poiché non fornisce accesso al kernel. Tuttavia, proprio per questo, si rivela uno strumento estremamente utile per lo sviluppo e il testing in ambiente userland.
Come funziona
- Iniezione del salvataggio –
make_save.pyinietta codice Lua nei file.metadi un salvataggio di DST - Escape dalla sandbox – Il payload è racchiuso in
loadstring([[...]])()che evadeRunInSandboxdi DST nell’ambiente globale_G. - Primitive – La manipolazione del bytecode di LuaJIT fornisce
fakeobj/addrof, che avviano lettura/scrittura arbitraria, poicallesyscallbasate su ROP. - TCP loader – Un server socket sulla porta 9066 accetta ed esegue script Lua a runtime
Avvio rapido
Costruire il salvataggio
cd save-gen
python3 make_save.py
# Output: build_dst/savedata.dat
Andare a hostare una partita
Inviare payload a runtime
Una volta eseguito il gioco con il salvataggio iniettato, il TCP loader resterà in ascolto sulla porta 9066. Inviare qualsiasi script Lua: cat payloads/ftp_server.lua | nc -q 0 <ps5_ip> 9066
Oppure eseguire one-liner: echo 'return eboot_base' | nc -q 0 <ps5_ip> 9066
Riferimento API
Tutte le primitive sono esportate come globali. Inviare script tramite il TCP loader sulla porta 9066.
Lettura memoria
read_bytes_abs(addr, count) → table o nil
Legge byte grezzi da un indirizzo assoluto. Restituisce una tabella di valori byte {b1, b2, ...} oppure nil in caso di errore.
local bytes = read_bytes_abs(0x19838000, 4)
-- bytes = {127, 69, 76, 70} (ELF magic)
read_u32_abs(addr) → number o nil
Legge un intero unsigned a 32 bit (little-endian).
local magic = read_u32_abs(eboot_base)
-- magic = 0x464C457F (ELF magic)
read_u64_abs(addr) → lo, hi o nil, nil
Legge un valore a 64 bit come due metà da 32 bit.
local lo, hi = read_u64_abs(eboot_base)
-- lo = 32 bit bassi, hi = 32 bit alti
-- valore completo = hi * 0x100000000 + lo
hexread(addr, count) → string
Dump esadecimale della memoria. Restituisce byte hex separati da spazio oppure "ERR".
local dump = hexread(eboot_base, 16)
-- "7f 45 4c 46 02 01 01 09 00 00 00 00 00 00 00 00"
Scrittura memoria
write_double(addr, value) → boolean
Scrive un double IEEE 754 da 8 byte a un indirizzo. Il valore deve essere un numero Lua. Restituisce true in caso di successo.
write_double(some_addr, 0) -- scrive 8 byte a zero (0.0 come double)
write_double(some_addr, 1.5) -- scrive il double 1.5
Nota: Questo scrive un float a doppia precisione, non un intero grezzo. Per scrivere pattern di byte raw, usare fakeobj per creare un valore con la rappresentazione di bit desiderata, poi passalo a write_double.
get_str_addr(s) → number
Ottiene l’indirizzo di memoria dell’oggetto GCstr di una stringa Lua. Il contenuto reale della stringa inizia all’offset +24.
local buf = string.rep("\0", 256)
local buf_addr = get_str_addr(buf) + 24 -- puntatore a 256 byte zero
Questo è il metodo principale per allocare buffer di memoria per le syscall:
-- Alloca un buffer e ottiene il suo indirizzo
local my_buf = string.rep("\0", 4096)
local my_buf_ptr = get_str_addr(my_buf) + 24
-- Usa come argomento syscall
syscall(3, fd, my_buf_ptr, 4096) -- read(fd, buf, 4096)
Manipolazione oggetti
addrof(obj) → number o nil
Ottiene l’indirizzo di memoria di un oggetto Lua (table, function, userdata, ecc..).
local addr = addrof(TheNet)
-- addr = 0x12345678
fakeobj(hi32, addr) → value o nil
Crea un valore Lua falso da componenti raw di puntatore taggato. hi32 sono i 32 bit alti (type tag), addr sono i 32 bit bassi (puntatore).
-- Crea un oggetto stringa falso all’indirizzo 0xDEADBEEF
local fake_str = fakeobj(0xFFFD8000, 0xDEADBEEF)
-- Crea un oggetto table falso
local fake_tab = fakeobj(0xFFFA0000, some_addr)
Tag comuni:
| Tag | Tipo |
|---|---|
0xFFFD8000 | string |
0xFFFA0000 | table |
0 | lightuserdata / numero (bit bassi) |
Chiamare funzioni
call(func_lo, func_hi, a1, a2, a3, a4, a5, a6) → ret_lo, ret_hi
Chiama una funzione arbitraria a un indirizzo assoluto. Fino a 6 argomenti (rdi, rsi, rdx, rcx, r8, r9). Argomenti e valore di ritorno sono divisi in coppie 32 bit lo/hi. Passa argomenti a 64 bit come tabelle {lo, hi}.
-- Chiama una funzione a un indirizzo noto
local ret_lo, ret_hi = call(func_lo, func_hi, arg1, arg2)
-- Esempio: call memcpy(dst, src, len)
local memcpy_lo = libc_base_lo + 0x32600
local memcpy_hi = libc_base_hi
call(memcpy_lo, memcpy_hi, dst_addr, src_addr, length)
-- Esempio: call con 6 argomenti
local ret_lo, ret_hi = call(func_lo, func_hi, a1, a2, a3, a4, a5, a6)
Syscall
syscall(num, a1, a2, a3, a4, a5, a6) → ret_lo, ret_hi
Esegue una syscall FreeBSD con fino a 6 argomenti. Restituisce il risultato grezzo; ret_lo >= 0x80000000 indica errore (ritornato -1 dal kernel).
-- getpid (syscall 20)
local pid = syscall(20)
-- open (syscall 5)
local path = "/dev/notification0\0"
local path_addr = get_str_addr(path) + 24
local fd = syscall(5, path_addr, 0, 0) -- open(path, O_RDONLY, 0)
-- read (syscall 3)
local buf = string.rep("\0", 256)
local buf_addr = get_str_addr(buf) + 24
local n = syscall(3, fd, buf_addr, 256)
-- write (syscall 4)
local msg = "hello\n"
local msg_addr = get_str_addr(msg) + 24
syscall(4, fd, msg_addr, #msg)
-- close (syscall 6)
syscall(6, fd)
-- socket (syscall 97)
local sock = syscall(97, 2, 1, 0) -- AF_INET, SOCK_STREAM, 0
-- setsockopt (5 argomenti)
local enable = "\x01\x00\x00\x00\x00\x00\x00\x00"
local enable_ptr = get_str_addr(enable) + 24
syscall(105, sock_fd, 0xFFFF, 4, enable_ptr, 4)
-- mmap (6 argomenti)
local addr_lo, addr_hi = syscall(477, 0, 0x4000, 3, 0x1022, -1, 0)
Numeri syscall comuni:
| Numero | Nome |
|---|---|
| 3 | read |
| 4 | write |
| 5 | open |
| 6 | close |
| 20 | getpid |
| 30 | accept |
| 37 | kill |
| 97 | socket |
| 104 | bind |
| 105 | setsockopt |
| 106 | listen |
| 188 | stat |
| 209 | poll |
| 272 | getdents |
| 477 | mmap |
Controllo errori
Gli errori syscall restituiscono -1 (lo stub libkernel gestisce il carry flag). In pratica, controlla ret_lo >= 0x80000000:
local fd = syscall(5, path_addr, 0, 0)
if not fd or fd >= 0x80000000 then
-- errore
end
Notifiche
notify(message)
Invia una notifica popup di sistema PS5.
notify("Hello from Lua!")
Logging
rlog(tag, message)
Log nella barra annunci in-game e nella socket TCP (se connessa).
rlog("info", "qualcosa è successo")
-- mostra: [info] qualcosa è successo
prosper_log(message)
Log nella console a schermo di Prosper Together.
prosper_log("FTP server avviato sulla porta 1337")
Globali
| Globale | Tipo | Descrizione |
|---|---|---|
eboot_base | Numero | Indirizzo base dell’eseguibile del gioco |
region | Stringa | "US" o "EU" |
libc_base_lo | Numero | Indirizzo base libc (32 bit bassi) |
libc_base_hi | Numero | Indirizzo base libc (32 bit alti) |
libkernel_base_lo | Numero | Base libkernel (32 bit bassi) |
libkernel_base_hi | Numero | Base libkernel (32 bit alti) |
syscall_lo | Numero | Indirizzo stub syscall (32 bit bassi) |
syscall_hi | Numero | Indirizzo stub syscall (32 bit alti) |
my_ip | Stringa | Indirizzo IP PS5 rilevato (es. "192.168.0.101") |
Utility
Shutdown()
Termina il processo del gioco (chiama kill(getpid(), SIGKILL)).
Shutdown()
Payload
FTP Server (payloads/ftp_server.lua)
Server FTP completo sulla porta 1337. Eseguire la connessione con qualsiasi client FTP (FileZilla, lftp, ecc..).
cat payloads/ftp_server.lua | nc -q 0 <ps5_ip> 9066
Configurazione FileZilla: Gestione siti > Generale > Crittografia: “Usa solo FTP semplice (non sicuro)”
Supporta: USER, PASS, PWD, CWD, CDUP, LIST, RETR, STOR, PASV, PORT, TYPE, SIZE, DELE, MKD, RMD, RNFR, RNTO, REST, SITE CHMOD, FEAT, SYST, QUIT
Esempio completo
-- Leggi header ELF
local magic = hexread(eboot_base, 16)
prosper_log("ELF: " .. magic)
-- Ottieni PID
local pid = syscall(20)
prosper_log("PID: " .. tostring(pid))
-- Leggi un file
local path = "/savedata0/savedata.dat\0"
local path_ptr = get_str_addr(path) + 24
local fd = syscall(5, path_ptr, 0, 0)
if fd and fd < 0x80000000 then
local buf = string.rep("\0", 128)
local buf_ptr = get_str_addr(buf) + 24
local n = syscall(3, fd, buf_ptr, 128)
prosper_log("Letti " .. tostring(n) .. " byte")
prosper_log(hexread(buf_ptr, 32))
syscall(6, fd)
end
-- Invia una notifica
notify("Exploit in esecuzione! PID=" .. tostring(pid))
Download: savedata.zip
Download: Source code Prosper Together
Alcune parti di questo articolo sono state generate con l’aiuto dell’intelligenza artificiale. Questo articolo contiene link affiliati a Amazon. Se acquisti tramite questi link, potrei guadagnare una commissione senza costi aggiuntivi per te.🔥 Prodotti in promozione e articoli più venduti: PS4
Vedi altri prodotti PS4
Ultimo aggiornamento 2026-05-13 / Link di affiliazione / Immagini da Amazon Product Advertising API
![[Scena PS4/PS5] Rilasciato ftpsrv v0.20 con nuove opzioni CLI, rebuild SDK PS4/PS5 e shell UI installer su PS5](https://www.biteyourconsole.net/wp-content/uploads/FTPS5A-238x178.webp)

![[Scena PSP] Apollo Save Tool PSP si aggiorna alla versione 2.3.2: upload FTP multiplo, nuove lingue e Apollo Patch Engine 2.0.4](https://www.biteyourconsole.net/wp-content/uploads/ApolloSaveToolPSP-238x178.webp)
![[Scena PS4/PS5] Rilasciato ftpsrv v0.20 con nuove opzioni CLI, rebuild SDK PS4/PS5 e shell UI installer su PS5](https://www.biteyourconsole.net/wp-content/uploads/FTPS5A-100x75.webp)

![[Scena PSP] Apollo Save Tool PSP si aggiorna alla versione 2.3.2: upload FTP multiplo, nuove lingue e Apollo Patch Engine 2.0.4](https://www.biteyourconsole.net/wp-content/uploads/ApolloSaveToolPSP-100x75.webp)