Home News Analisi accurata sulla sicurezza della Playstation 4 e stato della scena hack

Analisi accurata sulla sicurezza della Playstation 4 e stato della scena hack

177
8

Il developer e hacker cturt ha riportato un’accurata analisi sul funzionamento della Playstation 4, sul fronte hack ci si è soffermati a quello che sembra essere l’exploit webkit, in genere presente un po’ su tutte le console più recenti.

11873519_10204611077897574_3062064951489023823_n

L’hacker pare abbia svolto diverse prove sulla propria console con scoperte che hanno portato all’esecuzione di prove di ROP (Render Output Unit).

Se non si ha particolare familiarità con gli exploit, vi consigliamo di leggere l’articolo sugli exploit dei giochi DS attraverso lo stack nella vulnerabilità dei file di salvataggio (in lingua inglese).

Si può provare a scaricare il mio setup completo qui per eseguire questi test voi stessi; esso è attualmente solo per il firmware 1.76 al momento.

Informazioni base Playstation 4

La console PS4 dispone di una CPU AMD x86-64 personalizzata (a 8 core), e ci sono un sacco di ricerche disponibili per questa architettura, anche se questa versione specifica potrebbe non corrispondere perfettamente agli standard noti.
Ad esempio, PFLA (Page Fault Liberation Army) ha pubblicato un proof of concept implementato su di una macchina di Turing completa, utilizzando solo gli errori della pagina e  x86 MMU durante il congresso 29C3, controllare il loro impressionante video su YouTube. Interessante anche se si sta cercando di eseguire il codice all'interno di una macchina virtuale e si desidera eseguire istruzioni sulla CPU.

La console ha inoltre un’architettura della CPU ben documentata, e gran parte del software utilizzato dalla Playstation 4 è completamente open source. In particolare il sistema operativo PS4 Orbis è basato sul FreeBSD, proprio come per la PS3 (con in più parti di NetBSD); ma così come anche FreeBSD 9.0, un altro software inferme utilizzato comprende Mono VM, e WebKit.

Punto di ingresso webKit

WebKit è il motore di layout open source che lavora sulle pagine web del browser per iOS, Wii U, 3DS, PS Vita e PS4. Anche se in modo diffuso e maturo, webKIT presenta numerose vulnerabilità; potrete conoscere la maggior parte delle vulnerabilità seguendo questo link.

In particolare, il browser della Playstation 4, nel firmware 1.76 utilizza una versione di WebKit, vulnerabile alla CVE-2012-3748, un heap-based overflow del buffer nel metodo JSArray::sort(...).

Nel 2014, nas e Proxima erano riusciti con successo ad importare l’exploit sul browser della Playstation 4, rilasciando il codice PoC pubblicamente come primo punto di ingresso nel hacking della PS4.

Questo sostanzialmente ci permette di eseguire la lettura arbitraria e la scrittura a tutto il processo di webKit in grado di leggere e scrivere, che può essere utilizzato per scaricare i moduli, e sovrascrivere gli indirizzi di ritorno sullo stack, lasciando che a controllare il Program Counter (per ROP).

Da allora, molte altre vulnerabilità sono state trovate all’interno del WebKit, che potrebbero probabilmente permetterne il dumping dei moduli e dei POR sui successivi firmware della PS4, anche se al momento non sono stati ancora rilasciati pubblicamente.

Che cosa è POR?

Diversamente dai dispositivi primitivi come per le console portatili DS e PSP, la Playstation 4 ha un kernel che controlla le proprietà delle diverse aree di memoria. Le pagine di memoria che sono contrassegnate come eseguibili non possono essere sovrascritti, e pagine di memoria che vengono contrassegnate come scrivibili non possono essere eseguiti; questo è noto come Data Execution Prevention (DEP).

Questo significa che non possiamo copiare un payload in memoria ed eseguirlo. Tuttavia, siamo in grado di eseguire codice che è già caricato in memoria e marcato come eseguibile. Ciò non sarebbe molto utile per passare a un unico indirizzo, se non siamo in grado di scrivere il nostro codice a tale indirizzo, in modo da utilizzare ROP.

Return-Oriented Programming (ROP) è solo un’estensione tradizionale dello stack smashing, ma invece di sovrascrivere un solo valore come sul PC lo salta, possiamo concatenare molti indirizzi diversi, conosciuti come gadget.

Il gadget di solito è solo una singola istruzione desiderata seguita da un ret .

In x86_64 assembly, quando un ret raggiunge l’istruzione, un valore a 64 bit viene estratto dallo stack e il PC lo passa ad esso; dato che possiamo controllare lo stack, siamo in grado di realizzare ogni istruzione ret in grado di saltare al successivo gadget desiderato.

Ad esempio, su 0x80000 contiene le istruzioni:

mov rax, 0
ret

Mentre da 0x90000 può contenere le istruzioni:

mov rbx, 0
ret

Se si sovrascrive un indirizzo di ritorno sullo stack contenente 0x80000 seguito da 0x90000, poi, non appena viene raggiunto il primo ret, l’istruzione in esecuzione salta al mov rax, 0 , e subito dopo, nella prossima istruzione ret si aprirà a 0x90000 dallo stack e salta a mov rbx, 0 .

In effetti questa catena andrà ad impostare sia rax e rbx a 0, proprio come se avessimo scritto il codice in un unico luogo ed a eseguirlo da quel punto.

Le catene ROP non sono solo limitate a un elenco di indirizzi, ma; supponendo che da 0xa0000 contiene le istruzioni:

pop rax
ret

Siamo in grado di impostare il primo elemento della catena di 0xa0000 e l’elemento successivo a qualsiasi valore desiderato per rax .

Generalmente viene accettato un codice abbastanza grande per il dump, ci saranno abbastanza gadget per le funzionalità nel Turing-complete. POR è giusto un metodo praticabile per bypassare il DEP in quanto sarà ancora in grado di farci eseguire quasi tutto quello che vogliamo.

Trovare gadget

Pensate al POR come a scrivere un nuovo capitolo di un libro, utilizzando solo le parole che sono apparse alla fine delle frasi nei capitoli precedenti, è evidente dalla struttura della maggior parte delle frasi che probabilmente non sarà in grado di trovare parole come ‘e’ o ‘ma’ che compare giusto alla fine di ogni frase, ma ci sarà sempre il bisogno di questi connettivi per scrivere qualcosa di significativo.

