В статье рассматривается процесс написания простого антивирусного сканера.
Задача статьи состоит в объяснении базовых принципов работы антивирусных программ, использующих сигнатуры для обнаружения вредоносного кода.
Помимо самого сканера мы также напишем программку для создания базы сигнатур.

В силу простоты алгоритма проверки наш сканер сможет обнаруживать только вредоносные программы, распространяющиеся цельным файлом, т.е. не заражающие другие файлы, как PE-Вирусы, и не изменяющие свое тело в процессе деятельности, как полиморфные вирусы.
Впрочем, это относится к большинству вирусов, червей и практически ко всем троянам, поэтому написанный нами сканер имеет право на жизнь :)

Что такое сигнатура
Сигнатура в простом представлении является уникальной частью (последовательностью байт) в файле.
Однако эта часть должна максимально однозначно выделять файл среди множества других файлов.
Это значит, что выбранная последовательность должна присутствовать только в одном файле, которому она принадлежит, и ни в каких других.

На практике помимо самой последовательности применяются ещё дополнительные параметры, позволяющие максимально однозначно сопоставлять сигнатуру файлу.
Введение дополнительных параметров также направлено на ускорение поиска сигнатуры в файле.
Такими параметрами, например, могут являться размер файла, смещение последовательности байт, тип файла, специальные маски (отражение того, как примерно должен выглядеть файл, чтобы в нём могла содержаться искомая сигнатура) и многие другие.

В нашем сканере в качестве дополнительного параметра мы будем использовать смещение последовательности в файле относительно начала.
Данный метод довольно универсален в том плане, что подходит абсолютно для любых файлов независимо от типа.
Однако у использования смещения есть один очень значимый минус: чтобы "обмануть" сканер, достаточно слегка "передвинуть" последовательность байт в файле, т.е. изменить смещение последовательности (например, перекомпилировав вирус или добавив символ в случае скрипт-вируса).

Для экономии памяти и повышения скорости обнаружения на практике обычно используется контрольная сумма (хэш) последовательности.
Таким образом перед добавлением сигнатуры в базу считается контрольная сумма выбранного участка файла. Это также помогает не обнаруживать вредоносный код в собственных базах :)

Антивирусная база
Антивирусная база представляет собой один или несколько файлов, содержащих записи о всех известных сканеру вирусах.
Каждая запись обязательно содержит имя вируса (пользователь же должен знать,что за зверь поселился у него в системе), также в записи обязательно присутствует сигнатура, по которой выполняется поиск.
Помимо имени и сигнатуры запись может содержать некоторую дополнительную информацию, например, инструкции по лечению.

Алгоритм работы сканера
Алгоритм работы сканера, использующего сигнатуры, можно свести к нескольким пунктам:
1. Загрузка базы сигнатур
2. Открытие проверяемого файла
3. Поиск сигнатуры в открытом файле
4. Если сигнатура найдена
- принятие соответствующих мер
5. Если ни одна сигнатура из базы не найдена
- закрытие файла и переход к проверке следующего

Как видите, общий принцип работы сканера весьма прост.

Впрочем, достаточно теории. Переходим к практике.
Все дополнительные моменты будут разобраны в процессе написания сканера.

Подготовка к реализации
Прежде чем начинать писать код, стоит определить все составные части сканера и то, как они будут взаимодействовать.

Итак, для обнаружения вредоносных файлов нам необходим непосредственно сам сканер.
Сканеру для работы необходимы сигнатуры, которые хранятся в антивирусной базе.
База создается и наполняется специальной программой.
В итоге получается следующая зависимость:

Программа создания базы -> База -> Сканер

Однако прежде чем создавать базу, необходимо определиться с её форматом, который в свою очередь зависит от хранимой информации.

Информация сигнатуры

Сигнатура будет состоять из:
- Смещения последовательности в файле
- Размера последовательности
- Хэша последовательности

Для хэширования будем использовать алгоритм MD5.
Каждый MD5-хэш состоит из 16 байт, или 4 двойных слов.
Для хранения смещения и размера последовательности отведём по 4 байта для каждого.

Таким образом сигнатуру можно описать следующей структурой:

Код:
[Offset * 4 ]
[Lenght * 4 ]
[Hash * 16 ]

Запись антивирусной базы

Запись будет содержать:
- Сигнатуру
- Размер имени файла
- Имя файла

