-
Postagens
22 -
Registro em
-
Última visita
-
Dias Ganhos
7
Tipo de Conteúdo
Fóruns
Treinamentos
Notícias
Artigos
Contribuindo
Profissões
Materiais de estudo
Pesquisa
Downloads
Loja
Eventos
Blogs
Galeria
Posts postados por paulosgf
-
-
Obrigado @anderson_leite! Essa dica final me deu uma direção a tomar daqui em diante.
Vi que tu estavas sem tempo, porque tá elaborando um treinamento justamente sobre o radare2. Vou acompanhar para melhorar minhas habilidades nisso. ?
Abraços!
- 1
-
Bom, isso foi uma análise feita a partir da força bruta, então teve um certo nível de ajuda para chegar na solução.
Por acaso tu terias alguma estratégia de raciocínio que leve a solução sem força bruta?
Abraços!
- 1
-
Spoiler
Por fim RSI aponta para a string que serve de argumento para putchar() imprimir a resposta final:
$rsi : 0x00005555555592c0 → "Bom trabalho\n"
● 0x5555555551c4 mov edi, 0xa 0x5555555551c9 call 0x555555555040 <putchar@plt> → 0x5555555551ce nop 0x5555555551cf leave 0x5555555551d0 ret
- 1
-
A função de decodificação funciona da seguinte maneira:
Spoilergef➤ x /50i $rip => 0x55555555519d: mov eax,DWORD PTR [rbp-0x4] (contador) 0x5555555551a0: movsxd rdx,eax 0x5555555551a3: mov rax,QWORD PTR [rbp-0x18] (RAX recebe ponteiro para a string iniciando em 0x0000555555556043) 0x5555555551a7: add rax,rdx (RAX incrementa endereço em 1) 0x5555555551aa: movzx eax,BYTE PTR [rax] 0x5555555551ad: movsx eax,al 0x5555555551b0: xor eax,DWORD PTR [rbp-0x1c] (aqui é feito o XOR de cada caractere da string com 0x3b) 0x5555555551b3: mov edi,eax 0x5555555551b5: call 0x555555555040 <putchar@plt> (gera string de resposta decodificada no ponteiro $rsi) 0x5555555551ba: add DWORD PTR [rbp-0x4],0x1 (itera endereço da string em 1) 0x5555555551be: cmp DWORD PTR [rbp-0x4],0xb (compara com tamanho 11) 0x5555555551c2: jle 0x55555555519d (se menor, volta para início)
- 1
-
A partir dai tentei entender como o algoritmo funciona e achei o seguinte:
Spoilerpaulo@nirvana:~/crack/crackmes$ gdb --args ./callme 335
...Voltando àquela parte do código onde é feita a validação dos argumentos em 3 blocos, um para cada dígito, de trás para frente.
O primeiro bloco segue aquele raciocinio que expus acima. Já no bloco seguinte é desenvolvida a constante que fará um XOR com uma string fixa e retornará a resposta correta decodificada:
... 0x555555555381 mov eax, DWORD PTR [rax+0x4] (aqui coloca o valor do proximo dígito em EAX == 3) ... 0x555555555386 lea rdx, [rax*4+0x0] (em seguida multiplica esse valor por 4 e coloca em RDX == 0xC) ... 0x55555555538e lea rax, [rip+0x2cdb] # 0x555555558070 (carrega um valor prévio em RAX == 0x23) ... 0x555555555395 mov edx, DWORD PTR [rdx+rax*1] (guarda em EDX o valor da soma de RDX + RAX, que será a constante == 0x3b)
Por fim o terceiro bloco é carregada a string a ser decodificada por XOR, que é usada como argumento de chamada da função que decodifica:
0x555555555398 mov rax, QWORD PTR [rbp-0x10] (carrega em RAX o valor do primeiro dígito == 3) ... 0x5555555553a0 lea rcx, [rax*8+0x0] (carrega em RCX o valor de RAX * 3 == 0x18) ... 0x5555555553a8 lea rax, [rip+0x2cf1] # 0x5555555580a0 (RAX recebe o ponteiro que levará a string a ser decodificada) ... 0x5555555553af mov rax, QWORD PTR [rcx+rax*1] (a soma do ponteiro com o valor 0x18 vai apontar para a string em 0x0000555555556043)
Por fim
call rcx
chama a função de decodificação com este ponteiro e a constante como argumentos em 0x555555555185:
*0x555555555185 ( $rdi = 0x0000555555556043, $rsi = 0x000000000000003b, $rdx = 0x000000000000003b, $rcx = 0x0000555555555185 )
-
Obrigado pelo apoio amigo!
Pois então... eu já havia dado uma olhada prévia com o radare2, mas acho que a analise dinâmica sempre dá mais retorno.
De qualquer forma não consegui evoluir mais e parti para a força bruta, criei esse script aqui:
Spoiler#!/usr/bin/env python3 import random import subprocess as sp import time def main(): """ Testa aproximadamente 900 números compreendendo todos os valores de 3 casas decimais por força bruta em um processo para cada tentativa usando um valor escolhido aleatóreamente em cada tentativa. """ MIN = 100 # valor inicial MAX = 999 # valor final FAX = 899 # espaço de tentativas BIN = "/home/paulo/crack/crackmes/callme" randlist = [] # lista com os valores distribuidos aleatóreamente randlist = random.sample(range(MIN, MAX), FAX) start_time = time.time() # Para cada valor da lista é gerado um novo processo com o binário for i in range(len(randlist)): ex = sp.run([BIN, str(randlist[i])], stdout=sp.PIPE) # Se o valor de retorno do processo for zero, temos saída legível if ex.returncode == 0: print( "Entrada: " + str(randlist[i]) + " --- Saida: " + ex.stdout.decode(errors="replace") ) # Tempo total do atque em segundos: print("--- %s segundos ---" % (time.time() - start_time)) if __name__ == "__main__": main()
E dentre outros valores de saída, obtive a seguinte string que pareceu fazer sentido:
Entrada: 335 --- Saida: Bom trabalho
- 1
-
Este valor 5 do último dígito eu deduzi a partir dos valores encontrados subsequentemente ao endereço carregado em EAX:
Spoiler0x5555555580e0 gef➤ x /50xg 0x5555555580e0 0x5555555580e0: 0x0000000000000000 0x0000000000021233 0x5555555580f0: 0xffffffffffffffff 0x0000000000000913 0x555555558100: 0x000000000000ad2c 0x0000555555555185 0x555555558110: 0x0000000000000233 0x0000000000000000 0x555555558120 <stderr>: 0x00007ffff7f985c0 0x0000000000000000 gef➤ x /gx 0x555555558108 0x555555558108: 0x0000555555555185 gef➤ x /50i 0x0000555555555185 0x555555555185: push rbp 0x555555555186: mov rbp,rsp 0x555555555189: sub rsp,0x20 0x55555555518d: mov QWORD PTR [rbp-0x18],rdi 0x555555555191: mov DWORD PTR [rbp-0x1c],esi 0x555555555194: mov DWORD PTR [rbp-0x4],0x0 0x55555555519b: jmp 0x5555555551be 0x55555555519d: mov eax,DWORD PTR [rbp-0x4] 0x5555555551a0: movsxd rdx,eax 0x5555555551a3: mov rax,QWORD PTR [rbp-0x18] 0x5555555551a7: add rax,rdx 0x5555555551aa: movzx eax,BYTE PTR [rax] 0x5555555551ad: movsx eax,al 0x5555555551b0: xor eax,DWORD PTR [rbp-0x1c] 0x5555555551b3: mov edi,eax 0x5555555551b5: call 0x555555555040 <putchar@plt> 0x5555555551ba: add DWORD PTR [rbp-0x4],0x1 0x5555555551be: cmp DWORD PTR [rbp-0x4],0xb 0x5555555551c2: jle 0x55555555519d 0x5555555551c4: mov edi,0xa 0x5555555551c9: call 0x555555555040 <putchar@plt> 0x5555555551ce: nop 0x5555555551cf: leave 0x5555555551d0: ret
Entretanto, os valores dos outros dígitos não consegui achar...
Tu tens alguma dica?
- 1
-
Quando inciamos com um número de 3 dígitos e executamos o binário até retornar desta última chamada, ele volta para a main() e segue para o algorítimo principal.
Spoilerpaulo@nirvana:~/crack/crackmes$ gdb --args ./callme 985
GEF for linux ready, type `gef' to start, `gef config' to configure
96 commands loaded for GDB 9.2 using Python engine 3.8
[+] Configuration from '/home/paulo/.gef.rc' restored
[+] 32 extra commands added from '~/crack/GDB/gef-scripts/scripts'
Voltron loaded.
Reading symbols from ./callme...
(No debugging symbols found in ./callme)
gef➤ starti
Starting program: /home/paulo/crack/crackmes/callme 985Program stopped.
0x00007ffff7fd0100 in ?? () from /lib64/ld-linux-x86-64.so.2...
0x555555555326: call 0x5555555551d1
0x55555555532b: mov QWORD PTR [rbp-0x10],rax
0x55555555532f: cmp QWORD PTR [rbp-0x10],0x0
0x555555555334: jne 0x55555555535d
0x555555555336: mov rax,QWORD PTR [rip+0x2de3] # 0x555555558120 <stderr>
0x55555555533d: mov rcx,rax
0x555555555340: mov edx,0xb
0x555555555345: mov esi,0x1
0x55555555534a: lea rdi,[rip+0xd23] # 0x555555556074
0x555555555351: call 0x555555555080 <fwrite@plt>
0x555555555356: mov eax,0x1
0x55555555535b: jmp 0x5555555553cf
0x55555555535d: mov rax,QWORD PTR [rbp-0x10]
0x555555555361: mov eax,DWORD PTR [rax+0x8]
0x555555555364: cdqe
0x555555555366: lea rdx,[rax*8+0x0]
0x55555555536e: lea rax,[rip+0x2d6b] # 0x5555555580e0
0x555555555375: mov rax,QWORD PTR [rdx+rax*1]
0x555555555379: mov QWORD PTR [rbp-0x8],rax
0x55555555537d: mov rax,QWORD PTR [rbp-0x10]
0x555555555381: mov eax,DWORD PTR [rax+0x4]
0x555555555384: cdqe
0x555555555386: lea rdx,[rax*4+0x0]
0x55555555538e: lea rax,[rip+0x2cdb] # 0x555555558070
0x555555555395: mov edx,DWORD PTR [rdx+rax*1]
0x555555555398: mov rax,QWORD PTR [rbp-0x10]
0x55555555539c: mov eax,DWORD PTR [rax]
0x55555555539e: cdqe
0x5555555553a0: lea rcx,[rax*8+0x0]
0x5555555553a8: lea rax,[rip+0x2cf1] # 0x5555555580a0
0x5555555553af: mov rax,QWORD PTR [rcx+rax*1]
0x5555555553b3: mov rcx,QWORD PTR [rbp-0x8]
0x5555555553b7: mov esi,edx
0x5555555553b9: mov rdi,rax
0x5555555553bc: call rcx
0x5555555553be: mov rax,QWORD PTR [rbp-0x10]
0x5555555553c2: mov rdi,rax
0x5555555553c5: call 0x555555555030 <free@plt>
0x5555555553ca: mov eax,0x0
0x5555555553cf: leave
0x5555555553d0: ret
0x5555555553d1: nop WORD PTR cs:[rax+rax*1+0x0]
0x5555555553db: nop DWORD PTR [rax+rax*1+0x0]
0x5555555553e0: push r15
0x5555555553e2: lea r15,[rip+0x29ff] # 0x555555557de8
0x5555555553e9: push r14
0x5555555553eb: mov r14,rdx
0x5555555553ee: push r13
0x5555555553f0: mov r13,rsi
0x5555555553f3: push r12Então, sem seguida há um salto para 0x55555555535d onde começa a validação do valor do argumento do programa.
Há 3 blocos de código separados pela instrução cdqe e ao final há uma chamada à função em
0x5555555553bc: call rcx
Cada bloco trata de um dígito do argumento começando pelo último.
Aqui o dígito 5 do argumento de entrada 985 é multiplicado por 8, guardando 0x28 em RDX:
0x555555555366 lea rdx, [rax*8+0x0]
Depois RAX recebe um endereço em:
0x55555555536e lea rax, [rip+0x2d6b] # 0x5555555580e0
E por fim RAX fica com o resultado da soma de RDX com o próprio RAX, que é o endereço da função que será chamada ao final das validações em call rcx:
0x555555555375 mov rax, QWORD PTR [rdx+rax*1] ... $rax : 0x0000555555555185 → push rbp
- 1
-
Agora a solução do crackme em si, começando pelas suas verificações.
SpoilerHá uma chamada em
0x555555555326: call 0x5555555551d1
Basicamente o que ela faz é verificar o tamanho do argumento do programa, e se ele é apenas números.
0x5555555551d9: mov QWORD PTR [rbp-0x18],rdi
0x5555555551dd: mov rax,QWORD PTR [rbp-0x18]
0x5555555551e1: mov rdi,rax
0x5555555551e4: call 0x555555555050 <strlen@plt>
0x5555555551e9: mov edx,DWORD PTR [rip+0x2e99] # 0x555555558088
0x5555555551ef: movsxd rdx,edx
0x5555555551f2: lea rcx,[rdx*4+0x0]
0x5555555551fa: lea rdx,[rip+0x2e87] # 0x555555558088
0x555555555201: mov edx,DWORD PTR [rcx+rdx*1]
0x555555555204: movsxd rdx,edx
0x555555555207: cmp rax,rdx
0x55555555520a: je 0x555555555216
0x55555555520c: mov eax,0x0
0x555555555211: jmp 0x5555555552d5
0x555555555216: mov rax,QWORD PTR [rbp-0x18]
0x55555555521a: movzx eax,BYTE PTR [rax]
0x55555555521d: cmp al,0x2f
0x55555555521f: jle 0x55555555522c
0x555555555221: mov rax,QWORD PTR [rbp-0x18]
0x555555555225: movzx eax,BYTE PTR [rax]
0x555555555228: cmp al,0x39
0x55555555522a: jle 0x555555555236
0x55555555522c: mov eax,0x0
0x555555555231: jmp 0x5555555552d5
0x555555555236: mov rax,QWORD PTR [rbp-0x18]
0x55555555523a: add rax,0x1
0x55555555523e: movzx eax,BYTE PTR [rax]
0x555555555241: cmp al,0x2f
0x555555555243: jle 0x555555555254
0x555555555245: mov rax,QWORD PTR [rbp-0x18]
0x555555555249: add rax,0x1
0x55555555524d: movzx eax,BYTE PTR [rax]
0x555555555250: cmp al,0x39
0x555555555252: jle 0x55555555525b
0x555555555254: mov eax,0x0
0x555555555259: jmp 0x5555555552d5
0x55555555525b: mov rax,QWORD PTR [rbp-0x18]
0x55555555525f: add rax,0x2
0x555555555263: movzx eax,BYTE PTR [rax]
0x555555555266: cmp al,0x2f
0x555555555268: jle 0x555555555279
0x55555555526a: mov rax,QWORD PTR [rbp-0x18]
0x55555555526e: add rax,0x2
0x555555555272: movzx eax,BYTE PTR [rax]
0x555555555275: cmp al,0x39
0x555555555277: jle 0x555555555280depois da chamada à strlen(), há uma comparação em
0x555555555207: cmp rax,rdx
e se o tamanho for diferente de 3 o programa sairá com a mensagem de erro
CitarTry harder
Para cada dígito é verificado se o mesmo se encontra entre os caracteres ASCII 0x2f == '/' e 0x39 == 9, indicando se tratar de número ou não. Caso contrário o programa também sai com a mesma mensagem de erro acima.
- 1
-
Oi anderson_leite!
Cara, crackme difícil esse, heim!?
O fato de não ter símbolos já dificulta bastante. Eu consegui superar essa fase, mas não estou entendendo a lógica dele. Mas vamos ver até onde cheguei:
O binário não tem símbolos.
paulo@nirvana:~/crack/crackmes$ nm ./callme nm: ./callme: nenhum símbolo
Iniciei sem argumentos para ver o que ele retorna e depois tentei uma string aleatória como entrada.
paulo@nirvana:~/crack/crackmes$ ./callme Usage: ./callme key paulo@nirvana:~/crack/crackmes$ ./callme foo Try harder paulo@nirvana:~/crack/crackmes$
Agora no debugger. Uso a extensão GEF no GDB, que facilita bastante o trabalho, retornando o estado dos registradores, Stack, linhas de código executadas e à executar em a cada instrução. Recomento!
paulo@nirvana:~/crack/crackmes$ gdb --args ./callme foo GEF for linux ready, type `gef' to start, `gef config' to configure 96 commands loaded for GDB 9.2 using Python engine 3.8 [+] Configuration from '/home/paulo/.gef.rc' restored [+] 32 extra commands added from '~/crack/GDB/gef-scripts/scripts' Voltron loaded. Reading symbols from ./callme... (No debugging symbols found in ./callme) gef➤ break main Function "main" not defined. gef➤ info file Symbols from "/home/paulo/crack/crackmes/callme". Local exec file: `/home/paulo/crack/crackmes/callme', file type elf64-x86-64. Entry point: 0x10a0 0x00000000000002a8 - 0x00000000000002c4 is .interp 0x00000000000002c4 - 0x00000000000002e4 is .note.ABI-tag 0x00000000000002e8 - 0x0000000000000310 is .gnu.hash 0x0000000000000310 - 0x0000000000000448 is .dynsym 0x0000000000000448 - 0x00000000000004f6 is .dynstr 0x00000000000004f6 - 0x0000000000000510 is .gnu.version 0x0000000000000510 - 0x0000000000000530 is .gnu.version_r 0x0000000000000530 - 0x0000000000000698 is .rela.dyn 0x0000000000000698 - 0x0000000000000728 is .rela.plt 0x0000000000001000 - 0x0000000000001017 is .init 0x0000000000001020 - 0x0000000000001090 is .plt 0x0000000000001090 - 0x0000000000001098 is .plt.got 0x00000000000010a0 - 0x0000000000001441 is .text 0x0000000000001444 - 0x000000000000144d is .fini 0x0000000000002000 - 0x0000000000002080 is .rodata 0x0000000000002080 - 0x00000000000020cc is .eh_frame_hdr 0x00000000000020d0 - 0x0000000000002218 is .eh_frame 0x0000000000003de8 - 0x0000000000003df0 is .init_array 0x0000000000003df0 - 0x0000000000003df8 is .fini_array 0x0000000000003df8 - 0x0000000000003fd8 is .dynamic 0x0000000000003fd8 - 0x0000000000004000 is .got 0x0000000000004000 - 0x0000000000004048 is .got.plt 0x0000000000004060 - 0x0000000000004118 is .data 0x0000000000004120 - 0x0000000000004130 is .bss gef➤ break *0x10a0 Ponto de parada 1 at 0x10a0 gef➤ run Starting program: /home/paulo/crack/crackmes/callme foo Warning: Cannot insert breakpoint 1. Não é possível acessar a memória no endereço 0x10a0 gef➤ start [*] gdb is already running gef➤
Como o programa não resolve símbolos, não acha a função main() e nem mesmo o entrypoint. Nestes casos é preciso um recurso novo do GDB 7.x.
Spoilerpaulo@nirvana:~/crack/crackmes$ gdb --args ./callme foo
GEF for linux ready, type `gef' to start, `gef config' to configure
96 commands loaded for GDB 9.2 using Python engine 3.8
[+] Configuration from '/home/paulo/.gef.rc' restored
[+] 32 extra commands added from '~/crack/GDB/gef-scripts/scripts'
Voltron loaded.
Reading symbols from ./callme...
(No debugging symbols found in ./callme)
gef➤ starti
Starting program: /home/paulo/crack/crackmes/callme fooProgram stopped.
0x00007ffff7fd0100 in ?? () from /lib64/ld-linux-x86-64.so.2...
gef➤ info file
Symbols from "/home/paulo/crack/crackmes/callme".
Native process:
Using the running image of child process 137717.
While running this, GDB does not access memory from...
Local exec file:
`/home/paulo/crack/crackmes/callme', file type elf64-x86-64.
Entry point: 0x5555555550a0
0x00005555555542a8 - 0x00005555555542c4 is .interp...
gef➤ break *0x5555555550a0
Ponto de parada 1 at 0x5555555550a0
gef➤ continue
Continuing.Breakpoint 1, 0x00005555555550a0 in ?? ()
...
0x55555555509a add BYTE PTR [rax], al
0x55555555509c add BYTE PTR [rax], al
0x55555555509e add BYTE PTR [rax], al
●→ 0x5555555550a0 xor ebp, ebp
0x5555555550a2 mov r9, rdx
0x5555555550a5 pop rsi
0x5555555550a6 mov rdx, rsp
0x5555555550a9 and rsp, 0xfffffffffffffff0
0x5555555550ad push rax
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "callme", stopped 0x5555555550a0 in ?? (), reason: BREAKPOINT
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x5555555550a0 → xor ebp, ebp
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤O comando
starti
inicia o GDB resolvendo os simbolos da Glibc. Em seguida é preciso achar o entrypoint com o comando
info file
em seguida colocar um breakpoint em seu endereço e continuar a execução do programa.
gef➤ x /50i $rip
=> 0x5555555550a0: xor ebp,ebp
0x5555555550a2: mov r9,rdx
0x5555555550a5: pop rsi
0x5555555550a6: mov rdx,rsp
0x5555555550a9: and rsp,0xfffffffffffffff0
0x5555555550ad: push rax
0x5555555550ae: push rsp
0x5555555550af: lea r8,[rip+0x38a] # 0x555555555440
0x5555555550b6: lea rcx,[rip+0x323] # 0x5555555553e0
0x5555555550bd: lea rdi,[rip+0x213] # 0x5555555552d7
0x5555555550c4: call QWORD PTR [rip+0x2f16] # 0x555555557fe0O comando
x /50i $rip
retorna as próximas 50 instruções a partir do ponteiro de instrução. Percebe-se uma Call a uma função. Colocamos um breakpoint e seguimos até lá.
gef➤ break *0x5555555550c4
Ponto de parada 2 at 0x5555555550c4
gef➤ continue
Continuing.Breakpoint 2, 0x00005555555550c4 in ?? ()
...
●→ 0x5555555550c4 call QWORD PTR [rip+0x2f16] # 0x555555557fe0
0x5555555550ca hlt
0x5555555550cb nop DWORD PTR [rax+rax*1+0x0]
0x5555555550d0 lea rdi, [rip+0x3041] # 0x555555558118
0x5555555550d7 lea rax, [rip+0x303a] # 0x555555558118
0x5555555550de cmp rax, rdi
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
*0x555555557fe0 (
$rdi = 0x00005555555552d7 → push rbp,
$rsi = 0x0000000000000002,
$rdx = 0x00007fffffffdee8 → 0x00007fffffffe238 → "/home/paulo/crack/crackmes/callme",
$rcx = 0x00005555555553e0 → push r15,
$r8 = 0x0000555555555440 → ret
)...
gef➤ stepi
__libc_start_main (main=0x5555555552d7, argc=0x2, argv=0x7fffffffdee8, init=0x5555555553e0, fini=0x555555555440, rtld_fini=0x7ffff7fe0d50, stack_end=0x7fffffffded8) at ../csu/libc-start.c:137
137 ../csu/libc-start.c: Arquivo ou diretório inexistente.O GEF já resolve a chamada à função com seus argumentos. Entramos nela, que se revela a __libc_start_main (). Esta é a função nos binários ELF que por fim chama a main(). Vide seu 1º argumento. Então colocamos um breakpoint no endereço da main() e executamos até entrar nela.
gef➤ break *0x5555555552d7
Ponto de parada 3 at 0x5555555552d7
gef➤ continue
Continuing.Breakpoint 3, 0x00005555555552d7 in ?? ()
...
0x5555555552d1 mov rax, QWORD PTR [rbp-0x8]
0x5555555552d5 leave
0x5555555552d6 ret
●→ 0x5555555552d7 push rbp
0x5555555552d8 mov rbp, rsp
0x5555555552db sub rsp, 0x20
0x5555555552df mov DWORD PTR [rbp-0x14], edi
0x5555555552e2 mov QWORD PTR [rbp-0x20], rsi
0x5555555552e6 cmp DWORD PTR [rbp-0x14], 0x1A partir daqui já é possível listar todas as instruções da main() com o comando executado anteriormente a partir o ponteiro de instrução.
gef➤ x /70i $rip
=> 0x5555555552d7: push rbp
0x5555555552d8: mov rbp,rsp
0x5555555552db: sub rsp,0x20
0x5555555552df: mov DWORD PTR [rbp-0x14],edi
0x5555555552e2: mov QWORD PTR [rbp-0x20],rsi
0x5555555552e6: cmp DWORD PTR [rbp-0x14],0x1
0x5555555552ea: jg 0x555555555318
0x5555555552ec: mov rax,QWORD PTR [rbp-0x20]
0x5555555552f0: mov rdx,QWORD PTR [rax]
0x5555555552f3: mov rax,QWORD PTR [rip+0x2e26] # 0x555555558120 <stderr>
0x5555555552fa: lea rsi,[rip+0xd64] # 0x555555556065
0x555555555301: mov rdi,rax
0x555555555304: mov eax,0x0
0x555555555309: call 0x555555555060 <fprintf@plt>
0x55555555530e: mov eax,0x1
0x555555555313: jmp 0x5555555553cf
0x555555555318: mov rax,QWORD PTR [rbp-0x20]
0x55555555531c: add rax,0x8
0x555555555320: mov rax,QWORD PTR [rax]
0x555555555323: mov rdi,rax
0x555555555326: call 0x5555555551d1
0x55555555532b: mov QWORD PTR [rbp-0x10],rax
0x55555555532f: cmp QWORD PTR [rbp-0x10],0x0
0x555555555334: jne 0x55555555535d
0x555555555336: mov rax,QWORD PTR [rip+0x2de3] # 0x555555558120 <stderr>
0x55555555533d: mov rcx,rax
0x555555555340: mov edx,0xb
0x555555555345: mov esi,0x1
0x55555555534a: lea rdi,[rip+0xd23] # 0x555555556074
0x555555555351: call 0x555555555080 <fwrite@plt>
0x555555555356: mov eax,0x1
0x55555555535b: jmp 0x5555555553cf
0x55555555535d: mov rax,QWORD PTR [rbp-0x10]
0x555555555361: mov eax,DWORD PTR [rax+0x8]
0x555555555364: cdqe
0x555555555366: lea rdx,[rax*8+0x0]
0x55555555536e: lea rax,[rip+0x2d6b] # 0x5555555580e0
0x555555555375: mov rax,QWORD PTR [rdx+rax*1]
0x555555555379: mov QWORD PTR [rbp-0x8],rax
0x55555555537d: mov rax,QWORD PTR [rbp-0x10]
0x555555555381: mov eax,DWORD PTR [rax+0x4]
0x555555555384: cdqe
0x555555555386: lea rdx,[rax*4+0x0]
0x55555555538e: lea rax,[rip+0x2cdb] # 0x555555558070
0x555555555395: mov edx,DWORD PTR [rdx+rax*1]
0x555555555398: mov rax,QWORD PTR [rbp-0x10]
0x55555555539c: mov eax,DWORD PTR [rax]
0x55555555539e: cdqe
0x5555555553a0: lea rcx,[rax*8+0x0]
0x5555555553a8: lea rax,[rip+0x2cf1] # 0x5555555580a0
0x5555555553af: mov rax,QWORD PTR [rcx+rax*1]
0x5555555553b3: mov rcx,QWORD PTR [rbp-0x8]
0x5555555553b7: mov esi,edx
0x5555555553b9: mov rdi,rax
0x5555555553bc: call rcx
0x5555555553be: mov rax,QWORD PTR [rbp-0x10]
0x5555555553c2: mov rdi,rax
0x5555555553c5: call 0x555555555030 <free@plt>
0x5555555553ca: mov eax,0x0
0x5555555553cf: leave
0x5555555553d0: ret- 1
-
Salve galera!
Primeiramente quero agradecer a mais este exemplar para estudo de ER. Me ensinou conceitos em que eu só tinha vagamente ouvido falar. Realmente abriu a mente!
Como foi comentado pelos colegas, existe de fato pelo menos mais uma abordagem de solução. Trata-se do problema de fatoração em números primos de determinado número. A criptografia de chave pública RSA usa este conceito, e sua segurança se baseia no fato de que é computacionalmente trabalhoso fatorar números primos gigantescos, exigindo geralmente milhares de anos de processamento por força bruta para se quebrar as chaves com tecnologia atual. A não ser que se empregue computação quântica! ?
Como os valores em questão são de tamanho aceitável, é possível empregar um algoritmo próprio para a fatoração. Segue então um exemplo adaptado por mim deste código, cujo crédito é de rathbhupendra e a referência vem de https://www.geeksforgeeks.org/print-all-prime-factors-of-a-given-number/
#include <stdio.h> #include <math.h> // Imprimir todos fatores primos de dado número void primeFactors(int n) { // Passo 1: trata de números compostos (primo x primo) // Enquanto for par, divide número por 2 e imprime 2 while (n%2 == 0) { printf("%d", 2); n = n/2; } // Passo 2: ainda tratando de números compostos // Quando o número se tornar ímpar, inicia um laço iterando de // 3 à raiz quadrada do dado número, incrementando de 2 em 2, // imprime este índice e divide o número pelo mesmo for (int i = 3; i <= sqrt(n); i = i+2) { while (n%i == 0) { printf("%d", i); n = n/i; } } // Passo 3: trata dos números primos // Se o dado número não terminou em 1, então é um primo // maior que 2. Imprimir este valor, concluindo a fatoração if (n > 2) printf ("%d", n); printf("\n"); } int main() { int n; printf("%s", "Numero a ser fatorado: "); scanf("%d", &n); primeFactors(n); return 0; } // This is code is contributed by rathbhupendra
Segue resultado de sua execução:
paulo@nirvana:~/crack/crackmes$ ./fatoresPrimos Numero a ser fatorado: 720300 223557777 paulo@nirvana:~/crack/crackmes$ ./crackme-linux02 223557777 Isso aí!! A chave é válida! paulo@nirvana:~/crack/crackmes$
Abraços!
- 1
Keygen Feito em python
em Desafios e CTF
Postado
Galera, segue minha resolução do desafio nível "Fácil"
Primeiramente, verificamos no DiE que se trata de um crackme em Delphi que esconde a WinAPI debaixo de várias camadas de sua biblioteca própria, a Visual Component Library (VCL) usada em formulários e que só então chama as funções Windows em nível mais baixo. Então temos de usar um decodificador dos componentes do formulário Delphi, como o Interactive Delphi Reconstructor (IDR). [1]
Abrir o programa no IDR -> File -> Load File -> Autodetect Version
Localizar a main() do Delphi, classe TMain:
IDR -> Forms (F5) -> Text -> TMAIN {main} -> (duplo clique)
Localizar o objeto TButton e seu evento OnClick relacionado conforme imagem:
Há também o Delphi Decompiler (DeDe), que pode ser mais prático para alguns do que o IDR. [2]
Configurar o IDA Free com a assinatura FLIRT da VCL, pois facilita muito a identificação dos componentes. Certifique-se de que a assinatura FLIRT mssdk32 seja adicionada também. Vide em [3].
O Delphi usa a convenção de chamada fastcall, porém é diferente da chamada fastcall do Windows. Os parâmetros são passados nesta ordem: EAX, EDX, ECX, os outros na pilha. Para classes, o ponteiro this é passado em EAX. Quando um construtor é chamado, a estrutura da classe é passada em EAX e o ponteiro this é também retornado em EAX.
Configurar o IDA:
Options -> Compiler ->
Compiler = Delphi
Calling convention = Fastcall
O Delphi usa strings no estilo Pascal. O comprimento da string é especificado no início da string. As strings armazenadas no arquivo binário estão no formato estilo C, primeiro precisam ser convertidas chamando o método LStrAsg().
Novamente configurar o IDA em: Options -> String Literals -> Manage Defaults -> Default String Literal Type = Delphi
Agora buscar o evento Button4Click que aparece como TMain_Button4Click, visto que é derivado da TMain. Ele começa no offset 0x0043B374. Recebe a entrada no campo TMain.Edit4:TEdit através da função @TControl@GetText. Em seguida calcula o tamanho da entrada com a função @@LStrLen.
O Algoritmo é meio confuso, conforme se vê na seção codificação, então simulei ele em um script python para facilitar o entendimento...
... porém como é um algoritmo de Hash, não é reversível (one-way-hash). Sem as variáveis resto da divisão e, principalmente o tamanho da entrada original, não há como recuperá-la. Talvez se analisando o incremento a cada passada do hash dentro do espaço de endereçamento do algoritmo seja possível deduzir o tamanho, mas ai foge do escopo "Fácil", proposto pelo desafio.
A codificação é feita em cada char num laço repetido 16 vezes. Em seguida vem a 1ª validação comparando com o valor 0x3810.
No x64dbg, iniciar com um breakpoint no endereço de inicio da rotina de codificação, em 0x0043B3C3. Dar F9.
Após o algoritmo de codificação, há 4 testes nos endereços 0x0043B3FA, 0x0043B402, 0x0043B428, 0x0043B452. Definir um breakpoint em cada um e remover o do inicio do algoritmo de codificação. Dar novamente F9, parando no 1º teste. Executar a instrução de comparação com F8 e, em seguida na instrução de salto, habilidar a Zero Flag (ZF=1) com duplo-clique. Repetir os mesmos passos para os demais testes até chegar ao final, onde qualquer senha é aceita.
[]'s
[1] https://github.com/crypto2011/IDR
[2] https://www.softpedia.com/get/Programming/Debuggers-Decompilers-Dissasemblers/DeDe.shtml#download
[3] https://www.sawatzki.de/download.htm