A volte può capitare di concludere una frase con E ‘molto probabile però, che una frase è concluso con ‘sand’. Anche se l’autore può scegliere di destinare alla parola da leggere sempre e solo ‘s’, se si parte dalla lettura ‘a’, apparirà come una parola completamente diversa per caso, ‘e’.

Questi principi valgono anche per ROP.

Poiché la struttura di quasi tutte le funzioni segue qualcosa di simile a questo:

; Save registers
push    rbp
mov     rbp, rsp
push    r15
push    r14
push    r13
push    r12
push    rbx
sub     rsp, 18h

; Function body

; Restore registers
add     rsp, 18h
pop     rbx
pop     r12
pop     r13
pop     r14
pop     r15
pop     rbp
ret

Ci si potrebbe aspettare di trovare solamente i pop gadget, o, più raramente, qualcosa come xor rax, rax e di impostare il valore di ritorno a 0 prima di tornare.

Avere un confronto come:

cmp [rax], r12
ret

Non avrebbe alcun senso poiché il risultato del confronto non viene utilizzato dalla funzione. Tuttavia, c’è ancora una possibilità che possiamo trovare gadget come questi.

Le istruzioni x86_64 sono simili alle parole nella loro lunghezza variabile, e può significare qualcosa di completamente diverso a seconda di dove inizia la decodifica.

L'architettura x86_64 è un insieme di istruzioni CISC di lunghezza variabile. il ritorno della programmazione orientata su x86_64 sfrutta il fatto che l'insieme di istruzioni è molto "densa", cioè, qualsiasi sequenza casuale di byte è probabile che sia interpretabile come un insieme di istruzioni x86_64 valida.

Per dimostrarlo, date un’occhiata alla fine di questa funzione dal modulo WebKit:

000000000052BE0D                 mov     eax, [rdx+8]
000000000052BE10                 mov     [rsi+10h], eax
000000000052BE13                 or      byte ptr [rsi+39h], 20h
000000000052BE17                 ret

Ora date un’occhiata a ciò che il codice è simile se partiamo la decodifica su 0x52be14 :

000000000052BE14                 cmp     [rax], r12
000000000052BE17                 ret

Anche se questo codice non può essere eseguito, si trova all’interno di un’area di memoria che è stata contrassegnata come eseguibile, quindi è perfettamente valido da utilizzare come gadget.

Naturalmente, questo richiederebbe un tempo assai lungo per guardare tutti i modi possibili per interpretare manualmente il codice prima di ogni singola istruzione ret; ed è per questo che esistono degli strumenti adatto per lo scopo. Quello che uso per la ricerca di gadget del POR è rp++; e per generare un file di testo pieno di gadget, basta usare:

rp-win-x64 -f mod14.bin --raw=x64 --rop=1 --unique > mod14.txt

Errori di segmentazione

Se proviamo ad eseguire una pagina non eseguibile della memoria, o proviamo a scrivere su di una pagina non-scrivibile della memoria, potrebbe verificarsi un errore di segmentazione.

Ad esempio, cercando di eseguire codice sullo stack, che viene mappata solo in lettura e scrittura:

setU8to(chain.data + 0, 0xeb);
setU8to(chain.data + 1, 0xfe);

chain.add(chain.data);

.. e cercando di scrivere il codice, che viene mappato solo in lettura in esecuzione: 

setU8to(moduleBases[webkit], 0);

Se si verifica un errore di segmentazione, vedrete apparire un messaggio di errore sullo schermo che recita: “Non c’è abbastanza memoria di sistema libera”.

segfault

Ci sono altri possibili motivi del perché viene visualizzato questo messaggio, come ad esempio l’esecuzione di un’istruzione non valida o una chiamata di sistema implementata, ma un errore di segmentazione è il più comune.

Leggendo la protezione della memoria

Possiamo usare 2 di chiamate di sistema personalizzate da Sony, 547 e 572 per leggere le proprietà di una pagina di memoria (16KB), tra cui la sua protezione:

function getStackProtection() {
	var info = chain.data;
	
	chain.syscall("getMemoryInfo", 547, stack_base, info);
	
	chain.execute(function() {
		var base = getU64from(info + 0x0);
		var size = getU64from(info + 0x8) - base;
		var protection = getU32from(info + 0x10);
		
		logAdd("Stack base: 0x" + base.toString(16));
		logAdd("Stack size: 0x" + size.toString(16));
		logAdd("Stack protection: 0x" + protection.toString(16));
	});
}

function getStackName() {
	var info = chain.data;
	
	chain.syscall("getOtherMemoryInfo", 572, stack_base, 0, info, 0x40);
	
	chain.execute(function() {
		var base = getU64from(info + 0x0);
		var size = getU64from(info + 0x8) - base;
		var name = readString(info + 0x20);
		
		logAdd("Stack base: 0x" + base.toString(16));
		logAdd("Stack size: 0x" + size.toString(16));
		logAdd("Stack name: " + name);
	});
}

Il nome dello stack è “main stack” e la sua protezione è di 3 (RW).

ASLR

Address Space Layout Randomization (ASLR) è una tecnica di sicurezza che cambia gli indirizzi di base dei moduli ogni volta che si accende la console Playstation 4.

Molti dei più vecchi firmware, dalla versione 1.05 in giù non hanno la ASLR abilitatae pare che sia stata introdotta addirittura poco prima del firmware 1.70.

Per la maggior parte degli exploit ASLR questo potrebbe risultare un problema, perché se non si conoscono gli indirizzi dei gadget in memoria, non si avrebbe alcuna idea di cosa scrivere sullo stack.

Fortunatamente per noi, non siamo limitati a scrivere chain ROP statici. Possiamo usare JavaScript per leggere la tabella dei moduli, che ci diranno gli indirizzi di base di tutti i moduli caricati. Utilizzando queste basi, si può quindi calcolare gli indirizzi di tutti i nostri gadget, prima di attivare l’esecuzione dei ROP, bypassando ASLR.

La tabella dei moduli comprende anche i nomi dei file dei moduli:

  • WebProcess.self
  • libkernel.sprx
  • libSceLibcInternal.sprx
  • libSceSysmodule.sprx
  • libSceNet.sprx
  • libSceNetCtl.sprx
  • libSceIpmi.sprx
  • libSceMbus.sprx
  • libSceRegMgr.sprx
  • libSceRtc.sprx
  • libScePad.sprx
  • libSceVideoOut.sprx
  • libScePigletv2VSH.sprx
  • libSceOrbisCompat.sprx
  • libSceWebKit2.sprx
  • libSceSysCore.sprx
  • libSceSsl.sprx
  • libSceVideoCoreServerInterface.sprx
  • libSceSystemService.sprx
  • libSceCompositeExt.sprx

