Sony Playstation 2 (PS2): Hackeando o PS2 com o Yabasic
* * *
## Introdução
Recentemente, deparei com um disco demo para PS2 contendo Yabasic, um simples intérprete básico, e estava curioso para pesquisar se poderia ser usado para algo interessante. Esses [discos de demonstração] (https://wiki.pcsx2.net/Demo_Disc) foram fornecidos com todos os consoles PS2 da região PAL entre 2000 e 2003 como uma [tentativa] (https://www.theregister.co.uk/2000/11 / 07 / sony_adds_basic_to_playstation /) para classificar o PS2 como um computador pessoal em vez de um console de videogame por razões fiscais (que [em última instância] (https://www.theguardian.com/technology/2003/oct/01/business.games ) falhou, no entanto, atualmente, os consoles de videogame não estão mais sujeitos a esse imposto de importação).
Em particular, embora existam métodos existentes para executar homebrew em consoles PS2, nenhum deles é perfeito, pois todos parecem ter requisitos indesejáveis, como abrir o console ou comprar hardware não oficial, ou estão limitados apenas a modelos específicos.
O método mais desejável é usar o [FreeMCBoot] (https://github.com/TnA-Plastic/FreeMcBoot) para inicializar a partir de um cartão de memória, mas a instalação no referido cartão de memória requer um console já hackeado. Embora você possa adquirir um cartão de memória com o FreeMCBoot pré-instalado por outra pessoa, seria bom ter uma maneira de instalar a exploração por conta própria. É aí que vejo uma exploração Yabasic se encaixando perfeitamente, como um ponto de entrada para o lançamento do instalador do FreeMCBoot. Além disso, uma exploração Yabasic pode ser útil para pessoas com os mais recentes consoles slim, que não são vulneráveis ao FreeMCBoot.
Neste artigo, descreverei como desenvolvi uma exploração que permite executar código arbitrário através do Yabasic. Como esses programas podem ser salvos e carregados a partir do cartão de memória, a exploração só precisa ser digitada uma vez e pode ser recarregada de forma mais conveniente no futuro. Se você está interessado apenas em usar a exploração, mas não a análise técnica, pode [fazer check-out do repositório] (https://github.com/CTurt/PS2-Yabasic-Exploit) para obter detalhes.
## Configuração
Durante a duração deste artigo, analisarei o PBPX-95205, mas todas as versões do Yabasic são vulneráveis (a única diferença será encontrar os endereços corretos).
### Ferramentas
Desmontei e descompilei usando o [plugin PS2] (https://github.com/beardypig/ghidra-emotionengine) para o Ghidra. Eu depurei usando o emulador [PCSX2] (https://github.com/PCSX2/pcsx2) e um [plugin] (https://github.com/jackun/USBqemu-wheel/releases) que permite dispositivos USB (armazenamento teclado).
### Fonte
O Yabasic é [open-source] (http://www.yabasic.de/), mas a versão mais antiga ainda disponível é [2.77.1] (https://github.com/marcIhm/yabasic/releases/tag/2.77 .1), lançado [final de 2016] (http://www.yabasic.de/content_log.html). Isso pode parecer uma grande diferença no começo, mas, na realidade, o projeto ficou inativo por 9 anos e, portanto, foram feitas apenas algumas correções durante o tempo entre o lançamento do PS2 e o 2.77.1. Embora algumas dessas correções listadas possam ser interessantes, como “Um bug com as funções split () e token () foi corrigido, o que causou coredumps em alguns casos.”, Decidi procurar apenas meus próprios erros do que caçar a causa raiz desses bugs.
## Caça a bugs
Como o Yabasic não se destina a ser um limite de segurança em PCs modernos (você pode simplesmente usar o [system] (http://www.yabasic.de/yabasic.htm#ref_system) para executar comandos arbitrários), não houve muitas revisões de segurança anteriores da base de código.
### dotify
Houve um estouro de buffer [relatado em 2009] (https://bugs.launchpad.net/ubuntu/+source/yabasic/+bug/424602), [corrigido em 2016] (https://github.com/marcIhm/ yabasic / commit / bb5994214f738290e03d111faaeedcd70e92c885).
O PoC para esta questão é bastante direto:
`X = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`
A função `dotify` está em` 0x1240e8` e é realmente vulnerável, o que significa que podemos sobrecarregar o buffer de 200 bytes em `0x3eb0c8`.
Infelizmente, a memória adjacente parece principalmente com `0`s, e transbordar para ela não parece fazer nada imediatamente. Uma análise mais aprofundada com o desmontador mostrou que era apenas armazenamento para algumas outras seqüências e não parecia que a corrupção seria muito útil para a exploração.
### create_subr_link
A vulnerabilidade [first] (https://github.com/marcIhm/yabasic/issues/32) que encontrei foi outro estouro de buffer, semelhante ao acima, mas ocorre em um buffer de pilha, em vez de em um buffer estático:
“ “
vazio
create_subr_link (char * label) / * criar link para a sub-rotina * /
{
char global [200];
char * ponto;
comando struct * cmd;
if (! inlib) {
erro (sDEBUG, “fora da biblioteca, não criará um link para a sub-rotina”);
Retorna;
}
ponto = strchr (rótulo, ‘.’);
strcpy (global, library_stack [include_depth-1] -> short_name);
strcat (global, ponto);
“ “
Isso pode ser acionado exportando uma função com um nome longo em uma biblioteca:
`crash.yab`:
`import crashlib`
`crashlib.yab`:
exportação sub a.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa $ ()
retornar “foo”
end sub
Não acredito que este possa ser acionado no PS2, pois não suporta bibliotecas.
### create_subr_link
A vulnerabilidade [first] (https://github.com/marcIhm/yabasic/issues/32) que encontrei foi outro estouro de buffer, semelhante ao acima, mas ocorre em um buffer de pilha, em vez de em um buffer estático:
“ “
vazio
create_subr_link (char * label) / * criar link para a sub-rotina * /
{
char global [200];
char * ponto;
comando struct * cmd;
if (! inlib) {
erro (sDEBUG, “fora da biblioteca, não criará um link para a sub-rotina”);
Retorna;
}
ponto = strchr (rótulo, ‘.’);
strcpy (global, library_stack [include_depth-1] -> short_name);
strcat (global, ponto);
“ “
Isso pode ser acionado exportando uma função com um nome longo em uma biblioteca:
`crash.yab`:
`import crashlib`
`crashlib.yab`:
exportação sub a.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa $ ()
retornar “foo”
end sub
Não acredito que este possa ser acionado no PS2, pois não suporta bibliotecas.
### dim
A [segunda] (https://github.com/marcIhm/yabasic/issues/33) vulnerabilidade que encontrei foi um estouro de número inteiro ao calcular o tamanho de uma matriz.
O Yabasic suporta a declaração de matrizes de até 10 dimensões usando o comando `dim`, com a única restrição de que o comprimento de cada dimensão deve ser maior que 0. O tamanho da alocação é calculado adicionando 1 a todas as dimensões, multiplicando-as e depois multiplicando pelo tamanho do tipo, onde as matrizes podem conter `char *` ou `double`:
“ “
vazio
dim (comando struct * cmd) / * obtém espaço para a matriz * /
{
…
para (i = 0; i <cmd – = “”> args; i ++) {
nbounds [i] = 1 + (int) pop (stNUMBER) -> valor;
if (nbounds [i] <= 1) = “” {= “” sprintf = “” (string, = “” “array =” “index =” “% d =” “é =” “less =” “ou = “” igual = “” zero “, =” “cmd – =” “> args – i);
erro (ERRO, string);
Retorna;
}
…
}
…
/ * conta a memória necessária * /
total = 1;
para (i = 0; i <nar – = “”> dimensão; i ++) {
(nar-> limites) [i] = nbounds [i];
ntotal * = limites [i];
}
esize = (nar-> tipo == ‘s’)? sizeof (char *): sizeof (double); / * tamanho de um elemento da matriz * /
nar-> ponteiro = meu_malloc (ntotal * esize); </ =>
“ “
Nas versões mais recentes do Yabasic, `my_malloc` também adiciona` sizeof (int) `ao tamanho final, mas na versão PS2, isso não ocorre.
A vulnerabilidade é clara; não há verificação de excesso de número inteiro ao multiplicar as dimensões ou multiplicar pelo tamanho do tipo. O PoC é simples, basta solicitar uma matriz com a dimensão `0x20000000`, e o cálculo do tamanho` (0x20000000 + 1) * 8` transbordará para apenas `8` bytes. Note que como o PS2 Yabasic não suporta números hexadecimais com a notação `0x`, teremos que decodificá-los via` dec (“20000000”) `, ou simplesmente usar a representação decimal` 536870912`:
`dim x (536870912)`
O código falhará ao inicializar o buffer de 8 bytes com `0x20000000“ 0.0`s:
“ “
/ * inicializar matriz * /
for (i = 0; i <ntotal; = “” i ++) = “” {= “” if = “” (nar – = “”> tipo == ‘s’) {
nul = my_malloc (sizeof (char));
* nul = ‘\ 0’;
((char **) nar-> ponteiro) [i] = nul;
} outro {
((duplo *) nar-> ponteiro) [i] = 0,0;
}
}
## Explorando `dim`
Embora ainda não pareça, a vulnerabilidade `dim` nos dá o maior controle, por isso é com a qual vamos avançar.
### Tornar claro escuro
A falha acima não é muito útil; vamos ver se podemos controlar o conteúdo em vez de apenas escrever “0.0”, ou podemos impedir a inicialização.
O comando `dim` também pode ser usado para redimensionar uma matriz existente (ela só pode ser aumentada, não diminuída), então minha primeira ideia foi declarar uma matriz normal e redimensioná-la para um tamanho inválido, mas isso realmente não resolve o problema. problema, pois ainda travará na inicialização com `0.0` antes que a cópia ocorra:
“ “
/ * inicializar matriz * /
for (i = 0; i <ntotal; = “” i ++) = “” {= “” if = “” (nar – = “”> tipo == ‘s’) {
nul = my_malloc (sizeof (char));
* nul = ‘\ 0’;
((char **) nar-> ponteiro) [i] = nul;
} outro {
((duplo *) nar-> ponteiro) [i] = 0,0;
}
}
if (remo) {
/ * copia o conteúdo da matriz antiga para o novo * /
for (i = 0; i <ototal; = “” i ++) = “” {= “” off_to_ind = “” (i, = “” remo – = “”> limites, ind);
j = ind_to_off (ind, nar-> limites);
if (nar-> type == ‘s’) {
my_free (((char **) nar-> ponteiro) [j]);
((char **) nar-> ponteiro) [j] = ((char **) remo-> ponteiro) [i];
} outro {
((duplo *) nar-> ponteiro) [j] = ((duplo *) remo-> ponteiro) [i];
}
}
my_free (remo-> ponteiro);
my_free (remo);
}
“ “
E então eu percebi algo mágico; ambos `i` e` ntotal` são números inteiros assinados, portanto uma comparação assinada será usada para a condição do loop de inicialização acima.
Embora não possamos criar diretamente uma matriz com uma dimensão negativa, podemos criar uma matriz com um produto negativo de dimensões. No PoC abaixo, criamos uma matriz cujo `ntotal` é um número inteiro negativo` (2 * 0x40000000 = 0x80000000) `, mas cujo tamanho real do buffer` (0x80000000 * 8 + assignSize) `será excedido para apenas` assignSize`, onde `ocationSize` pode ser um múltiplo arbitrário de `16`:
alocaçãoSize = 16
dim x (2-1, dez (“40000000″) +) ((tamanho da atribuição / 8) / 2-1)
Nenhuma inicialização ocorrerá ao criar essa matriz (devido à verificação de inicialização assinada), portanto ela não trava imediatamente e somos livres para usar a matriz para acessar a memória fora dos limites:
para i = 0 a 256
imprimir x (0, i)
proximo eu
Também podemos acessar elementos de um índice negativo; por exemplo, para acessar os bytes `8` imediatamente antes do buffer, usaríamos o índice` 0xfffffff8 / 8 = 536870911`. Com isso em mente, temos leitura / gravação arbitrária em todo o espaço de endereço virtual, em relação ao buffer.
### Relativo à leitura / gravação absoluta
Antes de passarmos a usar a vulnerabilidade para executar qualquer corrupção, precisamos saber como acessar endereços de memória arbitrários em relação ao nosso buffer; para isso, precisamos apenas saber onde nosso buffer de matriz está alocado.
Podemos verificar onde nosso buffer de matriz é alocado, quebrando em `0x12cb0c` e examinando` v0`:
0012cb04 jal my_malloc
0012cb08 _mult a0, s0, a0
0012cb0c beq s3, zero, LAB_0012cb44
Como nosso buffer de matriz será alocado em tempo de execução, após a análise do programa, seu endereço será diferente dependendo de quantos comandos nosso programa contenha. Em particular, observei que cada comando de atribuição de matriz aloca bytes 0x240, portanto, se tivermos apenas um programa que aloca a matriz vítima e, em seguida, executar uma série de gravações de matriz n, observei que o buffer da matriz poderia ser calculado como `0xCD7B70 + n * 0x240`.
Portanto, se queremos criar um programa que grava em apenas um endereço de memória, nosso buffer de matriz estará localizado em `0xCD7B70 + 1 * 0x240 = 0xCD7DB0` e, para gravar no endereço` a`, calcularemos o índice da matriz como `((a – 0xCD7DB0) mod 0x100000000) / 8`.
### Fluxo de controle de seqüestro
Com leitura / gravação arbitrária, vamos para o fluxo de controle de seqüestro. O alvo de corrupção óbvio para isso é um endereço de retorno na pilha.
Quando executamos uma atribuição de array, o código responsável no Yabasic é a função `doarray`. No executável PS2, a atribuição real de memória ocorre no endereço `0x12d318` e, em seguida, essa função salta para o endereço de retorno em` 0x12d3b8`:
“ “
0012d318 sll v0, s3, 0x3
0012d31c ld v1, 0x10 (s5)
0012d320 addu v0, a1, v0
0012d324 beq zero, zero, LAB_0012d390
0012d328 _sd v1, 0x0 (v0)
LAB_0012d390:
0012d390 ld ra, local_10 (sp)
0012d394 ld s8, local_20 (sp)
0012d398 ld s7, local_30 (sp)
0012d39c ld s6, local_40 (sp)
0012d3a0 ld s5, local_50 (sp)
0012d3a4 ld s4, local_60 (sp)
0012d3a8 ld s3, local_70 (sp)
0012d3ac ld s2, local_80 (sp)
0012d3b0 ld s1, local_90 (sp)
0012d3b4 ld s0, 0x0 (sp) => local_a0
0012d3b8 jr ra
“ “
A pilha está em `0x1ffeac0` e o deslocamento do endereço de retorno é` 0x90`, portanto, queremos gravar na matriz a partir de um índice que corrompa `0x1ffeac0 + 0x90`.
Conforme calculado anteriormente, para um programa com uma única atribuição, o buffer da matriz será alocado em `0xcd7db0`, portanto, para gravar no endereço de retorno, precisamos compensar` (0x1ffeac0 + 0x90 – 0xcd7db0) / 8 = 2510260 `. Vamos escrever o valor `double` 1.288230067079646e-231` que codifica para` 0x1000000041414141`:
dim x (1,1073741824)
x (0,2510260) = 1,288230067079646e-231
Esse PoC é legal porque salta para um endereço inválido, o que também causa o travamento do emulador PCSX2; portanto, são 2 PoCs pelo preço de 1:
(16b0.2378): Violação de acesso – código c0000005 (!!! segunda chance !!!)
*** AVISO: Não foi possível verificar a soma de verificação para C: \ Arquivos de Programas (x86) \ PCSX2 1.4.0 \ pcsx2.exe
*** ERRO: Não foi possível encontrar o arquivo de símbolo. O padrão é exportar símbolos para C: \ Arquivos de Programas (x86) \ PCSX2 1.4.0 \ pcsx2.exe –
eax = 00004141 ebx = 41414141 ecx = bebf0000 edx = 19e1b3b8 esi = 41414141 edi = 00e280e0
eip = 02b93016 esp = 073af6f0 ebp = 073af6f8 iopl = 0 nv acima ei pl nz na pe nc
cs = 0023 ss = 002b ds = 002b es = 002b fs = 0053 gs = 002b efl = 00010206
pcsx2! AmdPowerXpressRequestHighPerformance + 0x1abfc0e:
02b93016 ff2419 jmp dword ptr [ecx + ebx] ds: 002b: 00004141 = ????????
Há algum tempo, acidentalmente, encontrei um [estouro de buffer de pilha em um emulador N64] (https://twitter.com/CTurtE/status/1126615666356883456) também. É engraçado como os emuladores de console de videogame geralmente têm uma segurança tão ruim que você pode facilmente encontrar vulnerabilidades como essa por acidente.
### Problema em dobro
Quando eu disse que tínhamos leitura / gravação arbitrária de todo o espaço de endereço, eu não era inteiramente preciso. Podemos acessar quaisquer 8 bytes no espaço de endereço, mas apenas como um `duplo`. Precisamos converter para frente e para trás através da codificação IEEE 754 ao ler ou gravar memória através de nossa matriz `double` para acessar a memória” nativa “, assim como você faria com uma matriz do tipo` Float64` subdimensionada em um mecanismo JavaScript 0 dias.
A primeira restrição disso é que não podemos criar `double`s válidos com todos os bits do expoente definidos. Isso significa que, se tentarmos escrever 8 bytes de dados hexadecimais começando com `0x7ff` ou` 0xfff`, o restante dos dados será todo `0` (` inf`) ou `800 … ‘(` nan `). O programa a seguir demonstra isso mostrando que 2 `double`s de expoente completo codificam para o mesmo valor; a saída é `inf inf 1`.
print 8.991e308
print 8.992e308
print 8.991e308 = 8.992e308
Um problema muito maior é que a versão para PS2 do Yabasic parece arredondar `double`s para` 0` depois de um certo ponto, o seguinte imprime `6.6e-308, 0`:
`print 6.6e-308,”, “, 6.6e-309`
Na prática, um dos problemas mais prováveis encontrados por não ser capaz de codificar nada com um expoente baixo é qualquer número de 64 bits com todos os 0 bytes superiores `0`, como qualquer ponteiro de 32 bits zero estendido para 64 bits , como “0x0000000041414141”. É por isso que eu tive que usar o endereço `0x1000000041414141` para o fluxo de controle seqüestrar o PoC acima, e é algo que precisamos abordar antes de prosseguir.
#### Gravando memória nativa
Não conseguir escrever valores arbitrários é um grande problema ao tentar corromper estruturas de dados e impõe grandes restrições à carga útil que podemos escrever.
Inicialmente, parece que nossa única solução será corrigir o código existente para solucionar isso, o que eu realmente não queria recorrer, pois traria problemas de coerência de cache que não poderemos depurar no emulador .
No entanto, depois de definir alguns pontos de interrupção de memória, eu rapidamente encontrei o código fonte Yabasic que analisa `double`s e percebi que é uma chamada` sscanf`, o que significa que seu comportamento pode ser influenciado apenas alterando os dados passados para o argumento 2:
“ “
caso 200:
YY_RULE_SETUP
{
{d duplo;
sscanf (texto yy, “% lg”, & d);
yylval.fnum = d;
retornar tFNUM;
}
}
“ “
Com alguma engenharia reversa, descobri que na versão PS2, o `sscanf` está localizado em 0x146618`, a chamada para` sscanf` está localizada em 0x136fcc` e a string `% lg` está localizada em 0x3e29b8`.
Se substituirmos o especificador de formato `double` por um especificador de formato para um número nativo, poderemos gravar diretamente a memória nativa. Vamos tentar `% lu` (`% lx` não funcionará bem porque obteremos um erro de sintaxe se incluirmos caracteres alfa no número).
Uma representação de `% lu` em hexadecimal que podemos usar é` 0x4141414100756c25`, que em `double` é` 2261634.0035834485`. O PoC abaixo substitui o formato `”% lg “` usado para analisar o `double`s, com esta representação` “% lu” `:
“ “
dim x (1, 1073742847)
# 0x3e29b8 “% lg” -> “% lu”
x (0, 535696769) = 2261634.0035834485
“ “
Com esse patch instalado, qualquer programa futuro executado usará o novo formato. Infelizmente, se confiarmos nisso, significa que precisamos executar 2 programas para a nossa exploração, em vez de executá-lo de uma só vez, pois toda a análise `double` acontece antes da execução da atribuição. O Yabasic suporta a geração dinâmica de código com [`compile`] (http://www.yabasic.de/yabasic.htm#ref_compile) que pode funcionar, no entanto, a versão PS2 não suporta isso.
#### Lendo a memória nativa
Embora não seja necessário se estiver escrevendo e executando uma carga útil, pode ser útil ler também a memória nativa para fins de depuração ao desenvolver a exploração.
Assim como nosso patch para gravar memória nativa, tentaremos corrigir um especificador de formato em vez do próprio código para evitar problemas de coerência do cache. Desta vez, queremos substituir a conversão de `double` para string pela conversão de número nativo para string. No Yabasic, essa conversão é feita aqui:
“ “
case ‘d’: / * imprime valor duplo * /
p = pop (stNUMBER);
d = p-> valor;
n = (int) d;
if (n == d && d <= long_max = “” && = “” d = “”> = LONG_MIN) {
sprintf (string, “% s% ld”, (último == ‘d’)? “”: “”, n);
} outro {
sprintf (string, “% s% g”, (last == ‘d’)? “”: “”, d);
}
onestring (string);
break; </ =>
“ “
Como você pode ver, ele já foi convertido em número inteiro antes de ser passado para `sprintf` no primeiro caso, portanto, não podemos alterar esse comportamento apenas corrigindo dados.
No entanto, existe a função `myformat` que podemos atacar, que possui uma lista de permissões de formatos permitidos que podemos corrigir:
“ “
if (! strchr (“feEgG”, c1) || forma [i]) {
retorna falso;
}
/ * parece bom, vamos imprimir * /
sprintf (dest, formato, num);
“ “
No executável do PS2, essa string `” feEgG “` está localizada em `0x3e2710`. Se fizermos o patch para `” feEgGx “`, podemos usar o formato `”% 0.8x “` para ler a memória nativa em hexadecimal:
“ “
# Execute o patch% lg ->% lu antes disso!
dim x (1,1073741824)
# 0x3e2710 “feEgG” -> “feEgGx”
x (0, 535696684) = 132248070612326.0
“ “
Após executar o patch acima, podemos abrir outra janela para atuar como nosso “depurador” e imprimir valores hexadecimais de 32 bits. O PoC abaixo imprime a memória em `0x480000`, que é uma região não utilizada que eu escolhi para gravar a saída de depuração de minhas cargas úteis:
dim x (1,1073741824)
imprima x (0, 535777310) usando “% 0.8x”
## Execução de código arbitrário
Com gravação arbitrária irrestrita, podemos começar a testar a execução de código arbitrário! Para o nosso primeiro PoC, vamos apenas escrever o endereço do endereço de retorno que corrompemos no endereço de memória não utilizado `0x480000` e depois retornar normalmente, para que possamos inspecioná-lo na janela” depurador “.
Para fazer isso, podemos voltar ao endereço de retorno original, `0x123428`. No entanto, vale a pena também restaurar o ponteiro da pilha (`- = 0xa0`), para que possamos voltar ao epílogo da função original em` 0x12d394` e restaurar todos os registros salvos do chamado novamente, pois isso vamos usar todos os registros livremente em nossa carga útil:
.set noreorder # Se estamos escrevendo assembly, por que queremos isso?
.globl _start
_começar:
li $ s0, 0x480000
sw $ v0, ($ s0)
retorno .globl
Retorna:
li $ ra, 0x123428
adicione $ s0, $ ra, (0x12d394 – 0x123428)
jr $ s0
sub $ sp, 0xa0
Compilando esta carga útil:
ee-gcc payload.s -o payload.elf -nostartfiles -nostdlib
ee-objcopy -O binário payload.elf payload.bin
E, em seguida, executando-o através do [este programa] (https://github.com/CTurt/PS2-Yabasic-Exploit/tree/master/maker.c) para converter uma carga em uma exploração Yabasic, obteremos o seguinte. Observe que escrevemos e saltamos para o nosso código através de endereços virtuais não armazenados em cache para evitar problemas de coerência de cache (EG: `0x20CD7B70` em vez de` 0xCD7B70`).
“ “
# Execute o patch% lg ->% lu antes disso!
dim x (1,1073741825)
x (0,67108864) = 12538584313560563784.0
x (0,67108865) = 4035001138559254546.0
x (0,67108866) = 279645527673511788.0
x (0,67108867) = 2575495349741289480.0
x (0,2509972) = 550340272,0
“ “
Depois de executar isso, podemos mudar para a janela “depurador” e imprimir o valor de depuração. Isso imprimirá 01ffeb50, exatamente como esperávamos, demonstrando que agora temos uma maneira de executar código arbitrário e inspecionar os resultados!
Ao escrever cargas úteis mais complicadas, lembre-se de que na entrada `$ ra` mantém o endereço para o qual saltamos, aponte para o endereço não armazenado em cache de nossa carga útil, mas observe também que na entrada` $ a1` aponta para o início do buffer, portanto esse é o endereço em cache da carga útil. Sabendo disso, podemos referenciar o código e os dados relativos a esses registradores para obter um código independente da posição.
## Usando seqüências de caracteres em cargas úteis
Agora que podemos explorar o Yabasic para executar código nativo arbitrário, precisamos escrever uma carga útil que possa carregar um ELF de uma fonte controlada (USB, HDD, Ethernet, iLink, disco gravado). Embora não exista realmente um limite rígido para o tamanho da carga, ele deve ser o menor possível para evitar a necessidade de digitar demais.
Como existe uma grande variedade de soluções criativas interessantes para isso, não vou abordá-las neste artigo, para não prejudicar o foco deste artigo. Vou trabalhar em alguns deles em segundo plano e carregá-los no repositório eventualmente – espero que, ao publicar este artigo, possa atrair algum interesse da comunidade de código aberto para contribuir aqui 🙂
No entanto, apenas para fornecer uma carga útil um pouco mais interessante para testar por enquanto e demonstrar como fazer referência a seqüências de caracteres, vamos tentar inicializar uma das muitas outras demos acessíveis no mesmo disco. Para o PBPX-95205, temos uma demonstração do FIFA 2001 que podemos tentar inicializar (`cdrom0: \ FIFADEMO \ GAMEZ.ELF`).
Para fazer isso, chamaremos a chamada de sistema `LoadExecPS2` (número 6), que permite especificar facilmente um caminho executável. Lembrando que queremos manter a carga útil o menor possível, podemos violar a convenção de chamada, deixando `argv` (` $ a2`) não inicializado, pois a contagem de argumentos (`$ a1`) é` 0`, mesmo que isso é tecnicamente incorreto:
.global _start
_começar:
addu $ a0, $ a1, 4 * 4
li $ a1, 0
li $ v1, 6
syscall
.asciiz “cdrom0: \\ FIFADEMO \\ GAMEZ.ELF”
Enquanto isso funciona, podemos facilitar a digitação, aproveitando o fato de que podemos armazenar seqüências de caracteres no Yabasic. Crie um arquivo “ boot-fifa.string “` correspondente e, em seguida, podemos referenciar os dados da string do endereço da carga útil menos `0x2e0`:
.global _start
_começar:
sub $ a0, $ a1, 0x2e0 # Aponte para s $ em cache
li $ a1, 0
li $ v1, 6
syscall # LoadExecPS2
Isso produz o seguinte, que é bem rápido de digitar:
“ “
# Execute o patch% lg ->% lu antes disso!
dim x (1,1073741824)
x (0,67108864) = 2595480760796642592.0
x (0,67108865) = 52143783942,0
x (0,2510080) = 550339408,0
s $ = “cdrom0: \ FIFADEMO \ GAMEZ.ELF”
“ “
## Portando para outros discos de demonstração
Todas as análises até este momento foram feitas no PBPX-95205, mas a Sony enviou outros 3 [discos de demonstração] (https://wiki.pcsx2.net/Demo_Disc) que contêm Yabasic, com seriados exclusivos. O PBPX-95204 tem exatamente o mesmo `YABASIC.ELF` que o PBPX-95205, no entanto, o PBPX-95506 tem uma versão diferente, para a qual eu [portou a exploração] (https://github.com/CTurt/PS2-Yabasic -Explorar/). Infelizmente, não consegui encontrar um disco PBPX-95520 no eBay, portanto não consegui portar para este disco de demonstração, mas, para ser completo, se alguém lendo tiver uma cópia, considere entrar em contato.
Observe que existem várias “versões” de discos de demonstração que têm o mesmo serial, como um PBPX-95204 impresso com o antigo logotipo “PS2” e um PBPX-95204 com o design atualizado do disco, mas que eu saiba, todos os demo discos com o mesmo número de série têm os mesmos arquivos executáveis. O PBPX-95205 é especialmente interessante, pois há uma versão impressa em um CD azul (semelhante a [this] (https://i.ytimg.com/vi/il7rkXlc0wI/maxresdefault.jpg)) e uma versão impressa em um DVD (parece com [este] (https://ia800608.us.archive.org/4/items/Playstation_2_Demo_Disc_PBPX-95205_Sony/Playstation%202%20Demo%20Disc%20%28PBPX-95205%29%28Sony%29.png) )
## Conclusão
Atualmente, os mecanismos de script para linguagens como JavaScript geralmente são a primeira coisa atacada nos consoles modernos de videogame, por isso acho estranho como o lançamento do Yabasic no PS2 recebeu tão pouca atenção dos hackers, uma vez que estava incluído em todos os consoles da região PAL para nos primeiros 3 anos e é fácil de despejar e analisar, principalmente com base no código-fonte aberto. Independentemente disso, esses discos estão prontamente disponíveis por um preço baixo hoje, e por isso espero que pelo menos algumas pessoas se beneficiem de um método homebrew um pouco mais conveniente do que abrir seus consoles ou comprar hardware não-oficial mais caro.
Infelizmente, as regiões NTSC nunca receberam uma porta Yabasic, no entanto, elas têm um intérprete básico diferente, “Basic Studio”, que eu poderia ver no futuro.
Finalmente, talvez agora que o Yabasic possa ser usado para executar código arbitrário, a Sony possa argumentar que realmente permitiu que o PS2 fosse [“programado livremente”] (https://www.casemine.com/judgement/uk/5a8ff7bb60d03e7f57eb19db) afinal, e eles podem reivindicar de volta seus impostos de importação: P