Jump to content

Crackme - Call me (Nivel 1)


anderson_leite
 Share

Recommended Posts

  • Nibble Supporter

Olá, decidi começar a publicar alguns desafios de engenharia reversa, criptografia e análise de malware nessa seção do forum!

Para iniciar, eis um crackme para Linux chamado "Call me" onde você só precisa encontrar a chave correta e receber a sua mensagem de resolução 😉

Algumas considerações:

  • Coloque sua resolução com a tag de spoiler, para não mostrar para todos como resolveu
  • Resoluções por brute-force no input são validas, porém é sempre mais elegante entender o que está acontecendo por trás

 

Dica: Caso sua "chave" esteja errada, você recebera um segmentation fault

Algum tipo de proteção ? Não

O Binário está stripped ? Sim

Qual o formato ? ELF

Algumas ferramentas open source que podem te ajudar a resolver são:

  1. Cutter
  2.  Radare
  3. gdb

 

Boa sorte 🙂

callme

Edited by anderson_leite
Link to comment
Share on other sites

  • 5 months later...

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.

Spoiler

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➤  starti
Starting program: /home/paulo/crack/crackmes/callme foo

Program 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]        # 0x555555557fe0

O 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], 0x1

A 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    

 

 

  • l33t 1
Link to comment
Share on other sites

 

Agora a solução do crackme em si, começando pelas suas verificações.

Spoiler

Há 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    0x555555555280

depois 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

Citar

Try 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.

 

Edited by paulosgf
  • l33t 1
Link to comment
Share on other sites

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.
 

Spoiler

paulo@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 985

Program 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   r12

     Entã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

 

 

  • l33t 1
Link to comment
Share on other sites

Este valor 5 do último dígito eu deduzi a partir dos valores  encontrados  subsequentemente ao endereço carregado em EAX:

Spoiler
0x5555555580e0

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?

Edited by paulosgf
  • l33t 1
Link to comment
Share on other sites

  • Nibble Supporter

@paulosgfExcelente analise! O desafio é bem complicadinho mesmo mas a resolução é extremamente simples!, vou colocar minha dica na tag de spoilers

 

 

Spoiler

Toda a sua analise está correta até agora, o ponto chave para resolver agora é notar que sua entrada na vdd é um indice, veja que voce soma ela com um endereço e é dessa maneira que vetores são estruturados em memoria!

 

Incrivel voce fazer tudo isso dentro do GDB! mas talvez se voce usasse o Cutter ou o Radare2, a analise estática já iria te revelar bastante coisa antes mesmo de entrar no debugger! Boa sorte

 

Fico muito feliz que você esteja resolvendo.

  • Curtir 1
Link to comment
Share on other sites

  • 2 weeks later...

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

 

 

Link to comment
Share on other sites

A partir dai tentei entender como o algoritmo funciona e achei o seguinte:

Spoiler

paulo@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
)

 

 

Link to comment
Share on other sites

A função de decodificação funciona da seguinte maneira:

Spoiler
gef➤  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)

 

 

Link to comment
Share on other sites

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    

 

 

Link to comment
Share on other sites

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!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...