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

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

![0981_Viruses_check_ru/image1.png](https://import.viva64.com/docx/blog/0981_Viruses_check_ru/image1.png)


> Мы опубликовали и перевели эту статью с разрешения правообладателя\\\. Автор статьи – \[Stariy\]\(https://habr\.com/ru/users/Stariy2003/\)\\\. Оригинал опубликован на сайте \[Habr\]\(https://habr\.com/en/post/664870/\)\\\.



![0981_Viruses_check_ru/image2.png](https://import.viva64.com/docx/blog/0981_Viruses_check_ru/image2.png)

Рисунок 1\. Virustotal

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

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

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

Итак, имеется файл [MD5\_Hash\_Changer\.exe](https://www.virustotal.com/gui/file/59eed8eb936b73868a189c8cd26368650ae8650ce9016216f8f0b513f4660e7a), который мы подозреваем в наличии каких\-то скрытых функций\. Для начала посмотрим его с помощью PEiD:

![0981_Viruses_check_ru/image3.png](https://import.viva64.com/docx/blog/0981_Viruses_check_ru/image3.png)

Рисунок 2\. PEiD

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

![0981_Viruses_check_ru/image4.png](https://import.viva64.com/docx/blog/0981_Viruses_check_ru/image4.png)

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

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

![0981_Viruses_check_ru/image5.png](https://import.viva64.com/docx/blog/0981_Viruses_check_ru/image5.png)

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

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

```cpp
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 будет поразвесистее, и на нем следует остановиться подробнее:

![0981_Viruses_check_ru/image6.png](https://import.viva64.com/docx/blog/0981_Viruses_check_ru/image6.png)

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

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

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

```cpp
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 байт и заполняется нулями:

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

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

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

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

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

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

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

![0981_Viruses_check_ru/image7.png](https://import.viva64.com/docx/blog/0981_Viruses_check_ru/image7.png)

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

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

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

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

## Ссылки

* [https://www\.jetbrains\.com/ru\-ru/decompiler/](https://www.jetbrains.com/ru-ru/decompiler/)
* [https://www\.virustotal\.com/gui/file/59eed8eb936b73868a189c8cd26368650ae8650ce9016216f8f0b513f4660e7a](https://www.virustotal.com/gui/file/59eed8eb936b73868a189c8cd26368650ae8650ce9016216f8f0b513f4660e7a)
* [https://github\.com/ewwink/MD5\-Hash\-Changer/releases](https://github.com/ewwink/MD5-Hash-Changer/releases)