quinta-feira, 9 de abril de 2020

Onde carregar programas de código máquina (II) aka smashing the ZX Spectrum stack

Como referido no artigo anterior, existem espaços definidos na RAM para carregar código máquina, isto também incluindo o LOAD "" CODE, que normalmente devolve controle ao BASIC para executar a rotina instrução USR, para saltar para código máquina.

No entanto, tradicionalmente depois estar em código máquina, desde que não se necessite de rotinas BASIC e/ou rotinas da ROM, pode-se usar mais áreas da RAM.

O truque para carregar código máquina em RAM a partir do BASIC/LOAD "" CODE, sem estar sujeito às limitações de voltar ao controle do BASIC e sem ter que manter a integridade do sistema operativo/ROM (com "excepção" do stack do processador) pode passar por o LOAD "" CODE nunca devolver o controle ao BASIC. 

Nesse caso, estaremos livres para usar a RAM como bem entendermos, no pressuposto que modificamos / esmagamos o  stack de código máquina para os nossos propósitos, de forma a saltarmos para uma rotina nossa (e nunca devolver o controle às rotinas da ROM/BASIC no fim do LOAD "" CODE, como mencionado).

Até podemos carregar os 48KB de RAM de uma só vez, podendo inclusive reescrever a área do BASIC (e áreas associadas) completamente (ignorando a estrutura da RAM definida pelas rotinas da ROM no artigo anterior), sem ter que escrever rotinas extras em assembly para aproveitar os cerca de 2.5KB usados pelas variaveis de sistema/BASIC. 

Temos então de subverter o stack. Analisando o stack do processador, apontado por SP, depois de invocar em BASIC:

CLEAR 65518 : LOAD "" CODE:

65518 = $FFEE

SP = $FFFD

$FFDF 6F 05  return address to middle LD_BYTES
$FFE1 3F 05  SA/LD_RET        <---- called at the END of LOAD "" CODE (via RET)
$FFE3 71 07  return address to middle SAVE_ETC
$FFE5 E3 5C  (some saved variable)
$FFE7 00 00  (some saved variable)
$FFE9 76 1B  STMT-RET
$FFEB 03 13  MAIN EXECUTION LOOP
$FFED 42     random value?
$FFEE 3E     marker RAMTOP (CLEAR)

Primeira word em $FFDF: $056F : retorno de CALL a LD_EDGE_1 na rotina LD_BYTES (i.e. a meio da rotina de carregamento de tape). 

Segunda word em $FFE1 : $053F:  retorno a SA/LD_RET, rotina que é colocada no stack como retorno de LD_BYTES no inicio do mesmo, que trata do tratamento de erro em caso de TAPE LOADING ERROR.

Terceira  word em $FFE3 : $0771:  retorno à rotina SAVE_ETC, invocada para processamento do comando LOAD "", depois de CALL LD_BYTES (i.e. retorno depois de carregar o bloco de dados do LOAD "" CODE)

Portanto, para não voltarmos ao BASIC, e devido a alguns emuladores aparentarem não voltar a SA/LD_RET, temos de reescrever no stack, quer o retorno por SA/LD_RET, quer o retorno de LD_BYTES. Ou seja, no nossos dados carregados por LOAD "" CODE, vamos garantir que é feito LOAD do(s) valor(es) nestas posições, da posição em memória em que se executa a nossa rotina código máquina em substituição dos valores que permitem o software da ROM voltar ao interprete de BASIC.

Ou concretamente, nas words em $FFE1 e $FFE3, fazemos reescrita destes endereços com a WORD do endereço da nossa rotina, que indica para onde vai passar a execução após abandonar a rotina que faz o load do bloco de tape.

Ou seja, fazemos LOAD "" CODE do seguinte código:

        ORG     $FFE1

        DW      PRINT_STRING
        DW      PRINT_STRING

PRINT_STRING:
        DI

        LD      IX,STRING

PRLOOP: LD      A,(IX)
        OR      A 
        JR      Z,LOOP
        RST     $10
        INC     IX
        JR      PRLOOP

LOOP:   JR      LOOP
STRING:
        DEFB    "Testing",0

BASIC

10 CLEAR 65518 : LOAD "" CODE

Resultado:


PS. Basicamente, isto é um exploit para a ROM do Spectrum 48K.

Note-se que este método pode até ser abusado, para executar código, sem que alguém mais distraído a olhar para o BASIC se aperceba que isso foi feito desde que o exploit/rotina esteja escrita para preservar os valores necessários para voltar ao BASIC.

No entanto, a maior utilidade é ultrapassar a limitação de nunca ter sido incluído um auto-arranque em LOAD "" CODE de código máquina, para ultrapassar a limitação de não ter o LOAD "" CODE + invocação de arranque código máquina, deixando assim de estar dependente do bom funcionamento do BASIC para arrancar rotinas do código máquina, depois de uma LOAD "" CODE (feito a partir do BASIC).

1 comentário: