>
>
>
Как я программу на вирусы проверял

Гость
Статей: 23

Как я программу на вирусы проверял

Поводом для написания данной статьи послужил диалог в одном телеграмном чате. Кто-то выложил программу для "уникализации" файлов путем изменения хэша MD5, а другой бдительный участник чата проверил ее на Virustotal и из-за двух положительных (и 68 отрицательных) результатов обвинил программу в наличии недекларированных функций, в том числе даже в краже паролей от различных аккаунтов, а всех установивших ее — в "излишках" ума. Увещевания его и рассказ о возможных ложноположительных срабатываниях не дали желаемого результата, беседа перестала быть конструктивной и затухла.

Мы опубликовали и перевели эту статью с разрешения правообладателя. Автор статьи – Stariy. Оригинал опубликован на сайте Habr.

Рисунок 1. Virustotal

Но я (как один из участников того диспута) потерял покой, сон и аппетит, ведь, с одной стороны, если антивирус ругается, то нет повода ему не верить и стоит быть осторожным. С другой — два не самых популярных антивируса, было бы из-за чего беспокоиться. Но самое главное — а если вообще было бы 0 детектов, дает ли это основания быть уверенным в программе? И как быть в таком случае? Ну и еще в довесок было интересно - а как, собственно, меняется MD5, просто добавлением лишних байтов (самый очевидный способ) или как-то по-умному?

Решил проверить, а заодно и рассказать алгоритм своих размышлений и действий, быть может, кому-то это пригодится. На экспертность не претендую, чисто на бытовом уровне поковыряемся.

Исследуем программу

Итак, имеется файл MD5_Hash_Changer.exe, который мы подозреваем в наличии каких-то скрытых функций. Для начала посмотрим его с помощью PEiD:

Рисунок 2. PEiD

Строчка про C#/.NET наводит на мысль о том, что программа написана на Шарпе, что в ряде случаев может позволить обойтись без дизассемблера. Скачиваем бесплатную программу JetBrains dotPeek, которая позволяет получить код на C# из exe-файла (при условии, естественно, что программа изначально написана на C#), и натравливаем ее на исследуемый файл:

Рисунок 3. Смотрим программу в dotPeek

Для начала следует посмотреть раздел Metadata, в котором первым делом необходимо обратить внимание на используемые строки, в которых могут попасться интересные имена, пути, IP-адреса и прочее.

Рисунок 4. Строковые ресурсы в dotPeek

При необходимости можно сразу посмотреть, где конкретно используется интересная строка, если такая окажется. В моем случае ничего подозрительного не обнаружилось, и я перешел к разделу с кодом. Как выяснилось, исходный код программы содержит два класса, Program и MainForm, при этом класс Program вполне стандартный и содержит лишь код запуска главного окна приложения:

using System; using System.Windows.Forms;
namespace MD5_Hash_Changer {
  internal static class Program {
    [STAThread] private static void Main() { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false);
      Application.Run((Form) new MainForm()); 
    } 
  } 
}

Класс MainForm будет поразвесистее, и на нем следует остановиться подробнее:

Рисунок 5. Код приложения

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

За это отвечает функция

private void changeMD5(string[] fileNames) {
  Random random = new Random();
  Thread.Sleep(1000);
  this.Invoke((Delegate) (() => this.btnStartMD5.Enabled = true));
  for (int i = 0; i < fileNames.Length; ++i) {
    if (!this.running) {
      this.Invoke((Delegate) (() => {
        this.btnStartMD5.Text = "Start Change MD5";
        this.running = false; 
      }));
      break; 
    } 
    int length1 = random.Next(2, 7);
    byte[] buffer = new byte[length1];
    for (int index = 0; index < length1; ++index)
      buffer[index] = (byte) 0;
    long length2 = new FileInfo(fileNames[i]).Length;
    if (length2 == 0L) {
      this.Invoke(
        (Delegate) (() => this.dgvMD5.Rows[i].Cells[3].Value = (object) "Empty")
      ); 
    } 
    else {
      using (FileStream fileStream = new FileStream(fileNames[i],
                                                    FileMode.Append)) 
        fileStream.Write(buffer, 0, buffer.Length);
      int bufferSize = length2 > 1048576L ? 1048576 : 4096;
      string md5hash = "";
      using (MD5 md5 = MD5.Create()) {
        using (FileStream inputStream = new FileStream(fileNames[i],
                                                       FileMode.Open,
                                                       FileAccess.Read,
                                                       FileShare.Read,
                                                       bufferSize)) 
          md5hash = BitConverter.ToString(md5.ComputeHash((Stream) inputStream))
                                .Replace("-", "");
      } 
      this.Invoke((Delegate) (() => { 
        if (this.dgvMD5.Rows[i].Cells[2].Value.ToString() != "") 
          this.dgvMD5.Rows[i].Cells[1].Value = 
            this.dgvMD5.Rows[i].Cells[2].Value;
        this.labelItem.Text = (i + 1).ToString();
        this.progressBarStatus.Value = i + 1;
        this.dgvMD5.Rows[i].Cells[2].Value = (object) md5hash;
        this.dgvMD5.Rows[i].Cells[3].Value = (object) "OK"; 
      })); 
    } 
  } 
  this.Invoke((Delegate) (() => { 
    this.btnStartMD5.Text = "Start Change MD5"; this.running = false; 
  }));
}

На вход функция получает список файлов, которые следует обработать, и в цикле пробегает по нему. Для каждого файла генерируется буфер случайной длины в интервале от 2 до 7 байт и заполняется нулями:

int length1 = random.Next(2, 7);
byte[] buffer = new byte[length1];
for (int index = 0; index < length1; ++index) 
  buffer[index] = (byte) 0;

Затем этот буфер просто дописывается в конец файла:

using (FileStream fileStream = new FileStream(fileNames[i],
                                              FileMode.Append))
  fileStream.Write(buffer, 0, buffer.Length);

и снова вычисляется MD5-хэш уже для измененного файла:

using (FileStream inputStream = new FileStream(fileNames[i],
                                               FileMode.Open,
                                               FileAccess.Read,
                                               FileShare.Read,
                                               bufferSize))
  md5hash = BitConverter.ToString(md5.ComputeHash((Stream) inputStream))
                        .Replace("-", "");

Все, больше ничего интересного тут не происходит. Как видим, программа весьма тривиальна и такого рода "уникализация" конечно формально изменяет хэш файла, но... Судить вам, насколько она пригодна для решения ваших задач.

Ну и напоследок, наверное, следует проверить на практике, что же происходит с файлами. Возьмем для примера Рисунок 1 из данной статьи и натравим на нее программу, а затем сравним файлы до обработки и после.

Рисунок 6. Сравниваем файлы

Во-первых, размер файла после обработки увеличился на 6 байт, во-вторых, как видно из скриншота, в конце файла появились 6 нулевых байт. Очевидно, это соответствует установленному в ходе изучения исходного кода алгоритму.

Важное дополнение

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

Ссылки