Под размер имени файла выделим 1 байт. Этого более чем достаточно, плюс экономия места =)
Имя файла может быть произвольного размера до 255 символов включительно.

Получается следующая структура:

Код:
[Signature]
[NameLen * 1 ]
[Name ... ]
После раскрытия структуры сигнатуры получается вот такая запись:

Код:
[Offset * 4 ]
[Lenght * 4 ]
[Hash * 16]
[NameLen * 1 ]
[Name ... ]
Именно в таком виде одна за другой в файле будут лежать записи для всех известных сканеру зловредов.

Помимо самих записей в файле базы должен быть заголовок, в котором будет содержаться число записей в базе и сигнатура файла "AVB" (не антивирусная :) ). Назначение сигнатуры – удостовериться, что это именно файл базы.

Таким образом файл базы будет иметь структуру вида:

Код:
[Sign * 3 ]
[RecordCount* 4 ]
[Records]
Переходим к написанию кода.

Реализация
Базовые структуры
Структур немного, всего 2.
Данные структуры будут использоваться как сканером, так и программой создания антивирусной базы.
Во-первых, необходимо объявить все нужные нам структуры.

Первой структурой будет структура сигнатуры SAVSignature.
Следующей структурой будет структура записи SAVRecord, объединяющая сигнатуру с именем.
Данная структура для удобства также содержит функцию выделения памяти под имя зловреда (allocName).

Все структуры будут находиться в заголовочном файле avrecord.h

Код:
#ifndef _AVRECORD_H__INCLUDED_
#define _AVRECORD_H__INCLUDED_
#include <windows.h>

//! Структура сигнатуры
typedef struct SAVSignature{
        SAVSignature(){
                this->Offset = 0;
                this->Lenght = 0;
                memset(this->Hash, 0, sizeof(this->Hash));
        }
        DWORD   Offset;         // - Смещение  файле
        DWORD   Hash[4];        // - MD5 хэш
        DWORD   Lenght;         // - Размер данных
} * PSAVSignature;

//! Структура записи о зловреде
typedef struct SAVRecord{
        SAVRecord(){
                this->Name           = NULL;
                this->NameLen        = 0;
        }
        ~SAVRecord(){
                if(this->Name != NULL)  this->Name;
        }
        //! Выделение памяти под имя
        void    allocName(BYTE NameLen){
                if(this->Name == NULL){
                        this->NameLen        = NameLen;
                        this->Name           = new CHAR[this->NameLen + 1];
                        memset(this->Name, 0, this->NameLen + 1);
                }
        }
        PSTR                    Name;           // - Имя
        BYTE                    NameLen;        // - Размер имени
        SAVSignature    Signature;      // - Сигнатура
       
} * PSAVRecord;

#endif
Класс работы с файлом базы
Теперь необходимо написать класс для работы с файлом антивирусной базы.
Если точнее, то классов будет несколько:
- Базовый класс файла "CAVBFile"
- Класс чтения файла "CAVBFileReader"
- Класс добавления записи "CAVBFileWriter"

Объявления всех этих классов находятся в файле CAVBFile.h
Вот его содержимое:
Код:
#ifndef _AVBFILE_H__INCLUDED_
#define _AVBFILE_H__INCLUDED_
#include <fstream>
#include <windows.h>
#include "avrecord.h"
using namespace std;

/*              Формат файла антивирусной базы
       
        [AVB]                                   // - Сигнатура
        [RecordCount    * 4 ]   // - Число записей
        [Records                ... ]

        Record:
                [Offset         * 4 ]   // - Смещение
                [Lenght         * 4 ]   // - Размер
                [Hash           * 16 ]  // - Контрольная сумма
                [NameLen        * 1 ]   // - Размер имени
                [Name           ... ]   // - Имя зловреда

*/

//! Класс Файла антивирусной базы
typedef class CAVBFile{
protected:
        fstream         hFile;                  // - Объект потока файла
        DWORD           RecordCount;    // - Число записей
public:
        CAVBFile();

        //! Закрытие файла
        virtual void close();
        //! Проверка состояния файла
        virtual bool is_open();
        //! Получение числа записей
        virtual DWORD getRecordCount();
} * PCAVBFile;

//! Класс для записи файла
typedef class CAVBFileWriter : public CAVBFile{
public:
        CAVBFileWriter() : CAVBFile(){
        }

        //! Открытие файла
        bool    open(PCSTR FileName);
        //! Добавление записи в файл
        bool    addRecord(PSAVRecord    Record);

} * PCAVBFileWriter;

