Aquí tenemos otro crackme de esos que te hacen sudar tinta china. Data del 2003 y su autor es Spider, quien mantiene activa una web con estilo retro donde podéis encontrar este y otros crackmes para seguir practicando.

Toda la mandanga comienza sobre la dirección 0x00406486:

; ── PASO 1 · Lectura del nombre y cálculo de valName ────────────────────

00406477 | push 1Eh
00406479 | push offset dword_4082E4
0040647E | push 3E9h
00406483 | push [ebp+hWnd]
00406486 | call GetDlgItemTextA          ; Lee el nombre (max 30 chars)
0040648B | test eax, eax
0040648D | jz   loc_4066DD              ; Vacío → "Didn't you forget something? / The name!!"

; Hash multiplicativo sobre los chars del nombre (recorrido inverso):
00406493 | mov  ecx, eax                ; ecx = longitud del nombre
00406495 | mov  eax, 8E310BD4h          ; semilla inicial
0040649A | movzx edx, [ecx+4082E3h]    ; carga char[i] (de atrás hacia adelante)
004064A1 | mul  edx                     ; eax *= char
004064A3 | xor  eax, 0D86F936Eh        ; mezcla con constante
004064A8 | dec  ecx
004064A9 | jnz  short loc_40649A       ; bucle hasta ecx == 0

; Reducción y cálculo de valName:
004064AB | xor  edx, edx
004064AD | mov  ebx, 0C350h             ; divisor = 50000
004064B2 | div  ebx                     ; edx = hash % 50000  → rango [0, 49999]
004064B4 | lea  esi, [edx+1F4h]        ; esi = (hash % 50000) + 500 → rango [500, 50499]
004064BA | mov  ds:dword_4066B3, esi   ; *** SELF-PATCH ***
                                        ; Sobreescribe los 4 bytes de la constante imm32
                                        ; en la instrucción "sub eax, imm32" de 0x4066B2,
                                        ; convirtiéndola en: sub eax, valName
                                        ;
                                        ; Para "deurus":
                                        ;   hash raw      = 0x6A373C56
                                        ;   hash % 50000  = 4822  (0x12D6)
                                        ;   valName       = 5322  (0x14CA)

; ── PASO 2 · Lectura y validación de longitud del serial ────────────────

004064C0 | push 100h
004064C5 | push offset dword_4082E4    ; buffer destino
004064CA | push 3E8h                   ; ID campo serial
004064CF | push [ebp+hWnd]
004064D2 | call GetDlgItemTextA        ; Lee el serial (max 256 chars)
004064D7 | test eax, eax
004064D9 | jz   loc_4066EB             ; Vacío → "Didn't you forget something? / The serial!!"
004064DF | cmp  eax, 1Ah               ; Longitud debe ser exactamente 26 chars
004064E2 | jnz  loc_4066FA             ; → "Far, far, very far!"

; ── PASO 3 · Parseo del serial: 26 chars ASCII → 13 bytes (serialSnippet) ──

004064E8 | xor  ecx, ecx
004064EA | mov  ebx, offset dword_406746  ; ebx = destino: 0x406746 (zona de código)
          ; El bucle convierte cada par de chars hex en un byte:
004064EF | xor  eax, eax
004064F1 | add  al,  [ecx*2+4082E5h]   ; nibble alto (char en posición impar)
004064FA | add  ah,  [ecx*2+4082E4h]   ; nibble bajo (char en posición par)
00406503 | sub  al, 30h                ; normaliza '0'-'9' → 0x00-0x09
0040650B | cmp  al, 16h
0040650D | ja   loc_4066FA             ; fuera de rango hex
00406513 | cmp  al, 9
00406515 | jbe  short loc_406521
00406517 | cmp  al, 11h
00406519 | jb   loc_4066FA             ; hueco 0xA-0x10 no permitido
0040651F | sub  al, 7                  ; ajuste A-F mayúsculas
00406521 | sub  ah, 30h                ; mismo proceso para nibble bajo
          ; ... (mismas validaciones para ah)
00406544 | aad  10h                    ; combina: al = ah*16 + al
00406546 | mov  [ecx+ebx], al         ; almacena byte en 0x406746+ecx
00406549 | inc  ecx
0040654A | jmp  short loc_4064EF      ; siguiente par
          ;
          ; Resultado para serial "33D20FBBD169C1CA1400004040":
          ; 0x406746: 33 D2 0F BB D1 69 C1 CA 14 00 00 40 40
          ; 0x406753: C3  ← ret permanente (no forma parte del serial)

