Пример построения простого antimalware сканера с собственными базами

Администраторы сетей нередко задают вопросы по поводу того, нет ли возможности выполнять зачистку от нежелательного ПО, троянов или запрещенных к применению на ПК пользователей программ на базе AVZ. Возможность такая существует, и реализуется на основе скрипт-языка.
Рассмотрим типовой пример:
 
var
  SignDB     : TStrings; // Сигнатурная база
  MalwareCnt : integer; // Счетчик найденных зловредов
  DelMalwareCnt : integer; // Счетчик удаленных зловредов
  DeleteMalware : boolean; // Признак того, что делать с найденными Malware
 
// Обработка найденного файла
Procedure ScanFile(AFileName : string);
var
  FMD5 : string;
begin
  SetStatusBarText(AFileName);
  // Вычисление MD5 суммы                                                        
  FMD5 := CalkFileMD5(AFileName);
  // Поиск MD5 в базе                                                          
  if (FMD5 <> '') and (SignDB.IndexOf(FMD5) >= 0) then begin
    AddToLog('Найден malware, файл = '+AFileName);
    inc(MalwareCnt);
    if DeleteMalware then begin
      DeleteFile(AFileName);
      inc(DelMalwareCnt);
    end;
  end;
end;
 
// Сканирование каталога
Procedure ScanDir(ADirName : string; AScanSubDir : boolean);
var
  FS : TFileSearch;
begin
  ADirName := NormalDir(ADirName);
  FS := TFileSearch.Create(nil);
  FS.FindFirst(ADirName + '*.*');
  while FS.Found do begin
    // В зависимости от типа - или сканируем подкаталог, или изучаем файл
    if (FS.FileName <> '.')   and (FS.FileName <> '..') then
      if FS.IsDir and AScanSubDir then
        ScanDir(ADirName + FS.FileName, AScanSubDir)
      else
        ScanFile(ADirName + FS.FileName);
    FS.FindNext;
  end;
  FS.Free;
end;
 
