Desde o meu último artigo , para minha grande surpresa, o interessou. Decidi complementar o resultado, uma versão hackeada do jogo "Contra (J) [T + Rus_Chronix]", com um pouco de funcionalidade, mostrando ao mesmo tempo "injeção de código" no NES. Desta vez, farei com que os jogadores iniciem o jogo com a pistola Spreadgun. Para entrar no jogo, você precisa selecionar o ícone "S", seguido de "R".

Todos os interessados são bem-vindos sob o gato.
Tradicionalmente:
gravação de vídeo chata e tediosa do processo E tradicionalmente planejamos a sequência de ações.
- Localizar endereços
- Descubra o valor da Spreadgun bombeada
- Descubra o que ele escreve nesses endereços no início do jogo
- Reescrever ROM
- Easyway - altere o valor da arma básica para o spreadgan bombeado
- Hardway - use uma injeção de código completo se a maneira mais fácil falhar
- salve o resultado em um novo arquivo
Para procurar endereços, usamos o método descrito anteriormente, mas lembrando que vidas estavam em endereços vizinhos, procuraremos apenas armas do primeiro jogador na esperança de que o segundo jogador esteja próximo. Abrindo a janela "Ram watch" após o início do primeiro nível, procuramos um valor desconhecido. Acredito que a arma base esteja definida como 0, mas não sei ao certo.
Corremos ao longo do nível sem avançar, disparamos em todas as direções e eliminamos os valores variáveis. A arma ainda não mudou.
Vamos usar outra opção da janela, ou melhor, no campo "Comparar com / por", destacando a frequência de rádio "Número de alterações", coloque no campo 0. O tipo de comparação é obviamente "Igual a". A arma ainda não mudou.
Então, saltando da opção "igual ao valor anterior", para a opção "o número de alterações é 0", você pode alcançar cerca de 10.000 endereços. Quando outros exames reduzirem a lista de endereços muito pouco ou nada, é possível avançar o suficiente para eliminar a primeira arma.
Depois de buscá-lo, usamos imediatamente o método de pesquisa "não igual ao valor anterior" e reduzimos ainda mais a lista pesquisando "o número de alterações é 1", a arma mudou exatamente uma vez.
No local onde pegamos nossa primeira arma, um amplificador de arma também aparece. Depois de selecioná-lo e selecioná-lo, você pode reduzir o número de endereços para 1, mas se não der certo, basta matar o seu personagem e mudar a arma novamente. (Não se esqueça da pesquisa após cada alteração).
Com um amplificador de arma, existe algum risco de que ela não mude o valor da arma em si, mas a bandeira em outro lugar na memória, mas esperemos que os autores do jogo economizem memória e instruções. E, por sorte, o bônus "R" alterou o próprio valor da arma e o endereço estava na coordenada AA 16 . (Eu posso estar errado, mas monitorei repetidamente a arma do herói em diferentes versões do jogo Contra, como em todo lugar em que esse endereço era AA 16 ).
Notei também que o bônus "R" aumentou o valor no endereço em 16 10 ou 10 16 , ou seja, aumentou em 1 o primeiro dígito do número hexadecimal.
Após o reinício na janela “Ram watch”, pode-se ver que o valor base é realmente 00 16 , o bônus “M” aumentou o segundo dígito em 1 e o bônus “R” o primeiro.
Você pode optar por um spreadgan, ele definitivamente se encontra nesse nível ou pode alterar o valor do endereço para ver quais números, quais armas eles criam. Empiricamente, descobri que, 01 16 é "Metralhadora", 02 16 é "Fogo", 03 16 é "Arma" e 04 16 é "Laser". Quando você insere outros valores no segundo dígito, ocorrem várias falhas.
Após reiniciar o jogo e inserir o valor 1x 16 , (onde "x" é uma das opções aceitáveis) antes de selecionar o bônus "Rápido", você pode descobrir que a re-seleção do bônus não muda nada.
Agora você pode reiniciar o jogo, iniciar o jogo para dois e tentar alterar os endereços adjacentes ao AA 16 . (Existem dois deles, a busca não será longa). Após atirar no segundo jogador, descobri rapidamente que as armas do segundo jogador estão realmente armazenadas nas proximidades, no endereço AB 16 . E agora sabemos os endereços de interesse para nós e o valor que deve ser colocado lá, é hora de descobrir o que ele escreve nesses endereços.
Jogando um ponto de interrupção no registro desse endereço, descobri que a gravação acontece várias vezes e uma delas após a tela inicial. O código a seguir faz essa entrada:
Morada | Opcode | Mnemônico | Argumentos | Um | X |
---|
C307 | A2 28 | LDX | # $ 28 | ?? | ?? |
C309 | A9 00 | Lda | # $ 00 | ?? | 28 ou 29 ou ... ou F0 |
C30B | 95 00 | STA | US $ 00, X | 00 | 28 ou 29 ou ... ou F0 |
C30d | E8 | Inx | | 00 | 28 ou 29 ou ... ou F0 |
C30e | E0 F0 | CPX | # $ F0 | 00 | 29 ou 30 ou ... ou F0 |
C310 | D0 F9 | Bne | $ C30B | 00 | 29 ou 30 ou ... ou F0 |
Se você ler atentamente o jogo aqui, zera o intervalo de endereços de 0028 16 a 00F0 16 , obviamente os dois endereços de interesse para nós no intervalo. Portanto, não haverá uma maneira fácil. Vou ter que usar "Code Injection" e a solução mais simples que encontrar para descobrir de onde chegamos daqui, redirecionar a execução para algum lugar livre de código e dados na memória, escrever lá minha versão do loop ocupando todo o intervalo, exceto os endereços 00AA 16 e 00AB 16 e retornar o carro execução de volta. A propósito, esta é a versão mais clássica da injeção. Você também pode supor que chegamos aqui a partir da instrução JSR (Jump to SubRoutine); isso é fácil de verificar com a pilha.
Como a pilha funciona?Nos processadores 6502, a pilha está sempre localizada no intervalo de endereços 0100 16 - 01FF 16 para todos os computadores baseados nesse processador e cresce de um endereço maior para outro menor. Há um registro separado apontando para o topo da pilha, inicialmente é igual a FF 16, pois um byte mais significativo nunca muda. O próprio registro sempre indica o byte mais alto desocupado com dados úteis.
O depurador do emulador não mostra o valor do registro "Stack Pointer", mas mostra o endereço ao qual o registro se refere e agora é 01F2 16 , cálculos simples mostram que os últimos dados na pilha são C3 16 e C2 16 , o que poderia levar Penso no endereço C3C2 16, mas 6502 é um processador do tipo "Little Endian" e, portanto, ao executar instruções e armazenar o endereço na memória ou na pilha, o byte menos significativo é escrito primeiro. E se o endereço realmente estiver no topo da pilha, este é o endereço C2C3 16 . E este é o endereço do último argumento da instrução JSR, se, novamente, é um endereço. É muito fácil verificar, basta olhar para o que está escrito dois bytes acima do endereço C2C3 16 .
Morada | Opcode | Mnemônico | Argumentos |
---|
C2C1 | 20 07 C3 | Jsr | $ C307 |
Como você pode ver, esta é uma instrução JSR para C307 16 , o que significa que a suposição da sub-rotina está correta.
Agora você precisa escrever o código de injeção corretamente, encontrar um local adequado para ele, escrever nesse local e redirecionar a instrução JSR para essa injeção.
Para isso, é muito conveniente usar um bloco de notas, eu tenho o Visual Studio Code para isso. Todo mundo tem seu próprio estilo de escrita, pessoalmente, sou o primeiro a escrever uma instrução JSR com seu endereço, a fim de saber onde alterar e um código de operação completo, para saber o que alterar.
Depois de alguns recuos, duplico o código do loop, ele já pode ser usado sem endereços, mas é extremamente útil ver mnemônicos com argumentos diferentes de opcodes, e é útil pegar as instruções após esse loop junto com o endereço para saber para onde retornar da injeção.
C2C1:20 07 C3 JSR $C307 A2 28 LDX #$28 A9 00 LDA #$00 95 00 STA $00,X E8 INX E0 F0 CPX #$F0 D0 F9 BNE $C30B C312:A2 07 LDX #$07
Alguns travessões abaixo, você pode escrever o código da injeção em si, na verdade, é uma duplicata do próprio ciclo com algumas adições.
C312:A2 07 LDX #$07 A2 28 LDX #$28 A9 00 LDA #$00 95 00 STA $00,X E8 INX E0 AA CPX #$AA D0 F9 BNE -7 A9 13 LDA #$13 95 00 STA $00,X E8 INX E0 AC CPX #$AC D0 F9 BNE -7 A9 00 LDA #$00 95 00 STA $00,X E8 INX E0 F0 CPX #$F0 D0 F9 BNE -7
Para maior clareza, indiquei o recuo em vez do endereço do local do salto.
Se você não conseguiu ler este códigoAqui está o mesmo ciclo, mas dividido em três partes, no primeiro ciclo comparamos o registrador X com o valor AA 16 , pois precisamos zerar todos os endereços para o endereço 00AA 16 , depois de inserir 13 16 no registrador A, o valor do spread bombeado e escrever o segundo ciclo para o endereço 00AC 16 a partir do qual o restante do intervalo deve ser zerado novamente. Voltamos ao registro A zero e zero o restante do intervalo.
É imperativo concluir as instruções de retorno da injeção no final.
E0 F0 CPX #$F0 D0 F9 BNE -7 4C 12 C3 JMP $C312
Agora, por conveniência, prefiro escrever os códigos de operação abaixo no arquivo.
4C 12 C3 JMP $C312 A2 28 A9 00 95 00 E8 E0 AA D0 F9 A9 13 95 00 E8 E0 AC D0 F9 A9 00 95 00 E8 E0 F0 D0 F9 4C 12 C3
E contando os códigos de operação, é fácil descobrir que existem 32 deles. Portanto, você precisa encontrar 20 16 endereços desocupados na ROM. Como regra, endereços desocupados são grandes espaços com os mesmos valores, geralmente zeros ou FF 16. É apenas uma peça tão grande que precisa ser encontrada, que deve ter pelo menos 35 10 endereços para que haja alguma margem.
No "Hex Editor", encontrei esse intervalo em B29E 16 -BFFF 16 . Usar o início desses sites gratuitos para injeções pode ser perigoso, portanto, aconselho a escrever um código de injeção no final. O endereço mais conveniente para iniciar a injeção é BFE0 16 , mas esse é o endereço na memória do console, para descobrir onde ele está localizado no arquivo ROM, clique com o botão direito do mouse e selecione "Go Here In ROM File".
Agora você pode copiar e colar todo o código operacional aqui (32 valores). Instruções de alteração do toque final
C2C1:20 07 C3 JSR $C307
pular para o endereço de injeção que tenho é BFE0 16 .
C2C1:20 E0 BF JSR $BFE0
Obviamente, encontrar o verdadeiro local de instrução na ROM.
A resposta à pergunta dos profissionais desatentos.O endereço da instrução JSR entra na pilha; portanto, independentemente do local do salto, o mesmo endereço de retorno estará lá e, a partir da injeção, retornamos ao código com uma instrução JMP que não afeta a pilha de forma alguma. Assim, a instrução RTS funciona no mesmo local que sem injeção e retorna ao mesmo local que sem injeção. Como resultado, essa injeção não quebra a pilha.
PS Para sempre, você ainda precisa garantir que, após a morte, os personagens renasçam com uma propagação bombeada, mas tenho certeza que armados com o conhecimento adquirido, você pode lidar com isso sozinho. Vou virar meus olhos para outra coisa. Motor Unity, por exemplo.