Anche se la PS4 utilizza prevalentemente la [Signed] PPU Relocatable Executable ([S]PRX) per i moduli, alcuni riferimenti alla stringa [Signed] Executable e Linking Format ([S]ELF) si possono trovare anche nel dump libSceSysmodule.sprx, come bdj.elf , web_core.elf e orbis-jsc-compiler.self . Questa combinazione di moduli e oggetti è simile a quello usato nella PSP e PS3.

Volendo è possibile visualizzare una lista completa di tutti i moduli disponibili (non solo quelli caricati dal browser) in libSceSysmodule.sprx. Siamo dunque in grado di caricare e scaricare alcuni di questi moduli attraverso alcune delle chiamate di sistema personalizzate di Sony, che saranno spiegate più avanti in questo articolo.

JUST-ROP

L’utilizzo di Javascript per scrivere ed eseguire chain ROP dinamiche ci dà un enorme vantaggio rispetto un attacco standard di buffer overflow.

Così come bypassando ASLR, noi possiamo anche leggere il programma utente del browser, fornendo un differente chain ROP  per le diverse versioni dei browser, dando al nostro exploit la massima compatibilità possibile.

Possiamo anche utilizzare JavaScript per leggere la memoria agli indirizzi dei nostri gadget, questo per verificare che siano corrette, dandoci quasi una perfetta affidabilità.

Scrivendo chain ROP dinamiche, piuttosto che generarle con uno script in anticipo, potrebbe aver senso. Ho creato un framework in JavaScript per la scrittura delle chain ROP, JuSt-ROP, proprio per questo motivo.

JavaScript caveats

JavaScript rappresenta numeri utilizzando l’IEEE-754 nel formato a doppia precisione (64 bit). Questo ci fornisce con precisione 53bit, che significa che non è possibile rappresentare ogni valore a 64bit, anche se approssimativamente potranno essere utilizzati per alcuni.

Se avete solo bisogno di impostare un valore a 64 bit a un qualcosa di più basso, come 256, poi setU64to andrà bene. Ma per le situazioni in cui è necessario scrivere un buffer o struct dei dati, vi è la possibilità che alcuni byte verranno scritti in modo errato se è stato scritto in blocchi a 64 bit.

Invece, si dovrebbe andare a scrivere i dati in blocchi a 32 bit (ricordando che la PS4 è little endian), per garantire che ogni byte sia esatto.

Chiamate di sistema

Interessante notare come la PS4 faccia uso della stessa convenzione di chiamata come Linux e MS-DOS per le chiamate di sistema, con gli argomenti memorizzati nei registri, piuttosto che il modo tradizionale di UNIX (che FreeBSD usa per impostazione predefinita), con argomenti memorizzati nello stack:

  • rax – numero di chiamata del sistema
  • rdi – Argomento 1
  • rsi – Argomento 2
  • rdx – Argomento 3
  • r10 – Argomento 4
  • r8 – Argomento 5
  • r9 – Argomento 6

Possiamo cercare di eseguire qualsiasi chiamata di sistema con il seguente metodo Just-ROP:

this.syscall = function(name, systemCallNumber, arg1, arg2, arg3, arg4, arg5, arg6) {
	console.log("syscall " + name);
	
	this.add("pop rax", systemCallNumber);
	if(typeof(arg1) !== "undefined") this.add("pop rdi", arg1);
	if(typeof(arg2) !== "undefined") this.add("pop rsi", arg2);
	if(typeof(arg3) !== "undefined") this.add("pop rdx", arg3);
	if(typeof(arg4) !== "undefined") this.add("pop rcx", arg4);
	if(typeof(arg5) !== "undefined") this.add("pop r8", arg5);
	if(typeof(arg6) !== "undefined") this.add("pop r9", arg6);
	this.add("pop rbp", stackBase + returnAddress - (chainLength + 8) + 0x1480);
	this.add("mov r10, rcx and syscall");
}

Utilizzando le chiamate di sistema, la macchina può restituirci una quantità enorme di informazioni sul kernel PS4. Non solo, ma utilizzando le chiamate di sistema è più probabile che questo sia l’unico modo in cui possiamo interagire con il kernel, e quindi potenzialmente innescare un kernel exploit.

Se si prova ad intervenire sui moduli attraverso il reverse engineering possiamo arrivare a identificare alcune delle chiamate di sistema personalizzate da parte della Sony, si può dunque incontrare una convenzione di chiamata alternativa:

A volte Sony effettua chiamate di sistema tramite una regolare chiamata di sistema 0 (che di solito non fa nulla in FreeBSD), con il primo argomento (rdi), controlla le chiamate e dice al sistema che deve essere eseguito.

  • rax – 0
  • rdi – numero di chiamata del sistema
  • rsi – Argomento 1
  • rdx – Argomento 2
  • r10 – Argomento 3
  • r8 – Argomento 4
  • r9 – Argomento 5

Sony molto probabilmente ha fatto tutto questo per avere una più facile compatibilità con la convenzione di funzione che si chiama, per esempio:

unsigned long syscall(unsigned long n, ...) {
	register unsigned long rax asm("rax");
	
	asm("mov r10, rcx");
	rax = 0;
	asm("syscall");
	return rax;
}

Usando questo metodo, si può provare ad eseguire qualsiasi chiamata di sistema da C.

Durante la scrittura delle chain ROP, possiamo scegliere di usare anche la convenzione:

// Both will get the current process ID:
chain.syscall("getpid", 20);
chain.syscall("getpid", 0, 20);

Siate consapevoli di questo, perché siamo in grado di utilizzare un qualsiasi gadget tra quelli disponibili.

getpid

Semplicemente utilizzando la chiamata di sistema 20, getpid (void), possiamo imparare molto sul kernel. Il fatto stesso che questa chiamata di sistema funzionerebbe su tutto ci dice che Sony non si è preoccupata molto mescolando i numeri delle chiamate di sistema come mezzo di security through obscurity (sotto la licenza BSD avrebbero potuto fare questo senza rilasciare i nuovi numeri delle chiamate di sistema).

Quindi, abbiamo automaticamente un vasto elenco delle chiamate di sistema nel kernel PS4 con cui poter provare.