begin
  // Режим сканирования
  DeleteMalware := false;
  // Обнуляем счетчик зловредов
  MalwareCnt := 0; DelMalwareCnt := 0;
  // Загружаем сигнатурную базу
  SignDB := TStringList.Create;
  SignDB.LoadFromFile('my_sign_db.txt');
  AddToLog('Баз загружена, количество сигнатур = '+inttostr(SignDB.Count));
  // Задействуем антируткит
  SearchRootkit(true, true);
  // Проверка дисков и (или) папок
  ScanDir('c:\', true);
  ScanDir('d:\MyFolder\', false);
  // Зачистка следов зловредов, если таковые были найдены
  if DelMalwareCnt > 0 then begin
    SaveLog('scan_result.txt');
    // Включение AVZGuard - он помешает зловредам восстановиться
    SetAVZGuardStatus(true);
    // Активация BootCleaner
    BC_ImportALL;
    BC_Activate;
    // Эвристическая чистка
    ExecuteSysClean;
    // Перезагрузка
    RebootWindows(true);
  end;
end.
 
 
Для работы данного примера необходима сигнатурная база. В нашем случае роли сигнатуры выступает MD5 сумма файла - ее крайне неудобно применять в реальных ситуациях (так как изменение хотя бы одного байта в файле приведет к изменению его MD5), однако для простейшего сканера такой подход удобен, так как рассчитать MD5 любого файла очень просто, например в AVZ меню "Сервис/Вычислить MD5 сумму файла". Записи базы просты - по одной MD5 сумме в каждой строке, без пробелов до или после суммы
Для примера можем создать базу my_sign_db.txt, состоящую из единственной записи:
 
44D88612FEA8A8F36DE82E1278ABB02F
 
Это MD5 сумма файла EICAR - тестового "вируса", который можно взять где угодно, например вот тут: http://support.kaspersky.ru/downloads/eicar/eicar.zip (только нужно не забыть распаковать его - так как наш пример архивы не проверяет).
 
Работа данного примера достаточно просто, разберем ее шаг за шагом:
1. В скрипте декларируется класс SignDB для работы с массивами строк типа TStringList.
2. Производится загрузка базы. Обработку ошибок в скрипте мы не производим, поэтому в случае ошибки AVZ прервет работу скрипта и выведет сообщение об ошибке
3. С помощью SearchRootkit нейтрализуем перехваты. Это необязательный шаг, однако перехватчик может маскировать или блокировать доступ к файлам зловреда, что затруднит его поиск
4. Производим поиск с помощью созданной нами функции ScanDir. У нее два параметра - стартовый каталог и режим сканирования подкаталогов. Если второй параметр TRUE, то сканируется указанный каталог и все его подкаталоги. Если FALSE, то подкаталоги не сканируются - это в частности удобно для поиска файлов в корне диска. Вызовов ScanDir может быть несколько.
5. Проверяем показания нашего счетчика DelMalwareCnt - если он больше нуля, то в ходе работы были удалены описанной базой программы. В этом случае производится сохранение протокола работы, активация AVZGuard при помощи SetAVZGuardStatus, настройка BootCleaner, после чего выполняется эвриcтическая чистка системы и перезагрузка при помощи RebootWindows. Конкретный набор операций определяется конкретной задачей, в минимуме можно оставить:
 
if DelMalwareCnt > 0 then begin
  // Эвристическая чистка
  ExecuteSysClean;
end;
 
Процедура ScanDir в данном скрипте отвечает за рекурсивный обход каталогов на диске и поиск файлов, ничего примечательного в ее работе нет, используется описанный в справке AVZ класс TFileSearch  
 
Процедура ScanFile выполняет самое интересное - изучение файла. Она выполняет следующие операции:
1. Выводит в строке статуса имя сканируемого файла через SetStatusBarText. Это чисто "косметическая" операция, которая замедляет работу скрипта, зато можно наблюдать за тем, как идет сканирование. Из реального скрипта ее можно исключить
2. При помощи CalkFileMD5 вычисляется MD5 сумма файла
3. Если MD5 вычислен успешно (в этом случае CalkFileMD5 возвращает строку с MD5 суммой) и такая MD5 сумма найдена в базе, то выполняются действия над найденным файлом - делается отметка в протоколе, а файл удаляется при условии, что DeleteMalware = true. В данном случае если DeleteMalware = true, то получаем "режим лечилки", если False - то "режим сканера" (данный параметр задается в начале скрипта).
 
Может возникнуть резонный вопрос - а как быть, если скажем имеется коллекция malware, насчитывающая сотни файлов. Не получать же вручную их MD5 для данной базы ... в данной ситуации мы можем построить несложный скрипт, который создаст базу сигнатур:
 
var
  SignDB     : TStrings; // Сигнатурная база
 
// Обработка найденного файла
Procedure ScanFile(AFileName : string);
var
  FMD5 : string;
begin
  SetStatusBarText(AFileName);
  // Вычисление MD5 суммы
  FMD5 := CalkFileMD5(AFileName);
  // Добавление в базу без повторов                              
  if (FMD5 <> '') and (SignDB.IndexOf(FMD5) < 0) then
    SignDB.Add(Trim(FMD5));
end;
 
// Сканирование каталога
Procedure ScanDir(ADirName : string; AScanSubDir : boolean);
var
  FS : TFileSearch;
begin
  ADirName := NormalDir(ADirName);
  FS := TFileSearch.Create(nil);
  FS.FindFirst(ADirName + '*.*');
  while FS.Found do begin
  // В зависимости от типа - или сканируем подкаталог, или изучаем файл
  if (FS.FileName <> '.')   and (FS.FileName <> '..') then
    if FS.IsDir and AScanSubDir and then
      ScanDir(ADirName + FS.FileName, AScanSubDir)
    else
     ScanFile(ADirName + FS.FileName);
    FS.FindNext;
  end;
  FS.Free;
end;
 
begin
  // Инициализируем сигнатурную базу
  SignDB := TStringList.Create;
  SignDB.LoadFromFile('my_sign_db.txt');
  AddToLog('Баз загружена, количество сигнатур = '+inttostr(SignDB.Count));
  // Сканирование папки с образцами Malware
  ScanDir('c:\MalwareSamplesDir\', true);
  // Сохранение базы
  AddToLog('Баз пополнена, количество сигнатур = '+inttostr(SignDB.Count));
  SignDB.SaveToFile('my_sign_db.txt');
  SignDB.Free;
end.
 
В примере использовались: AddToLog, CalkFileMD5, NormalDir, SetStatusBarText и класс TFileSearch
 
 
Как несложно заметить, данный скрипт похож на предыдущий - выполняется рекурсивное сканирование папок с коллекцией Malware, однако вместо поиска по базе ведется ее пополнение. После заполнения база сохраняется. При добавлении новых MD5 в базу производится контроль повторов, поэтому выполнять его можно многократно - он загружает текущую базу и в случае надобности пополняет ее новыми сигнатурами.