IntermediodatasetsbenchmarksEMBERSORELevaluación

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.

MalwareIntel Research··15 min lectura
Serie: AI/ML para Malware — Parte 19

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:

  1. Byte histogram (256 features): distribución de bytes del binario
  2. Byte-entropy histogram (256 features): entropía local por ventanas
  3. String information (104 features): estadísticas de strings extraídas
  4. General file information (10 features): tamaño, número de secciones, timestamps
  5. Header information (62 features): campos del PE header
  6. Section information (255 features): entropía, tamaño y propiedades de secciones
  7. Imports information (1.280 features): hashes de imports por librería
  8. 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:

  1. Temporal split correcto: entrenar con datos de agosto-diciembre 2019 y evaluar con enero-marzo 2020
  2. Medir degradación temporal: cómo se comporta un modelo entrenado con datos de hace N meses
  3. 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

DatasetMuestrasFeaturesBinariosTemporalFamiliasUso recomendado
EMBER 20181M2.381NoNoNoBaseline ML, investigacion academica
SOREL-20M20M~2.400OpcionalParcialSiDeep learning, escala industrial
BODMAS134K~500NoSiSiConcept drift, evaluacion temporal
Malimg9.3KImagenNoNo25CNN sobre imagenes binarias
BIG 201521.7KBinario+ASMSiNo9Clasificacion multi-clase
MalwareBazaarContinuoMetadatosSiSiSiRetraining, datos frescos
VirusShare50M+NingunaSiParcialNoFeature 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.