; ── PASO 4 · Motor de desensamblado: valida serialSnippet como código x86 ──

0040654C | push 0
0040654E | push 0
00406550 | push ebx                    ; puntero al byte actual del serialSnippet
00406551 | push offset unk_4084EC      ; estructura de info de la instrucción
00406556 | push ebx
00406557 | call sub_401000             ; desensamblador: devuelve tamaño de la instrucción
          ;                            ; y rellena la estructura en 0x4084EC
0040655C | lea  ecx, [ebx+eax]        ; ecx → siguiente instrucción
0040655F | cmp  ecx, 406753h          ; ¿llegamos al final (13 bytes)?
00406565 | ja   loc_4066FA            ; superado el límite → error

          ; Comprobaciones de opcode permitido:
0040656B | movzx ecx, byte ptr [ebx]
0040656E | cmp  byte ptr unk_408000[ecx], 1  ; tabla[opcode] == 1 → permitido
00406575 | jnz  loc_4066FA
0040657B | cmp  cl, 0Fh               ; ¿opcode de escape (instrucción de 2 bytes)?
0040657E | jnz  short loc_406590
00406580 | mov  cl, [ebx+1]           ; lee segundo byte del opcode
00406583 | cmp  byte ptr unk_408100[ecx], 1  ; tabla2[opcode2] == 1 → permitido
0040658A | jnz  loc_4066FA

          ; Comprobaciones de tipo de operandos:
00406590 | mov  ecx, dword_408570     ; número de operandos de la instrucción
00406596 | cmp  ecx, 0FFFFFFFFh       ; -1 → opcode inválido
00406599 | jz   loc_4066FA
0040659F | mov  esi, offset unk_408504 ; esi → info del primer operando
004065A4 | jecxz short loc_4065F5     ; sin operandos → avanza al siguiente
004065A6 | mov  edx, [esi+4]          ; tipo del operando
004065A9 | test edx, 1                ; ¿dirección directa (jmp/call)? → no permitido
004065AF | jnz  loc_4066FA
004065B5 | cmp  byte ptr [ebx], 8Dh  ; excepción: 'lea' sí está permitida
004065B8 | jz   short loc_4065C0
004065BA | test edx, 2                ; ¿dirección de memoria? → no permitido
004065C0 | jnz  loc_4066FA
004065C6 | test edx, 800h             ; ¿inmediato? → permitido
004065CC | jnz  short loc_4065EF
004065CE | test edx, 8                ; ¿registro general? → permitido
004065D4 | jz   short loc_4065EF
004065D6 | cmp  dword ptr [esi], 1    ; ¿operando corto? → permitido
004065D9 | jz   short loc_4065EF
004065DB | cmp  dword ptr [esi+0Ch], 4 ; comprobaciones adicionales de subtipo
004065DF | jz   loc_4066FA
004065E5 | cmp  dword ptr [esi+0Ch], 5
004065E9 | jz   loc_4066FA
004065EF | dec  ecx                   ; siguiente operando
004065F0 | add  edi, 24h
004065F3 | jmp  short loc_4065A4

004065F5 | add  ebx, eax              ; ebx → primera instrucción del siguiente token
004065F7 | cmp  ebx, 406753h          ; ¿fin del serialSnippet?
004065FD | jz   short loc_406604      ; sí → todo OK, continúa
004065FF | jmp  loc_40654C            ; no → desensambla siguiente instrucción

; ── PASO 5 · Bucle de verificación matemática (0xF4240 iteraciones) ─────

00406604 | xor  eax, eax
00406606 | call loc_406622            ; entra en el bucle

00406622 | push dword ptr fs:[eax]    ; setup manejador de excepciones SEH
00406625 | mov  fs:[eax], esp
00406628 | push ebp
00406629 | mov  ebp, 0F4240h          ; ebp = contador externo = 1.000.000

          ; Bucle interno ROR/XOR: produce el valor n
