Ir para conteúdo

paulosgf

Apoiador Byte
  • Postagens

    22
  • Registro em

  • Última visita

  • Dias Ganhos

    7

Posts postados por paulosgf

  1. 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]

    facil.thumb.png.1a06a8613be396bfa884e07f6bd7b222.png

    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:

    facil0.thumb.png.d3701b2d9a87335e17eca8019b25ee2d.png

    facil1.thumb.png.6d8db58d0130dc226882b45e178fa3a0.png

    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

    facil2.thumb.png.f9ac90eb9dda8eb3cfe623476f88eb7b.png

    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

    facil3.thumb.png.4a51535971e564fffdc1359f68b1e13d.png

    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.

    facil4.thumb.png.bcf0d65af34028530a1d39684d4d0a8b.png

     

    O Algoritmo é meio confuso, conforme se vê na seção codificação, então simulei ele em um script python para facilitar o entendimento...

    #!/usr/bin/env python3
    
    txt = input("no que estou pensando... ") # Entrada
    laco = 16   # 16 passadas
    acumul = 0  # Variável temporaria que acumula codificação de cada char da entrada
    cod = 0     # Saída codificada
    
    while (laco > 0):
        print("LACO: " + str(laco)) # Informa nova passada
        tam = len(txt)              # Tamanho da entrada
        contador = 1                # Conta cada char da entrada
        for n in range(0,tam):      # Laço percorrendo toda a entrada
            ch = ord(txt[n])
            print(chr(ch) + ": " +  hex(ch))    # Char e seu valor ASCII
            resto = contador + acumul           
            ch = ch + resto
            acumul = ch
            print("acumulador: " + hex(acumul)) # Char codificado nesta volta
            cod += cod
            cod = cod ^ acumul
            ch = acumul
            resto = ch % contador   
            resto += 1
            print("resto: " + hex(resto))       # Resto da divisão entre char e contador
            ch = int(ch / contador)
            print("quociente: " + hex(ch))      # Quociente da divisão
            cod += int(resto)                   # Valor codificado da entrada nesta volta
            print("codificacao: " + "0x{}".format(hex(cod)[len(hex(cod))-8:len(hex(cod))]))
            contador += 1
            print("contador: " + hex(contador)) # Incrementa contador de char
            tam -= 1
            print("tamanho: " + hex(tam))       # Decrementa tamanho da entrada
            print("\n")
    
        laco -= 1                   # Decrementa uma volta no laço

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

    facil5.thumb.png.0d2948a7237dabc23b9880d42a80f955.png

    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.

    facil6.thumb.png.175c438d60c63a5bc44c5282c6773ac7.png

    facil7.thumb.png.4eb93115ce0965cdaeb650ea5bfb91fd.png

    facil8.thumb.png.af8be609b7b7c8d710b91ddedb298e2d.png

    facil9.thumb.png.e2db79496eed491a2e64b6ea5c8c8a43.png

    []'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

     

    • Curtir 2
  2. 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    

     

     

    • Agradecer 1
  3. 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)

     

     

    • Agradecer 1
  4. 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
    )

     

     

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

     

     

    • Agradecer 1
  6. 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?

    • l33t 1
  7. 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
  8.  

    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.

     

    • l33t 1
  9. 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
  10. 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!
     

    • l33t 1
×
×
  • Criar Novo...