In secondo luogo, chiamando getpid(), riavviare il browser, e chiamarlo di nuovo, si ottiene un valore di ritorno 1 superiore al valore precedente. Anche se FreeBSD sembra aver sostenuto la randomizzazione PID dal 4.0, l’allocazione PID sequenziale è il comportamento predefinito.

Il fatto che l’allocazione PID è impostato di default indica un comportamento predefinito, e pare che Sony probabilmente non si è preoccupata più di tanto nel cercare di aggiungere eventuali miglioramenti di sicurezza aggiuntive quali quelli incoraggiati da progetti come HardenedBSD.

Chiamate di sistema personalizzate

L’ultimo standard per le chiamate di sistema FreeBSD 9 è wait6, il numero 532; un qualcosa di più alto per una chiamata di sistema personalizzata.

Invocando la maggior parte delle chiamate di sistema personalizzate della Sony, senza gli argomenti corretti restituirà l’errore 0x16, "Invalid argument"; tuttavia, tutte le chiamate di compatibilità o di sistema non implementate daranno errore “Non c’è sufficiente memoria di sistema libera”.

Attraverso diversi tentativi ed errori, ho scoperto che il numero delle chiamate di sistema è 617, con l’ultima chiamata di sistema Sony, non c’è nient’altro. Da questo, possiamo concludere che ci sono 85 chiamate di sistema Sony personalizzate nel kernel del PS4 (617-532).

Questo è significativamente inferiore alla PS3, che aveva in totale quasi 1000 chiamate di sistema. Questo indica che abbiamo meno possibilità di attacco sui vettori, ma che queste saranno più semplici da documentare per tutte le chiamate di sistema.

Inoltre, 9 di queste 85 chiamate di sistema tornano sempre a 0x4e , ENOSYS , questo ci suggerisce che possono essere richiamabili solo dalle unità di sviluppo, lasciandoci con appena 76 chiamate di sistema utilizzabili.

Di questi 76, solo 45 fanno riferimento a libkernel.sprx (che tutte le applicazioni non-core utilizzano per effettuare chiamate di sistema), per cui gli sviluppatori potranno utilizzare solo 45 chiamate di sistema personalizzate.

È interessante notare come, anche solo con 45 chiamate di sistema (perché libkernel.sprx ha wrapper per loro), alcuni degli altri 31 sono ancora richiamabili dal processo di browser Internet, è più probabile per questi sistemi abbiano delle vulnerabilità, dal momento che hanno probabilmente avuto la minima quantità di test.

libkernel.sprx

Per identificare come le chiamate di sistema su misura sono utilizzati dalla libkernel, è innanzitutto necessario ricordare che si tratta solo di una modifica allo standard delle librerie FreeBSD 9.0.

Ecco un estratto del codice _libpthread_init da thr_init.c:

/*
 * Check for the special case of this process running as
 * or in place of init as pid = 1:
 */
if ((_thr_pid = getpid()) == 1) {
	/*
	 * Setup a new session for this process which is
	 * assumed to be running as root.
	 */
	if (setsid() == -1)
		PANIC("Can't set session ID");
	if (revoke(_PATH_CONSOLE) != 0)
		PANIC("Can't revoke console");
	if ((fd = __sys_open(_PATH_CONSOLE, O_RDWR)) < 0)
		PANIC("Can't open console");
	if (setlogin("root") == -1)
		PANIC("Can't set login to root");
	if (_ioctl(fd, TIOCSCTTY, (char *) NULL) == -1)
		PANIC("Can't set controlling terminal");
}

La stessa funzione è disponibile all’indirizzo 0x215F0 da libkernel.sprx . Questo si presenta come l’estratto di cui sopra appare all’interno di un dump del libkernel:

call    getpid
mov     cs:dword_5B638, eax
cmp     eax, 1
jnz     short loc_2169F

call    setsid
cmp     eax, 0FFFFFFFFh
jz      loc_21A0C

lea     rdi, aDevConsole ; "/dev/console"
call    revoke
test    eax, eax
jnz     loc_21A24

lea     rdi, aDevConsole ; "/dev/console"
mov     esi, 2
xor     al, al
call    open

mov     r14d, eax
test    r14d, r14d
js      loc_21A3C
lea     rdi, aRoot       ; "root"
call    setlogin
cmp     eax, 0FFFFFFFFh
jz      loc_21A54

mov     edi, r14d
mov     esi, 20007461h
xor     edx, edx
xor     al, al
call    ioctl
cmp     eax, 0FFFFFFFFh
jz      loc_21A6C

Analizzare le chiamate di sistema nel dump del modulo

libkernel purtroppo non è completamente open source; ci sono anche un sacco di codici personalizzati che possono aiutare a rivelare alcune delle chiamate di sistema della Sony.

Anche se questo processo può variare a seconda del sistema di chiamata che si sta cercando; per alcuni, è abbastanza facile da ottenere attraverso una conoscenza di base degli argomenti che vengono passati al suo interno.

La chiamata di sistema wrapper può essere dichiarato da qualche parte nel libkernel.sprx, e quasi sempre segue questo modello:

000000000000DB70 syscall_601     proc near
000000000000DB70                 mov     rax, 259h
000000000000DB77                 mov     r10, rcx
000000000000DB7A                 syscall
000000000000DB7C                 jb      short error
000000000000DB7E                 retn
000000000000DB7F
000000000000DB7F error:
000000000000DB7F                 lea     rcx, sub_DF60
000000000000DB86                 jmp     rcx
000000000000DB86 syscall_601     endp

Si noti che per l’istruzione mov r10, rcx non significa necessariamente che la chiamata di sistema richieda almeno 4 argomenti; tutte le chiamate di sistema per wrappers lo hanno, anche quelli che prendono argomenti, come ad esempio getpid.

Una volta trovato il wrapper, è possibile cercare i diversi riferimenti esterni ad esso associati:

0000000000011D50                 mov     edi, 10h
0000000000011D55                 xor     esi, esi
0000000000011D57                 mov     edx, 1
0000000000011D5C                 call    syscall_601
0000000000011D61                 test    eax, eax
0000000000011D63                 jz      short loc_11D6A

Potrebbe risultare cosa buona cercare molte di queste chiamate, questo solo per assicurarsi che i registri non sono stati modificati da qualcosa di estraneo.

0000000000011A28                 mov     edi, 9
0000000000011A2D                 xor     esi, esi
0000000000011A2F                 xor     edx, edx
0000000000011A31                 call    syscall_601
0000000000011A36                 test    eax, eax
0000000000011A38                 jz      short loc_11A3F

