У статті розглядається процес написання простого антивірусного сканера.
Завдання статті полягає у поясненні базових принципів роботи антивірусних програм, що використовують сигнатури для виявлення шкідливого коду.
Крім самого сканера ми напишемо програмку для створення бази сигнатур.

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

//! Структура сигнатури
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
#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();
//! Перевірка стану файлу
віртуальний 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
#include
#include
#include
#include
#include

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

#include
#include

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 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»

//
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 RecordCount; RecID++){
PSAVRecord Record = &AVRCollection->Record[RecID];
// — Якщо файл занадто маленький, то перепустками запис
if(FileSize 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”
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