Antivirus: Uso de la librería LibClamAV desde C

En esta entrada vamos a ver como utilizar la librería libclamav que incluye el antivirus ClamAV, con el fin de poder usar el motor antivirus desde nuestro propio código.

Lo primero vamos a instalar el motor antivirus y las librerías de desarrollo. En entornos Debian/Ubuntu:

❯ sudo apt install clamav libclamav-dev

No vamos a instalar el daemon clamav-daemon ya que para este ejemplo no es necesario.

Comprobamos que tenemos el motor funcionando indicando que nos muestre la versión con clamscan:

❯ clamscan --version
ClamAV 0.102.4/26096/Tue Mar  2 12:59:11 2021

Por defecto nos instala un servicio clamav-freshclam.service que se encarga de actualizar la base de datos de firmas. Si necesitamos actualizar las firmas de forma manual tenemos que parar el servicio y lanzar freshclam:

❯ systemctl stop clamav-freshclam.service
❯ sudo freshclam
Tue Mar  2 19:57:40 2021 -> ClamAV update process started at Tue Mar  2 19:57:40 2021
Tue Mar  2 19:57:40 2021 -> daily database available for download (remote version: 26096)
Time: 185.4s, ETA: 0.0s [=============================>] 100.09MiB/100.09MiB      
Tue Mar  2 20:00:47 2021 -> Testing database: '/var/lib/clamav/tmp.c147d/clamav-298472be78d9d176bfd76fdbd1823f80.tmp-daily.cvd' ...
Tue Mar  2 20:00:53 2021 -> Database test passed.
Tue Mar  2 20:00:53 2021 -> daily.cvd updated (version: 26096, sigs: 3956874, f-level: 63, builder: raynman)
Tue Mar  2 20:00:53 2021 -> main database available for download (remote version: 59)
Time: 187.3s, ETA: 0.0s [=============================>] 112.40MiB/112.40MiB      
Tue Mar  2 20:04:01 2021 -> Testing database: '/var/lib/clamav/tmp.c147d/clamav-ed7c4dd929043cd69887f5fce7573ba1.tmp-main.cvd' ...
Tue Mar  2 20:04:04 2021 -> Database test passed.
Tue Mar  2 20:04:04 2021 -> main.cvd updated (version: 59, sigs: 4564902, f-level: 60, builder: sigmgr)
Tue Mar  2 20:04:04 2021 -> bytecode.cld database is up to date (version: 332, sigs: 93, f-level: 63, builder: awillia2)
Tue Mar  2 20:04:04 2021 -> !NotifyClamd: Can't connect to clamd on localhost:3310: Connection refused
Tue Mar  2 20:04:04 2021 -> !NotifyClamd: Can't connect to clamd on localhost:3310: Connection refused 

Para las pruebas vamos a usar los ficheros Eicar que contienen firmas que detectan los antivirus, pero completamente inofensivos. Descargamos algunos para las pruebas:

❯ wget https://secure.eicar.org/eicar.com.txt
❯ wget https://secure.eicar.org/eicarcom2.zip

El programa que vamos a crear lee los ficheros de la carpeta actual desde donde lo ejecutamos y los pasa por el motor antivirus, sacando los resultados por pantalla junto un un resumen final.

El programa esta dividido en varias funciones, las más importantes son load_engine_av que es la encargada de crear un nuevo motor, cargar la lista de firmas y añadir las opciones que se usarán durante el escaneo y luego con scan_file le pasamos los ficheros que queremos que nos analice. Nos devuelve AVOK en caso de no encontrar virus, AVINFECTED en caso de encontrar virus, AVFILENF si tiene problemas de acceso al fichero (por ejemplo, no tener permisos de acceso) o AVERROR en caso de que se produzca algún error en el proceso.

En la función main() creamos una estructura que contendrá los datos de conexión y resultados de cada operación que realicemos en el motor. Arrancará el motor y le pasará los ficheros del directorio desde donde lancemos la aplicación al motor del antivirus. Para saber más de cada una de las funciones que se utilizan podemos consultar la documentación oficial de la librería en la web de libclamav.

El código es el siguiente:

clamav-dev.c
/*  
 * clamav-devc.c  
 *  
 * (c) Author: David Quiroga  
 * e-mail: david (at) clibre (dot) io  
 *  
 ***************************************************************  
 * Descripción:  
 *  
 * Uso de la libreria libclamav para buscar malware en ficheros  
 *  
 * SPDX-License-Identifier: GPL-3.0  
 *  
 * gcc -Wall clamav-devc.c -o clamav-devc `pkg-config libclamav --cflags --libs`  
*/  
 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <clamav.h> 