00406633 | xor  eax, ebp              ; eax ^= ebp  (una sola vez por iteración externa)
00406635 | mov  ecx, ebp
00406637 | movzx ecx, cl              ; ecx = byte bajo de ebp
0040663A | or   ecx, 1                ; fuerza impar (nunca rota 0 bits)
0040663D | ror  eax, cl               ; ─┐ bucle interno:
0040663F | xor  eax, ecx              ;  │ ecx iteraciones
00406641 | loop loc_40663D            ; ─┘
          ; eax = n  (resultado del hash de mezcla)

          ; Ajuste de n al rango de caracteres hex válidos:
00406643 | cmp  eax, 0FFFFFFF8h
00406646 | jbe  short loc_40664A
00406648 | sub  al, 7
0040664A | inc  eax                   ; n+1

; ── PASO 6 · Cuatro llamadas al serialSnippet ───────────────────────────
          ;
          ; El serialSnippet (0x406746) se ejecuta como función.
          ; ecx sale del loop con valor 1 (última iteración lo deja en 0, luego
          ; btc ecx,0 lo pone en 1 → alterna 1/0/1/0 entre llamadas sucesivas).
          ; El parámetro (n+k) se pasa en el stack pero el serial usa ecx directamente.
          ;
          ; serialSnippet ejecutado (para "deurus", valName=0x14CA):
          ;   xor  edx, edx           ; edx = 0
          ;   btc  ecx, edx           ; flip bit0(ecx): 0→1 o 1→0
          ;   imul eax, ecx, 0x14CA   ; eax = ecx_modificado * valName
          ;   inc  eax                ;
          ;   inc  eax                ; eax = ecx_modificado * valName + 2
          ;   ret
          ;
          ; Valores producidos (ecx alterna 0/1):
          ;   call 1: ecx=0 → btc → 1 → f(n+1) = 1*0x14CA + 2 = 0x14CC = 5324
          ;   call 2: ecx=1 → btc → 0 → f(n+3) = 0*0x14CA + 2 = 0x0002 = 2
          ;   call 3: ecx=0 → btc → 1 → f(n+5) = 1*0x14CA + 2 = 0x14CC = 5324
          ;   call 4: ecx=1 → btc → 0 → f(n+7) = 0*0x14CA + 2 = 0x0002 = 2

0040664B | push eax                   ; push n+1
0040664C | call dword_406746          ; f(n+1) → eax = 0x14CC
00406651 | test eax, 0FFFF0000h       ; debe caber en 16 bits
00406656 | jnz  short loc_4066C3
00406658 | mov  dword_4082E4, eax     ; guarda f(n+1) = 0x14CC
0040665D | dec  eax                   ; f(n+1) no puede ser 1
0040665E | pop  eax                   ; restaura puntero
0040665F | jz   loc_4066F9

00406665 | inc  eax                   ; n+2
00406666 | inc  eax                   ; n+3 (avance +2 en n)
00406667 | push eax
00406668 | call dword_406746          ; f(n+3) → eax = 0x0002
0040666D | test eax, 0FFFF0000h
00406672 | jnz  short loc_4066C3
00406674 | mov  dword_4082E8, eax     ; guarda f(n+3) = 0x0002
00406679 | dec  eax
0040667A | pop  eax
0040667B | jz   short loc_4066F9

0040667D | inc  eax                   ; n+4
0040667E | inc  eax                   ; n+5
0040667F | push eax
00406680 | call dword_406746          ; f(n+5) → eax = 0x14CC
00406685 | test eax, 0FFFF0000h
0040668A | jnz  short loc_4066C3
0040668C | mov  dword_4082EC, eax     ; guarda f(n+5) = 0x14CC
00406691 | dec  eax
00406692 | pop  eax
00406693 | jz   short loc_4066F9

00406695 | inc  eax                   ; n+6
00406696 | inc  eax                   ; n+7
00406697 | push eax
00406698 | call dword_406746          ; f(n+7) → eax = 0x0002
0040669D | test eax, 0FFFF0000h
004066A2 | jnz  short loc_4066C3
004066A4 | cmp  ax, 1                 ; f(n+7) == 1 → reinicia lectura (caso especial)
004066A8 | jz   short loc_406692

; ── PASO 7 · Verificación de la ecuación ────────────────────────────────

004066AA | mov  edx, dword_4082EC     ; edx = f(n+5) = 0x14CC
004066B0 | mul  edx                   ; eax = f(n+7) * f(n+5) = 2 * 5324 = 10648
004066B2 | sub  eax, 14CAh           ; *** constante self-patched con valName ***
                                      ; eax = 10648 - 5322 = 5326
