Vencendo campo minado hackeando
O objetivo do jogo é destravar todo o tabuleiro sem acertar as minas espalhadas aleatoriamente pelo tabuleiro. Se você abrir um slot sem uma mina, ele será marcado com a quantidade de minas que estão próximas, assim:
O objetivo deste artigo é mostrar como resolver o jogo usando engenharia reversa e como localizar a função de vitória do jogo. Abaixo, mostrarei como construiremos um arquivo dll que injetaremos no software para nos permitir vencer assim que pressionarmos a tecla "mágica" selecionada.
Também apresentarei outra solução, escrevendo um loader para o software que nos permitirá vencer assim que pressionarmos a tecla "enter".
Abaixo está um vídeo que demonstra como realizar o processo:
Para baixar o jogo:
Neste artigo, usaremos 2 ferramentas:
- WinDbg – Para conseguir realizar e-debugging
- Ida – Para conseguir ler o código e a documentação
Além do fato de que no vídeo eu mostrei a vocês o processo de depuração usando OllyDbg, no artigo prefiro demonstrar como você pode fazer a mesma coisa usando WinDbg.
Antes de depurar, devemos examinar a funcionalidade do software que estamos pesquisando e tentar encontrar uma característica'Qualquer ou janela que possa nos ajudar a encontrar a funcionalidade que queremos explorar e não necessariamente explorar todo o jogo.
A janela do jogo é assim:
Você pode ver que este jogo tem poucas funcionalidade:
- Início de um novo jogo
- Diferentes níveis de jogo
- Cores e marcações
- Áudio
- Tabela de resultados
Se você marcar a opção Áudio e começar a tocar, você verá os seguintes sons:
- O som do relógio
- Som de mina
- Som de vitória
Desta forma, é possível concluir que se encontrarmos a função responsável pelos sons, podemos voltar ao código e ver qual função foi chamada e assim encontrar a função de vitória do jogo.
Vamos abrir o jogo em-Windbg:
Não se esqueça de marcar o processo de gravação. Começamos o jogo, agora jogamos um pouco e fechamos a janela do jogo..
E assim que você se encontrará na janela windbg dentro da gravação da execução do jogo, a gravação permite que você se mova para frente e para trás enquanto roda (time travel debugging), Desta forma, você não terá que executar o software novamente se tiver perdido algo para o qual deseja retornar.
Agora precisamos encontrar todas as funções que o software usa: Essas funções estão em uma tabela chamada Import Address Table, esta tabela contém uma lista de todas as dlls que usamos, a função que usamos e, claro, o endereço da função.
Para chegar à tabela devemos primeiro encontrar os endereços do módulo que nos interessa (base address) Em seguida, adicionar a isso a distância (offset) Até a tabela, para fazer isso usaremos um comando:
!dh winmine
E obteremos o seguinte resultado:
Você pode ver que o endereço base é 0100 0000 e a distância da tabela é 1000. Além disso, o tamanho da tabela é 1B8, de modo que para imprimir a tabela usaremos o comando: dps 01000000+1000 01000000+1000+1B8
Claro que você também pode usar o nome do módulo em vez de escrever o endereço base porque ele realmente contém o endereço base, por exemplo, se escrevermos:
?winmine
Parece ser realmente igual ao seu próprio endereço de base, que é 0100 0000
Então você poderia escrever a mesma ordem: dps winmine+1000 winmine+1000+1B8
E o resultado será assim.:
Esta é basicamente uma lista de todas as funções que estão nas bibliotecas externas que o software usa, agora vamos realizar um ponto de interrupção na função PlaySoundW usando o comando: bp WINMM!PlaySoundW
E pressionar g para continuar executando o software. Irá ver que irá parar a função imediatamente antes de o jogo começar, provavelmente é o som do jogo que não está relacionado com a vitória pois ainda nem iniciamos o jogo em si.
Pressione g novamente e pressione a segunda função de call stack, esta é a função que realmente chamou- PlaySoundW:
Clique nele duas vezes e você passará para ele:
Pode-se observar que esta função recebe 3 tipos de sons de acordo com o valor esp+4]] E pule para o som usando je, para verificar o tipo de som iremos realizar um novo break point com as condições de impressão na primeira linha da função e examinaremos os sons que estamos recebendo atualmente: bp 010038ed ".echo SOUND VALUE; ?poi(@esp+4)"
Vamos agora reiniciar o software sem executar tudo novamente pressionando o botão reset e depois o botão go, então você verá que parou na linha desejada e imprimiu o som que é na verdade 1.
Escreva g nos comandos e você verá que parou novamente com 1
Comentário: Se você parou em PlaySoundW cancele o ponto de interrupção, usando bd 0 que é na verdade a sigla para desativar o ponto de interrupção e 0 é o número de pontos de interrupção da lista de pontos de interrupção que podem ser vistos usando o comando bl.
Continue a executar o comando g até perder o jogo, você notou que antes da perda você obteve o som 3?
Portanto, a tarefa é clara, precisamos encontrar uma função que possa nos devolver o som 2 que pode ser deduzido disso que esse é o som da vitória.
Voltamos usando: g-
Até o ponto no tempo em que foi impressos 3:
E vamos verificar qual função chamou este código, voltando no tempo com a ajuda do comando: t-
E assim que chegamos ao seguinte trecho de código:
Pode-se ver que este trecho de código é afetado por esi, então vamos inverter a lógica e ver o qual era o esi quando obtivemos o número 3:`
eax = 3 sub eax, 3 adc eax, eax neg eax
`
E descobrimos que esi era na verdade 0, agora faremos o mesmo cálculo para o dígito 2 que queremos obter (Mesmas operações matemáticas, mas desta vez colocamos o número 2 em vez de 3 em eax)
E nós temos 1 (Não se esqueça do-CF), Dê uma olhada na função, será que receber 1 no ESI é algo possível?
Pode-se ver que esi é afetado pelo argumento que passa para a função, então já aqui o exercício pode ser resolvido realizando um breakpoint na linha 01003488 e alterando o valor esi para 1.
Ou apenas crie uma dll que execute a função 0100347c com o argumento 1.
Testaremos nossa funcão agora executando o software novamente, porém sem viajar no tempo desta vez:
Antes de jogar usando g, execute um break point na linha 0100347c e ative o jogo pelo comando g, agora tente localizar uma mina e você verá que o jogo parou em nosso break point:
Execute step into 3 vezes atravez do comando:
t
E você verá que alcançamos exatamente a linha 01003488 e esi é igual a 0 exatamente como calculamos.
Agora mude o valor de esi usando o comando: r esi=1
Para ver, escreva r, que será assim:
Agora continue o jogo pelo comando g, e você ouvirá o som da vitória do jogo:
Vamos voltar uma função e tentar entender o que faz com que o jogo envie 1 para a nossa função, clique em
t-
Até você voltar uma função e chegar aqui:
É hora de abrir um IDA e olhar para o mesmo endereço usando G, que vai ser assim.:
Você pode ver que a função sabe receber 1 e 0, vamos até o topo da função e realizar um breakpoint na linha 01003512:
Dentro do WinDbg assim:
bp 01003512
Jogue o jogo até parar no breakpoint, agora execute o comando uf para que possamos ver as funções confortavelmente:
Você pode ver que temos uma condição interessante que verifica se o slot que abrimos é igual a 80. Se não for igual há um salto para a área que pode potencialmente retornar 1 caso contrário continuamos normais no código e alcançamos a área que nos retorna 0.
Para testar o código de forma eficaz, realizaremos 2 break points inteligentes, o primeiro nos mostrará o valor da comparação e o segundo nos mostrará quanto vale o eax antes de decidir retornar 1 na próxima seção:
O primeiro breakpoint é assim:
bp 01003529 ".echo VALUE; db @edx L 1 ;gc"
E o outro é assim:
bp 010035A1 ".echo CMP; ?@eax; gc"
Brinque um pouco no jogo e examine os valores impressos:
Você pode ver que perdemos assim que clicamos em um slot que tinha uma mina e além disso o valor que apareceu no depurador era 80, nas outras vezes que abrimos slots sem uma mina o valor era 0.
Como você pode ver, a entrada no CMP exibia 12 assim que 12 slots fossem abertos clicando na placa. Em seguida, abri um único slot e o valor exibido foi 13 e, novamente, um slot e então cheguei a 14. Parece haver 14 campos abertos (Sinta-se à vontade para contar 😊)
Então o jogo basicamente decide que ganhamos assim que conseguimos abrir todos os campos do jogo sem acertar o campo que valia 80.
A comparação é feita com um número em 010057a0, que é afetado pelo tamanho do tabuleiro que você escolhe no menu..
No próximo artigo iremos construir uma dll para injetar no jogo e até mesmo escrever um carregador que irá carregar o jogo e vencer para nós.