Microcorruption - Addis Ababa#
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: 3150 eaff add #0xffea, sp
443c: 8143 0000 clr 0x0(sp)
4440: 3012 e644 push #0x44e6
4444: b012 c845 call #0x45c8 <printf>
4448: b140 1b45 0000 mov #0x451b ">> ", 0x0(sp)
444e: b012 c845 call #0x45c8 <printf>
4452: 2153 incd sp
4454: 3e40 1300 mov #0x13, r14
4458: 3f40 0024 mov #0x2400, r15
445c: b012 8c45 call #0x458c <getsn>
4460: 0b41 mov sp, r11
4462: 2b53 incd r11
4464: 3e40 0024 mov #0x2400, r14
4468: 0f4b mov r11, r15
446a: b012 de46 call #0x46de <strcpy>
446e: 3f40 0024 mov #0x2400, r15
4472: b012 b044 call #0x44b0 <test_password_valid>
4476: 814f 0000 mov r15, 0x0(sp)
447a: 0b12 push r11
447c: b012 c845 call #0x45c8 <printf>
4480: 2153 incd sp
4482: 3f40 0a00 mov #0xa, r15
4486: b012 5045 call #0x4550 <putchar>
448a: 8193 0000 tst 0x0(sp)
448e: 0324 jz $+0x8 <main+0x5e>
4490: b012 da44 call #0x44da <unlock_door>
4494: 053c jmp $+0xc <main+0x68>
4496: 3012 1f45 push #0x451f
449a: b012 c845 call #0x45c8 <printf>
449e: 2153 incd sp
44a0: 0f43 clr r15
44a2: 3150 1600 add #0x16, spEntão, vamos analisar o que ele faz:
- Exibe na tela a mensagem solicitando nome de usuário e senha com
<printf> - Define o limite de caracteres de input para
0x13, e define a posição pra ser escrita na memória em0x2400. - Abre a Janela solicitando nome de usuário e senha com
<getsn> - Copia a string do input de
0x2400para0x3c6eusando<strcpy> - Testa se a senha é válida com
<test_password_valid> - Reescreve nosso input na tela usando
<printf> - Adiciona alguns
0xaao stack frame com<putchar> - Se os bytes em
0x3c6cforem zero, imprime que a senha está incorreta. - Se os bytes em
0x3c6cnão forem zero, destranca a porta.
Bem, com essa informação, vamos verificar as possibilidades:
Até onde nosso Input vai?#
Como já foi dito, estamos limitados a 0x13 caracteres de entrada (ou 19 caracteres em decimal).
Talvez possamos usar o retorno em <main> e o redirecionar para a função <unlock_door>. Isso criaria uma solução comum, mas talvez funcione.
Mas há um problema. Se verificarmos a função <main>, nenhuma chamada de retorno (return) é feita, ela simplesmente “vaza” para a próxima função <__stop_progExec__>, fazendo com que o programa pare. Portanto, isso não é uma possibilidade.
O estranho na casa: printf#
Essa é uma função incomum. Na maioria dos desafios anteriores, era usado <puts> para imprimir as strings e informações, então por que mudar?
Isso me leva a crer que a solução está nesta função. Esta é uma função comum/famosa na biblioteca C, então estamos lidando com uma compilação em C? Vamos verificar a função printf completa:
45c8 <printf>
45c8: 0b12 push r11
45ca: 0a12 push r10
45cc: 0912 push r9
45ce: 0812 push r8
45d0: 0712 push r7
45d2: 0412 push r4
45d4: 0441 mov sp, r4
45d6: 3450 0c00 add #0xc, r4
45da: 2183 decd sp
45dc: 1b44 0200 mov 0x2(r4), r11
45e0: 8441 f2ff mov sp, -0xe(r4)
45e4: 0f4b mov r11, r15
45e6: 0e43 clr r14
45e8: 0b3c jmp $+0x18 <printf+0x38>
45ea: 1f53 inc r15
45ec: 7d90 2500 cmp.b #0x25, r13
45f0: 0720 jnz $+0x10 <printf+0x38>
45f2: 6d9f cmp.b @r15, r13
45f4: 0320 jnz $+0x8 <printf+0x34>
45f6: 1f53 inc r15
45f8: 0d43 clr r13
45fa: 013c jmp $+0x4 <printf+0x36>
45fc: 1d43 mov #0x1, r13
45fe: 0e5d add r13, r14
4600: 6d4f mov.b @r15, r13
4602: 4d93 tst.b r13
4604: f223 jnz $-0x1a <printf+0x22>
4606: 0f4e mov r14, r15
4608: 0f5f add r15, r15
460a: 2f53 incd r15
460c: 018f sub r15, sp
460e: 0941 mov sp, r9
4610: 0c44 mov r4, r12
4612: 2c52 add #0x4, r12
4614: 0f41 mov sp, r15
4616: 0d43 clr r13
4618: 053c jmp $+0xc <printf+0x5c>
461a: af4c 0000 mov @r12, 0x0(r15)
461e: 1d53 inc r13
4620: 2f53 incd r15
4622: 2c53 incd r12
4624: 0d9e cmp r14, r13
4626: f93b jl $-0xc <printf+0x52>
4628: 0a43 clr r10
462a: 3740 0900 mov #0x9, r7
462e: 4a3c jmp $+0x96 <printf+0xfc>
4630: 084b mov r11, r8
4632: 1853 inc r8
4634: 7f90 2500 cmp.b #0x25, r15
4638: 0624 jz $+0xe <printf+0x7e>
463a: 1a53 inc r10
463c: 0b48 mov r8, r11
463e: 8f11 sxt r15
4640: b012 5045 call #0x4550 <putchar>
4644: 3f3c jmp $+0x80 <printf+0xfc>
4646: 6e48 mov.b @r8, r14
4648: 4e9f cmp.b r15, r14
464a: 0620 jnz $+0xe <printf+0x90>
464c: 1a53 inc r10
464e: 3f40 2500 mov #0x25, r15
4652: b012 5045 call #0x4550 <putchar>
4656: 333c jmp $+0x68 <printf+0xf6>
4658: 7e90 7300 cmp.b #0x73, r14
465c: 0b20 jnz $+0x18 <printf+0xac>
465e: 2b49 mov @r9, r11
4660: 053c jmp $+0xc <printf+0xa4>
4662: 1a53 inc r10
4664: 1b53 inc r11
4666: 8f11 sxt r15
4668: b012 5045 call #0x4550 <putchar>
466c: 6f4b mov.b @r11, r15
466e: 4f93 tst.b r15
4670: f823 jnz $-0xe <printf+0x9a>
4672: 253c jmp $+0x4c <printf+0xf6>
4674: 7e90 7800 cmp.b #0x78, r14
4678: 1c20 jnz $+0x3a <printf+0xea>
467a: 2b49 mov @r9, r11
467c: 173c jmp $+0x30 <printf+0xe4>
467e: 0f4b mov r11, r15
4680: 8f10 swpb r15
4682: 3ff0 ff00 and #0xff, r15
4686: 12c3 clrc
4688: 0f10 rrc r15
468a: 0f11 rra r15
468c: 0f11 rra r15
468e: 0f11 rra r15
4690: 1a53 inc r10
4692: 079f cmp r15, r7
4694: 0338 jl $+0x8 <printf+0xd4>
4696: 3f50 3000 add #0x30, r15
469a: 023c jmp $+0x6 <printf+0xd8>
469c: 3f50 5700 add #0x57, r15
46a0: b012 5045 call #0x4550 <putchar>
46a4: 0b5b add r11, r11
46a6: 0b5b add r11, r11
46a8: 0b5b add r11, r11
46aa: 0b5b add r11, r11
46ac: 0b93 tst r11
46ae: e723 jnz $-0x30 <printf+0xb6>
46b0: 063c jmp $+0xe <printf+0xf6>
46b2: 7e90 6e00 cmp.b #0x6e, r14
46b6: 0320 jnz $+0x8 <printf+0xf6>
46b8: 2f49 mov @r9, r15
46ba: 8f4a 0000 mov r10, 0x0(r15)
46be: 2953 incd r9
46c0: 0b48 mov r8, r11
46c2: 1b53 inc r11
46c4: 6f4b mov.b @r11, r15
46c6: 4f93 tst.b r15
46c8: b323 jnz $-0x98 <printf+0x68>
46ca: 1144 f2ff mov -0xe(r4), sp
46ce: 2153 incd sp
46d0: 3441 pop r4
46d2: 3741 pop r7
46d4: 3841 pop r8
46d6: 3941 pop r9
46d8: 3a41 pop r10
46da: 3b41 pop r11
46dc: 3041 retEssa é uma função longa e um tanto complexa (chata), porque ela pula de um lugar para outro com algumas verificações e outras sem.
Então, como vamos analisar essa função? Bem, primeiro vamos verificar as ramificações dessa função, em outras palavras, vamos listar as linhas “Compare” (cmp).
cmp.b #0x25, r13=> Compara r13 com o literal0x25(%)cmp.b @r15, r13=> Compara r13 com @r15cmp r14, r13=> Compara r13 com r14cmp.b #0x25, r15=> Compara r15 com o literal0x25(%)cmp.b r15, r14=> Compara r15 com r14cmp.b #0x73, r14=> Compara r14 com o literal0x73(s)cmp.b #0x78, r14=> Compara r14 com o literal0x78(x)cmp r15, r7=> Compara r15 com r7cmp.b #0x6e, r14=> Compara r14 com o literal0x6e(n)
Podemos começar verificando os caracteres ASCII literais, que são %, s, x, n. Como já mencionamos, esses são termos comuns para desenvolvedores em C.
Specifier Characters#
Você pode conferir os detalhes na documentação Aqui, mas estamos tratando de:
%s: Cadeia de caracteres (String)%x: Número inteiro em hexadecimal sem sinal (Unsigned hexadecimal integer)%n: Nada é impresso e o número de caracteres escritos até o momento é armazenado no local indicado.
Então, vamos dar um exemplo com %s:
printf ("%s text here \n", "A string");
// | |________|
// +----------------------
// output : "A string text here"
Pegamos um argumento e a substituímos na string original. Isso também funciona com %x:
printf ("100 decimal in hex : %x", 100);
// output : "100 decimal in hex : 64"
Esta função recebe uma informação e a lê como um valor hexadecimal. Por fim, o especificador %n:
// cria uma variável int
int val;
// Metade A e metade B, armazene %n na variável val
printf("AA AA A %n BBBBBBBBBBBBB\n", &val);
// Imprime o valor de val
printf("val = %d \n", val);
// output : "val = 8"
O resultado é 8 porque %n está considerando o comprimento dos caracteres antes da invocação. Ele conta tudo o que vem antes dele (“AA AA A”) e armazena em val, resultando em 8. Tudo o que vem depois de %n é ignorado, portanto, a seção ‘B’ não é contabilizada.
Sabendo disso, vamos fazer algumas experiências.
Tentei inserir alguns inputs aleatórios, algo como:
AAAAAA%s%sAAA
AAA%x%x%x%x%x
%x%s%n%s%xE assim por diante, até que eu tentei:
AA%x%n%n%nIsso gerou um erro (“load address unaligned: 4141”) e o programa parou no meio do <printf>. Isso foi interessante, minhas suposições estavam certas, a solução está relacionado ao printf. Eu não sabia muito sobre C, então procurei por vulnerabilidades na função printf. Aqui está o que encontrei no site da OWASP:
A seguir, apresentamos alguns parâmetros que podem ser usados e suas consequências:
•"%x" Lê dados da pilha (stack)
•"%s" Lê os caracteres da memória do processo atual.
•"%n" Escreve um int para lugares na memória do processo atual.Isso me fez pensar: e se eu usasse isso para manipular a pilha (stack) e a memória?
Modificando a memória e %x%n#
No início da função <printf>, os valores dos registradores r11, r10, r9, r8, r7 e r4 são colocados na pilha. Esses valores serão usados posteriormente, e talvez algum input possa manipular o resultado.
O que podemos ver com nosso último input (AA%x%n%n%n) é que o valor desalinhado era “4141”, que em ASCII é “AA”. Então, ele simplesmente tentou modificar o valor em 0x4141 na memória. Ok, talvez com isso possamos modificar algum valor.
Lembre-se: para chamar a função <unlock_door>, os bytes em 0x3c6c não podem ser zero. Talvez possamos modificar esse endereço? Vamos tentar com a seguinte entrada:
l<%x%n%n%nO l< é uma representação dos bytes 0x6c e 0x3c da tabela ASCII, que é o nosso endereço de destino corrigido para endianness.
Com isso, o valor em 0x3c6c não é mais zero, fazendo com que a porta se abra.
Depois de um tempo eu tentei limpar um pouco o input, então a entrada final que usei para abrir a porta foi:
l<%x%n