Microcorruption - Whitehorse#
Microcorruption nos dá um depurador e uma senha para desbloquear um dispositivo. Nossa tarefa é encontrar essa senha lendo o código assembly e usando algumas técnicas de engenharia reversa. Cada desafio leva o nome de uma cidade, e cada um é mais difícil que o anterior.
Se você ainda não resolveu o desafio sozinho, recomendo que pare de ler, resolva o desafio e confira minha solução depois. Não estrague o desafio e divirta-se!
Como resolver#
Primeiramente, vamos verificar a função main:
4438 <main>
4438: b012 f444 call #0x44f4 <login>A única coisa que faz é chamar a função login, então vamos verificar essa função também:
44f4 <login>
44f4: 3150 f0ff add #0xfff0, sp
44f8: 3f40 7044 mov #0x4470 "Enter the password to continue.", r15
44fc: b012 9645 call #0x4596 <puts>
4500: 3f40 9044 mov #0x4490 "Remember: passwords are between 8 and 16 characters.", r15
4504: b012 9645 call #0x4596 <puts>
4508: 3e40 3000 mov #0x30, r14
450c: 0f41 mov sp, r15
450e: b012 8645 call #0x4586 <getsn>
4512: 0f41 mov sp, r15
4514: b012 4644 call #0x4446 <conditional_unlock_door>
4518: 0f93 tst r15
451a: 0324 jz $+0x8 <login+0x2e>
451c: 3f40 c544 mov #0x44c5 "Access granted.", r15
4520: 023c jmp $+0x6 <login+0x32>
4522: 3f40 d544 mov #0x44d5 "That password is not correct.", r15
4526: b012 9645 call #0x4596 <puts>
452a: 3150 1000 add #0x10, sp
452e: 3041 retResumindo, esta função:
- Escreve na tela solicitando uma senha com
<puts> - Solicita a entrada de senha com
<getsn> - Verifica se a senha está correta com
<conditional_unlock_door> - Se a senha estiver correta, escreve que a senha está correta.
- Se a senha estiver incorreta, escreve que a senha está incorreta.
Depois de tudo isso, podemos ver também que na linha 0x452a há uma adição ao stack pointer, talvez isso possa ser útil. Mas primeiro vamos verificar a função conditional_unlock_door:
4446 <conditional_unlock_door>
4446: 0412 push r4
4448: 0441 mov sp, r4
444a: 2453 incd r4
444c: 2183 decd sp
444e: c443 fcff mov.b #0x0, -0x4(r4)
4452: 3e40 fcff mov #0xfffc, r14
4456: 0e54 add r4, r14
4458: 0e12 push r14
445a: 0f12 push r15
445c: 3012 7e00 push #0x7e
4460: b012 3245 call #0x4532 <INT>
4464: 5f44 fcff mov.b -0x4(r4), r15
4468: 8f11 sxt r15
446a: 3152 add #0x8, sp
446c: 3441 pop r4
446e: 3041 retEssa função, basicamente, apenas envia valores entre os ponteiros usando o sp (stack pointer), depois envia o valor 0x7e para a pilha e chama <INT>:
4532 <INT>
4532: 1e41 0200 mov 0x2(sp), r14
4536: 0212 push sr
4538: 0f4e mov r14, r15
453a: 8f10 swpb r15
453c: 024f mov r15, sr
453e: 32d0 0080 bis #0x8000, sr
4542: b012 1000 call #0x10
4546: 3241 pop sr
4548: 3041 retA função INT lê 2 bytes após o sp (ponteiro de pilha) e os envia para r14, que é movido para r15 e então realizar uma ação bis com 0x8000 e sr. Pense no comando bis como um comando ou (0x8000 | sr).
Em seguida, chamará a função em 0x10 (<__trap_interrupt>) para verificar se a porta será aberta ou não.
Pequeno intervalo: Como exatamente a porta se abre
A função trap_interrupt (
<__trap_interrupt>) lida com o hardware, enviando uma mensagem através do ponteirosrpara verificar o que o hardware fará com o sistema operacional.No nosso caso, o valor
ff00precisa ser definido emsrpara que a porta seja aberta, mesmo que a função<__trap_interrupt>seja chamada, ela não abrirá com outros valores.
Ao executar nosso código com uma senha aleatória, podemos ver que o valor de sr na função de verificação é sempre 0xfe00, próximo do que precisamos, mas ainda não está correto. Precisamos alterar esse valor.
Se formos alterar alguns valores, primeiro vamos verificar o limite de entrada, fazendo um buffer overflow e verificando na memória. Primeiro, vamos tentar adicionar vários 6.
666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666Após inserir e executar um comando de ‘passo’ (s), podemos ver que nossa entrada alcança entre 0x3cb8 e 0x3ce7.
Agora vamos ver se o comando em login nas linhas 0x452a e 0x452e se sobrepõe à nossa entrada. Essas linhas são:
452a: 3150 1000 add #0x10, sp
452e: 3041 retEssas linhas adicionam 0x10 ao sp e então usam a posição do sp para retornar à função que o chamou. Devemos verificar se essa posição no sp chamada pelo retorno (ret) pode ser modificada pela nossa entrada.
Para fazer isso:
- Adicione um ponto de interrupção (breakpoint) em
0x452ae execute-o. - Verifique a posição do
spna memória. - Se a posição estiver entre
0x3cb8e0x3ce7, podemos alterar o endereço de retorno.
Para nossa sorte, a posição de retorno é em 0x3cc8, uma posição que nossa entrada pode alterar.
Com essa informação, vamos modificar o retorno para outro local, vamos para 0x451c. Após alguns testes, nossa entrada ficará assim:
666666666666666666666666666666661c45Essa entrada fará o salto para a linha 0x451c, no texto “Acesso Concedido”. Mesmo com esse salto, nossa porta ainda não está destrancada. Desta vez, tentaremos retornar para a função <INT>, talvez executando-a duas vezes nos permitirá manipular melhor o fluxo do programa. Nossa próxima entrada é:
666666666666666666666666666666663245Após o redirecionamento para o endereço 0x4532 (<INT>), a pilha armazenará ainda mais dados, fazendo com que o valor em r14 e r15 seja lido novamente, mas agora em uma posição que nossa entrada pode manipular. Esses valores serão adicionados ao ponteiro sr, então talvez possamos fazer com que sr seja 0xff00 para destrancar a porta.
A segunda leitura ocorre 3 bytes após o último valor de retorno que modificamos. Por isso, adicionaremos 2 bytes zero (0000) à nossa entrada e, em seguida, adicionaremos o valor de desbloqueio ao sr (0xff00). A entrada será algo como:
6666666666666666666666666666666632450000ff00Com isso, retornamos à função <INT>, adicionamos mais valores à pilha, e modificamos o sr para ser 0xff00 para destrancar a porta.