Coerentemente, i primi tre registri delle chiamate di sistema a convenzione ( rdi , rsi , e rdx ) vengono modificate prima di richiamare la chiamata, in modo che possiamo concludere con ragionevole certezza che ci vogliono 3 argomenti.

Per chiarezza, questo è come vorremmo replicare le chiamate in JuSt-ROP:

chain.syscall("unknown", 601, 0x10, 0, 1);
chain.syscall("unknown", 601, 9, 0, 0);

Come per la maggior parte delle chiamate di sistema, verrà restituito 0 in caso di successo, come si è visto dal condizionale jz dopo il valore di ritorno testzione.

Alzando lo sguardo al di là di tutta la quantità di argomenti, questo richiederà una più approfondita analisi del codice, prima e dopo la chiamata, e di comprenderne il contesto, ma questo dovrebbe aiutarti a iniziare.

Bruteforce chiamate di sistema

Anche se il modulo per il reverse engineering del dump è il modo più affidabile per identificare le chiamate di sistema, alcune di queste non sono referenziate per tutte nel dump che abbiamo così avremo bisogno di analizzarli ciecamente.

Se si indovina che una certa chiamata di sistema potrebbe richiedere un particolare insieme di argomenti, possiamo provare con un bruteforce per tutte le chiamate di sistema che restituiscono un certo valore (0 per il successo) con gli argomenti che abbiamo scelto, e ignorare tutto quello che restituiscono un errore.

Possiamo anche passare 0s per tutti gli argomenti, e con bruteforce su tutte le chiamate di sistema che restituiscono errori utili come 0xe , "Bad address" , il che indicherebbe che prendono almeno un puntatore.

In primo luogo, ci sarà bisogno di eseguire il chain ROP non appena si effettua il caricamento della pagina. Possiamo farlo attaccando la nostra funzione al body dell’elemento onload :

<body onload="exploit()">

Successivamente avremo bisogno di eseguire una specifica chiamata di sistema a seconda di un valore HTTP GET. Anche se questo può essere fatto con JavaScript, ti mostrerò come fare questo con PHP per semplicità:

var Sony = 533;
chain.syscall("Sony system call", Sony + <?php print($_GET["b"]); ?>, 0, 0, 0, 0, 0, 0);
chain.write_rax_ToVariable(0);

Una volta eseguita la chiamata di sistema, saremo in grado di controllare il valore di ritorno, e se non è interessante, andremo a reindirizzare la pagina alla successiva chiamata di sistema:

if(chain.getVariable(0) == 0x16) window.location.assign("index.php?b=" + (<?php print($_GET["b"]); ?> + 1).toString());

L’esecuzione della pagina con ?b=0 aggiunto alla fine prenderà il via per il bruteforce dalla prima chiamata di sistema Sony.

Anche se questo metodo richiede un sacco di sperimentazione, passando da valori diversi per alcune delle chiamate di sistema, cercando attraverso bruteforce e analizzando i nuovi valori di ritorno, ci sono un paio di chiamate di sistema che si dovrebbe essere in grado di identificare in parte.

Chiamata di sistema 538

Per fare un esempio, mi prendo uno sguardo alla chiamata di sistema 538, senza fare affidamento su eventuali dump del modulo.

Questi sono i valori restituiti seconda da ciò che viene passato come primo argomento:

  • 0 – 0x16, "Invalid argument"
  • 1 – 0xe, "Bad address"
  • Pointer to 0s – 0x64 initially, but each time the page is refreshed this value increases by 1

Altri argomenti possibili da provare sarebbero PID, ID di thread, e descrittore di file.

Sebbene la maggior parte delle chiamate di sistema torneranno a 0 in caso di successo, a causa della natura del valore di ritorno crescente dopo ogni volta che viene chiamato, sembra che per l’allocazione di un certo numero di risorse, come ad esempio un descrittore di file.

La prossima cosa da fare sarebbe quella di guardare i dati prima e dopo l’esecuzione della chiamata di sistema, per vedere se è stata scritta.

Poiché non vi è alcuna variazione dei dati, si può supporre che si tratta solo di un ingresso per ora.

Allora ho provato a passare una lunga serie come primo argomento. Si dovrebbe sempre provare questo con ogni input e cercare di trovare il perché non vi è la possibilità di scoprire un buffer overflow.

writeString(chain.data, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);

Il valore di ritorno per questo è 0x3f , ENAMETOOLONG. Purtroppo sembra che questa chiamata di sistema limita correttamente il nome (a 32 byte, compresi truncator NULL), ma ci dice che si aspetta una stringa, anziché una struct.

Ora abbiamo un paio di possibilità per quello che sta facendo questa chiamata di sistema, la più ovvia vedrebbe qualcosa legato al filesystem (ad esempio come una consuetudine mkdir o open ), ma questo non sembra particolarmente probabile visto che una risorsa è stata assegnata anche prima di aver scritto tutti i dati al puntatore.

Per verificare se il primo parametro è un percorso, siamo in grado di disgregare con multipli/caratteri per vedere se questo permette una stringa più lunga:

writeString(chain.data, "aaaaaaaaaa/aaaaaaaaaa/aaaaaaaaaa");
chain.syscall("unknown", 538, chain.data, 0, 0, 0, 0, 0);

Dal momento che questo restituisce anche 0x3f, si può supporre che il primo argomento non è un percorso; è un nome di un qualcosa che viene assegnato ad un identificatore sequenziale.

Dopo aver analizzato a fondo alcune chiamate di sistema, ho scoperto che vengono tutte condivise con lo stesso identico comportamento:

  • 533
  • 538
  • 557
  • 574
  • 580

Dalle informazioni che abbiamo finora, è quasi impossibile individuare esattamente ciò che fanno realmente queste chiamate di sistema, ma più prove si eseguono e più verranno rivelate ulteriori informazioni.

Utilizzando la conoscenza generale di come funziona un kernel, si può intuire, e quindi verificare, quali chiamate di sistema si stanno assegnando semaphores, mutexes, etc).

Dumping di moduli aggiuntivi

Siamo in grado di scaricare moduli aggiuntivi seguendo queste fasi:

  • Caricare il modulo
  • Prendere l’indirizzo di base del modulo
  • Dumpare il modulo

Ho provato noiosamente a caricare e scaricare manualmente ogni singolo modulo all’interno del browser Internet, e ho pubblicato i risultati sul psdevwiki. Tutti i moduli con un Yes accanto a potranno essere scaricati con questo metodo.