//! Класс для чтения файла
typedef class CAVBFileReader : public CAVBFile{
public:
        CAVBFileReader() : CAVBFile(){

        }
        //! Открытие файла
        bool    open(PCSTR FileName);
        //! Чтение записи
        bool    readNextRecord(PSAVRecord Record);

} * PCAVBFileReader;

#endif

Теперь перейдем к реализации объявленных классов.
Их реализация будет находиться в файле AVBFile.cpp
Естественно, помним, что необходимо подключить заголовочный файл AVBFile.h

В некоторых функциях нам понадобится проверка существования файла, поэтому сначала напишем именно её.

Код:
//! Проверка существования файла
bool    isFileExist(PCSTR FileName){
        return GetFileAttributesA(FileName) != DWORD(-1);
};
Данный способ проверки существования файла является самым быстрым и используется в большинстве примеров в MSDN, так что его можно считать стандартом для Windows.
Функция GetFileAttributes возвращает атрибуты файла или 0xffffffff в случае, если файл не найден.

Переходим к реализации функций базового класса.

Код:

CAVBFile::CAVBFile(){
        this->RecordCount = 0;
}
//! Закрытие файла
void CAVBFile::close(){
        if(hFile.is_open())     hFile.close();
}
//! Проверка состояния файла
bool CAVBFile::is_open(){
        return hFile.is_open();
}
//! Получение числа файлов
DWORD   CAVBFile::getRecordCount(){
        return this->RecordCount;
}
Здесь всё просто и в комментариях не нуждается.

Теперь реализуем функции класса для записи файла

Код:

//
// - CAVBFileWriter
//

//! Открытие файла
bool CAVBFileWriter::open(PCSTR FileName){
        if(FileName == NULL) return false;
        // - Если файл не найден то создаем его прототип
        if(!isFileExist(FileName)){
                hFile.open(FileName, ios::out | ios::binary);
                if(!hFile.is_open()) return false;
                hFile.write("AVB", 3);                                                                        // - Сигнатура файла
                hFile.write((PCSTR)&this->RecordCount, sizeof(DWORD));   // - Число записей
        // - Иначе открываем и проверяем валидность
        }else{
                hFile.open(FileName, ios::in | ios::out | ios::binary);
                if(!hFile.is_open()) return false;
                // - Проверка сигнатуры
                CHAR    Sign[3];
                hFile.read((PSTR)Sign, 3);
                if(memcmp(Sign, "AVB", 3)){
                        hFile.close();  // - Это чужой файл
                        return false;
                }
                // - Читаем число записей
                hFile.read((PSTR)&this->RecordCount, sizeof(DWORD));
        }
        return true;
}

bool CAVBFileWriter::addRecord(PSAVRecord Record){
        if(Record == NULL || !hFile.is_open()) return false;
        // - Перемещаемся в конец файла
        hFile.seekp(0, ios::end);
        // - Добавляем запись
        hFile.write((PSTR)&Record->Signature.Offset,     sizeof(DWORD)); // - Смещение сигнатуры
        hFile.write((PSTR)&Record->Signature.Lenght,     sizeof(DWORD)); // - Размер сигнатуры
        hFile.write((PSTR)&Record->Signature.Hash,  4 *  sizeof(DWORD)); // - Контрольная сумма
        hFile.write((PSTR)&Record->NameLen, sizeof(BYTE));       // - Размер имени
        hFile.write((PSTR)Record->Name, Record->NameLen); // - Имя
        // - Смещаемся к числу записей
        hFile.seekp(3, ios::beg);
        // - Увеличиваем счётчик записей
        this->RecordCount++;
        hFile.write((PSTR)&this->RecordCount, sizeof(DWORD));

        return true;
}

При открытии файла, если файл не найден, создается новый файл, и в него записывается заголовок файла (сигнатура и число записей).
Если же файл существует, то происходит проверка сигнатуры файла и чтение числа записей.

Функция addRecord в качестве параметра принимает ссылку на структуру добавляемой записи.
Сначала происходит перемещение в конец файла (новые записи дописываются в конец файла).
Затем происходит запись данных в файл согласно оговорённому выше формату.
После записи происходит увеличение счётчика записей.

Класс чтения записей немного проще.

