Datasets y Benchmarks para ML en Malware: EMBER, SOREL, BODMAS
Guía completa de datasets públicos para entrenar modelos de detección de malware: EMBER, SOREL-20M, BODMAS, MalwareBazaar, VirusShare. Métricas de evaluación, leaderboards y cómo elegir el dataset adecuado para tu investigación.
El problema de los datos en detección de malware
El componente más infravalorado de cualquier sistema de ML para malware no es el modelo ni el algoritmo: son los datos. Un Random Forest entrenado con un dataset curado, balanceado y representativo superará a un transformer entrenado con datos ruidosos, desbalanceados y con label leakage.
El ecosistema de datasets de malware ha mejorado enormemente en la última década. Hasta 2017, la mayoría de investigadores construían sus propios datasets ad-hoc, con muestras de fuentes opacas y etiquetado inconsistente. Hoy existen datasets públicos de referencia con millones de muestras, features precomputadas, baselines reproducibles y comunidades activas.
Este artículo cubre los datasets y benchmarks esenciales, sus fortalezas y limitaciones, y las métricas de evaluación que deberías usar (y las que deberías evitar).
EMBER: el estándar de facto
EMBER (Elastic Malware Benchmark for Empowering Researchers) es el dataset más usado en investigación de detección de malware con ML. Fue creado por Hyrum Anderson y Phil Roth de Elastic (anteriormente Endgame) en 2018 y actualizado en 2019.
Contenido del dataset
EMBER 2018 contiene un millón de muestras de binarios PE de Windows:
- 400.000 muestras maliciosas
- 400.000 muestras benignas
- 200.000 muestras sin etiquetar (para semi-supervised learning)
- Cada muestra tiene 2.381 features precomputadas agrupadas en 8 categorías
Las 8 categorías de features:
- Byte histogram (256 features): distribución de bytes del binario
- Byte-entropy histogram (256 features): entropía local por ventanas
- String information (104 features): estadísticas de strings extraídas
- General file information (10 features): tamaño, número de secciones, timestamps
- Header information (62 features): campos del PE header
- Section information (255 features): entropía, tamaño y propiedades de secciones
- Imports information (1.280 features): hashes de imports por librería
- Export information (128 features): información de exports
Usar EMBER en Python
import ember
import numpy as np
from sklearn.metrics import roc_auc_score
import lightgbm as lgb
# Descargar EMBER 2018 (~1.8 GB)
# ember.download("ember2018/")
# Cargar features vectorizadas
X_train, y_train, X_test, y_test = ember.read_vectorized_features(
"ember2018/",
feature_version=2
)
# Filtrar muestras sin etiquetar (label = -1)
train_mask = y_train != -1
X_train = X_train[train_mask]
y_train = y_train[train_mask]
print(f"Train: {len(X_train)} muestras ({y_train.sum():.0f} maliciosas)")
print(f"Test: {len(X_test)} muestras ({y_test.sum():.0f} maliciosas)")
print(f"Features: {X_train.shape[1]}")
# Baseline LightGBM (reproduce el paper original)
params = {
"boosting": "gbdt",
"objective": "binary",
"num_iterations": 1000,
"learning_rate": 0.05,
"num_leaves": 2048,
"max_depth": 15,
"min_data_in_leaf": 50,
"feature_fraction": 0.5,
"verbose": -1,
}
train_data = lgb.Dataset(X_train, y_train)
model = lgb.train(params, train_data)
# Evaluacion
y_pred = model.predict(X_test)
auc = roc_auc_score(y_test, y_pred)
print(f"\nAUC: {auc:.6f}") # Esperable: ~0.9991
Limitaciones de EMBER
EMBER tiene limitaciones que debes considerar antes de publicar resultados:
Solo Windows PE: no incluye ELF (Linux), Mach-O (macOS), APK (Android), documentos maliciosos ni scripts. Los modelos entrenados con EMBER solo aplican a binarios PE.
Features precomputadas: no tienes acceso a los binarios originales, solo a las features extraídas. Si quieres probar nuevas features o técnicas de feature engineering, necesitas acceso a los binarios (que EMBER no proporciona).
Temporal bias potencial: todas las muestras son de 2017-2018. El malware de 2025 tiene características diferentes. Un modelo entrenado exclusivamente con EMBER puede no generalizar bien a malware actual.
Etiquetado estático: las etiquetas se asignaron en el momento de recolección y no se actualizan. Muestras que eran "desconocidas" en 2018 podrían ser detectadas hoy por todos los AV.
SOREL-20M: escala industrial
SOREL-20M (Sophos/ReversingLabs 20 Million) es el dataset de malware más grande publicado hasta la fecha. Fue creado por Sophos AI y ReversingLabs en 2020.
Contenido
- 20 millones de muestras de binarios PE
- 10 millones con etiquetas de malicioso/benigno
- 10 millones solo con features (sin etiqueta)
- Features precomputadas similares a EMBER pero con mas granularidad
- Etiquetas basadas en detecciones de múltiples AV con consenso
Estructura del dataset
import json
import lmdb
# SOREL usa LMDB para almacenamiento eficiente
# Descarga: ~8 TB para binarios, ~25 GB para features/labels
def load_sorel_features(db_path: str, sample_keys: list[str]):
"""Carga features de SOREL desde LMDB."""
env = lmdb.open(db_path, readonly=True, max_dbs=2)
features_db = env.open_db(b"features")
results = []
with env.begin(db=features_db) as txn:
for key in sample_keys:
data = txn.get(key.encode())
if data:
features = json.loads(data.decode())
results.append(features)
env.close()
return results
# Estructura de labels de SOREL
# Cada muestra tiene etiquetas de multiples AV:
# {
# "sha256": "abc123...",
# "is_malware": 1,
# "avclass_label": "ransomware.lockbit",
# "detection_count": 45,
# "total_av_count": 70,
# "first_seen": "2020-03-15",
# "tags": ["ransomware", "pe", "x86"]
# }
Ventajas de SOREL sobre EMBER
Escala: 20 millones de muestras permiten entrenar modelos deep learning que necesitan grandes volúmenes de datos. EMBER con 1 millón puede no ser suficiente para transformers o CNN profundas.
Etiquetado multi-AV: cada muestra tiene detecciones de múltiples motores AV, no una etiqueta binaria. Puedes definir tu propio umbral de consenso (por ejemplo, "malicioso si detectado por al menos 30 de 70 AV").
Acceso a binarios: SOREL ofrece opcionalmente acceso a los binarios originales (con acuerdo de uso). Esto permite extraer features propias o aplicar técnicas como binary visualization.
Limitaciones de SOREL
Tamaño: el dataset completo con binarios ocupa mas de 8 TB. Incluso las features precomputadas son demasiado grandes para cargar en RAM en una máquina estándar. Necesitas infraestructura distribuida o muestreo.
Licencia restrictiva: el acceso requiere aceptar términos de uso que limitan el uso comercial y la redistribución. No es tan abierto como EMBER.
Misma limitación temporal: las muestras son de un periodo específico (2018-2020), con los mismos problemas de generalización temporal que EMBER.
BODMAS: drift temporal incluido
BODMAS (Blue Hexagon Open Dataset for Malware AnalysiS) fue creado por Blue Hexagon (adquirida por Juniper Networks) en 2021. Su característica diferenciadora es que incluye datos con drift temporal explícito.
Contenido
- 57.293 muestras de malware
- 77.142 muestras benignas
- Features extraídas de binarios PE
- Timestamps de recolección que abarcan 8 meses (agosto 2019 a marzo 2020)
- Etiquetas de familia de malware (no solo benigno/malicioso)
Por qué BODMAS es importante
BODMAS resuelve un problema que EMBER y SOREL ignoran: el concept drift temporal. El dataset incluye timestamps reales de recolección, lo que permite:
- Temporal split correcto: entrenar con datos de agosto-diciembre 2019 y evaluar con enero-marzo 2020
- Medir degradación temporal: cómo se comporta un modelo entrenado con datos de hace N meses
- Evaluar estrategias de retraining: comparar retraining mensual vs trimestral vs sin retraining
import pandas as pd
from sklearn.model_selection import TimeSeriesSplit
# BODMAS viene con timestamps
df = pd.read_csv("bodmas_features.csv")
df["collection_date"] = pd.to_datetime(df["collection_date"])
# Split temporal (NO aleatorio)
train_mask = df["collection_date"] < "2020-01-01"
test_mask = df["collection_date"] >= "2020-01-01"
X_train = df[train_mask].drop(["label", "collection_date", "sha256"], axis=1)
y_train = df[train_mask]["label"]
X_test = df[test_mask].drop(["label", "collection_date", "sha256"], axis=1)
y_test = df[test_mask]["label"]
print(f"Train: {len(X_train)} (ago-dic 2019)")
print(f"Test: {len(X_test)} (ene-mar 2020)")
# Evaluar degradacion temporal
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
model = RandomForestClassifier(n_estimators=200, random_state=42)
model.fit(X_train, y_train)
# AUC por mes de test
for month in ["2020-01", "2020-02", "2020-03"]:
mask = df["collection_date"].dt.strftime("%Y-%m") == month
if mask.sum() > 0:
X_month = df[mask].drop(
["label", "collection_date", "sha256"], axis=1)
y_month = df[mask]["label"]
y_pred = model.predict_proba(X_month)[:, 1]
auc = roc_auc_score(y_month, y_pred)
print(f"AUC {month}: {auc:.4f} ({mask.sum()} muestras)")
Limitaciones de BODMAS
Escala reducida: con 134.435 muestras totales, BODMAS es mucho mas pequeno que EMBER o SOREL. Puede ser insuficiente para deep learning.
Periodo corto: 8 meses de datos no capturan ciclos largos de evolución de malware.
Solo PE: misma limitación que los anteriores.
Otros datasets relevantes
MalwareBazaar (abuse.ch)
MalwareBazaar no es un dataset ML estático sino un repositorio vivo de muestras de malware etiquetadas por la comunidad. Su valor para ML:
- Muestras etiquetadas por familia (tags comunitarios)
- API pública para descarga de metadatos
- Actualizaciones diarias con malware reciente
- Multiples formatos (PE, ELF, documentos, scripts)
import requests
# Consultar muestras recientes etiquetadas
response = requests.post(
"https://mb-api.abuse.ch/api/v1/",
data={
"query": "get_recent",
"selector": "time"
}
)
samples = response.json().get("data", [])
for sample in samples[:5]:
print(f"SHA256: {sample['sha256_hash']}")
print(f" Family: {sample.get('signature', 'unknown')}")
print(f" Tags: {sample.get('tags', [])}")
print(f" First seen: {sample['first_seen']}")
print()
MalwareBazaar es ideal como fuente de datos para retraining continuo, pero no como dataset de evaluación porque sus etiquetas no están curadas para ML.
VirusShare
VirusShare es uno de los repositorios más grandes de malware, con mas de 50 millones de muestras. Requiere registro para acceso.
Para ML: VirusShare es útil como fuente de binarios para feature extraction propia, pero no incluye etiquetas ML ni features precomputadas. Necesitas etiquetar las muestras tú mismo (usando VirusTotal, YARA rules, o sandboxing).
Malimg Dataset
Malimg es un dataset pequeño (9.339 muestras) pero con un enfoque diferente: las muestras de malware están representadas como imágenes (binary visualization). Cada binario se convierte en una imagen en escala de grises donde cada byte es un pixel.
Es el dataset de referencia para investigación en detección de malware con CNN sobre imágenes binarias.
# Malimg: 25 familias de malware como imagenes
# Estructura de carpetas:
# malimg/
# Adialer.C/ (125 imagenes)
# Agent.FYI/ (116 imagenes)
# Allaple.A/ (2,949 imagenes)
# ...
# Yuner.A/ (800 imagenes)
from torchvision import datasets, transforms
transform = transforms.Compose([
transforms.Resize((256, 256)),
transforms.Grayscale(num_output_channels=1),
transforms.ToTensor(),
])
dataset = datasets.ImageFolder("malimg/", transform=transform)
print(f"Total imagenes: {len(dataset)}")
print(f"Familias: {len(dataset.classes)}")
Microsoft Malware Classification Challenge (BIG 2015)
Dataset del concurso de Kaggle de 2015 con 21.741 muestras de 9 familias de malware. Incluye tanto representación binaria como desensamblado ASM. Sigue siendo relevante como benchmark de clasificación multi-clase.
Métricas de evaluación para malware
No todas las métricas son iguales para detección de malware. Accuracy es casi inutil en este contexto.
Métricas que importan
AUC-ROC (Area Under the Receiver Operating Characteristic Curve): la métrica principal. Mide la capacidad del modelo de separar clases a cualquier umbral. Un AUC de 0.99 es bueno; un AUC de 0.999 es excelente; un AUC de 0.9999 es estado del arte.
FPR@1%FNR (False Positive Rate at 1% False Negative Rate): la métrica operativa más importante. En producción, quieres detectar al menos el 99% del malware (FNR = 1%). La pregunta es: ¿cuántos falsos positivos generas para alcanzar ese recall? Un FPR del 1% significa una alerta falsa por cada 100 ficheros benignos escaneados.
Precision-Recall AUC: más informativa que ROC-AUC cuando los datos están desbalanceados (lo cual es siempre en malware real: la mayoría de ficheros son benignos).
Detection Rate @ FPR=0.1%: para productos antivirus, el FPR aceptable es muy bajo (0.1% o menos). ¿Qué porcentaje de malware detectas con esa restricción?
Métricas que NO usar como indicador principal
Accuracy: con 99% de ficheros benignos, un modelo que siempre dice "benigno" tiene 99% de accuracy. Inútil.
F1 score: asume que precision y recall son igualmente importantes. En malware, recall (detectar todo el malware) suele ser más importante que precision (no generar falsos positivos).
Implementar metricas correctas
from sklearn.metrics import (
roc_auc_score, precision_recall_curve, auc,
confusion_matrix
)
import numpy as np
def malware_evaluation_report(y_true, y_scores, thresholds=None):
"""Reporte completo de evaluacion para malware."""
# AUC-ROC
roc_auc = roc_auc_score(y_true, y_scores)
# PR-AUC
precision, recall, _ = precision_recall_curve(y_true, y_scores)
pr_auc = auc(recall, precision)
# FPR @ 1% FNR
fpr_at_1pct_fnr = _fpr_at_target_fnr(y_true, y_scores, 0.01)
# Detection rate @ 0.1% FPR
dr_at_01pct_fpr = _detection_rate_at_target_fpr(
y_true, y_scores, 0.001)
# Detection rate @ 1% FPR
dr_at_1pct_fpr = _detection_rate_at_target_fpr(
y_true, y_scores, 0.01)
report = {
"roc_auc": roc_auc,
"pr_auc": pr_auc,
"fpr_at_1pct_fnr": fpr_at_1pct_fnr,
"detection_rate_at_01pct_fpr": dr_at_01pct_fpr,
"detection_rate_at_1pct_fpr": dr_at_1pct_fpr,
}
# Confusion matrix a threshold 0.5
y_pred = (y_scores > 0.5).astype(int)
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
report["confusion_matrix"] = {
"true_negatives": int(tn),
"false_positives": int(fp),
"false_negatives": int(fn),
"true_positives": int(tp)
}
return report
def _fpr_at_target_fnr(y_true, y_scores, target_fnr):
"""FPR cuando FNR esta en el objetivo."""
thresholds = np.sort(np.unique(y_scores))[::-1]
for t in thresholds:
y_pred = (y_scores >= t).astype(int)
fn = np.sum((y_pred == 0) & (y_true == 1))
fp = np.sum((y_pred == 1) & (y_true == 0))
tp = np.sum((y_pred == 1) & (y_true == 1))
tn = np.sum((y_pred == 0) & (y_true == 0))
fnr = fn / max(fn + tp, 1)
fpr = fp / max(fp + tn, 1)
if fnr <= target_fnr:
return fpr
return 1.0
def _detection_rate_at_target_fpr(y_true, y_scores, target_fpr):
"""Detection rate (recall) al FPR objetivo."""
thresholds = np.sort(np.unique(y_scores))[::-1]
for t in thresholds:
y_pred = (y_scores >= t).astype(int)
fp = np.sum((y_pred == 1) & (y_true == 0))
tp = np.sum((y_pred == 1) & (y_true == 1))
tn = np.sum((y_pred == 0) & (y_true == 0))
fn = np.sum((y_pred == 0) & (y_true == 1))
fpr = fp / max(fp + tn, 1)
if fpr <= target_fpr:
return tp / max(tp + fn, 1)
return 0.0
Comparativa de datasets
| Dataset | Muestras | Features | Binarios | Temporal | Familias | Uso recomendado |
|---|---|---|---|---|---|---|
| EMBER 2018 | 1M | 2.381 | No | No | No | Baseline ML, investigacion academica |
| SOREL-20M | 20M | ~2.400 | Opcional | Parcial | Si | Deep learning, escala industrial |
| BODMAS | 134K | ~500 | No | Si | Si | Concept drift, evaluacion temporal |
| Malimg | 9.3K | Imagen | No | No | 25 | CNN sobre imagenes binarias |
| BIG 2015 | 21.7K | Binario+ASM | Si | No | 9 | Clasificacion multi-clase |
| MalwareBazaar | Continuo | Metadatos | Si | Si | Si | Retraining, datos frescos |
| VirusShare | 50M+ | Ninguna | Si | Parcial | No | Feature extraction propia |
Errores comunes con datasets de malware
Random split en lugar de temporal split
El error más grave y más común. Si mezclas muestras de diferentes fechas aleatoriamente en train y test, el modelo puede aprender patrones temporales (compiladores populares en un periodo, cambios en APIs de Windows) que inflan artificialmente las métricas.
# MAL: random split
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
) # No hacer esto
# BIEN: temporal split
train_mask = df["collection_date"] < cutoff_date
test_mask = df["collection_date"] >= cutoff_date
X_train = df[train_mask][feature_cols]
X_test = df[test_mask][feature_cols]
Evaluar solo en un dataset
Un modelo que alcanza 0.9995 AUC en EMBER puede tener 0.97 AUC en BODMAS o en datos propios. Siempre evalúa en al menos dos datasets para verificar generalización.
Ignorar el desbalanceo de producción
En producción, la proporción de malware es del 1-5% del tráfico total. Los datasets de investigación suelen estar balanceados 50/50. Un modelo entrenado con datos balanceados y evaluado con datos balanceados mostrará métricas diferentes a las reales en producción.
# Simular desbalanceo de produccion
from sklearn.utils import resample
# Dataset original: 50/50
# Produccion real: 2% malware, 98% benigno
benign_samples = X_test[y_test == 0]
malware_samples = X_test[y_test == 1]
# Resamplear para simular produccion (2% malware)
n_total = 10000
n_malware = int(n_total * 0.02) # 200
n_benign = n_total - n_malware # 9800
X_prod = pd.concat([
resample(benign_samples, n_samples=n_benign, random_state=42),
resample(malware_samples, n_samples=n_malware, random_state=42)
])
y_prod = np.array([0] * n_benign + [1] * n_malware)
# Evaluar con proporcion realista
report_prod = malware_evaluation_report(y_prod, model.predict_proba(X_prod)[:, 1])
print(f"AUC (produccion simulada): {report_prod['roc_auc']:.6f}")
Confundir detección con clasificación
Detección (benigno vs malicioso) y clasificación (identificar la familia) son problemas diferentes con requisitos diferentes. Un modelo excelente para detección puede ser mediocre para clasificación multi-clase, y viceversa. Usa las métricas adecuadas para cada problema.
Construir tu propio dataset
Para investigación aplicada o producción, los datasets públicos son un punto de partida pero no un destino. Tu propio dataset, construido con datos relevantes para tu entorno, superará a cualquier dataset genérico.
Pipeline de construcción
class MalwareDatasetBuilder:
"""Pipeline para construir datasets de malware propios."""
def __init__(self, feature_extractor, label_source):
self.feature_extractor = feature_extractor
self.label_source = label_source
self.samples = []
def add_from_malwarebazaar(self, days: int = 30,
max_samples: int = 10000):
"""Anadir muestras recientes de MalwareBazaar."""
# Solo metadatos y features, nunca binarios
recent = self._fetch_malwarebazaar_metadata(days)
for sample in recent[:max_samples]:
features = self.feature_extractor.extract(sample)
label = 1 # MalwareBazaar solo tiene malware
self.samples.append({
"hash": sample["sha256"],
"features": features,
"label": label,
"source": "malwarebazaar",
"date": sample["first_seen"],
"family": sample.get("signature", "unknown")
})
def add_benign_samples(self, directory: str,
max_samples: int = 10000):
"""Anadir muestras benignas de software legitimo."""
# Fuentes de binarios benignos:
# - Software instalado en el sistema
# - Repositorios de software verificado
# - Windows System32 / Program Files
pass
def export(self, path: str, format: str = "parquet"):
"""Exportar dataset con metadatos de version."""
df = pd.DataFrame(self.samples)
metadata = {
"version": datetime.utcnow().strftime("v%Y.%m.%d"),
"total_samples": len(df),
"malicious": int((df["label"] == 1).sum()),
"benign": int((df["label"] == 0).sum()),
"sources": df["source"].unique().tolist(),
"date_range": [
df["date"].min(),
df["date"].max()
]
}
df.to_parquet(f"{path}/features.parquet")
with open(f"{path}/metadata.json", "w") as f:
json.dump(metadata, f, indent=2, default=str)
Recursos adicionales
Papers de referencia:
- Anderson & Roth (2018): "EMBER: An Open Dataset for Training Static PE Malware Machine Learning Models"
- Harang & Rudd (2020): "SOREL-20M: A Large Scale Benchmark Dataset for Malicious PE Detection"
- Yang et al. (2021): "BODMAS: An Open Dataset for Learning based Temporal Analysis of PE Malware"
- Raff et al. (2018): "Malware Detection by Eating a Whole EXE" (binary input sin features)
Repositorios:
- EMBER: github.com/elastic/ember
- SOREL: github.com/sophos/SOREL-20M
- BODMAS: github.com/EvolvingML/BODMAS
- MalwareBazaar: bazaar.abuse.ch
- VirusShare: virusshare.com (requiere registro)
Leaderboards y competiciones:
- Machine Learning Static Evasion Competition (MLSEC): competición anual de evasión adversarial contra modelos de malware
- Kaggle Microsoft Malware Classification (BIG 2015): clasificación multi-clase, resultados históricos
- CAMLIS Conference: Machine Learning in Security, con tracks de benchmarking
Elegir el dataset correcto es la primera decisión que determina si tu investigación será relevante o quedará en un ejercicio académico desconectado de la realidad operativa. Usa datasets públicos para baseline y validación, construye tu propio dataset para producción, y mide siempre con las métricas que importan en tu contexto operativo.
Preguntas frecuentes
Artículos relacionados
Este contenido tiene fines exclusivamente educativos y de investigación en ciberseguridad defensiva. No se proporcionan binarios maliciosos ni payloads ejecutables. El uso indebido de esta información es responsabilidad exclusiva del usuario. Leer disclaimer completo.