Вступление
Поводом для написания данной статьи послужил диалог в одном телеграмном чате. Кто-то выложил программу для "уникализации" файлов путем изменения хэша MD5, а другой бдительный участник чата проверил ее на Virustotal и из-за двух положительных (и 68 отрицательных) результатов обвинил программу в наличии недекларированных функций, в том числе даже в краже паролей от различных аккаунтов, а всех установивших ее - в "излишках" ума. Увещевания его и рассказ о возможных ложноположительных срабатываниях не дали желаемого результата, беседа перестала быть конструктивной и затухла.
Но я (как один из участников того диспута) потерял покой, сон и аппетит, ведь с одной стороны, если антивирус ругается, то нет повода ему не верить и стоит быть осторожным. С другой - два не самых популярных антивируса, было бы из-за чего беспокоиться. Но самое главное - а если вообще было бы 0 детектов, дает ли это основания быть уверенным в программе? И как быть в таком случае? Ну и еще в довесок было интересно - а как, собственно, меняется MD5, просто добавлением лишних байтов (самый очевидный способ) или как-то по умному?
Решил проверить, а заодно и рассказать алгоритм своих размышлений и действий, быть может, кому-то это пригодится. На экспертность не претендую, чисто на бытовом уровне поковыряемся.
Исследуем программу
Итак, имеется файл MD5_Hash_Changer.exe, который мы подозреваем в наличии каких то скрытых функций. Для начала, посмотрим его с помощью PEiD:
Строчка про C#/.NET наводит на мысль о том, что программа написана на Шарпе, что в ряде случаев может позволить обойтись без дизассемблера. Скачиваем бесплатную программу JetBrains dotPeek, которая позволяет получить код на C# из exe-файла (при условии, естественно, что программа изначально написана на C#), и натравливаем ее на исследуемый файл:
Для начала следует посмотреть раздел Metadata, в котором первым делом необходимо обратить внимание на используемые строки, в которых могут попасться интересные имена, пути, IP-адреса и прочее.
При необходимости можно сразу посмотреть, где конкретно используется интересная строка, если такая окажется. В моем случае ничего подозрительного не обнаружилось и я перешел к разделу с кодом. Как выяснилось, исходный код программы содержит два класса, 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 будет поразвесистее, и на нем следует остановиться подробнее:
Как видно, при запуске формы запускается функция 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("-", "");
Все, больше ничего интересного тут не происходит. Как видим, программа весьма тривиальна и такого рода "уникализация" конечно формально изменяет хэш файла, но... Судить вам, насколько она пригодна для решения ваших задач.
Ну и напоследок, наверное, следует проверить на практике, что же происходит с файлами. Возьмем для примера первую картинку из данной статьи и натравим на нее программу, а затем сравним файлы до обработки и после.
Во первых, размер файла после обработки увеличился на 6 байт, во вторых, как видно из скриншота, в конце файла появились 6 нулевых байт. Очевидно, это соответствует установленному в ходе изучения исходного кода алгоритму.
Важное дополнение
В дополнение считаю необходимым отметить, что описанный мной алгоритм проверки не позволяет сделать однозначного вывода о наличии в программе зловредного функционала, ведь существуют способы внедрения в ехе-файл на более низком уровне, поэтому не следует пренебрегать также и анализом возможного сетевого трафика после запуска в "песочнице", и более детальным исследованием собственно исполняемого кода, однако это требует какой-никакой квалификации и опыта, а описанный мной алгоритм вполне доступен даже неподготовленному и далекому от реверса пользователю.
Ссылки
Комментарии (17)
Aleho
08.05.2022 19:53Думается что все же не стоит торопится называть Malwarebytes не самым популярным антивирусом.
И чуть более полная картинка с VirusTotal выглядит так:Stariy2003 Автор
08.05.2022 23:04+1а разве он самый популярный?
Aleho
08.05.2022 23:21-1в сравнении с MaxSecure наверняка.
Stariy2003 Автор
08.05.2022 23:32А при чем тут MaxSecure? Почему именно с ним надо сравнивать?
Aleho
08.05.2022 23:48-2а почему вы решили что Malwarebytes не самый популярный антивирус?
а почему вас до потери сна и аппетита заинтересовала именно эта программа?
а почему вы не прошлись по списку предупреждений на вирустотале и не рознесли его в пух и прах?
а почему вы даже туда не заглянули?
Не очень констуктивно, не правда ли.
Продолжать в таком духе смысла нет.
qw1
08.05.2022 22:55+5Исходники тут есть: github.com/ewwink/MD5-Hash-Changer/releases
И бинарник там же, на который VirusTotal даёт 2 детекта.
DaemonGloom
09.05.2022 07:59+3Из личного опыта — софтина на Go, которую прогнали через UPX для сжатия, успешно давала кучу детектов. Без сжатия — никаких детектов, всё прекрасно. Так что теперь она занимает на 5 мегабайт больше на диске у каждого пользователя, зато без ругани антивируса.
Stariy2003 Автор
09.05.2022 08:59У меня такой же эффект был после применения pyinstaller к совершенно невинному скрипту.
qw1
09.05.2022 09:59+2Вредно сжимать программы UPX-ом.
Каждый запуск придётся читать весь файл целиком и тратить время на расжатие, когда несжатые файлы просто мапятся в память, и странички с кодом и данными подчитываются в момент первого доступа к ним. То есть файл в 70 мегабайт может при запуске считаться на 100КБ, если остальной код не получил управление.SergeyMax
09.05.2022 11:40-2Сомнительно. Как минимум будет проверяться подпись файла, а для этого потребуется полностью его прочесть.
qw1
09.05.2022 17:42Подпись проверяется только при запуске с повышением привилегий (при отображении окна UAC).
Но обычно на программах, которые хотят паковать UPX-ом, нет подписи.
qw1
09.05.2022 18:01+1Кстати, читать весь файл не надо.
Я взял explorer.exe (размер 5.1МБ) из Windows 10, и вытащил PKCS#7 Signed Message, это примерно 54КБ. Из них 2 сертификата по 1.2КБ и Payload на 45КБ. Подпись заверяет целостность Payload, и его можно проверить, не читая весь файл. Внутри Payload хеши страниц, так что теоретически загрузчик может проверять хеши только тех страниц, которые он подкачивает в память (но не знаю, пользуется ли он на практике такой возможностью. Я думаю, что при запуске програм в userspace, подпись не влияет на возможность запуска файла).
tbl
Помимо того, как меняются файлы, я бы посмотрел на то, как программа формирует fileName: как бы там не было формирования странных урлов. Анализ в песочнице в этом случае был бы крайне полезен.