// 06 intermediário reversão de algoritmo

Keygen
Simples

Pela primeira vez, você não patcha nada. Você entende o algoritmo e escreve o gerador.

Ativador de software. Pede nome de usuário e uma chave no formato XXXX-XXXX.

Até aqui você só precisou patchear. Desta vez o objetivo é diferente:
entender o que a função generate_key faz e escrever um keygen que produza a chave certa para qualquer nome.

~/cracklab/06-keygen-simples
$ ./challenge
Nome de usuário: willian
Chave (XXXX-XXXX): 0000-0000
✗ Chave inválida para o usuário 'willian'.
$ ./challenge
Nome de usuário: willian
Chave (XXXX-XXXX): 0006-0545
✓ CHAVE VÁLIDA — Software ativado.
// conceitos necessários
REVERSÃO DE ALGORITMO
Diferente de patch, aqui você lê o disassembly e reconstrói a lógica em Python. O objetivo é produzir o input certo, não corromper o fluxo.
IMUL — multiplicação
imul multiplica dois operandos com sinal. Frequentemente aparece em loops de hash como acc += byte * (pos + 1).
MODULO ARITMÉTICO
O compilador otimiza % 9973 com multiplicação por recíproco. O resultado é equivalente ao módulo — mas parece horrível no disassembly.
LITTLE-ENDIAN + ATOI
A chave é lida como string decimal e convertida por atoi. Então o valor gerado é comparado direto. Formatar com :08d dá o XXXX-XXXX certo.

Abre o disassembly de generate_key.
Procura o loop: tem um imul dentro dele. O que é multiplicado pelo quê?
Depois do loop, procura um imul com uma constante grande — isso é o módulo otimizado.
Reproduz essa operação em Python. Gera a chave. Testa.

// solução passo a passo
01
Localizar e ler generate_key
bash
objdump -d ./challenge | grep -A 40 "<generate_key>:"
O disassembly vai mostrar este padrão dentro do loop:
asm
4012db:  8b 45 f8       mov   -0x8(%rbp),%eax      ; i
4012de:  48 63 d0       movslq %eax,%rdx
4012e1:  48 8b 45 e8    mov   -0x18(%rbp),%rax     ; name
4012e5:  48 01 d0       add   %rdx,%rax
4012e8:  0f b6 00       movzbl (%rax),%eax          ; byte = name[i]
4012eb:  0f b6 c0       movzbl %al,%eax
4012ee:  8b 55 f8       mov   -0x8(%rbp),%edx       ; i
4012f1:  83 c2 01       add   $0x1,%edx              ; i + 1
4012f4:  0f af c2       imul  %edx,%eax             ; byte * (i+1)
4012f7:  01 45 fc       add   %eax,-0x4(%rbp)        ; acc += ...

O loop soma byte × (posição + 1) para cada caractere do nome.
Isso é a função hash — acc = Σ name[i] × (i+1).
Depois do loop vem o módulo por 9973 (otimizado com recíproco) e uma segunda operação com 1337.

02
Entender o módulo otimizado
O compilador não usa div — usa multiplicação por um magic number. No disassembly após o loop:
asm
40130b:  b8 55 70 48 d2    mov  $0xd2487055,%eax
401310:  48 0f af c2       imul %rdx,%rax              ; acc * magic
401314:  48 c1 e8 20       shr  $0x20,%rax             ; >> 32
401318:  c1 e8 0d          shr  $0xd,%eax              ; >> 13 = quociente
40131b:  69 d0 f5 26 00 00 imul $0x26f5,%eax,%edx     ; q * 9973
401321:  29 d1             sub  %edx,%ecx              ; acc - q*9973 = acc % 9973
Você não precisa entender isso — só sabe que é % 9973. O código-fonte deixa claro. Agora o main aplica mais uma transformação:
asm — dentro de main
401476:  e8 31 fe ff ff   call  4012ac <generate_key>
40147b:  89 45 fc         mov   %eax,-0x4(%rbp)      ; expected = generate_key(name)
401481:  69 d0 39 05 00 00 imul $0x539,%eax,%edx    ; expected * 1337
401489:  48 69 c0 f7...   imul ...                   ; % 99991 (magic)
4014a8:  8b 45 8c         mov   -0x74(%rbp),%eax     ; provided (da entrada)
4014ab:  39 45 f8         cmp   %eax,-0x8(%rbp)     ; key_val == provided?
4014ae:  75 4d            jne   4014fd              ; não bate → erro

A fórmula completa: key_val = (generate_key(name) * 1337) % 99991

03
Escrever o keygen
python3 — keygen.py
#!/usr/bin/env python3
# keygen.py — CRACKLAB #06

import sys

def generate_key(name: str) -> int:
    acc = 0
    for i, c in enumerate(name):
        acc += ord(c) * (i + 1)
    return acc % 9973

def keygen(name: str) -> str:
    expected = generate_key(name)
    key_val  = (expected * 1337) % 99991
    s = f"{key_val:08d}"
    return f"{s[:4]}-{s[4:]}"

name = sys.argv[1] if len(sys.argv) > 1 else input("Nome: ")
print(f"Chave para '{name}': {keygen(name)}")
~/cracklab/06-keygen-simples
$ python3 keygen.py willian
Chave para 'willian': 0006-0545
$ python3 keygen.py root
Chave para 'root': 0051-7427
$ python3 keygen.py qualquercoisa
Chave para 'qualquercoisa': 0022-1133
04
Verificar
bash
# testa com um nome qualquer
KEY=$(python3 keygen.py alice)
printf "alice\n$KEY\n" | ./challenge

Por que isso é mais importante que patching? Você acabou de fazer engenharia reversa de verdade.
Não corrigiu um byte — reconstruiu o algoritmo original em outra linguagem.
A partir daqui, patches ficam mais difíceis (anti-debug, checksums). Mas keygen nunca falha.

// variações pra explorar
variaçãoo que faz
patch clássicoTrocar jne 0x4014ae → NOP. Funciona, mas não entende o algoritmo.
patch retornoForçar cmp a sempre ser igual. Igualmente burro e mais difícil de localizar.
keygen (correto)Reproduz a lógica. Funciona pra qualquer nome, qualquer instância, sem tocar no binário.

O módulo é 9973 (primo). Por que usar um número primo como módulo numa função hash ajuda a distribuir melhor os valores? E o que aconteceria se o módulo fosse uma potência de 2?