Quienes trabajan en la industria saben que una de las mejores técnicas para atrapar el malware mediante el aprendizaje automático (ML) sólo es posible con la informática distribuida. Antes de mostrarles las matemáticas de por qué requiere la computación distribuida, déjenme decirles qué es esta técnica y por qué es la mejor.
La mejor técnica en el negocio
Esta técnica, que será nombrada en un momento, es una de las mejores extracciones de características técnicas porque
- Funciona con todo tipo de muestras
- No depende de características hechas a mano, es automático.
Si estás familiarizado con el ML, te preguntarás, ¿por qué no confiar en los rasgos hechos a mano marca la diferencia?
La detección de malware es un dominio de adversarios, donde los hackers encuentran constantemente maneras de evadir los detectores de malware. Pueden «rellenar» el malware con características benignas, ocultar el malware dentro de archivos benignos («troyanos») y tener una miríada de otras formas de evitar ser detectados. En particular, las características diseñadas a mano son demasiado fáciles de diseñar en forma inversa para que los hackers puedan evadirlas.
Entonces, ¿qué es esta técnica? Es… caracterizando las muestras a través de N-gramas de su secuencia de código de bytes. Esta técnica es más o menos la columna vertebral estándar de la industria para un detector de malware ML. Antes de mostrarte cómo implementarlo en código, vamos a entender cómo funciona y mirar las matemáticas para ver por qué necesitas la informática distribuida para ejecutarlo.
Cómo funciona
Tome un archivo de muestra, digamos el instalador de python 3.7.7, disponible en https://www.python.org/ftp/python/3.7.7/python-3.7.7-amd64.exe
Podemos usar la herramienta xxd en bash para echar un vistazo a los primeros bytes de este archivo
xxd –p pitón–3.7.7–amd64.exe | cabeza –n 1 |
La salida es una secuencia truncada de bytes:
4d5a90000300000004000000ffff0000b8000000000000004000000000000000 |
Hay muchos más bytes de donde vinieron esos. El archivo es de aproximadamente 25.5mb. ¿Puedes decir cuántos bytes son?
Hagamos un cálculo rápido. 1mb es 1024 kb y 1kb es 1024 bytes, por lo que el número de bytes es aproximadamente
25.5 x 1024 x 1024 = 26,738,688 |
En otras palabras, un archivo típico es una secuencia de unos 27 millones de bytes. La técnica requiere «N-Gramas» en esta secuencia de bytes, así que entendamos lo que eso significa.
En términos generales, un «N-Gram» es una secuencia de «N» gramos consecutivos. La «N» es un marcador de posición para un número. Por ejemplo, hay «1-Gramos», «2-Gramos», «3-Gramos», y así sucesivamente.
El «Gram» en nuestro contexto es un byte. Así que en nuestro archivo de muestra,
– El 1–Gramos son “4d”, “5a”, “90”, “00”, “00”,... – El 2–Gramos son “4d 5a”, “5a 90”, “90 00”, “00 00”,... – El 3–Gramos son “4d 5a 90”, “5a 90 00”,... – etc. |
Estos N-Gramas contienen en ellos información estadística local sobre la muestra que se está examinando. Por esa razón los clasificadores ML son capaces de aprovechar estos N-Grams para obtener predicciones precisas sobre si un archivo es malicioso o benigno, ¡incluso antes de que se ejecute!
El desafío computacional
Ahora, para introducir los N-Gramas en un clasificador, hay que seguir varios pasos, como seleccionar el N óptimo en N-Gramas, seleccionar los N-Gramas más significativos estadísticamente y producir vectores de características para las muestras a partir de los recuentos de estos «mejores» N-Gramas seleccionados. Para nuestra discusión, el problema más importante es contar los N-Gramas en todos los archivos. Este problema es un Everest computacional.
Hagamos las cuentas a un nivel alto. Queremos un diccionario de cuentas para cada N-Gram sobre todos los archivos. Una empresa de ciberseguridad tendrá literalmente millones de muestras en su conjunto de datos. Cada muestra es típicamente varios millones de bytes. Esto significa que tendrá varios millones de N-Grams. Dado que hay 256 bytes, hay 256^N tipos de N-Grams. Esto significa que el diccionario tendrá hasta 256^N claves. Para tener una idea de la magnitud, note que
– Para N=2: 256^N = 65.536,
– Para N=3: 256^N = ~16,7 millones,
– Para N=4: 256^N = ~4 mil millones
Los requisitos de tiempo y espacio son gigantescos. Afortunadamente, es posible (y necesario) distribuir la tarea, que es donde el Hadoop y Spark entran en juego.
Tómese un momento para pensar en este problema en términos de Map-Reduce. Cuando esté listo, siga leyendo.
Solución de reducción de mapas
En un paradigma de Mapa-Reducción, nuestros mapeadores tomarán grupos de archivos, y para cada archivo, producirán un diccionario de recuentos de N-Gram. Nuestros reductores añadirán estos diccionarios. Nuestro resultado final será la suma de todos estos diccionarios, es decir, todos los recuentos de N-Gram. Así que ahora veamos cómo codificar realmente este problema en PySpark.
Implementación de PySpark
Para el resto, el código y el conjunto de datos están disponibles en https://github.com/hadoopinrealworld/how-to-catch-malware-using-spark. Tenga en cuenta que el conjunto de datos contiene muestras vivas, por lo que sólo debe ejecutarse en un entorno seguro, como una máquina virtual (VM).
- Empieza por clonar los datos:
!git clon https://github.com/emmanueltsukerman/how-to-catch-malware-using-spark.git |
- Descomprimir y crear directorios para los datos. La contraseña es «infectada». De nuevo, sólo hazlo en un entorno seguro o salta las muestras maliciosas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
!7z e «/contenido/cómo atrapar el malware usando Spark/muestras PE Dataset/Benign PE Samples 1.7z» –o«/contento/beneficio» !7z e «/contenido/cómo atrapar el malware usando Spark/muestras PE Dataset/Benign PE Samples 2.7z» –o«/contento/beneficio» !7z e «/contenido/cómo atrapar el malware usando Spark/muestras PE Dataset/Benign PE Samples 3.7z» –o«/contento/beneficio» !7z e «/contenido/cómo atrapar el malware usando Spark/muestras PE Dataset/Benign PE Samples 4.7z» –o«/contento/beneficio» !7z e «/contenido/cómo atrapar el malware usando Spark/muestras PE Dataset/Benign PE Samples 5.7z» –o«/contento/beneficio» !7z e «/contenido/cómo atrapar el malware usando Spark/muestras PE Dataset/Benign PE Samples 6.7z» –o«/contento/beneficio» # Usar la contraseña estándar de la industria para los archivos zip con malware !7z e «/contenido/cómo atrapar el malware usando Spark/muestras de PE Dataset/Muestras de PE Malicioso 1.7z» –o«/contento/malicioso» –p«infectado !7z e «/contenido/cómo atrapar el malware usando Spark/muestras de PE Dataset/Muestras de PE Malicioso 2.7z» –o«/contento/malicioso» –p«infectado !rm –r «/contento/benigno/muestras de PE benignas 1» !rm –r «/contento/benigno/muestras de PE 2» !rm –r «/contento/benigno/muestras de PE benignas 3» !rm –r «/contento/benigno/muestras de PE benignas 4» !rm –r «/contento/benigno/muestras de PE benignas 5» !rm –r «/contento/benigno/muestras de PE benignas 6» !rm –r «/contento/benigno/malicioso Muestras de PE 1» !rm –r «/contento/benigno/malicioso Muestras de PE 2» |
- Instala PySpark si no lo tienes ya.
!aptos–consigue instalar openjdk–8–jdk–sin cabeza importación os os.environ[[«JAVA_HOME»] = «/usr/lib/jvm/java-8-openjdk-amd64» !pip instalar pyspark |
- Inicie su sesión de Spark
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
de pyspark.sql importación SparkSession # Construye la SparkSession Spark = SparkSession.constructor .maestro(«local») .appName(«Detector de Malware») .config(«Spark.memoria.de.ejecución», «1gb») .getOrCreate() sc = Spark.sparkContext |
- Vamos a crear una tubería para contar los N-Grams y luego los caracterizaremos.
Terminaremos con los más frecuentes NUM_NGRAMS=100
N-grams, que también son generalmente los más significativos estadísticamente. A continuación, entrenaremos un clasificador utilizando estos como características.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
de pyspark.ml.artículo de fondo importación NGram, CountVectorizer, VectorAssembler de pyspark.ml importación Oleoducto de pyspark.ml.clasificación importación LogisticRegression NUM_NGRAMS = 100 def build_ngrams(inputCol=«byte_contento», N=2): ngrams = [[ NGram(n=N, inputCol=«byte_contento», outputCol=«{0}_gramos».formato(N)) ] vectorizadores = [[ CountVectorizer(inputCol=«{0}_gramos».formato(N), outputCol=«{0}_cuentas».formato(N), vocabSize = NUM_NGRAMS) ] ensamblador = [[VectorAssembler( inputCols=[[«{0}_cuentas».formato(N)], outputCol=«características» )] lr = [[LogisticRegression(maxIter=100 )] volver Oleoducto(etapas=ngrams + vectorizadores + ensamblador+lr) |
- Leído en los archivos binarios en RDDs:
benignos_rdd = sc.binaryFiles(«benigno») malicioso_rdd = sc.binaryFiles(«malicioso») |
Echemos un vistazo a lo que tenemos hasta ahora:
benignos_rdd.primero() (‘file:/content/benign/ImagingDevices.exe’, b‘MZx90x00x03x00x00x00x04x00x00x00xff...) |
Está viendo el nombre de la muestra y su secuencia de bytes.
- Convertiremos estos RDD en marcos de datos para facilitar su uso. También limitaremos el número de bytes que tomamos de cada muestra a
BYTES_UPPER_LIMIT = 5000
para hacer el cálculo más fácil para este tutorial.
de colecciones importación namedtuple BYTES_UPPER_LIMIT = 5000 Muestra = namedtuple(«Muestra», [[«nombre_de_fichero», «byte_contento»]) benigno_df = benignos_rdd.mapa(lambda rec: Muestra(rec[[0], [[str(x) para x en bytes(rec[[1])][[0:BYTES_UPPER_LIMIT])).toDF() malicioso_df = malicioso_rdd.mapa(lambda rec: Muestra(rec[[0], [[str(x) para x en bytes(rec[[1])][[0:BYTES_UPPER_LIMIT])).toDF() |
- Etiquetar las muestras benignas con 0 y las malignas con 1
de pyspark.sql.funciones importación iluminado benigno_df = benigno_df.conColumna(«etiqueta»,iluminado(0)) malicioso_df = malicioso_df.conColumna(«etiqueta»,iluminado(1)) |
Los marcos de datos ahora se ven así:
benigno_df.mostrar(5) malicioso_df.mostrar(5) |
- Combine los marcos de datos en uno solo, ya que vamos a ejecutar la tubería en todos los archivos a la vez.
df = benigno_df.unionAll(malicioso_df) |
- Crear una división de pruebas de entrenamiento
df_train, df_test = df.randomSplit([[0.8,0.2]) |
- Encajar la tubería en los datos.
trained_vectorizer_pipeline = build_ngrams().encajar(df) (este línea debería ser borrado) trained_vectorizer_pipeline = build_ngrams().encajar(df_train) |
Nótese que en el paso 5, hemos especificado en nuestro CountVectorizer que construyamos un vocabulario que sólo considere los NUM_NGRAMS superiores = 100 N-Gramos ordenados por frecuencia de término a través del corpus. Se pueden utilizar implementaciones más avanzadas, como la extracción de características basadas en información mutua o la selección de características de Bosque al azar. Sin embargo, a pesar de la aparente simplicidad del enfoque de la frecuencia, se ha demostrado que es el método más exitoso para seleccionar N-Gramas, tanto en la literatura (ver, por ejemplo «Una investigación de las características del Byte N-Gram para el malware Clasificación»(Raff et al.) y en la industria.
- Caracterizar y clasificar el conjunto de datos de las pruebas.
df_pred = trained_vectorizer_pipeline.Transformar(df_test) |
Vean que hemos logrado que nuestras muestras
- Por último, podemos calcular la precisión de nuestro clasificador en los datos de las pruebas
exactitud = df_pred.Filtro(df_pred.etiqueta == df_pred.predicción).cuenta() / flotación(df_pred.cuenta()) |
Nuestra precisión es
que es espectacular para un conjunto de datos de este tamaño!
Hemos logrado perfeccionar nuestras muestras y entrenar un prototipo de clasificador que podrá decirnos cuándo un nuevo archivo es un malware, protegiéndonos a nosotros y a nuestros datos de cualquier daño.