Escanear archivos y URLs con la API de VirusTotal

Escanear archivos y URLs con la API de VirusTotal

VirusTotal es un servicio en la nube para analizar archivos, URLs y direcciones de IP (IPv4) en busca de algún tipo de malware. Para ello se vale, actualmente, de más de 60 motores de antivirus.

Provee una API pública que permite, a través de peticiones HTTP y con ciertas limitaciones, enviar y/u obtener los resultados de análisis para un determinado archivo, URL o dirección de IP. Por otro lado existe una API privada para fines comerciales que excede el espectro de este artículo.

Para comenzar, lo primordial es registrarnos en virustotal.com y dirigirnos a la sección «My API Key», en donde se mostrará un código alfanumérico de 64 bits.

API pública de VirusTotal

Una vez obtenida la clave, utilizando requests podemos fácilmente comenzar a operar con la API. Sin embargo, para no reinventar la rueda, vamos a utilizar el paquete virustotal-api que provee una API de más alto nivel y pythonica. Lo instalamos vía:

pip install virustotal-api

O bien descargando el código de fuente y ejecutando:

python setup.py install

Hecho esto, procedemos a importar la clase PublicApi y a crear una instancia de ella pasándole como argumento la clave que obtuvimos en el paso anterior.

from virus_total_apis import PublicApi

API_KEY = "[colocar-clave-de-64-bits]"

api = PublicApi(API_KEY)

VirusTotal contiene una enorme base de datos de escaneos de archivos. Desde la API podemos obtener el resultado de alguno de ellos, o bien enviar un nuevo archivo para ser analizado, de tal modo que será puesto en una cola y al cabo de unos minutos sus resultados estarán disponibles públicamente.

Para obtener el resultado del análisis de un archivo, empleamos la función PublicApi.get_file_report.

from hashlib import md5

from virus_total_apis import PublicApi

API_KEY = "[colocar-clave-de-64-bits]"

api = PublicApi(API_KEY)

with open("Dbgview.exe", "rb") as f:
    file_hash = md5(f.read()).hexdigest()

response = api.get_file_report(file_hash)

if response["response_code"] == 200:
    if response["results"]["positives"] > 0:
        print("Archivo malicioso.")
    else:
        print("Archivo seguro.")
else:
    print("No ha podido obtenerse el análisis del archivo.")

La base de datos de VirusTotal trabaja con el algoritmo MD5 para identificar a los archivos analizados. Por esto leemos el contenido y utilizamos el módulo hashlib para reducirlo a una cadena de 32 bits. Luego, le enviamos la cadena obtenida a la API y ésta nos retorna el resultado del análisis.

response es un diccionario que contiene variada información. En particular, response["results"] alberga los datos sobre el análisis. Para mayor comodidad podemos guardar la respuesta en un archivo en formato JSON.

import json

# ...

response = api.get_file_report(file_hash)
with open("response.json", "w") as f:
    json.dump(response, f, indent=4)

Lo cual se ve más o menos así (se redujo considerablemente la clave «scans» para mejorar la legibilidad):

{
    "results": {
        "scans": {
            "Microsoft": {
                "result": null,
                "detected": false,
                "version": "1.1.13804.0",
                "update": "20170608"
            },
            "BitDefender": {
                "result": null,
                "detected": false,
                "version": "7.2",
                "update": "20170608"
            },
            "TrendMicro": {
                "result": null,
                "detected": false,
                "version": "9.740.0.1012",
                "update": "20170608"
            },
            ...
        },
        "resource": "baaca87fe5ac99e0f1442b54e03056f4",
        "verbose_msg": "Scan finished, information embedded",
        "total": 62,
        "response_code": 1,
        "md5": "baaca87fe5ac99e0f1442b54e03056f4",
        "permalink": "https://www.virustotal.com/file/1244bd02a203bec1b1e0ab85ed8ed83501ec4d17e613f1934951abeb7956abb0/analysis/1496956924/",
        "sha256": "1244bd02a203bec1b1e0ab85ed8ed83501ec4d17e613f1934951abeb7956abb0",
        "positives": 0,
        "scan_id": "1244bd02a203bec1b1e0ab85ed8ed83501ec4d17e613f1934951abeb7956abb0-1496956924",
        "sha1": "77a8060d1629183e457fdbc2f34143a5070bdd46",
        "scan_date": "2017-06-08 21:22:04"
    },
    "response_code": 200
}