Код:
//
// - CAVBFileReader
//
bool    CAVBFileReader::open(PCSTR FileName){
        if(FileName == NULL) return false;
        // - Если файл не найден, то создаем его прототип
        if(isFileExist(FileName)){
                hFile.open(FileName, ios::in | ios::out | ios::binary);
                if(!hFile.is_open()) return false;
                // - Проверка сигнатуры
                CHAR    Sign[3];
                hFile.read((PSTR)Sign, 3);
                if(memcmp(Sign, "AVB", 3)){
                        hFile.close();  // - Это чужой файл
                        return false;
                }
                // - Читаем число записей
                hFile.read((PSTR)&this->RecordCount, sizeof(DWORD));
        }else{ return false; }
        return true;
}

bool    CAVBFileReader::readNextRecord(PSAVRecord Record){
        if(Record == NULL || !hFile.is_open()) return false;

        hFile.read((PSTR)&Record->Signature.Offset,      sizeof(DWORD)); // - Смещение сигнатуры
        hFile.read((PSTR)&Record->Signature.Lenght,      sizeof(DWORD)); // - Размер сигнатуры
        hFile.read((PSTR)&Record->Signature.Hash,  4 *   sizeof(DWORD)); // - Контрольная сумма
        hFile.read((PSTR)&Record->NameLen, sizeof(BYTE));        // - Размер имени
        Record->allocName(Record->NameLen);
        hFile.read((PSTR)Record->Name, Record->NameLen);  // - Имя
        return true;
}
В данном случае, если при попытке открытия файла выясняется, что файл не существует, функция вернет значение false, свидетельствующее об ошибке.
Чтение записей происходит последовательно и обеспечивается функцией readNextRecord, которая в качестве параметра принимает ссылку на структуру записи, в которую будут прочитаны данные из файла.

На этом написание общего кода закончено.
Пора переходить к реализации программы создания записей и сканера.

Реализация программы для создания базы

Как было выяснено выше, сканнер без сигнатур не имеет смысла. Именно поэтому первым делом будет реализована программа для создания базы.

В качестве параметров программа будет принимать путь до файла зловреда, путь до файла базы, смещение последовательности в файле зловреда, размер последовательности и, наконец, имя зловреда.
Аргументы передаются формате -A[Value], где A — это соответствующий ключ, а Value – значение.
Обозначим все аргументы:
-s = Путь до файла зловреда
-d = Путь до файла базы
-o = Смещение последовательности
-l = Размер последовательности
-n = Имя файла

Алгоритм работы программы следующий:
1. Открыть файл зловреда
2. Перейти по указанному смещению
3. Расчитать MD5-хэш последовательности байт
4. Добавить запись в базу

Реализация алгоритма здесь приводиться не будет, т.к. не относится к теме статьи, но её можно найти в файле md5hash.cpp
Здесь же мы просто объявим соответствующую функцию getMD5, которая принимает указатель на данные, их размер и указатель на буфер из 16 байт, куда будет записан хэш.

Код программы находится в файле avrec.cpp

Сначала подключим все необходимые заголовочные файлы и объявим функцию подсчёта MD5, а также напишем вспомогательную функцию, которая понадобится при разборе аргументов программы.

Код:
// - Необходимые включения
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <windows.h>
#include <avrecord.h>
#include <AVBFile.h>

using namespace std;

//! Копирование аргумента
bool copyArg(PCSTR Arg, DWORD Offset, PSTR Buffer, DWORD Size){
        int ArgLen = strlen(Arg) - Offset;
        if(ArgLen > Size - 1 || ArgLen <= 0) return false;
        memcpy(Buffer, (void*)((DWORD)Arg + Offset), ArgLen);
        Buffer[ArgLen] = 0x00;
}

void getMD5(const void* pData, size_t nDataSize, PDWORD RetHash);
Рассмотрим по частям главную функцию main, весь полезный код находится в ней.

Сначала происходит разбор аргументов. Вот он:

Код:
  cout << endl;
        // - Проверка числа аргументов
        if(argc < 2){
                cout << "ttAvRec v1.0 by Av-School.ru" << endl;
                cout << endl;
                cout << "    Usage:" << endl;
                cout << "        avrec.exe [OPTIONS...]" << endl;
                cout << endl;
                cout << "    Options:" << endl;
                cout << "        -s[PATH]    Source infected file"      << endl <<
                                "        -d[PATH]    Output database file"    << endl <<
                                "        -o[NUM]     Signature offset"                << endl <<
                                "        -l[NUM]     Signature size"          << endl <<
                                "        -n[NAME]    Record name"           << endl;
                cout << endl;
                return 0;
        }

        // - Объект новой записи
        SAVRecord       Record;
        CHAR            SrcFile[256];
        CHAR            DstFile[256];

        // - Разбор аргументов
        for(int ArgID = 1; ArgID < argc; ArgID++){
                // - Исходный путь
                if(!memcmp(argv[ArgID], "-s", 2)){
                        if(!copyArg(argv[ArgID], 2, SrcFile, sizeof(SrcFile))){
                                cout << "> Error in -s argument. Stop" << endl;
                                return 0;
                        }
                // - Файл базы
                }else if(!memcmp(argv[ArgID], "-d", 2)){
                        if(!copyArg(argv[ArgID], 2, DstFile, sizeof(SrcFile))){
                                cout << "> Error in -d argument. Stop" << endl;
                                return 0;
                        }
                // - Смещение сигнатуры
                }else if(!memcmp(argv[ArgID], "-o", 2)){
                        Record.Signature.Offset = atoi((PCSTR)((DWORD)argv[ArgID] + 2));

                // - Размер сигнатуры
                }else if(!memcmp(argv[ArgID], "-l", 2)){
                        Record.Signature.Lenght = atoi((PCSTR)((DWORD)argv[ArgID] + 2));
                // - Имя
                }else if(!memcmp(argv[ArgID], "-n", 2)){
                        int NameLen = strlen(argv[ArgID]) - 2;
                        if(NameLen <= 0){
                                cout << "> Error in -n argument. Stop" << endl;
                                return 0;
                        }
                        Record.allocName(NameLen);
                        copyArg(argv[ArgID], 2, Record.Name, NameLen + 1);
                }
        }
Если задано слишком мало аргументов, то будет выведено сообщение об использовании программы, иначе происходит их "опознание".
Функция copyArg копирует в указанное место значение аргумента без ключа.

После того, как данные о записи получены, можно приступать к расчёту контрольной суммы сигнатуры.

Код:
        //
        // - Открытие исходного файла
        ifstream        hSrcFile;
        hSrcFile.open(SrcFile, ios::in | ios::binary);
        if(!hSrcFile.is_open()){
                cout << "> Can't open source file. Stop." << endl;
                return 0;
        }
        // - Чтение данных для расчёта контрольной суммы
        PBYTE   Buffer = new BYTE[Record.Signature.Lenght];
        if(Buffer == NULL){
                cout << "> Can't alloc memory for sign data. Stop." << endl;
                hSrcFile.close();
                return 0;
        }
        hSrcFile.seekg(Record.Signature.Offset, ios::beg);
        hSrcFile.read((PSTR)Buffer, Record.Signature.Lenght);
        // - Закрытие исходного файла
        hSrcFile.close();
        // - Расчёт хэша сигнатуры
        getMD5(Buffer, Record.Signature.Lenght, Record.Signature.Hash);
       
        // - Очистка буфера
         Buffer;
Сначала открываем файл, затем переходим к указанному смещению и вычисляем хэш.
Всё просто.

И наконец, добавляем запись в файл базы, попутно выводя информацию в консоль.
Для добавления используется класс CAVBFileWriter.

Код:
        // -
        // - Добавление сигнатуры
        cout << "Record info:" << endl;
        printf( "    Name:         %sn", Record.Name);
        printf( "    Offset:       0x%x (%d)n", Record.Signature.Offset, Record.Signature.Offset);
        printf( "    Lenght:       0x%x (%d)n", Record.Signature.Lenght, Record.Signature.Lenght);
        printf( "    CheckSumm:    0x%x%x%x%xn", Record.Signature.Hash[0], Record.Signature.Hash[1], Record.Signature.Hash[2], Record.Signature.Hash[3]);
        CAVBFileWriter  hAVBFile;
        hAVBFile.open(DstFile);
        if(!hAVBFile.is_open()){
                cout << "> Can't open database file. Stop." << endl;
                return 0;
        }
        hAVBFile.addRecord(&Record);
        hAVBFile.close();
       
        cout << "Record added." << endl;

        return 0;