#include <errno.h> 
#include <dirent.h> 
#include <locale.h> 
 
#define MAX_BUF 200 
typedef struct clamav_eng_s { 
    struct cl_engine *engine; 
    struct cl_scan_options options; 
    unsigned long int tot_size; 
    unsigned int tot_files; 
    unsigned int tot_infected_files; 
    unsigned int tot_fileserr; 
    unsigned int sigs; 
    int ret; 
    char resbuff[MAX_BUF]; 
} clamav_eng_s; 
 
enum { AVOK, AVINFECTED, AVERROR, AVFILENF }; 
 
/* 
 * Exit codes: 
 * AVOK: no se detecto virus 
 * AVINFECTED: infectado 
 * AVERROR: error en el motor 
 * AVFILENF: error al abrir el fichero 
 */ 
 
#define VERDE "\033[1;32m"  
#define ROJO "\033[0;31m"  
#define NEUTRO "\033[0m"  
 
int load_engine_av(clamav_eng_s *clamav_eng) 
{ 
    clamav_eng->tot_size = 0; 
    clamav_eng->sigs = 0; 
    clamav_eng->tot_files = 0; 
    clamav_eng->tot_fileserr = 0; 
    clamav_eng->tot_infected_files = 0; 
 
    if ((clamav_eng->ret = cl_init(CL_INIT_DEFAULT)) != CL_SUCCESS) { 
        snprintf(clamav_eng->resbuff, MAX_BUF,  
                "No se puede inicializar libclamav: %s",  
                cl_strerror(clamav_eng->ret)); 
        return AVERROR; 
    } 
 
    if (!(clamav_eng->engine = cl_engine_new())) { 
        snprintf(clamav_eng->resbuff, MAX_BUF,  
                "No se puede crear un nuevo motor"); 
        return AVERROR; 
    } 
 
    // carga la db  
    if ((clamav_eng->ret = cl_load(cl_retdbdir(), clamav_eng->engine,  
                            &clamav_eng->sigs, CL_DB_STDOPT)) != CL_SUCCESS) { 
        snprintf(clamav_eng->resbuff, MAX_BUF,  
                "Error en cl_load: %s", cl_strerror(clamav_eng->ret)); 
        cl_engine_free(clamav_eng->engine); 
        return AVERROR; 
    } 
 
    // construye el motor  
    if ((clamav_eng->ret = cl_engine_compile(clamav_eng->engine)) != CL_SUCCESS) { 
        snprintf(clamav_eng->resbuff, MAX_BUF,  
            "Error al inicializar la DB: %s", cl_strerror(clamav_eng->ret)); 
        cl_engine_free(clamav_eng->engine); 
        return AVERROR; 
    } 
 
    // añadimos las opciones  
    memset(&clamav_eng->options, 0, sizeof(struct cl_scan_options)); 
    clamav_eng->options.parse |= ~0;  
    clamav_eng->options.general |= CL_SCAN_GENERAL_HEURISTICS;  
     
    snprintf(clamav_eng->resbuff, MAX_BUF,  
            "Clamav iniciado: Cargadas %u firmas", clamav_eng->sigs); 
    return AVOK; 
} 
 
int scan_file (clamav_eng_s *clamav_eng, char *filename) 
{  
    int fd=0; 
    const char *virname; 
    errno=0;  
  
    if ((fd = open(filename, O_RDONLY)) == -1) { 
        clamav_eng->tot_fileserr++; 
        snprintf(clamav_eng->resbuff, MAX_BUF,  
                "%s - %s%s%s", filename, ROJO, strerror(errno), NEUTRO); 
        return AVFILENF; 
    }  
     
    clamav_eng->ret = cl_scandesc(fd, filename, &virname, &clamav_eng->tot_size,  
                                    clamav_eng->engine, &clamav_eng->options); 
    close(fd); 
    clamav_eng->tot_files++; 
     
    if (clamav_eng->ret == CL_VIRUS) { 
        clamav_eng->tot_infected_files++; 
        snprintf(clamav_eng->resbuff, MAX_BUF, "Virus detectado: %s", virname); 
        return AVINFECTED; 
    } else { 
        if (clamav_eng->ret == CL_CLEAN) { 
         snprintf(clamav_eng->resbuff, MAX_BUF, "%s✔%s", VERDE, NEUTRO); 
         return AVOK; 
        } else { 
         snprintf(clamav_eng->resbuff, MAX_BUF, "Error: %s",  
                    cl_strerror(clamav_eng->ret)); 
         return AVERROR; 
        } 
    } 
} 
 
