Introducción
Hoy tenemos aquí un bonito crackme matemático realizado por Spider. El crackme está realizado en ensamblador y precisamente por eso, vamos a tener que lidiar con ciertas peculiaridades al realizar el keygen con un lenguaje de bajo nivel.
Al inicio comprueba la longitud del nombre y de el número de serie. El nombre debe tener al menos 6 caracteres y el número de serie debe tener 10. Os adelanto ya que la asignación de memoria del nombre es de 9 caracteres, es decir, da igual la longitud del nombre que solo va a usar 9.
004014AD | E8 1A 02 00 00 | call <pythagoras.GetWindowTextA> | ;Lee el nombre 004014B2 | 83 F8 06 | cmp eax,6 | ;Nombre >=6 caracteres 004014B5 | 0F 82 03 01 00 00 | jb pythagoras.4015BE | 004014BB | 6A 14 | push 14 | 004014BD | 68 D9 31 40 00 | push pythagoras.4031D9 | ;004031D9:"1234567890" 004014C2 | FF 35 10 32 40 00 | push dword ptr ds:[403210] | 004014C8 | E8 FF 01 00 00 | call <pythagoras.GetWindowTextA> | ;Lee el serial 004014CD | 83 F8 0A | cmp eax,A | ;Serial debe tener 10 (A) caracteres 004014D0 | 0F 85 E8 00 00 00 | jne pythagoras.4015BE |
Sabiendo esto introducimos Nombre: deurus y Serial: 1234567890
A continuación chequea que nuestro serial tenga caracteres hexadecimales.
004014DA | 8A 81 D9 31 40 00 | mov al,byte ptr ds:[ecx+4031D9] | ; ecx+004031D9:"1234567890" 004014E0 | 3C 00 | cmp al,0 | ; contador del bucle 004014E2 | 74 1F | je pythagoras.401503 | ; fin del bucle 004014E4 | 3C 30 | cmp al,30 | ; 0x30 = número 1 004014E6 | 0F 82 D2 00 00 00 | jb pythagoras.4015BE | ; < 30 bad boy 004014EC | 3C 46 | cmp al,46 | ; 0x46 = letra F 004014EE | 0F 87 CA 00 00 00 | ja pythagoras.4015BE | ; > 46 bad boy 004014F4 | 3C 39 | cmp al,39 | ; 0x39 = número 9 004014F6 | 76 08 | jbe pythagoras.401500 | ; <=39 ok continua el bucle 004014F8 | 3C 41 | cmp al,41 | ; 0x41 = letra A 004014FA | 0F 82 BE 00 00 00 | jb pythagoras.4015BE | ; <41 bad boy 00401500 | 41 | inc ecx | ; contador += 1 00401501 | EB D7 | jmp pythagoras.4014DA | ; bucle
Continua realizando un sumatorio con nuestro nombre, pero tenemos que tener especial cuidado al tratamiento de los datos, ya que el crackme al estar hecho en ensamblador puede jugar con los registros como quiere y eso nos puede inducir a error.
0040150B | 3C 00 | cmp al,0 | ; ¿Fin bucle? 0040150D | 74 05 | je pythagoras.401514 | ; Salta fuera del bucle si procede 0040150F | 02 D8 | add bl,al | ; bl = bl + al 00401511 | 41 | inc ecx | ; contador +=1 00401512 | EB F1 | jmp pythagoras.401505 | ; bucle
Si os fijáis utiliza registros de 8 bits como son AL y BL. Debajo os dejo una explicación de EAX pero para EBX es lo mismo.
EAX ----------------------------------- AX ----------------- AH AL -------- -------- 00000000 00000000 00000000 00000000 (8bit) (8bit) (8bit) (8bit) EAX (32 bit) -------- AX (16 bit) ---- AHAL (AH y AL 8 bit) -------- 00000000
El uso de registros de 8 bits nos implica tomar precauciones al realizar el Keygen debido a que por ejemplo, en .Net no tenemos la capacidad de decirle que haga una suma y que nos devuelva solamente 8 bits del resultado. Veamos como ejemplo para el nombre «deurus». La suma de los caracteres hexadecimales quedaría:
64+65+75+72+75+73 = 298, es decir, EAX = 00000298
Pero recordad que el crackme solo cogerá el 98 que es lo correspondiente al registro AL. De momento nos quedamos con nuestro SUMNOMBRE = 98.
Primera condición
A continuación coge los dos primeros caracteres del serial y les resta nuestro SUMNOMBRE y comprueba que el resultado esté entre 4 (0x4) y -4 (0xFC).
0040154B | 0F B6 05 F3 31 40 00 | movzx eax,byte ptr ds:[4031F3] | 00401552 | 8A C8 | mov cl,al | 00401554 | 2A CB | sub cl,bl | ; CL = CL - BL | CL = 12 - 98 = 7A 00401556 | 80 F9 04 | cmp cl,4 | ; Compara CL con 4 00401559 | 7F 63 | jg pythagoras.4015BE | ; Salta si es mayor 0040155B | 80 F9 FC | cmp cl,FC | ; Compara CL con FC (-4) 0040155E | 7C 5E | jl pythagoras.4015BE | ; Salta si es menor
Como veis, el resultado de la resta da 7A (122) que al ser mayor que 4 nos echa vilmente. Aquí de nuevo utiliza registros de 8 bits por lo que debemos tener cuidado con las operaciones matemáticas para no cometer errores, veamos un ejemplo para clarificar de aquí en adelante.
Utilizando 8 bits ----------------- 12 - 98 = 7A que en decimal es 122 Utilizando 16 bits ------------------ 0012 - 0098 = FF7A que en decimal es -134
Ahora ya veis la diferencia entre FC (252) y FFFC (-4). Estrictamente, el crackme comprueba el rango entre 4 (4) y FC (122) al trabajar con registros de 8 bits pero nosotros, como veremos más adelante tomaremos el rango entre 4 y -4. De momento, para poder continuar depurando cambiamos los dos primeros caracteres del serial de 12 a 98, ya que 98 – 98 = 0 y cumple la condición anterior.
Introducimos Nombre: deurus y Serial: 9834567890
Segunda condición
Analicemos el siguiente código.
00401560 | F7 E0 | mul eax | ; EAX = EAX * EAX 00401562 | 8B D8 | mov ebx,eax | ; EBX = EAX 00401564 | 0F B7 05 F4 31 40 00 | movzx eax,word ptr ds:[4031F4] | ; EAX = 3456 (4 dígitos siguientes del serial) 0040156B | F7 E0 | mul eax | ; EAX = EAX * EAX 0040156D | 03 D8 | add ebx,eax | ; EBX = EBX + EAX 0040156F | 0F B7 05 F6 31 40 00 | movzx eax,word ptr ds:[4031F6] | ; EAX = 7890 (4 últimos dígitos del serial) 00401576 | F7 E0 | mul eax | ; EAX = EAX * EAX 00401578 | 33 C3 | xor eax,ebx | ; EAX 0040157A | 75 42 | jne pythagoras.4015BE | ; Salta si el flag ZF no se activa
En resumen:
- 98 * 98 = 5A40 (98²)
- 3456 * 3456 = 0AB30CE4 (3456²)
- 0AB36724 + 5A40 = 0AB36724
- 7890 * 7890 = 38C75100 (7890²)
- 38C75100 XOR 0AB36724 = 32743624
- Si el resultado del XOR no es cero nuestro serial no pasa la comprobación.
Es decir, Pitágoras entra en escena -> 7890² = 98² + 3456²
Serial = aabbbbcccc
Tercera condición
Finalmente comprueba lo siguiente:
0040157C | 66 A1 F6 31 40 00 | mov ax,word ptr ds:[4031F6] | ; AX = 7890 00401582 | 66 2B 05 F4 31 40 00 | sub ax,word ptr ds:[4031F4] | ; AX = 7890 - 3456 = 443A 00401589 | 2C 08 | sub al,8 | ; AL = 3A - 8 = 32 0040158B | 75 31 | jne pythagoras.4015BE | ; Si el resultado de la resta no ha sido cero, serial no válido 0040158D | 6A 30 | push 30 | 0040158F | 68 B0 31 40 00 | push pythagoras.4031B0 | ;004031B0:":-) Well done!!!" 00401594 | 68 7F 31 40 00 | push pythagoras.40317F | ;0040317F:"Bravo, hai trovato il seriale di questo CrackMe!" 00401599 | FF 75 08 | push dword ptr ds:[ebp+8] |
En resumen:
- 7890 – 3456 – 8 = 0
Creación del Keygen
Nuestro serial tiene que cumplir tres condiciones para ser válido.
- a – SUMNOMBRE debe estar entre 4 y -4
- c² = a² + b²
- c – b – 8 = 0
Como hemos dicho anteriormente, tomaremos el SUMNOMBRE y le sumaremos y restaremos valores siempre y cuando el resultado esté entre 4 y -4. Para deurus hemos dicho que el SUMNOMBRE es 98 por lo que los posibles valores de «a» se pueden ver debajo. Además debemos tener en cuenta que el crackme solo lee los 9 primeros dígitos del nombre.
98-4 = 94 98-3 = 95 98-2 = 96 98-1 = 97 98-0 = 98 98+1 = 99 98+2 = 9A 98+3 = 9B 98+4 = 9C
Es evidente que para encontrar el valor de «c» vamos a tener que utilizar fuerza bruta chequeando todos los valores de «b» comprendidos entre 0 y FFFF (65535). Además, como trabajaremos en un lenguaje de alto nivel, debemos descartar los resultados decimales. Esto nos limitará los seriales válidos asociados a un determinado nombre. Si realizáramos el keygen en ensamblador obtendríamos bastantes más seriales válidos.
Una vez encontrados los valores enteros de la operación «c² = a² + b²», se debe cumplir que «c – b – 8 = 0», lo que nos limitará bastante los resultados.
Private Sub btn_generar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_generar.Click Try If txt_nombre.TextLength > 5 Then lst_serials.Items.Clear() Dim tmp, c, cx As String Dim sumanombre, tmp2 As Integer If txt_nombre.TextLength > 9 Then tmp2 = 8 Else tmp2 = txt_nombre.TextLength - 1 'Calculo el SUMNOMBRE For i = 0 To tmp2 sumanombre += Asc(Mid(txt_nombre.Text, i + 1, 1)) 'Acumulo suma tmp = Strings.Right(Hex(sumanombre).ToString, 2) 'Solo 8 bits (Registro AL) sumanombre = Val("&H" & tmp) 'Paso a decimal Next tmp = Strings.Right(Hex(sumanombre).ToString, 2) sumanombre = CInt("&H" & tmp) txtdebug.Text = "- SumNombre = " & Hex(sumanombre) & vbCrLf txtdebug.Text &= "----------------------------------------------" & vbCrLf Dim a(8) As Integer ' 'a - sumanombre >=4 y <=4 ' a(0) = sumanombre - 4 a(1) = sumanombre - 3 a(2) = sumanombre - 2 a(3) = sumanombre - 1 a(4) = sumanombre a(5) = sumanombre + 1 a(6) = sumanombre + 2 a(7) = sumanombre + 3 a(8) = sumanombre + 4 txtdebug.Text &= "- Posibles valores de 'a'" & vbCrLf For i = 0 To a.Length - 1 txtdebug.Text &= Hex(a(i)) & " " Next txtdebug.Text &= "----------------------------------------------" & vbCrLf txtdebug.Text &= "- Buscando valores de b y c" & vbCrLf txtdebug.Text &= "Serial = aabbbbcccc" & vbCrLf ' 'c = sqr(a^2 + b^2) ' txtdebug.Text &= "(1) c = raiz(a^2 + b^2)" & vbCrLf txtdebug.Text &= "(2) c - b - 8 = 0" & vbCrLf For i = 0 To a.Length - 1 ' todas las posibilidades de a For b = 0 To 65535 'b -> 0000 - FFFF c = Math.Sqrt(a(i) ^ 2 + b ^ 2) If c.Contains(".") Then 'busco enteros Else cx = c - b - 8 cx = Hex(cx).PadLeft(4, "0"c) lbl_info.Text = cx If cx = "0000" Then txtdebug.Text &= " (1) " & Hex(c).PadLeft(4, "0"c) & " = raiz(" & Hex(a(i)).PadLeft(2, "0"c) & "^2 + " & Hex(b).PadLeft(4, "0"c) & "^2)" & vbCrLf lst_serials.Items.Add(Hex(a(i)).PadLeft(2, "0"c) & Hex(b).PadLeft(4, "0"c) & Hex(c).PadLeft(4, "0"c)) txtdebug.Text &= " (2) " & Hex(c).PadLeft(4, "0"c) & " - " & Hex(b).PadLeft(4, "0"c) & " - 8 = 0" & vbCrLf End If End If Application.DoEvents() Next Next lbl_info.Text = "Búsqueda finalizada" End If Catch ex As Exception MsgBox(ex.ToString) End Try