004066B7 | sub  eax, dword_4082E4    ; eax = 5326 - f(n+1) = 5326 - 5324 = 2
004066BD | sub  eax, dword_4082E8    ; eax = 2 - f(n+3) = 2 - 2 = 0  ✓

004066C3 | pop  eax
004066C4 | jnz  short loc_4066F9     ; ≠ 0 → "Far, far, very far!"
004066C6 | dec  ebp                   ; siguiente iteración del bucle externo
004066C7 | jz   short loc_4066CE     ; ebp == 0 → se han superado las 1.000.000 iter.
004066C9 | jmp  loc_406633           ; ebp > 0 → repite con nuevo valor de ebp

; ── RESULTADO ────────────────────────────────────────────────────────────

004066CE | pop  ebp
004066CF | push 30h
004066D1 | push mastermind.4082A3
004066D6 | push mastermind.40828C    ; "You are a mastermind!!"
004066DB | jmp  mastermind.406706
          ; → MessageBoxA → ÉXITO

004066DD | push 10h
004066DF | push mastermind.408263    ; "The name!!"
004066E4 | push mastermind.408239    ; "Didn't you forget something?"
004066E9 | jmp  mastermind.406706

004066EB | push 10h
004066ED | push mastermind.408256    ; "The serial!!"
004066F2 | push mastermind.408239    ; "Didn't you forget something?"
004066F7 | jmp  mastermind.406706

004066F9 | pop  ebp
004066FA | push 10h
004066FC | push mastermind.408282    ; "1 + 1 = 3"
00406701 | push mastermind.40826E    ; "Far, far, very far!"
00406706 | push [ebp+8]
00406709 | call MessageBoxA

; ════════════════════════════════════════════════════════════════════════
; SOLUCIÓN PARA nombre = "deurus"
; ════════════════════════════════════════════════════════════════════════
;
;  hash raw:     0x6A373C56
;  hash % 50000: 4822  (0x12D6)
;  valName:      5322  (0x14CA)
;
;  serialSnippet generado (bytes en 0x406746..0x406752):
;    33 D2  → xor  edx, edx
;    0F BB D1 → btc  ecx, edx
;    69 C1 CA 14 00 00 → imul eax, ecx, 0x14CA   ← valName incrustado
;    40     → inc  eax
;    40     → inc  eax
;    C3     → ret  (permanente en 0x406753)
;
;  Verificación (constante por todas las iteraciones, ecx alterna 0↔1):
;    f(n+1) = 0x14CC = 5324
;    f(n+3) = 0x0002 = 2
;    f(n+5) = 0x14CC = 5324
;    f(n+7) = 0x0002 = 2
;
;    f(n+7)*f(n+5) - valName - f(n+1) - f(n+3)
;    = 2*5324 - 5322 - 5324 - 2
;    = 10648 - 5322 - 5324 - 2
;    = 0  ✓
;
;  SERIAL: 33D20FBBD169C1CA1400004040
; ════════════════════════════════════════════════════════════════════════

Con todo esto estamos en disposición de hacer un Keygen:

const readline = require("readline");

function generarSerial(name) {
    let serial = "33D20FBBD169C1xxxx00004040";
    let val = 0x8E310BD4 >>> 0;

    for (let i = name.length - 1; i >= 0; i--) {
        val = Math.imul(val, name.charCodeAt(i)) >>> 0;
        val = (val ^ 0xD86F936E) >>> 0;
    }

    val = val % 0xC350;
    val = val + 0x1F4;

    let buffer = val.toString(16).toUpperCase();
    let serialArray = serial.split("");

    if (buffer.length === 2) {
        serialArray[14] = buffer[0];
        serialArray[15] = buffer[1];
        serialArray[16] = "0";
        serialArray[17] = "0";
    } else if (buffer.length === 3) {
        serialArray[14] = buffer[1];
        serialArray[15] = buffer[2];
        serialArray[16] = "0";
        serialArray[17] = buffer[0];
    } else {
        serialArray[14] = buffer[2];
        serialArray[15] = buffer[3];
        serialArray[16] = buffer[0];
        serialArray[17] = buffer[1];
    }

    return serialArray.join("");
}

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

console.log("...^^...\n");

rl.question("Your name: ", function(name) {
    const serial = generarSerial(name);
    console.log("\nSerial: " + serial);
    rl.close();
});