Per caricare un modulo dovremo utilizzare la funzione sceSysmoduleLoadModule dal libSceSysmodule.sprx + 0x1850. Il primo parametro è l’ID del modulo da caricare, e gli altri 3 devono solo essere passati a 0.

Il seguente metodo JuSt-ROP può essere utilizzato per eseguire una chiamata di funzione:

this.call = function(name, module, address, arg1, arg2, arg3, arg4, arg5, arg6) {
	console.log("call " + name);
	
	if(typeof(arg1) !== "undefined") this.add("pop rdi", arg1);
	if(typeof(arg2) !== "undefined") this.add("pop rsi", arg2);
	if(typeof(arg3) !== "undefined") this.add("pop rdx", arg3);
	if(typeof(arg4) !== "undefined") this.add("pop rcx", arg4);
	if(typeof(arg5) !== "undefined") this.add("pop r8", arg5);
	if(typeof(arg6) !== "undefined") this.add("pop r9", arg6);
	this.add("pop rbp", stack_base + return_va - (chainLength + 8) + 0x1480);
	this.add(module_bases[module] + address);
}

Quindi, per caricare libSceAvSetting.sprx ( 0xb ):

chain.call("sceSysmoduleLoadModule", libSysmodule, 0x1850, 0xb, 0, 0, 0);

Come la maggior parte delle chiamate di sistema, questo deve restituire 0 in caso di successo. Per visualizzare l’ID del modulo caricato che è stato assegnato, si può utilizzare una delle chiamate di sistema personalizzate di Sony, numero 592 , per ottenere un elenco dei moduli attualmente caricati:

var countAddress = chain.data;
var modulesAddress = chain.data + 8;

// System call 592, getLoadedModules(int *destinationModuleIDs, int max, int *count);
chain.syscall("getLoadedModules", 592, modulesAddress, 256, countAddress);

chain.execute(function() {
	var count = getU64from(countAddress);
	for(var index = 0; index < count; index++) {
		logAdd("Module: 0x" + getU32from(modulesAddress + index * 4).toString(16));
	}
});

L’esecuzione di questa sequenza senza caricare alcun modulo aggiuntivo produrrà il seguente elenco:

0x0, 0x1, 0x2, 0xc, 0xe, 0xf, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1e, 0x37, 0x59

Ma se lo facciamo funzionare dopo il caricamento del modulo 0xb , abbiamo visto una voce aggiuntiva, 0x65 . Ricordare che l’ID del modulo non è lo stesso loaded ID module.

Ora possiamo usare un’altra chiamata di sistema personalizzata di Sony, il numero 593 , che prende un ID del modulo caricato e un buffer, e riempie il buffer con le informazioni sul modulo caricato, compreso il suo indirizzo di base. Dal momento che l’ID del modulo caricato è sempre 0x65 , possiamo effettuare un hardcoded nel nostro chain, piuttosto che dover memorizzare il risultato dalla lista dei moduli.

Il buffer deve iniziare con la dimensione della struttura che deve sempre ritornare, altrimenti viene restituito l’errore 0x16, "Invalid argument" :

setU64to(moduleInfoAddress, 0x160);
chain.syscall("getModuleInfo", 593, 0x65, moduleInfoAddress);

chain.execute(function() {
	logAdd(hexDump(moduleInfoAddress, 0x160));
});

Ritornerà 0 in caso di successo, e andrà a riempire il buffer con una struttura che può essere letto in questo modo:

var name = readString(moduleInfoAddress + 0x8);
var codeBase = getU64from(moduleInfoAddress + 0x108);
var codeSize = getU32from(moduleInfoAddress + 0x110);
var dataBase = getU64from(moduleInfoAddress + 0x118);
var dataSize = getU32from(moduleInfoAddress + 0x120);

Ora abbiamo tutto quello che serve per scaricare il modulo!

dump(codeBase, codeSize + dataSize);

C’è un’altra chiamata di sistema Sony che porta il numero 608 e che funziona in modo del tutto simile alla chiamata 593, ma sembra fornisca informazioni leggermente diverse sul modulo caricato:

setU64to(moduleInfoAddress, 0x1a8);
chain.syscall("getDifferentModuleInfo", 608, 0x65, 0, moduleInfoAddress);
logAdd(hexDump(moduleInfoAddress, 0x1a8));

Purtroppo le informazioni sembrano poco chiare.

Navigando il filesystem

Noi possiamo usare le chiamate di sistema open e read il sistema nella directory /dev/ per elencare tutto ciò che contiene:

writeString(chain.data, "/dev/");
chain.syscall("open", 5, chain.data, 0, 0);
chain.write_rax_ToVariable(0);

chain.read_rdi_FromVariable(0);
chain.syscall("read", 3, undefined, chain.data + 0x10, 1028);

Questa è invece il risultato della memoria:

0000010: 0700 0000 1000 0205 6469 7073 7700 0000  ........dipsw...
0000020: 0800 0000 1000 0204 6e75 6c6c 0000 0000  ........null....
0000030: 0900 0000 1000 0204 7a65 726f 0000 0000  ........zero....
0000040: 0301 0000 0c00 0402 6664 0000 0b00 0000  ........fd......
0000050: 1000 0a05 7374 6469 6e00 0000 0d00 0000  ....stdin.......
0000060: 1000 0a06 7374 646f 7574 0000 0f00 0000  ....stdout......
0000070: 1000 0a06 7374 6465 7272 0000 1000 0000  ....stderr......
0000080: 1000 0205 646d 656d 3000 0000 1100 0000  ....dmem0.......
0000090: 1000 0205 646d 656d 3100 0000 1300 0000  ....dmem1.......
00000a0: 1000 0206 7261 6e64 6f6d 0000 1400 0000  ....random......
00000b0: 1000 0a07 7572 616e 646f 6d00 1600 0000  ....urandom.....
00000c0: 1400 020b 6465 6369 5f73 7464 6f75 7400  ....deci_stdout.
00000d0: 1700 0000 1400 020b 6465 6369 5f73 7464  ........deci_std
00000e0: 6572 7200 1800 0000 1400 0209 6465 6369  err.........deci
00000f0: 5f74 7479 3200 0000 1900 0000 1400 0209  _tty2...........
0000100: 6465 6369 5f74 7479 3300 0000 1a00 0000  deci_tty3.......
0000110: 1400 0209 6465 6369 5f74 7479 3400 0000  ....deci_tty4...
0000120: 1b00 0000 1400 0209 6465 6369 5f74 7479  ........deci_tty
0000130: 3500 0000 1c00 0000 1400 0209 6465 6369  5...........deci
0000140: 5f74 7479 3600 0000 1d00 0000 1400 0209  _tty6...........
0000150: 6465 6369 5f74 7479 3700 0000 1e00 0000  deci_tty7.......
0000160: 1400 020a 6465 6369 5f74 7479 6130 0000  ....deci_ttya0..
0000170: 1f00 0000 1400 020a 6465 6369 5f74 7479  ........deci_tty
0000180: 6230 0000 2000 0000 1400 020a 6465 6369  b0.. .......deci
0000190: 5f74 7479 6330 0000 2200 0000 1400 020a  _ttyc0..".......
00001a0: 6465 6369 5f73 7464 696e 0000 2300 0000  deci_stdin..#...
00001b0: 0c00 0203 6270 6600 2400 0000 1000 0a04  ....bpf.$.......
00001c0: 6270 6630 0000 0000 2900 0000 0c00 0203  bpf0....).......
00001d0: 6869 6400 2c00 0000 1400 0208 7363 655f  hid.,.......sce_
00001e0: 7a6c 6962 0000 0000 2e00 0000 1000 0204  zlib............
00001f0: 6374 7479 0000 0000 3400 0000 0c00 0202  ctty....4.......
0000200: 6763 0000 3900 0000 0c00 0203 6463 6500  gc..9.......dce.
0000210: 3a00 0000 1000 0205 6462 6767 6300 0000  :.......dbggc...
0000220: 3e00 0000 0c00 0203 616a 6d00 4100 0000  >.......ajm.A...
0000230: 0c00 0203 7576 6400 4200 0000 0c00 0203  ....uvd.B.......
0000240: 7663 6500 4500 0000 1800 020d 6e6f 7469  vce.E.......noti
0000250: 6669 6361 7469 6f6e 3000 0000 4600 0000  fication0...F...
0000260: 1800 020d 6e6f 7469 6669 6361 7469 6f6e  ....notification
0000270: 3100 0000 5000 0000 1000 0206 7573 6263  1...P.......usbc
0000280: 746c 0000 5600 0000 1000 0206 6361 6d65  tl..V.......came
0000290: 7261 0000 8500 0000 0c00 0203 726e 6700  ra..........rng.
00002a0: 0701 0000 0c00 0403 7573 6200 c900 0000  ........usb.....
00002b0: 1000 0a07 7567 656e 302e 3400 0000 0000  ....ugen0.4.....
00002c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

Volendo si può scegliere di modificare il writeString chiamata a leggere alcuni dei dispositivi elencati, per esempio: la lettura /dev/urandom riempiranno la memoria con dati casuali.

Purtroppo, non sono riuscito a leggere le diverse directory da /dev/ a causa del sandboxing. Ho cercato di leggere i file e le directory che non esistono ma sono limitate, e questo genera l’errore 2, ENOENT , "No such file or directory" .

Sandboxing

Così come per alcuni file di sistema che potrebbero mancare in determinati percorsi, ci sono anche altre ragioni per cui una chiamata di sistema potrebbe fallire.

Più comunemente, una chiamata di sistema non consentita darebbe un ritorno di errore a 1 , EPERM , "Operation not permitted"; come ad esempio cercando di usare ptrace, ma altre chiamate di sistema potrebbero non riuscire per diversi motivi:

Le chiamate di sistema compatibilità sono disabilitate. Se si sta cercando di chiamare mmap per esempio, è necessario utilizzare il sistema di chiamata numero 477, non 71 o 197; altrimenti sarà attivato un segfault.

Altre chiamate di sistema come exit possono andare ad innescare un altro errore di segmentazione:

chain.syscall("exit", 1, 0);

Se si cerca di creare una presa SCTP ​​il sistema ci restituirà l’errore 0x2b , EPROTONOSUPPORT , indicando che i socket SCTP ​​sono state disattivate nel kernel PS4:

//int socket(int domain, int type, int protocol);
//socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
chain.syscall("socket", 97, 2, 1, 132);

Anche provando a chiamare mmap con PROT_READ | PROT_WRITE | PROT_EXEC ci restituisce un puntatore valido, il flag PROT_EXEC viene invece ignorato. Leggendo la sua protezione restituirà 3 (RW), e qualsiasi tentativo di eseguire la memoria attiverà un segfault:

chain.syscall("mmap", 477, 0, 4096, 1 | 2 | 4, 4096, -1, 0);
chain.write_rax_ToVariable(0);
chain.read_rdi_FromVariable(0);
chain.add("pop rax", 0xfeeb);
chain.add("mov [rdi], rax");
chain.add("mov rax, rdi");
chain.add("jmp rax");

L’elenco dei software open source utilizzati nella PS4 non elenca alcun tipo di software sandboxing come Capsicum, così la PS4 deve utilizzare sia pure FreeBSD Jails, o per un qualche tipo di costume, di proprietà, o di sistema sandboxing (improbabile).

Jails

Siamo in grado di dimostrare l’esistenza di Jails FreeBSD utilizzato attivamente nel kernel della PS4 attraverso la chiamata di sistema auditon anche se impossibile da eseguire all’interno di un ambiente jailed:

chain.syscall("auditon", 446, 0, 0, 0);

La prima cosa che la chiamata di sistema auditon non fà è la verifica jailed qui, e in caso affermativo, ritorna a ENOSYS :

if (jailed(td->td_ucred))
	return (ENOSYS);

In caso contrario, la chiamata di sistema ci restituirebbe EPERM dal mac_system_check_auditon qui:

error = mac_system_check_auditon(td->td_ucred, uap->cmd);
if (error)
	return (error);

Oppure da priv_check qui:

error = priv_check(td, PRIV_AUDIT_CONTROL);
if (error)
	return (error);

Il punto più lontano in assoluto che la chiamata di sistema potrebbe raggiungere sarebbe subito dopo la priv_check, qui, e prima di tornare a EINVAL a causa della lunghezza dell’ argomento è 0:

if ((uap->length <= 0) || (uap->length > sizeof(union auditon_udata)))
	return (EINVAL);

Poiché mac_system_check_auditon e priv_check non torneranno mai su ENOSYS, avendo il passaggio di controllo jailed questo è l’unico modo in cui ENOSYS potrebbe essere restituito.

