Descripción general del análisis
Este documento presenta un análisis técnico del sistema de verificación de licencias en WinRAR versión 7.12 (build 712sc). Se examinan los mecanismos de validación del archivo de注册密钥 (rarreg.key) y la implementación de criptografía de curva elíptica (ECC) utilizada por el software.
Localización del proceso de verificación
WinRAR permite el registro mediante el archivo rarreg.key, que debe copiarse a la carpeta de instalación o al directorio %APPDATA%\WinRAR. También es posible arrastrar el archivo directamente a la ventana de WinRAR.
Punto de entrada principal
La búsqueda de la cadena "rarreg.key" permite localizar la función principal de procesamiento: buscar_nueva_clave_14019A158. El flujo de registro mediante arrastre se gestiona en la función sub_14019C2F4, que utiliza la interfaz IDropTarget para detectar cuando se suelta un archivo sobre la ventana.
La implementación básica del gestor de arrastre es:
gestor_arrastre* crear_gestor_arrastre(HWND ventana_padre) {
gestor_arrastre* gestor = new gestor_arrastre();
if (gestor) {
gestor->vtabla = &vtabla_arrastre_14020BA40;
}
return registrar_arrastre(ventana_padre, gestor);
}
Función de carga y verificación
Una vez cargado el archivo, se invoca cargar_y_verificar_1400C62CC que realiza la lectura del archivo y llama a la validación principal:
bool cargar_y_verificar_1400C62CC(ruta_archivo* ruta, char indicador) {
archivo_licencia licencia;
bool resultado = false;
if (abrir_archivo(&licencia, &ruta)) {
datos = (char*)asignar_memoria(0x2000);
if (datos) {
tamano = leer_datos(&licencia, datos, 0x2000);
if (tamano > 0 && verificar_datos_1400C6390(datos, tamano, indicador)) {
resultado = true;
}
liberar(datos);
}
}
cerrar_archivo(&licencia);
return resultado;
}
Verificación de datos del archivo de licencia
La función verificar_datos_1400C6390 constituye el núcleo del sistema de validación. Cuando el tercer parámetro es diferente de cero, se realiza una verificación completa; de lo contrario, solo se valida el CRC32.
Proceso de verificación
El sistema realiza las siguientes validaciones:
- Verifica el CRC32 de la clave pública ECC incluidahardcoded:
70c2441db366d92ea7be1342b3bf629026ba92bb675f06e684bdd34511097434 - Procesa las líneas del archivo de licencia extrayendo:
- Nombre de usuario
- Tipo de licencia
- UID
- Datos de registro (RegisterData[0-3])
- Parsed los primeros bytes de lic_data para obtener las longitudes de los 4 segemntos RegisterData
- Calcula el checksum y verifica la integridad
Extracción de datos de registro
El formato del archivo de licecnia utiliza un formato estructurado donde los primeros caracteres indican la longitud de cada segmento:
// Formato: LLDDDD... (L = longitud en ASCII, D = datos)
// Ejemplo: "64" seguido de 64 caracteres = RegisterData[0]
// "122" seguido de 122 caracteres = RegisterData[1]
// etc.
extraer_longitudes(&lic_data, &len0, &len1, &len2, &len3);
// Extraer cada segmento
RegisterData[0] = lic_data[10 : 10 + len0]
RegisterData[1] = lic_data[10 + len0 : 10 + len0 + len1]
RegisterData[2] = lic_data[10 + len0 + len1 : ...]
RegisterData[3] = lic_data[... : lic_data.tamaño - checksum]
checksum = lic_data[ resto ]
Flujo de validación de RegisterData
El sistema de validación ECC realiza cuatro verificaciones independientes:
Primera verificación
Se utiliza RegisterData[1] como firma, lic_type como mensaje, y la clave pública hardcoded de WinRAR para verificar la firma del tipo de licencia:
clave_publica = obtener_clave_publica_interna();
resultado1 = verificar_firmaECC(&tipo_licencia, &clave_publica, &RegisterData[1]);
Segunda verificación
RegisterData[2] contiene la firma; el mensaje es la concatenación de username + RegisterData[0]:
mensaje2 = concatenar(nombre_usuario, RegisterData[0]);
resultado2 = verificar_firmaECC(&mensaje2, &clave_publica_interna, &RegisterData[2]);
Tercera verificación
A partir de RegisterData[3] se deriva una clave privada, cuya clave pública对应 RegisterData[0]. Luego se genera una firma del mensaje:
clave_privada = derivar_clave_privada(RegisterData[3]);
clave_publica_generada = calcular_clave_publica(clave_privada);
// Verificar que coincide con RegisterData[0]
firmar_mensaje(mensaje2, clave_privada, &firma_temporal);
Cuarta verificación
Se verifica que la firma temporal es correcta usando RegisterData[0] como clave pública:
resultado_final = verificar_firmaECC(&mensaje2, &RegisterData[0], &firma_temporal);
Generación de clave y firma ECC
La función generar_clave_firma_1400CECC4 implementa la derivación de claves y generación de firmas:
void generar_clave_firma_1400CECC4(mensaje* msg, datos* entrada, firma* salida) {
generador generador;
clave_privada clave_priv;
// Inicializar generador a partir de los datos de entrada
iniciales_sha1_1400D0888(&generador, entrada);
// Derivar la clave privada mediante 15 iteraciones SHA1
obtener_clave_privada_sha1_1400D0908(&generador, &clave_priv);
// Calcular hash del mensaje
sha1_mensaje_1400D0758(&generador, msg, msg->tamano, 2);
convertir_hex_bigint(generador.hash_mensaje, &hash);
// Firmar hasta obtener una firma válida
do {
obtener_valor_aleatorio_sha1_1400D0908(&generador, &aleatorio);
generar_firma_ECC_1400CF368(&clave_priv, &aleatorio, &hash, &firma);
} while (!firma.r.tamano_valido);
// Formatear firma de salida
formatear_firma(&firma, salida);
}
Estructuras de datos principales
struct HashNumero {
int tamano_total;
int indice;
int generador_sha1[5];
int hash_mensaje[11];
};
struct NumeroGrande {
unsigned int tamano;
unsigned int valor[17];
};
struct ContextoSHA1 {
unsigned int estado[5];
unsigned long long conteo;
unsigned char buffer[64];
};
Obtención del generador
La función obtener_generador_sha1_1400D0888 calcula el valor inicial del generador usando SHA1:
void obtener_generador_sha1_1400D0888(HashNumero* destino, datos* entrada) {
ContextoSHA1 ctx;
inicializar_sha1(&ctx);
actualizar_sha1(&ctx, entrada->datos, entrada->tamano);
finalizar_sha1(&ctx, destino->generador_sha1);
destino->tamano_total = 6;
}
Derivate de clave privada
La función obtener_clave_privada_1400D0908 genera la clave privada mediante iteraciones SHA1:
// Pseudocódigo del proceso:
// para i = 1 hasta 15:
// sha1 = hashlib.sha1()
// sha1.update(i.to_bytes(4, "little") + generador)
// clave_privada += sha1.digest()[3:1:-1] // Primeros 2 bytes
int obtener_clave_privada_sha1_1400D0908(HashNumero* generador, NumeroGrande* clave_priv) {
clave_priv->tamano = 15;
for (int i = 1; i < 16; i++) {
// Calcular valor SHA1 y tomar solo primeros 2 bytes
*(&clave_priv->tamano + i) = calcular_sha1_parcial(generador);
}
return 0;
}
Algoritmo de hash de mensaje
Elhash del mensaje se calcula mediante una variantede SHA1 con un proceso iterativo:
unsigned long long sha1_mensaje_1400D0758(HashNumero* destino, mensaje* msg,
long tamano_msg, long iteraciones) {
unsigned char contador = 0;
ContextoSHA1 contextos[2];
// Iniciar tantos contextos como iteraciones especificadas
for (int i = 0; i < iteraciones; i++) {
inicializar_sha1(&contextos[i]);
}
// Actualizar todos los contextos con el mensaje
for (int i = 0; i < iteraciones; i++) {
actualizar_sha1(&contextos[i], msg, tamano_msg);
}
// Recolectar resultados
for (int i = 0; i < iteraciones; i++) {
finalizar_sha1(&contextos[i], &destino->hash_mensaje[4*i + i]);
inicializar_sha1(&contextos[i]); // Reiniciar para hash vacío
}
// Calcular tamaño total
destino->tamano_total = iteraciones + 4 * iteraciones + 6;
return destino->tamano_total;
}
Parámetros de la curva elíptica
WinRAR utiliza una curva elíptica sobre GF(2^m) con los siguientes parámetros:
- Ecuación: y² + xy = x³ + ax² + b (donde a=0, b=0xa1)
- Campo: GF(2¹³⁷)
- Orden de la curva: 0x1026DD85081B82314691CED9BBEC30547840E4BF72D8B5E0D258442BBCD31
Punto base G
// Coordenada X
Gx = {
0x11, 0x56FD, 0x289E, 0x59DC, 0x0CC2, 0x4CB7, 0x0025,
0x55CE, 0x4FEB, 0x0D67, 0x4882, 0x4468, 0x1B89, 0x45AA, 0x2510, 0x52F, 0x38CC
};
// Coordenada Y
Gy = {
0x20DA, 0x1974, 0x2BF2, 0x216F, 0x4F0E, 0x3B5E, 0x7911,
0x12D6, 0x4D07, 0x539B, 0x54C1, 0x7388, 0x3412, 0x18C4, 0x65F2, 0x31A7
};
逆向工程 del proceso de generación
El proceso inverso presenta varios desafíos técnicos:
Desafío 1: Clave privada ECC de WinRAR
La clave privada de WinRAR se calcula a partir de un generador determinado. El generador inicial se obtiene mediante SHA1 y luego se procesa a través de 15 iteraciones para obtener la clave privada:
clave_privada_winrar = 0x59fe6abcca90bdb95f0105271fa85fb9f11f467450c1ae9044b7fd61d65e
Desafío 2: RegisterData[3]
RegisterData[3] se genera a partir del nombre de usuario. Primero se calcula la clave pública del usuario, luego se concatena '60' con los primeros 48 caracteres hexadecimales de dicha clave:
clave_publica_usuario = derivar_clave_publica(nombre_usuario)
RegisterData[3] = "60" + clave_publica_usuario.hex()[:48]
Desafío 3: UID
El campo UID no es validado activamente durante el proceso de verificación. Se calcula como:
UID = clave_publica_usuario.hex()[48:] + RegisterData[0][:4]
// 8 bytes + 2 bytes = 10 bytes
Notas finales
Este análisis revela que WinRAR implementa un sistema robusto de verificación de licencias basado en criptografía de curva elíptica. El sistema utiliza una clave pública hardcoded para validar parcialmente las credenciales, mientras que la generación de claves específicas de usuario permite la verificación de la autenticidad del archivo de licencia.
Los archivos de licencia legítimos contienen estructuras de datos específicas que incluyen firmas ECC válidas, derivadas de las claves privadas correctas.