Недавно я прочитал статью Криса Касперски, посвященную VirusTotal'у, и всерьез загорелся идеей созданием такого сервиса. Почему бы и нет? Проблема малвари сейчас стоит довольно остро, а необходимость проверки одного, но весьма подозрительного файла появляется у пользователей с завидным постоянством.

 

Материалы и методы

Итак, что же нам нужно? Рассмотрим по пунктам.

  • Выделенный сервер. Не VDS, а именно Dedicated. Я успел отхватить себе сервак с Core Duo, 2 Гб RAM и безлимитным трафиком (10 Мб/с) за $100 в месяц. Средние же расценки сейчас заметно выше :).
  • Опыт работы с Linux — в качестве платформы я выбрал именно эту ОС, поскольку виндовый (особенно — высоконагруженный) сервер кажется мне не очень хорошей идеей. Лично я выбрал Ubuntu Server 10.04.
  • Знание C++/Qt. Писать мы будем именно на нем, поскольку для линукса приплюснутый Си вполне логичен, а Qt я выбрал, потому что в нем есть очень удобные классы для взаимодействия с процессами.
  • Знание PHP + AJAX. Ну а как иначе?
  • Умение верстать/рисовать, либо человек, который это сделает. Без хорошего дизайна сервис долго не проживет.
  • Установочные пакеты антивирусов и дипломатические навыки для общения с антивирусными компаниями.
 

Перейдем к практике

Набросаем небольшой план. Что должна делать наша софтина? Она запускает консольный сканер антивируса, читает и парсит его stdout. Чтобы понять стиль поведения антивируса, запустим его с параметром --help, внимательно вкурим в результат, а затем скормим ему здоровый и зараженный (поочередно, естественно) файлы и сравним вывод антивируса. Как это будет реализовано в софте?

Как тебе уже известно (если ты читал мою статью в майском ][ ), в Qt есть очень полезный класс QProcess, который служит для запуска внешних программ. Он умеет запускать программу, получать ее вывод и сообщать нам о завершении программы. Но нам этого недостаточно, поэтому мы немного допишем класс. Что будет в новом классе? Он должен собирать выведенный в консоль текст по мере его поступления и генерировать сигнал с этим самым выводом и именем процесса при его завершении. Сказано — сделано:

class QAvProcess : public QProcess {
void inline startProcess(
const QString &name,
const QStringList ¶ms);
......
signals:
void onAvFinished(QAvProcess *sender,
const QString &av,
QString &output,
const int exitCode);
private slots:
void onFinished(int exitCode,
QProcess::ExitStatus exitStatus);
void onReadyRead();
};

Как видишь, класс наследуется от QProcess. Рассмотрим
методы и слоты этого класса:

void QAvProcess::startProcess(
const QString &name,
const QStringList ¶ms)
{
QFileInfo info(name);
avName = info.fileName();
start(name, params);
}

Как нетрудно догадаться, этот метод служит для запуска программы и сохранения имени процесса перед запуском. Код, заключенный в слот onReadyRead, просто аппендит прочитанный вывод к уже имеющемуся, а слот onFinished() генерирует сигнал emit onAvFinished(this, avName, avBuffer, exitCode). Все тривиально, поэтому я предлагаю двигаться дальше, к главному классу:

class QAv : public QObject{
void startCheck(const QString &fName);
private:
QMap<QString, QString > avs;
QList<QResultPair > results;
QString fileName;
inline QAvProcess* createProcess();
QVirInfo inline parseOutput(
const QString &avName,
const QString &output);
signals:
void onAVDone(const QString avName,
const QString avResult);
private slots:
void onAvFinished(QAvProcess *sender,
const QString &av,
const QString &output,
const int exitCode);
};

Разберем код по порядку. За запуск антивирусов у нас отвечает функция startCheck():

void QAv::startCheck(const QString &fName)
{
qDebug() << "[*] Scanning file";
fileName = fName;
QStringList params;
QAvProcess *process;
// BitDefender
process = createProcess();
params << "--action=ignore"
<< fileName;
process->startProcess("bdscan",
params);
params.clear();
}

В параметр fName, как нетрудно догадаться, передается имя файла, который мы будем проверять. В данной статье я буду показывать взаимодействие только с одним антивирусом — дальше ты сможешь продолжить сам.