Como se observa, en response["results"]["positives"] tenemos la cantidad de motores de antivirus que retornaron positivo (esto es, «infectado») para el archivo enviado.

En response["results"]["scans"] se nos presenta una lista de alrededor de los 60 programas que efectuaron el análisis, con el particular valor "detected" que indica si el resultado fue, para un antivirus en particular, positivo o negativo (por ejemplo, response["results"]["scans"]["Microsoft"]["detected"]).

response["results"]["verbose_msg"] muestra el mensaje retornado por VirusTotal de acuerdo a la operación efectuada, response["results"]["scan_date"] indica la fecha en la que se realizó el escaneo, response["results"]["permalink"] contiene una URL a la página de VirusTotal con los detalles del análisis, entre otras.

Para solicitar un análisis que no se encuentre en la base de datos o bien quiera ser escaneado nuevamente, le pasamos la ruta del archivo a la función PublicApi.scan_file.

response = api.scan_file("Dbgview.exe")

Si todo marcha bien, response es un diccionario más o menos así:

{
    "response_code": 200,
    "results": {
        "md5": "baaca87fe5ac99e0f1442b54e03056f4",
        "response_code": 1,
        "sha1": "77a8060d1629183e457fdbc2f34143a5070bdd46",
        "scan_id": "1244bd02a203bec1b1e0ab85ed8ed83501ec4d17e613f1934951abeb7956abb0-1497900207",
        "resource": "1244bd02a203bec1b1e0ab85ed8ed83501ec4d17e613f1934951abeb7956abb0",
        "verbose_msg": "Scan request successfully queued, come back later for the report",
        "sha256": "1244bd02a203bec1b1e0ab85ed8ed83501ec4d17e613f1934951abeb7956abb0",
        "permalink": "https://www.virustotal.com/file/1244bd02a203bec1b1e0ab85ed8ed83501ec4d17e613f1934951abeb7956abb0/analysis/1497900207/"
    }
}

Luego de unos minutos podremos retornar el análisis vía PublicApi.get_file_report().

Para analizar URLs, el procedimiento es similar:

# Obtener los resultados de un análisis.
response = api.get_url_report("https://www.recursospython.com/")

# Poner en cola una URL para ser escaneada.
response = api.scan_url("https://www.recursospython.com/")

La API también provee funciones de lectura para dominios de internet y direcciones de IP vía:

# Obtener análisis de un dominio.
response = api.get_domain_report("google.com")

# Obtener análisis de una dirección de IPv4.
response = api.get_ip_report("xxx.xxx.xxx.xxx")

Por último, la función PublicApi.put_comments permite añadir un comentario público a un archivo o dirección de URL.

# Comentar en un archivo identificado por MD5.
with open("Dbgview.exe", "rb") as f:
    api.put_comments(md5(f.read()).hexdigest(), "Excelente herramienta.")

# Comentar en una URL.
api.put_comments("http://google.com/", "Otro comentario.")

Los comentarios se visualizan en la pestaña correspondiente en la página de VirusTotal, aquella indicada en la clave response["results"]["permalink"].

Nótese que la API pública del servicio permite, como máximo, 4 peticiones por minuto.

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

4 comentarios.

    • Recursos Python says:

      Hola, Ludmila. ¿Completaste la variable API_KEY con la clave que te otorga VirusTotal? ¿Cuál es el código que te está dando error?

      Saludos

  1. Guillermo Brown says:

    Buenos días, siempre que intento consumir algun recurso de la API de VT me sale este mensaje de error.

    {‘error’: ‘You tried to perform calls to functions for which you require a Private API key.’, ‘response_code’: 403}

    ¿Actualmente para poder consumirla se debe silicitar una private API key?

    • Recursos Python says:

      Hola Guillermo, acabo de chequearlo y todo sigue funcionando correctamente con la API pública. Te invito a que pases por el foro y nos muestres cuál es el código que está arrojando ese error.

Deja una respuesta