Пишем свой антивирус на C++ — HackZona.Ru

Пишем свой антивирус на C++

Пишем свой антивирус на C++

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

В силу простоты алгоритма проверки наш сканер сможет обнаруживать только вредоносные программы, распространяющиеся цельным файлом, т.е. не заражающие другие файлы, как 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
#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::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
----------------------------------------------------------------------------------------------------------


//
// — 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
---------------------------------------------------------------------------------------------------------
//
// — 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
#include
#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
#include
#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
----------------------------------------------------------------------------------------------------------

Исходник: av-school.ru/up/article/file/cpp/avscan.rar
Нравится
Не нравится

4 комментария

18:47
Дык, всё сразу трудно усвоить, наверно стоило разбить на несколько статей
18:47
Дык, всё сразу трудно усвоить, наверно стоило разбить на несколько статей
01:11
Сложновато для начинающего
17:46
неплохо, только я бы не использовал фаловы потоки, лучше воспользоваться системными функциями для повышения скорости приложения.