На этом всё, программа готова. Можно компилировать :)
А пока она компилируется, переходим к написанию самого сканера!

Реализация сканера

Наконец-то добрались и до главной цели — сканера.
Сканер пока будет просто проверять, является ли файл вредоносным или нет.
Лечение, удаление, карантин оставим на потом.
Файл с базой должен находиться в одной папке со сканером и называться avbase.avb
Программа принимает один-единственный параметр — путь до папки, в которой необходимо провести проверку.
Кода в сканере будет немного больше, но в целом всё так же просто.

Алгоритм работы следующий:
1. Загрузка файла базы
2. Получение списка файлов в указанной папке
3. Если это файл — проверяем. Если папка — рекурсивно переходим к пункту 2.

Загрузка файла базы будет происходить в специальную структуру SAVRecordCollection, которую мы объявим, несмотря на то, что можно было использовать стандартный vector или другой контейнер.

Проверка файла сводится к простому перебору всех сигнатур.
Если сигнатура присутствует, то сообщаем, что файл злой, в противном случае сообщаем, что всё хорошо.

А теперь ближе к коду :)

Код:
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <windows.h>

#include <avrecord.h>
#include <AVBFile.h>

using namespace std;

//! Коллекция записей
typedef struct SAVRecordCollection{
        SAVRecordCollection(DWORD RecordCount){
                this->RecordCount    = RecordCount;
                this->Record         = new SAVRecord[this->RecordCount];
        }
        ~SAVRecordCollection(){
                []      this->Record;
        }
        DWORD           RecordCount;
        PSAVRecord      Record;
} * PSAVRecordCollection;

// - Коллекция записей
PSAVRecordCollection            AVRCollection   = NULL;

void    processPath(PCSTR Path);
void getMD5(const void* pData, size_t nDataSize, PDWORD RetHash);

функция processPath будет рассмотрена ниже.
Итак, вначале стандартный разбор аргументов, а также получение пути для

Код:
        if(argc < 2){
                cout << "ttAVScan v1.0" << endl;
                cout << endl;
                cout << "    Usage:" << endl;
                cout << "        avscan.exe [Path]" << endl;
                cout << endl;
                cout << "    Arguments:" << endl;
                cout << "        Path    - Dirrectory to scan" << endl;
                cout << endl;
                return 0;
        }

        PCSTR   SrcPath = argv[1];      // - Путь для сканирования

        // - Получение пути до файла с базой
        CHAR    AVBPath[MAX_PATH];      // - Путь до папки с программой
        memset(AVBPath, 0, MAX_PATH);
        PCHAR   NamePtr;
        GetFullPathNameA(argv[0], MAX_PATH, AVBPath, &NamePtr);
        *NamePtr = 0x00;
        strcat_s(AVBPath, MAX_PATH, "avbase.avb");
Функция GetFullPathName в третьем параметре возвращает указатель на имя исполняемого файла, в начало которого мы ставим нуль-терминатор. Таким образом отрезая его и оставляя только путь до папки, в которой расположен исполняемый файл.

Следующий шаг — загрузка базы.

Код:

        // - Загрузка записей
        cout << endl;
        cout << "Loading bases...";
        CAVBFileReader  hAVBFile;
        if(!hAVBFile.open(AVBPath)){
                cout << "Can't open AV Bases file. Stop." << endl;
                return 0;
        }
        if(hAVBFile.getRecordCount() > 0){
                // - Создание коллекции
                AVRCollection = new SAVRecordCollection(hAVBFile.getRecordCount());
                for(DWORD RecID = 0; RecID < AVRCollection->RecordCount; RecID++){
                        if(!hAVBFile.readNextRecord(&AVRCollection->Record[RecID])){
                                cout << "> Error loading record #" << RecID << endl;
                        }
                }
                hAVBFile.close();
        }else{
                hAVBFile.close();
                cout << "> Empty AV Base. Stop." << endl;
                return 0;
        }
        cout << "t" << AVRCollection->RecordCount << " records loaded." << endl;
       
        //
        cout << endl;
        cout << "Starting scan for viruses" << endl;
        cout << endl;

        processPath(SrcPath);
Открываем файл, выделяем память под записи, после чего читаем в них информацию из файла.
Если всё прошло хорошо, то будет вызвана функция processPath, которая выполняет рекурсивную проверку по указанному пути.

Вот так выглядит эта функция:

Код:

void    processPath(PCSTR Path){
        string  SrcPath = Path;
        string  File;
        File    = Path;
        File    += "*.*";

        WIN32_FIND_DATAA        FindData;       
        HANDLE hFind = FindFirstFileA(File.c_str(), &FindData);

        do{
                // - Пропускаем папки . и ..
                if(!strcmp(FindData.cFileName, ".") || !strcmp(FindData.cFileName, "..")) continue;

                File = Path;
                File += "";
                File += FindData.cFileName;

                // - Если папка, то сканируем рекурсивно
                if((FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)){
                        processPath(File.c_str());
                // - Иначе проверяем на вирусы
                }else{
                        checkFile(File.c_str());
                }

        } while(FindNextFileA(hFind, &FindData));

}
Получаем список файлов и папок (за исключением папок "." и ".."), при этом если нам попалась папка, то проводим рекурсивный просмотр, а если файл — проверяем его функцией checkFile.

Ниже приведён листинг функции checkFile

Код:

void    checkFile(PCSTR FileName){
        cout << FileName << "t";
        // - Открываем файл
        HANDLE  hFile = CreateFileA(FileName, FILE_READ_ACCESS, NULL, NULL, OPEN_EXISTING, NULL, NULL);
        if(hFile == INVALID_HANDLE_VALUE){
                cout << "Error" << endl;
                return;
        }
        // - Получаем размер файла
        DWORD FileSize = GetFileSize(hFile, NULL);

        // - Отображаем файл в память
        HANDLE  hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, NULL, FileSize, NULL);
        if(hFile == INVALID_HANDLE_VALUE){
                cout << "Error" << endl;
                CloseHandle(hFile);
                return;
        }
        LPVOID  File = MapViewOfFile(hMap, FILE_MAP_READ, NULL, NULL, FileSize);
        if(File == NULL){
                cout << "Error" << endl;
                CloseHandle(hMap);
                CloseHandle(hFile);
                return;
        }
       
        // - Поиск по сигнатурам
        bool    Detected = false;
        for(DWORD RecID = 0; RecID < AVRCollection->RecordCount; RecID++){
                PSAVRecord      Record = &AVRCollection->Record[RecID];
                // - Если файл слишком маленький, то пропускам запись
                if(FileSize < (Record->Signature.Offset + Record->Signature.Lenght)) continue;
                // - Переходим вычисляем контрольную сумму для сигнатуры
                DWORD   Hash[4];
                getMD5((PBYTE)((DWORD)File + Record->Signature.Offset), Record->Signature.Lenght, Hash);

                // - Детектим
                if(!memcmp(Hash, Record->Signature.Hash, 4 * sizeof(DWORD))){
                        cout << " DETECTEDt" << Record->Name << endl;
                        Detected = true;
                        break;
                }
        }

        UnmapViewOfFile(File);
        CloseHandle(hMap);
        CloseHandle(hFile);

        if(!Detected)   cout << "OK" << endl;
}
Рассмотрим её подробнее.
Во-первых, в функции вместо чтения файла использовано его отображение в память, при котором файл помещается в адресное пространство процесса, и для доступа к нему не требуется производить операции чтения или записи. Доступ осуществляется, как к обычному массиву.
Данный подход выбран по той причине, что при проверке сигнатур требуется постоянно перемещаться по файлу согласно смещению сигнатуры.
Перемещение по массиву намного быстрее перемещения по файлу. Также для расчёта хэша достаточно просто передать указатель на начало последовательности.
При стандартном подходе потребовалось бы каждый раз считывать информацию из файла, что не только долго, но и просто неудобно.

Функция MapViewOfFile возвращает адрес, начиная с которого отображен файл.
Этот адрес и является началом файла, или если представить файл как массив, то данный адрес будет началом массива.

Поиск сигнатуры выполняется следующим образом:
В цикле перебираются все записи из коллекции.
Если для записи сумма смещения сигнатуры и её размера меньше, чем размер файла (т.е. сигнатура помещается в файл), то производится хэширование последовательности данных.
После этого производится сравнение полученного хэша с хэшем из сигнатуры.
Если они совпадают, то это значит, что файл известен как опасный (или ложное срабатывание =) )

Осталось только скомпилировать и протестировать.

Код:
avrec.exe -sVirus.vbs -davbase.avb -o253 -l280 -nVirus.VBS.Baby
Проверяем:

Файл действительно детектируется.