Кстати говоря, знаешь, почему параметр передается в таком странном виде (const QString &fName)? Дело в том, что при передаче параметра по значению (то есть, например, просто QString fName) в стек переменная будет копироваться целиком, и это совсем не гуд, а при передаче по указателю (QString fName) в стек будет копироваться только адрес переменной. Минус в том, что мы будем вынуждены работать с ней как с указателем. Ну а передача по константной ссылке — const QString &fName — это комбинация двух предыдущих методов. В итоге в стек копируется только указатель на переменную (то есть sizeof(void*)), и работаем мы с ней, как с обычной переменной.

Не обошлось, конечно же, и без ложки дегтя — мы не можем изменять переменную в нашей функции. А оно нам надо? В данном случае — нет, ну а если понадобится, то можно завести переменную в самой функции и скопировать ее туда. «Некрасиво», — скажешь ты. Может быть, зато очень эффективно — функция будет вызываться намного быстрее, если все переменные влезут в регистры (при условии юзанья фастколла).

Ладно, лирику в сторону, поехали дальше.
Наверное, ты обратил внимание на qDebug(). Что это? Это поток для вывода отладочной информации в Qt (очень удобная вещь, между прочим).
Далее мы создаем процесс и привязываем его к имеющимся слотам с помощью функции createProcess(). Она абсолютно тривиальна:

QAvProcess *process = new QAvProcess;
connect(process, SIGNAL(onAvFinished
(QAvProcess*,QString, QString,int)),
this, SLOT(onAvFinished(QAvProcess*,
QString,QString,int)));
return process;

Как только процесс завершается, начинает свою работу слот onAvFinished().
И снова введу тебя в курс дела: avs — это QMap из typedef QPair<QString, QString > QResultPair;
Он содержит пары «имя процесса; название антивируса» для простого распознавания завершившегося процесса.

Одна из важнейших функций в нашей софтине — parseOutput(). Как видно из названия, она парсит вывод антивируса при его завершении и выдает результат сканирования. Выглядит она так:

QVirInfo info;
info.isInfo = info.isInfected = false;
if ( avName == "bdscan" ) { // BitDefender
if ( output.indexOf("ok") > 0 ) {
info.isInfo = true;
return info;
}
int index = output.indexOf("infected:");
if ( index == -1 )
return info;
info.description = output.mid (index + 9,
output.indexOf("\n", index) index - 9).trimmed();
info.isInfo = info.isInfected = true;
}

Тут происходит самый обыкновенный разбор строки. Если мы не находим какого-то ключевого слова — значит, ошибка. Если находим слово, соответствующее отрицательному результату (то есть, файл не заражен) — возвращаем ОК. Иначе — копируем вывод антивируса и возвращаем его.

Вернемся к слоту onAvFinished(). После парсинга вывода мы пишем
результат в файл в виде HTML-таблицы для удобного вывода в браузер. Все! Костяк сервиса создан. Встает вопрос: «А как на этом можно заработать»? На мой взгляд, есть два варианта:

  1. «Приватная» версия сервиса. Не отправлять на проверку файлы за небольшую денежку. У меня — 1 цент за 1 антивирус. Как реализовать? Iptables тебе в руки!
  2. Реклама. Можно размещать баннеры тематических форумов/сервисов и/или контекстную рекламу от того же гугла.
  3. Продажа лицензионных версий антивирусов.
 

Заключение

Как показала практика, самый напряжный момент в разработке — создание сайта. Ненавижу PHP! После понятного и логичного C++ разработка на PHP + Ajax подобна пытке, но в итоге использование в качестве Ajax-библиотеки Sajax, а SQL-базы SQLite решило все мои проблемы.

На этом позволь свернуть мое краткое повествование и пожелать тебе удачной разработки. А будут вопросы — пиши. Мыла не указываю, поскольку настоящий хакер всегда и так сможет меня найти :)..

 

«Слот» onAvFinished()

QString avName = avs.find(av).value();
if ( avName.isEmpty() ){
qDebug() << "[-] Unknown process finished";
return;
}
avsRemains--;
QVirInfo info = parseOutput(av, output);
if ( ! info.isInfo ) {
writeResult(avName, "ERROR");
return;
}
if ( ! info.isInfected )
writeResult(avName, "OK");
else {
writeResult(avName, info.description);
avsFound++;
}
delete sender;
if ( ! avsRemains ) {
qDebug() << endl << endl << "Done,"
<< avsFound << "/" << totalAVs << "found!";
qDebug() << endl << "RankoR, Ax-Soft.Ru,
Russia, 2010";
writeFooter();
QCoreApplication::exit();
}

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии