Introducción a la Validación de Licencias de Binary Ninja 5.1.8005 Personal
Este artículo detalla la mecánica de validación de licencias implementada en la versión 5.1.8005 Personnel de Binary Ninja. El objetivo es comprender cómo la aplicación verifica las credenciales de la licencia y cómo se podría manipular este proceso.
Función Principle de Verificación de Licencias
La lógica central de la verificación de licencias reside en una función clave dentro del binario. Al inicio, esta función comprueba si una licencia ya ha sido validada. De no ser así, procede a examinar la variable de entorno BN_LICENSE. Si se encuentra, su contenido se utiliza como información de la licencia. En ausencia de la variable de entorno, la aplicación intenta cargar la licencia desde un archivo (probablemente license.dat u otro archivo predefinido). Si no se encuentra un archivo, la verificación de la licencia fallará.
Una vez que se obtiene el contenido de la licencia (ya sea de la variable de entorno o de un archivo), se procesa a través de una serie de pasos:
- Decodificación JSON: El contenido de la licencia se espera que sea un objeto JSON que contenga varios campos, como
product,email,serial,created,type,count,dataysignature. Opcionalmente, también puede incluirexpiresEpoch. - Decodificación de Datos Cifrados: El campo
datacontiene información cifrada. Primero se decodifica de Base64. El contenido binario resultante se somete a un hash MD5, y este hash se utiliza como clave para un cifrado RC4. Una cadena hexadecimal específica incrustada en el binario se descifra utilizando esta clave RC4. - Construcción del Mensaje a Firmar: Se concatena una cadena de los campos de la licencia (
product,email,serial,created,type,count,data) utilizando un byte nulo como separador. - Carga y Decodificación de la Clave Pública: El binario incrusta una clave pública RSA que está cifrada mediante una operación XOR simple con una clave de 4 bytes (
0x67F97244). Esta clave se decodifica y se carga para su uso en la verificación de la firma. - Verificación de Firma RSA: El mensaje construido en el paso 3 se somete a un hash SHA256. Luego, este hash se verifica contra la
signatureproporcionada en el JSON utilizando la clave pública RSA decodificada. Este paso es crítico para asegurar la autenticidad de la licencia. - Lista Negra de Series: La aplicación comprueba si el hash SHA256 del campo
serialde la licencia coincide con alguna entrada en una lista negra interna de seriales. Si hay una coincidencia, la licencia se considera inválida. - Validación del Tipo de Producto: Finalmente, se valida el campo
productpara asegurar que coincide con los valores esperados, como "Binary Ninja" o "Binary Ninja Personal".
Si todos estos pasos se completan con éxito, la licencia se considera válida. De lo contrario, la aplicación rechazará la licencia y posiblemente lanzará una excepción.
Mecanismo de Verificación de Firma RSA (Botan)
La verificación de la firma se realiza mediante una función que utiliza la biblioteca criptográfica Botan. Esta función, a grandes rasgos, toma la clave pública, el mensaje original y la firma, y determina si la firma es válida para el mensaje dado. Se verifica el formato de la firrma y el tamaño, y se realiza una comparación de bajo nivel para asegurar que los datos coinciden con la codificación canónica DER esperada.
long long PK_Verificador_Interno(ClavePublica* clave, const char* datos_firma, size_t tam_firma)
{
// ... inicialización y comprobaciones de formato ...
// Desensambla la firma en componentes más pequeños
// Itera sobre los componentes de la firma
// Une los componentes para obtener el mensaje firmado
// Compara el mensaje firmado con los datos originales para asegurar la codificación DER
// Si la comparación falla, lanza un error de decodificación
// Realiza la verificación real de la firma RSA utilizando la clave pública
// Retorna 1 si la firma es válida, 0 en caso contrario
}
Estrategias de Modificación (Patching)
Existen varias formas de eludir la verificación de licencias:
Opción 1: Ignorar la Verificación de Firma
La estrategia más directa es modificar la función de verificación de firma (PK_Verificador_Interno) para que siempre devuelva un valor de "éxito" (por ejemplo, 1 o true), sin importar la validez real de la firma. Esto se puede lograr inyectando un MOV AL, 1 seguido de un RET al principio de la función.
; Dirección de inicio de la función de verificación de firma
offset_verif_func:
MOV AL, 1 ; Mueve el valor 1 al registro AL
RET ; Retorna de la función
Opción 2: Modificar la Clave Pública Incrustada
Una alternativa es modificar directamente la clave pública incrustada en el binario. Como se mencionó, la clave pública está codificada con XOR. Se podría cambiar los bytes XOR de la clave pública por una clave propia y luego firmar las licencias con la clave privada correspondiente. Esto requeriría una comprensión precisa de dónde reside la clave XOR y cómo se decodifica en tiempo de ejecución.
; Sección donde se almacenan los datos de la clave pública XORed
public_key_data_start:
MOV DWORD PTR DS:[RAX], 0xVALOR_XOR_1 ; Primeros 4 bytes
MOV DWORD PTR DS:[RAX+4], 0xVALOR_XOR_2 ; Siguientes 4 bytes
; ... y así sucesivamente para toda la clave ...
; Lógica de decodificación XOR
xor_loop:
MOV EDX, DWORD PTR DS:[RBX+RAX*4] ; Carga un dword de la clave pública XORed
XOR EDX, 0x67F97244 ; Aplica la operación XOR
; ... y almacena el byte decodificado ...
Otra técnica relacionada sería enganchar (hook) la función que carga la clave pública (load_pubkey_1818D6FC0) y modificar el búfer de la clave pública en memoria antes de que se utilice para la verificación.
Generación de Licencias con Python
El siguiente script de Python recrea la lógica de generación de licencias, permitiendo crear licencias válidas una vez que se tiene acceso a la clave privada correspondiente a la clave pública incrustada en Binary Ninja.
import base64
import hashlib
import json
import random
from Crypto.Cipher import ARC4
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from datetime import datetime, timezone
def obtener_cadena_tiempo_utc():
"""Obtiene la hora UTC actual y la formatea como una cadena ISO."""
ahora_utc = datetime.now(timezone.utc)
return ahora_utc.isoformat(timespec='milliseconds') + '+00:00'
def generar_datos_internos_licencia():
"""Genera y codifica los datos internos para el campo 'data' de la licencia."""
datos_aleatorios = random.randbytes(0x100)
clave_rc4 = hashlib.md5(datos_aleatorios).digest()
cifrador_rc4 = ARC4.new(key=clave_rc4)
# Cadena hexadecimal fija que se espera descifrar por RC4
datos_rc4_originales = bytes.fromhex('9C2AAA09A4E2252B0BA125DB1E1CD272207D97CCA8446899')
datos_cifrados = cifrador_rc4.encrypt(datos_rc4_originales)
# Combina datos aleatorios y datos cifrados, luego codifica en Base64
return base64.standard_b64encode(datos_aleatorios + datos_cifrados).decode()
def firmar_mensaje_licencia(mensaje_a_firmar: bytes, clave_privada_pem: bytes):
"""Firma un mensaje utilizando una clave privada RSA y SHA256."""
clave_privada = RSA.import_key(clave_privada_pem)
firmador = pkcs1_15.new(clave_privada)
hash_sha256 = SHA256.new(mensaje_a_firmar)
firma_generada = firmador.sign(hash_sha256)
return base64.standard_b64encode(firma_generada).decode()
def crear_licencia(cantidad_usuarios: int, correo_electronico: str, serial_hex: str = None):
"""
Genera una licencia Binary Ninja en formato JSON.
La clave privada debe ser proporcionada para la firma.
"""
if serial_hex is None:
serial_hex = random.randbytes(0x10).hex() # Genera un serial aleatorio si no se proporciona
datos_licencia = {
"product": "Binary Ninja Personal",
"email": correo_electronico,
"serial": serial_hex,
"created": obtener_cadena_tiempo_utc(),
"type": "User",
"count": cantidad_usuarios,
"data": generar_datos_internos_licencia()
}
# Clave privada obtenida de AMPED Keygen (ejemplo, se debe reemplazar con la real)
# Esta es una clave de ejemplo y debe ser la que corresponda a la clave pública de BN
clave_privada_para_firma = bytes.fromhex('''
308204A30201000282010100D2BF8069B298618B54272B13CE402C37826D906FA0DB47C916E304D61CFE847306AD1763A332A6FACBEF133DE5E634B333739EFFFE9F7513F7C38CDF4EB7CE27B56B728424F9410DB4CD3AB33D2A367123470D62324211876D83C15B59FB7A4D5A74E56F9E443DBEFF30289D3E4F84E58E6AB23AD4F43870034605E68EDF1FF90256AA027C6102981B8A7742C3DCFC536A4D98C4E22702F2BFFDE2985E232A2446D5750E20EDD27E59FA2475CFF2882CA33347209F62DED6965D85B03BDE6E02B99F680F33B7DC08F8730C0BCE62256FCA5613213A1182C00A36A9D496629D15C1B604550F97388C2DFD60CC8DC15CF5D61A829167CE07F9798168C92D6037470203010001028201005BC7FDC74A79D58565C5571BDD87921A2CA9C5ACEFCB7FD4622CC536F052A1E12C67A6978483F337A727FBE3C9A33B914D978D87E45E9290FB26C54B9D4F2C2F9BF16AE284EDAE78A72477EB867843547B6E1EB484B9C4438C1CC4D1217B855479D00DF9D1DDDB5C3A6BC14C55CE30CCFE7C96194C13FE1E3E36B92C234DA5F0B362663B5B353949FF83F3987080A20326CC8A4FC5E51FF5A91026BB72F1BF4EAA5EB893892E2AC6FEB828EC2D093F992589D7EDEE5DA8EA94C6F8EA61E1FF1D3686EE2B97859E0123CF438F457C97860C04263380EE82C84DB0CADCE121C93F5AD1EB0A802C7ABFF14B4265805CAC6C37F4BF4E17B034E29F3DE64EA98450CD02818100FFDC7E6D1275D1956316116CD79CD5A44F76A6284DD3C35E5A607C1C612D454BFB94DFF5EE63DDB695C8E3A9E398D188A25100959C632DBD3A23FC31F975484D1531151AA7CD6711C960018E366F1507FEB787757464F7E2F05AD097DAD9C8D34BAB3BD584948C7DABD750B3F9B651C3FCDE7133232CA2228F7880410A7FC89502818100D2DCBF521CC7FC91AE554A7ADE811CA07356C50227EC07A4DB06A2B681E29CA8F4D54A7D40D7DFAA38A1B6F03D9E4ACFBEF7C7AC45A6496C94BFD8FA0FB1C2528097AAACFDD0FAA5C9CD42A010018CB04A488A6437B5F4328B30D2FBE9290AA3C9937DD1DB92DFAE4431FC690B7EF879FFDDBAD9D3784A5869C6D8039B249D6B028181009A9EF0540FE4DD7C2EBE2657A5512516BFE2CEF4EA5B7FE4642F8CB145D4AADD093365C8E480BB7ADCB7E345446C29255C4E9B8B5B1258A7DA1461FE13F84ADE5CF59B30C41BDF27CA03A819624B52A7B8365FBD97236964B31BF5FF1751349B6CF32B2DD0CDB0CAFE18A243E2F390BDEA9D0EF8DDCC2DB5491695BF0725CD8A50281802101306917DC2DAA57D13DD131969FF67557358AFAD8B4F196DED9051C1B6E4DFBD48ECE402209FE48D2F7216F63A16E17040D9AE763F9C6271A484A0BBED51DB8C7048E03447C970A99383E7982E4948B6C034D6072F88018CD5198E08BEE006902CF04D40B8F3B65AD3546F3E7B1D8D6B5CC13604849CAC0F3C0C7FFB6A175028180616C870F1920FD24DEBE793A273591CB3E858962A9A93022AF36FB15CEF57F3C3EE101F1A8AF206DF757EC7A7EBD99D7E1C5B18870EB8B66E78F3FA005E4431D71B25F350103C2E68BC4474DF3BDAC57F8D9327304C65E5069DDB25C178615D1A3B264B22B8826E33D21F4CD50433FD6210ED5699741FB219E75F6DD8F5DB714
'''.replace('\n', '').strip().encode())
mensaje_a_firmar_str = '\x00'.join(
(datos_licencia["product"], datos_licencia["email"], datos_licencia["serial"],
datos_licencia["created"], datos_licencia["type"], str(datos_licencia["count"]),
datos_licencia["data"])
)
datos_licencia["signature"] = firmar_mensaje_licencia(
mensaje_a_firmar_str.encode(), clave_privada_para_firma
)
return json.dumps([datos_licencia], indent=4, ensure_ascii=False)
def decodificar_clave_xor(datos_xor: bytes, clave_xor: bytes = bytes([0x44, 0x72, 0xf9, 0x67])):
"""Decodifica los bytes de la clave pública aplicando una operación XOR cíclica."""
resultado = bytearray(len(datos_xor))
for i in range(len(datos_xor)):
resultado[i] = datos_xor[i] ^ clave_xor[i % len(clave_xor)]
return bytes(resultado)
if __name__ == '__main__':
# Ejemplo de uso:
ruta_licencia = 'license.dat'
guardar_archivo = True
cantidad_licencias = 123
correo_usuario = "ejemplo@dominio.com"
texto_licencia = crear_licencia(cantidad_licencias, correo_usuario)
print("Licencia generada:")
print(texto_licencia)
if guardar_archivo:
with open(ruta_licencia, 'w', encoding='utf8') as f:
f.write(texto_licencia)
# Ejemplo de cómo decodificar la clave pública incrustada
# Esta es la clave pública XORed extraída del binario de Binary Ninja
clave_publica_xor = bytes.fromhex('''
74f0f845747fff6e6ef4b1e1b37ff8664577f964c673f66774f0f86d46f0f86644a495f98cae7f949f1a4bdffb14fc362777de93a843e05c2d06c786a22252d6e6559a06f4717a957914916d2c6c4adf983886f61595cb9190c590049560d3e81ceb8a0c9a09687bdb58ac8369d87c018fdfbad92c69d65f513b3a25b86b0892d7a0bd33306feecab6530f54e1deb48f1a84fb516c813927e0da5fba35d2fb87a9168d67cc02b7a45dd71a7882ccce78439e05db831fda1f58cd80f52e0745eca0bec4664af7cbdbd0c294297dc4f015e6430f38f6086235b8da390f27111a61615c080b5353641c5de174f85c8b9569a9fd9c6b9c0b90986bb3cd1cc216b87aeb97084aa3e87d1e7fb66cb123c5dfee58776c22046764f5b048e82c87770fa664473f967
'''.replace('\n', '').strip())
clave_publica_decodificada = decodificar_clave_xor(clave_publica_xor)[:0x126] # Truncar a la longitud correcta
# print("\nClave pública decodificada (hex):", clave_publica_decodificada.hex())
try:
clave_rsa_publica = RSA.import_key(clave_publica_decodificada)
# print("Exponente público (e):", clave_rsa_publica.e)
# print("Módulo (n):", clave_rsa_publica.n)
except ValueError as e:
print(f"\nError al importar la clave RSA decodificada: {e}")