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();
});