Quando si esegue il chain, ENOSYS viene restituito ( 0x48 ).

Questo ci dice che qualunque sandbox utilizzi il sistema PS4 è almeno in base a jails, perché il jailed di controllo lo passa.

FreeBSD exploit del kernel 9.0

Non ha molto senso cercare di trovare nuove vulnerabilità nel codice sorgente del kernel di FreeBSD 9.0, perché dalla sua uscita nel 2012, sono stati già trovato diversi exploit del kernel, a cui la PS4 potrebbe potenzialmente essere vulnerabile.

Possiamo subito respingere alcune di queste per ovvie ragioni:

Tuttavia, ci sono anche alcune vulnerabilità piccole, che potrebbero portare a qualcosa:

getlogin

Una vulnerabilità che sembra facile da provare sta utilizzando la chiamata di sistema getlogin per far fuoriuscire una piccola quantità di memoria del kernel.

La chiamata di sistema getlogin ha lo scopo di copiare il nome del login nella sessione corrente, e nella memoria userland, tuttavia, a causa di un bug, l’intero buffer viene sempre copiato, e non solo nella dimensione della stringa nome. Questo significa che siamo in grado di leggere alcuni dati non inizializzate dal kernel, che potrebbe essere di qualche utilità.

Si noti che la chiamata di sistema (49) è in realtà int getlogin_r(char *name, int len); e non char *getlogin(void); 

Quindi, proviamo a copiare qualche memoria del kernel in una parte non utilizzata della memoria userland:

chain.syscall("getlogin", 49, chain.data, 17);

Purtroppo 17 byte è la maggior parte dei dati che possiamo ottenere, dal momento che:

Nomi di login sono limitati a MAXLOGNAME (da <sys/param.h> ) caratteri, attualmente 17 tra cui null.

Dopo l’esecuzione del chain, il valore di ritorno è statoa dello 0, il che significa che la chiamata di sistema ha funzionato! Un ottimo inizio. Ora diamo uno sguardo alla memoria che abbiamo puntato.

Prima di eseguire il chain:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00

Dopo l’esecuzione del chain:

72 6f 6f 74 00 fe ff ff 08 62 61 82 ff ff ff ff
00

Dopo la decodifica dei primi 4 byte come ASCII:

root

Così il browser viene eseguito come root! Qualcosa di inaspettato.

Ma ancor più interessante, la perdita di memoria appare come un puntatore a qualcosa nel kernel, che è sempre lo stesso ogni volta la che il chain viene eseguito; questa è la prova per sostenere le affermazioni di Yifanlu che la PS4 non ha ASLR Kernel!

Sommario

Dalle informazioni attualmente disponibili, il kernel della Playstation 4 sembra essere molto simile a quella dello stock FreeBSD 9.0 kernel.

È importante sottolineare come le differenze che sono presenti appaiono come standard da configurazione del kernel cambia (ad esempio la disattivazione del socket SCTP), piuttosto che da codice modificato. Sony ha anche aggiunto molto del proprio sistema personalizzato alle chiamate del kernel, ma a parte questo, il resto del kernel sembra abbastanza intatto.

A questo proposito, sono propenso a credere che le azioni della PS4 siano per la maggior parte delle stesse e succose vulnerabilità come per il kernel di FreeBSD 9.0!

Purtroppo, la maggior parte degli exploit del kernel non possono essere attivate ​​dal punto di ingresso del WebKit che abbiamo attualmente a causa di vincoli nel sandboxing (probabilmente a causa dello stock FreeBSD jails).

E con FreeBSD 10 fuori, è improbabile che qualcuno riponga via qualsiasi exploit privato ​​per FreeBSD 9, quindi a meno che non ne venga improvvisamente rilasciato uno nuovo, siamo bloccati con ciò che è attualmente disponibile.

Questo non potrà mai essere possibile sfruttare il kernel della PS4 sfruttando alcune delle vulnerabilità nella corruzione della memoria dei kernel attuali, ma certamente non può essere facile.

L’approccio migliore da qui sembra essere il reverse engineering di tutti i moduli che possono essere scaricati, per documentare come molto del sistema personalizzato di Sony chiama possibile; Ho la sensazione che avremo più fortuna mirando questi, che le chiamate di sistema FreeBSD standard.

Recentemente Jaicrab ha scoperto due porte UART sulla PS4 che ci mostra come ci siano ancora hacker interessati all’hardware della PS4.

Anche se il ruolo degli hacker hardware viene conosciuta tradizionalmente per scaricare la RAM di un sistema, come con il DSi, che possiamo già utilizzare grazie all’exploit del WebKit, c’è anche la possibilità di trovare una qualche vulnerabilità hardware che può essere innescata all’interno del kernel, come per geohot’s original PS3 hypervisor hack.

Resta comunque più probabile che si trovi un kernel exploit sulla PS4 attraverso le vulnerabilità del sistema di chiamata.

Grazie

  • nas
  • Proxima
  • Takezo
  • Fame
  • Xerpi
  • droogie
  • SKFU

8 Commenti

  1. Scusami francesco, non sono molto avvezzo nel hacking, ma da come ho capito, se si riesce a bucare il webkit si potrebbe fare una specie di modifica come per la wii?

    • difficile pronunciarmi, è ancora troppo presto, credo che comunque la strada intrapresa dagli hacker sia quella giusta, forse un giorno riusciranno ad avviare applicazioni homebrew (non giochi, questa non è la loro intenzione).

  2. Siamo in alto mare, in mezzo all’oceano in tempesta. Da quel che leggo non vedo speranze di nulla a breve termine. Anche vero che i momenti cruciali delle grandi scoperte sono avvenute quasi sempre durante periodi di calma piatta… O viene leakato qualcosa (come ai tempi le chiavette per entrare nel service mode) o non ci sono speranze. Alla fine Sony se vuole romper le palle ha dimostrato di essere capacissima di farlo.

  3. personalmente mi tengo ps3 con CFW e xbox360 con lt3.0 finchè non uscirà una modifica per xone.
    ho ancora tanti e tanti giochi da giocare…
    senza contare quelli che comunque continuano ad uscire.

    non spendo di certo 400euro, più gioco o giochi, per una grafica poco più bella.
    l’ottimo kojima ne ha dato un bell’esempio con MGS in uscita.

LASCIA UN COMMENTO

Per favore inserisci il tuo commento!
Per favore inserisci il tuo nome qui

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.