Analiza archivos en busca de virus con .NET y código abierto
Analiza con ClamAV un archivo en espera bajo el control de la aplicación antes de aceptarlo. Este programa de consola de .NET 10 envía el archivo a un demonio local y devuelve resultados diferenciados de archivo limpio, archivo infectado y error del analizador. Nunca desinfecta, modifica ni elimina el archivo de entrada.
Introducción
Solo un análisis completado con resultado de archivo limpio permite pasar al siguiente paso de procesamiento. Si falta el archivo, se alcanza un límite de tamaño, el demonio no está disponible, la respuesta tiene un formato incorrecto, se agota el tiempo de espera o se cancela la operación, el archivo debe permanecer sin aceptarse. Un resultado de archivo limpio describe el análisis del motor con su configuración y sus firmas actuales; no garantiza que el archivo sea inofensivo.
Herramientas antivirus de código abierto para .NET
ClamAV proporciona el analizador. El cliente TCP integrado de .NET puede comunicarse mediante su protocolo INSTREAM documentado, por lo que este ejemplo no necesita una biblioteca de NuGet que encapsule el analizador. Mantén el demonio en el mismo host: su interfaz TCP no tiene autenticación.
Configura ClamAV
Instala ClamAV siguiendo sus instrucciones para cada plataforma,
configura su directorio de bases de datos y actualiza las firmas con freshclam.
En clamd.conf, vincula TCPAddr a 127.0.0.1,
usa TCPSocket 3310 y establece StreamMaxLength en al menos 25 MiB.
Configura AlertExceedsMax yes para que los límites del análisis generen una alerta en lugar
de omitir contenido silenciosamente. Revisa MaxFileSize,
MaxScanSize, los límites de los archivos contenedores y la política para archivos
cifrados según tu carga de trabajo.
Inicia clamd con esa configuración mediante el administrador de servicios
de tu plataforma.
Instala el SDK de .NET 10 y luego crea la aplicación:
dotnet new console --framework net10.0 --name FileScan
cd FileScan
Implementa el análisis de archivos en C#
Reemplaza Program.cs por este programa completo. Transmite como máximo 25 MiB en
bloques de 64 KiB, limita el tamaño de la respuesta del demonio y cancela todas las operaciones de
red y de archivos al transcurrir 30 segundos o al pulsar Ctrl+C.
using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Text;
public enum Verdict { Clean, Infected, ScannerError }
public static class Program
{
public static async Task<int> Main(string[] args)
{
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(30));
Console.CancelKeyPress += (_, e) => { e.Cancel = true; deadline.Cancel(); };
if (args.Length != 1) { Console.Error.WriteLine("Pass one local file."); return 2; }
var verdict = await FileScanner.ScanAsync(args[0], 3310, deadline.Token);
Console.WriteLine(verdict);
return verdict switch { Verdict.Clean => 0, Verdict.Infected => 1, _ => 2 };
}
}
public static class FileScanner
{
private const int MaxBytes = 25 * 1024 * 1024;
public static async Task<Verdict> ScanAsync(string path, int port, CancellationToken ct)
{
try
{
await using var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
if (file.Length > MaxBytes) return Verdict.ScannerError;
using var client = new TcpClient();
await client.ConnectAsync(IPAddress.Loopback, port, ct);
using var stream = client.GetStream();
await stream.WriteAsync(Encoding.ASCII.GetBytes("zINSTREAM\0"), ct);
var buffer = new byte[64 * 1024];
var header = new byte[4];
long sent = 0;
int count;
while ((count = await file.ReadAsync(buffer, ct)) != 0)
{
sent += count;
if (sent > MaxBytes) return Verdict.ScannerError;
BinaryPrimitives.WriteUInt32BigEndian(header, (uint)count);
await stream.WriteAsync(header, ct);
await stream.WriteAsync(buffer.AsMemory(0, count), ct);
}
BinaryPrimitives.WriteUInt32BigEndian(header, 0);
await stream.WriteAsync(header, ct);
var reply = new List<byte>();
var next = new byte[1];
while (reply.Count < 4096)
{
if (await stream.ReadAsync(next, ct) != 1) return Verdict.ScannerError;
if (next[0] == 0)
{
if (await stream.ReadAsync(next, ct) != 0) return Verdict.ScannerError;
string result = Encoding.UTF8.GetString(reply.ToArray());
if (result == "stream: OK") return Verdict.Clean;
if (result.StartsWith("stream: ", StringComparison.Ordinal) &&
result.EndsWith(" FOUND", StringComparison.Ordinal) && result.Length > 14 &&
!result.Contains("Heuristics.Limits.Exceeded", StringComparison.Ordinal))
return Verdict.Infected;
return Verdict.ScannerError;
}
reply.Add(next[0]);
}
return Verdict.ScannerError;
}
catch (Exception) { return Verdict.ScannerError; }
}
}
Crea un archivo de texto sintético y analízalo desde el directorio del proyecto:
printf 'ordinary test text\n' > sample.txt
dotnet run -- sample.txt
Los códigos de salida son 0 para un archivo limpio,
1 para uno infectado y 2 para errores del
analizador. Comprueba el código de salida antes de aceptar un archivo. Una conexión rechazada debe
producir ScannerError, nunca Clean.
Mantén el análisis no destructivo
El programa abre el archivo de entrada solo para lectura. Mantén los archivos en espera en un directorio bajo el control de la aplicación e impide escrituras simultáneas; procesa los mismos bytes que se analizaron. Una API antivirus que combina el análisis con la eliminación de amenazas no es adecuada cuando la aplicación debe conservar los archivos originales.
Prueba análisis con archivos limpios, infectados y con errores
Usa un archivo sintético común para probar el caso de archivo limpio y el archivo de prueba oficial de EICAR para el caso de detección. Prueba también con un demonio no disponible y un archivo que supere el límite. Verifica que todos los archivos de entrada permanezcan intactos después de cada resultado. Un demonio privado con una base de datos de firmas exclusiva para pruebas verifica la integración, pero no demuestra la cobertura de la base de datos de malware en producción.
Buenas prácticas
Mantén actualizadas las firmas de producción, revisa los límites del motor y conserva los archivos en espera según la política de retención de tu aplicación. Los errores de análisis requieren decidir entre reintentar o rechazar el archivo; no autorizan su publicación. Excluye las credenciales y los diagnósticos del analizador de las respuestas visibles para el usuario.
Conclusión
La interfaz de transmisión de ClamAV permite una integración pequeña y no destructiva con gestión explícita de errores. Transloadit también ofrece un Virus Scan Robot para archivos en un pipeline de procesamiento.