void clouse_engine_av(clamav_eng_s *clamav_eng) 
{ 
    // liberar memoria  
    cl_engine_free(clamav_eng->engine); 
} 
 
long double sizescan_engine_av(unsigned long int tot_size) 
{ 
    long double mb = 0; 
    mb = tot_size * (CL_COUNT_PRECISION / 1024) / 1024.0; 
    return mb; 
} 
 
void print_resume (clamav_eng_s clamav_eng)  
{ 
    printf("\n------\n"); 
    printf("Cargadas %u firmas\n", clamav_eng.sigs); 
    printf("Ficheros escaneados: %d\n", clamav_eng.tot_files); 
    printf("Ficheros no accesibles: %d\n", clamav_eng.tot_fileserr); 
    printf("Ficheros infectados: %d\n", clamav_eng.tot_infected_files); 
    printf("Total datos escaneados: %2.2Lf MB\n",  
            sizescan_engine_av(clamav_eng.tot_size)); 
} 
 
int main(void) 
{ 
    DIR *dirp; 
    struct dirent *dp; 
    clamav_eng_s clamav_eng = {}; 
     
    setlocale(LC_ALL, ""); 
     
    printf("Iniciando motor clamav...\n"); 
    // Inicializamos el motor antivirus y cargamos las firmas 
    if (load_engine_av(&clamav_eng) != AVOK) { 
     printf("load_engine_av: %s\n", clamav_eng.resbuff); 
     exit(AVERROR); 
    } 
    printf("%s\n", clamav_eng.resbuff); 
    // Analizamos todos los ficheros del directorio 
    dirp = opendir ("."); 
    if (dirp == NULL) { 
     printf("No se puede acceder a los ficheros del direcctorio\n"); 
     exit(EXIT_FAILURE); 
    } 
    printf("Escaneando ficheros:\n\n"); 
    do { 
        errno=0; 
        dp = readdir(dirp); 
        if (dp == NULL) 
         break;  
        if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) 
        continue; 
        if (dp->d_type == DT_REG) { 
        switch (scan_file (&clamav_eng, dp->d_name)) 
            { 
            case AVINFECTED:// virus encontrado 
            printf("%s", ROJO); 
            case AVOK: // fichero no infectado 
            printf("%s - %s%s\n", dp->d_name, clamav_eng.resbuff, NEUTRO); 
            break; 
            case AVFILENF: // error al abril el fichero 
            printf("%s\n", clamav_eng.resbuff); 
            break; 
            default: // cualquier otro valor  
            printf("scan_file: %s\n", clamav_eng.resbuff); 
            clouse_engine_av (&clamav_eng); 
            exit(AVERROR); 
            }  
        } 
    } while (!0); 
     
    if (errno != 0) { 
     printf("Se han encontrado errores al leer los fichero: %s\n",  
            strerror(errno)); 
     exit(EXIT_FAILURE); 
    } 
    if (closedir(dirp) == -1) { 
     printf("Error en closedir\n"); 
     exit(EXIT_FAILURE);  
    }  
     
    // Imprimimos el resumen de resultados 
    print_resume (clamav_eng);  
     
    // Cerramos el motor y liberamos 
    clouse_engine_av (&clamav_eng); 
     
    return EXIT_SUCCESS; 
} 

Para construir el ejecutable:

❯ gcc -Wall clamav-devc.c -o clamav-devc `pkg-config libclamav --cflags --libs`

Y lo lanzamos en una carpeta que tenga algunos ficheros, incluidos los que hemos descargado de prueba con Eicar:

Uso de libclamav

Conclusión

La librería libclamv nos permite de forma muy sencilla usar un motor antivirus e incluirlo en cualquier aplicación que queramos desarrollar. De esta forma usamos una copia local de la base de datos de firmas para realizar el escaneo de malware.

El uso de la librería libclamv es una de la maneras que tenemos de interactuar con el motor de ClamaAV. La otra forma de interacción la tenemos en el uso de socket contra el demonio clamav-daemon, de esta forma nos evitamos tener que cargar las firmas cada vez que queremos escanear un fichero e incluso podemos tener el motor en una VM o centralizado en otra máquina, pero esto será el asunto de una próxima entrada.

 

 

Modificado por última vez enJueves, 04 Marzo 2021 10:46
(2 votos)
Etiquetado como :

Deja un comentario

Asegúrese de introducir toda la información requerida, indicada por un asterisco (*). No se permite código HTML.