Вы получили подозрительный .exe-файл — возможно, из письма, скачали с малоизвестного сайта или достали из архива старого проекта. Руки не доходят до загрузки на сайт, а проверить нужно. Или у вас десятки файлов и нужна автоматизация. В обоих случаях помогает VirusTotal API. Разберёмся, как с ним работать без лишней теории — только то, что нужно для результата.
- Что на самом деле даёт VirusTotal API
- Получаем API-ключ и готовим окружение
- Проверка по хешу — самый быстрый способ
- Загрузка файла на анализ
- Получаем результат анализа
- Собираем метаданные из отчёта
- Подробные метаданные PE-файла
- Собираем всё в единый скрипт
- Какие данные действительно важны, а какие — шум
- Сценарии использования
- Частые ошибки при работе с API
- Как лучше организовать хранение и обработку результатов
- Обработка ошибок и отказоустойчивость
- Что в итоге и с чего начать
Что на самом деле даёт VirusTotal API
VirusTotal — это сервис, который прогоняет файл или URL через несколько десятков антивирусных движков и собирает результаты. Через веб-интерфейс вы просто кидаете файл и смотрите отчёт. Через API вы делаете то же самое программно: отправляете файл или хеш, получаете JSON с результатами и можете дальше их обрабатывать как угодно.
Что именно вы получаете в ответе API:
- Результаты проверки каждым антивирусным движком (обнаружен / не обнаружен, название угрозы).
- Метаданные файла: размер, тип, хеши (MD5, SHA-1, SHA-256).
- Информация о структуре PE-файла (для .exe): секции, импортируемые библиотеки, точки входа, таймстампы.
- Результаты статического анализа: строки, сигнатуры, поведенческие индикаторы.
- Данные о сертификатах, если файл подписан.
- История: когда файл впервые появился в базе, сколько раз загружался, какие мнения у сообщества.
API бесплатный, но с ограничениями. Бесплатный ключ позволяет делать до 500 запросов в минуту и до 30 000 в сутки. Для личных задач и небольших проектов этого хватает с головой. Если нужно больше — есть платные планы с повышенными лимитами и дополнительными возможностями.
Получаем API-ключ и готовим окружение
Сначала регистрируемся на virustotal.com. Можно через Google, GitHub или почту. После входа заходим в профиль — там будет ваш API-ключ. Скопируйте его и храните безопасно: это ваш идентификатор, и по нему считаются все ваши запросы.
Для работы с API подойдёт любой язык, который умеет делать HTTP-запросы. Я покажу примеры на Python — он самый удобный для таких задач. Если вы пишете на другом языке, логика будет идентичной, меняется только синтаксис.
Установите библиотеку requests, если её ещё нет:
pip install requests
Проверка по хешу — самый быстрый способ
Если вы уже знаете хеш файла (SHA-256, SHA-1 или MD5), не нужно загружать сам файл на сервер. Достаточно отправить хеш и посмотреть, есть ли этот файл в базе VirusTotal. Это экономит трафик и время.
import requests
import json
API_KEY = "ваш_ключ_здесь"
FILE_HASH = "a3b9c2d1e4f5..." # SHA-256 хеш файла
url = f"https://www.virustotal.com/api/v3/files/{FILE_HASH}"
headers = {
"x-apikey": API_KEY
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
print(json.dumps(data, indent=2))
elif response.status_code == 404:
print("Файл не найден в базе VirusTotal")
else:
print(f"Ошибка: {response.status_code}")
print(response.text)
Если хеш найден, вы получаете полный отчёт. Если нет — придётся загрузить сам файл. Это логично: если файл никто раньше не проверял, его нужно отправить на анализ.
Загрузка файла на анализ
Когда файла нет в базе или вы хотите получить свежий результат, загружаем сам .exe. Для файлов размером до 32 МБ используем простой POST-запрос:
import requests
import os
API_KEY = "ваш_ключ_здесь"
FILE_PATH = "C:/path/to/suspicious_file.exe"
url = "https://www.virustotal.com/api/v3/files"
headers = {
"x-apikey": API_KEY
}
with open(FILE_PATH, "rb") as f:
files = {"file": (os.path.basename(FILE_PATH), f)}
response = requests.post(url, headers=headers, files=files)
if response.status_code == 200:
data = response.json()
analysis_id = data["data"]["id"]
print(f"ID анализа: {analysis_id}")
else:
print(f"Ошибка загрузки: {response.status_code}")
print(response.text)
После загрузки вы получаете не результат, а ID анализа. VirusTotal нужно время, чтобы прогнать файл через все движки. Обычно это занимает от 15 секунд до пары минут. Результат запрашивается отдельно по этому ID.
Получаем результат анализа
Загрузили файл — подождите секунд 30–60, потом запрашивайте результат. Делаем GET-запрос с ID анализа:
analysis_id = "u-a3b9c2d1e4f5-1234567890"
url = f"https://www.virustotal.com/api/v3/analyses/{analysis_id}"
headers = {
"x-apikey": API_KEY
}
response = requests.get(url, headers=headers)
data = response.json()
status = data["data"]["attributes"]["status"]
print(f"Статус: {status}")
if status == "completed":
stats = data["data"]["attributes"]["stats"]
print(f"Безопасных: {stats.get('harmless', 0)}")
print(f"Подозрительных: {stats.get('suspicious', 0)}")
print(f"Вредоносных: {stats.get('malicious', 0)}")
print(f"Неопределённых: {stats.get('undetected', 0)}")
Статус может быть queued (в очереди), in-progress (анализируется) или completed (готово). Если статус не готов, подождите ещё немного и повторите запрос. В реальном коде это оборачивают в цикл с задержкой.
Собираем метаданные из отчёта
Когда анализ завершён, из того же ответа можно вытащить метаданные файла. Вот что реально полезно:
attributes = data["data"]["attributes"]
# Основные хеши
md5 = attributes.get("md5")
sha1 = attributes.get("sha1")
sha256 = attributes.get("sha256")
print(f"MD5: {md5}")
print(f"SHA-1: {sha1}")
print(f"SHA-256: {sha256}")
# Размер и тип
size = attributes.get("size")
file_type = attributes.get("type_description")
print(f"Размер: {size} байт")
print(f"Тип: {file_type}")
# Результаты по каждому движку
results = attributes.get("results", {})
for engine, result in results.items():
category = result.get("category", "unknown")
if category in ("malicious", "suspicious"):
print(f"{engine}: {result.get('result')} [{category}]")
Это базовый набор. Но есть и более глубокие метаданные, которые доступны через отдельные эндпоинты.
Подробные метаданные PE-файла
Для .exe-файлов VirusTotal извлекает информацию из PE-заголовка (Portable Executable). Это структура, по которой можно понять, что за файл перед вами, даже не запуская его.
Эти данные доступны через отдельный запрос — не в основном отчёте по анализу, а через эндпоинт файла по его SHA-256:
sha256 = "a3b9c2d1e4f5..."
url = f"https://www.virustotal.com/api/v3/files/{sha256}"
response = requests.get(url, headers=headers)
data = response.json()
attributes = data["data"]["attributes"]
# PE-метаданные
pe_info = attributes.get("pe_info", {})
if pe_info:
print("=== PE-секции ===")
for section in pe_info.get("sections", []):
print(f" {section['name']}: размер {section['size']}, энтропия {section.get('entropy', 'N/A')}")
print("\n=== Импортируемые библиотеки ===")
for entry in pe_info.get("import_list", []):
print(f" {entry['library_name']}:")
for func in entry.get("imported_functions", [])[:5]:
print(f" - {func}")
if len(entry.get("imported_functions", [])) > 5:
print(f" ... и ещё {len(entry['imported_functions']) - 5}")
# Таймстамп компиляции
compile_time = pe_info.get("compile_time")
if compile_time:
from datetime import datetime
print(f"\nДата компиляции: {datetime.fromtimestamp(compile_time)}")
# Сертификаты
signers = attributes.get("signature_info", {})
if signers:
print("\n=== Цифровая подпись ===")
for signer in signers.get("signers", []):
print(f" Подписант: {signer.get('name', 'неизвестно')}")
print(f" Сертификат действителен: {signer.get('valid from', '?')} — {signer.get('valid to', '?')}")
Энтропия секций — интересный показатель. Если секция с кодом имеет энтропию выше 7.0, это может указывать на упаковку или шифрование — малвари часто так прячутся. Энтропия 7.5–8.0 — это уже серьёзный повод присмотреться внимательнее.
Собираем всё в единый скрипт
Теперь объединим всё в один рабочий скрипт. Он принимает путь к файлу, вычисляет хеш, проверяет по базе, если нет — загружает, ждёт результат и собирает метаданные:
import requests
import hashlib
import time
import json
import os
import sys
from datetime import datetime
API_KEY = "ваш_ключ_здесь"
BASE_URL = "https://www.virustotal.com/api/v3"
HEADERS = {"x-apikey": API_KEY}
def calculate_sha256(file_path):
sha256 = hashlib.sha256()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha256.update(chunk)
return sha256.hexdigest()
def get_file_report(file_hash):
url = f"{BASE_URL}/files/{file_hash}"
response = requests.get(url, headers=HEADERS)
if response.status_code == 200:
return response.json()
return None
def upload_file(file_path):
url = f"{BASE_URL}/files"
with open(file_path, "rb") as f:
files = {"file": (os.path.basename(file_path), f)}
response = requests.post(url, headers=HEADERS, files=files)
if response.status_code == 200:
return response.json()["data"]["id"]
return None
def wait_for_analysis(analysis_id, max_wait=120):
url = f"{BASE_URL}/analyses/{analysis_id}"
start = time.time()
while time.time() - start < max_wait:
response = requests.get(url, headers=HEADERS)
if response.status_code == 200:
status = response.json()["data"]["attributes"]["status"]
if status == "completed":
return response.json()
print(f" Статус: {status}, ждём...")
time.sleep(15)
return None
def extract_metadata(report_data):
attrs = report_data["data"]["attributes"]
metadata = {
"hashes": {
"md5": attrs.get("md5"),
"sha1": attrs.get("sha1"),
"sha256": attrs.get("sha256"),
},
"size": attrs.get("size"),
"type": attrs.get("type_description"),
"detection": {},
"pe_info": {},
"signatures": [],
"first_seen": attrs.get("first_submission_date"),
"last_seen": attrs.get("last_analysis_date"),
"reputation": attrs.get("reputation"),
}
# Результаты детекции
results = attrs.get("results", {})
malicious = []
suspicious = []
for engine, result in results.items():
cat = result.get("category", "undetected")
if cat == "malicious":
malicious.append({"engine": engine, "name": result.get("result")})
elif cat == "suspicious":
suspicious.append({"engine": engine, "name": result.get("result")})
metadata["detection"] = {
"malicious_count": len(malicious),
"suspicious_count": len(suspicious),
"malicious_engines": malicious,
"suspicious_engines": suspicious,
}
# PE-информация
pe = attrs.get("pe_info", {})
if pe:
metadata["pe_info"] = {
"sections": [
{"name": s["name"], "size": s["size"], "entropy": s.get("entropy")}
for s in pe.get("sections", [])
],
"imports": [
{"library": imp["library_name"], "functions": imp.get("imported_functions", [])}
for imp in pe.get("import_list", [])
],
"compile_time": pe.get("compile_time"),
}
# Подписи
sig_info = attrs.get("signature_info", {})
if sig_info:
for signer in sig_info.get("signers", []):
metadata["signatures"].append({
"name": signer.get("name"),
"valid_from": signer.get("valid from"),
"valid_to": signer.get("valid to"),
})
return metadata
def analyze_exe(file_path):
print(f"Анализ файла: {file_path}")
file_hash = calculate_sha256(file_path)
print(f"SHA-256: {file_hash}")
# Пробуем найти по хешу
report = get_file_report(file_hash)
if report:
print("Файл найден в базе VirusTotal, используем существующий отчёт")
else:
print("Файл не найден, загружаем на анализ...")
analysis_id = upload_file(file_path)
if not analysis_id:
print("Ошибка загрузки файла")
return None
print(f"Файл загружен, ID анализа: {analysis_id}")
report = wait_for_analysis(analysis_id)
if not report:
print("Таймаут ожидания анализа")
return None
metadata = extract_metadata(report)
return metadata
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Использование: python vt_analyzer.py <путь_к_файлу>")
sys.exit(1)
result = analyze_exe(sys.argv[1])
if result:
print("\n" + "=" * 60)
print("РЕЗУЛЬТАТ")
print("=" * 60)
print(json.dumps(result, indent=2, ensure_ascii=False, default=str))
Этот скрипт уже рабочий. Запускаете его с путём к файлу, и он выдаёт структурированный JSON со всеми метаданными. Можно сохранять в базу, передать в SIEM или просто анализировать глазами.
Какие данные действительно важны, а какие — шум
Когда вы получаете полный отчёт, в нём сотни полей. Не все они одинаково полезны на практике. Вот на что смотреть в первую очередь:
| Параметр | Что показывает | Когда важен |
|---|---|---|
| Количество детекций (malicious/total) | Сколько антивирусов считают файл вредоносным | Всегда — это первичный индикатор |
| PE-секции с высокой энтропией | Упаковка, обфускация, шифрование кода | При анализе подозрительных файлов |
| Импортируемые функции | Какие API вызывает программа (сеть, реестр, файловая система) | Для понимания поведения без запуска |
| Цифровая подпись | Подписан ли файл и кем | Для проверки легитимности |
| Дата компиляции (compile_time) | Когда файл был собран | Для хронологии и выявления аномалий |
| Строки (strings) | Текстовые данные внутри файла: URL, пути, команды | Для поиска индикаторов компрометации |
| Reputation (репутация) | Социальная оценка от пользователей VT | Дополнительный ориентир |
| Last submission date | Когда файл последний раз проверялся | Чтобы понять актуальность данных |
Сценарии использования
Сценарий 1: Быстрая проверка одного файла. Вы скачали что-то и хотите понять, безопасно ли это. Вычисляете SHA-256, делаете запрос по хешу, смотрите количество детекций. Если 0/70 — скорее всего чисто. Если 15/70 — стоит разобраться, какие именно движки ругаются и на что именно.
Сценарий 2: Массовая проверка. У вас папка с 500 .exe-файлами из старого архива. Пишете скрипт, который проходит по всем файлам, вычисляет хеши, проверяет через API и сохраняет результаты в CSV или базу данных. Здесь важно соблюдать лимиты — не больше 4 запросов в минуту для бесплатного плана.
Сценарий 3: Интеграция в pipeline безопасности. Файлы поступают из почты или скачиваются из интернета, автоматически проверяются через API, результаты логируются. Если обнаружен вредоносный файл — алерт уходит аналитику. Здесь уже нужен платный API для высокой пропускной способности.
Сценарий 4: Расследование инцидента. Вы нашли подозрительный файл на машине и хотите понять, что он делает. Загружаете на VirusTotal, анализируете PE-структуру, строки, импорты. Это заменяет первичный статический анализ без запуска в песочнице.
Частые ошибки при работе с API
Не учитывать лимиты запросов. Бесплатный API — это 4 запроса в минуту. Если отправите больше, получите HTTP 429 (Too Many Requests). Решение: добавляйте time.sleep(15) между запросами или используйте очередь.
Ждать результат сразу после загрузки. После отправки файла вы получаете ID анализа, а не результат. VirusTotal нужно время. Если запросить результат через 2 секунды, получите статус queued. Нормальная практика — ждать 30–60 секунд перед первым запросом результата.
Использовать MD5 как единственный идентификатор. MD5 коллизируются, для серьёзного анализа используйте SHA-256. Он надёжнее и является основным идентификатором в VirusTotal.
Не сохранять промежуточные результаты. Если вы массово проверяете файлы и упадёте на 50-м из 500 — начинать заново обидно. Сохраняйте результаты каждого файла в локальную базу или файл по мере обработки.
Слепо доверять нулевым детекциям. Если файл не обнаружен ни одним движком, это не гарантия безопасности. Новые малвари, кастомные бэкдоры и хорошо упакованные файлы часто проходят мимо антивирусов. Смотрите на другие признаки: подозрительные импорты, высокую энтропию, отсутствие цифровой подписи.
Забывать про приватность файлов. Всё, что вы загружаете на VirusTotal, становится доступно подписчикам платного плана. Не загружайте конфиденциальные файлы — внутренние инструменты, клиентские данные, проприетарное ПО. Для таких случаев есть настройка «Do not share» в корпоративных аккаунтах.
Как лучше организовать хранение и обработку результатов
Если вы делаете больше одной проверки в неделю, заведите простую базу результатов. Минимум — JSON-файл или SQLite:
import sqlite3
import json
from datetime import datetime
def init_db(db_path="vt_results.db"):
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS scans (
sha256 TEXT PRIMARY KEY,
md5 TEXT,
file_name TEXT,
file_size INTEGER,
malicious_count INTEGER,
suspicious_count INTEGER,
first_seen TEXT,
last_scanned TEXT,
raw_report TEXT,
scanned_at TEXT
)
""")
conn.commit()
return conn
def save_scan(conn, metadata):
conn.execute("""
INSERT OR REPLACE INTO scans
(sha256, md5, file_name, file_size, malicious_count,
suspicious_count, first_seen, last_scanned, raw_report, scanned_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
metadata["hashes"]["sha256"],
metadata["hashes"]["md5"],
metadata.get("file_name", ""),
metadata["size"],
metadata["detection"]["malicious_count"],
metadata["detection"]["suspicious_count"],
metadata.get("first_seen", ""),
metadata.get("last_seen", ""),
json.dumps(metadata, default=str),
datetime.utcnow().isoformat()
))
conn.commit()
С такой базой вы можете быстро проверять: был ли уже проанализирован этот файл, когда и с каким результатом. Это экономит запросы к API и время.
Обработка ошибок и отказоустойчивость
API иногда недоступен, иногда возвращает ошибки. Хороший скрипт это обрабатывает:
def safe_request(url, headers, method="get", kwargs):
max_retries = 3
for attempt in range(max_retries):
try:
if method == "get":
response = requests.get(url, headers=headers, timeout=30)
else:
response = requests.post(url, headers=headers, timeout=60, kwargs)
if response.status_code == 429:
print("Лимит запросов, ждём 60 секунд...")
time.sleep(60)
continue
if response.status_code == 200:
return response.json()
if response.status_code == 404:
return None
print(f"Ошибка {response.status_code}: {response.text[:200]}")
time.sleep(5)
except requests.exceptions.Timeout:
print(f"Таймаут запроса, попытка {attempt + 1}/{max_retries}")
time.sleep(10)
except requests.exceptions.ConnectionError:
print("Проблема с соединением, ждём 30 секунд...")
time.sleep(30)
return None
Оборачивайте все обращения к API в подобную функцию. Сеть бывает нестабильной, а VirusTotal иногда перегружен. Лучше подождать и повторить, чем потерять результат.
Что в итоге и с чего начать
Если вам нужно проверить .exe-файл через VirusTotal API:
- Получите API-ключ на virustotal.com — это займёт 2 минуты.
- Начните с проверки по SHA-256 — это бесплатно по времени и трафику.
- Если файла нет в базе — загрузите его и подождите 30–60 секунд перед запросом результата.
- Извлекайте из отчёта то, что важно: количество детекций, PE-метаданные, импорты, сертификаты.
- Сохраняйте результаты в локальную базу, чтобы не проверять один и тот же файл дважды.
- Не забывайте про лимиты: 4 запроса в минуту для бесплатного плана.
Приведённый выше скрипт покрывает 90% реальных задач. Его можно расширить: добавить логирование в файл, интеграцию с Telegram-ботом для алертов, параллельную обработку нескольких файлов с соблюдением лимитов. Но основа — именно такая: хеш → поиск → загрузка → ожидание → извлечение метаданных. Работает надёжно и решает задачу.
