Утилита на Python для анализа вредоносных программ

Анализ вредоносных программ

В сегодняшней статье я покажу, как написать программу на Python для анализа вирусов и других вредоносных программ.

Еще по теме:

От­сле­живать про­цес­сы будем с помощью механиз­ма WMI. Это дела­ется дос­таточ­но прос­то:

Здесь notify_filter может при­нимать сле­дующие зна­чения:

  • «operation» — реаги­руем на все воз­можные опе­рации с про­цес­сами;
  • «creation» — реаги­руем толь­ко на соз­дание (запуск) про­цес­са;
  • «deletion» — реаги­руем толь­ко на завер­шение (унич­тожение) про­цес­са;
  • «modification» — реаги­руем толь­ко на изме­нения в про­цес­се.

Да­лее (в треть­ей стро­ке) мы соз­даем объ­ект‑наб­людатель process_watcher, который будет сра­баты­вать каж­дый раз, ког­да нас­тупа­ет событие с про­цес­сами, опре­делен­ное в notify_filter (в нашем слу­чае при его запус­ке). Пос­ле чего мы в бес­конеч­ном цик­ле выводим имя вновь запущен­ного про­цес­са и вре­мя его запус­ка. Вре­мя пред­став­лено в виде стро­ки в фор­мате yyyymmddHHMMSS.mmmmmmsYYY (более под­робно об этом фор­мате мож­но почитать здесь), поэто­му для вывода вре­мени в более при­выч­ной фор­ме мож­но написать неч­то вро­де фун­кции пре­обра­зова­ния фор­мата вре­мени:

Во­обще, делать такие вещи прос­то в бес­конеч­ном цик­ле не очень хорошо, поэто­му мы офор­мим все это в виде клас­са, что­бы потом запус­кать его в отдель­ном потоке. Таким обра­зом мы получим воз­можность отсле­живать в одном потоке, нап­ример, момен­ты соз­дания про­цес­сов, а в дру­гом — их унич­тожения. Итак, класс ProcessMonitor:

При ини­циали­зации клас­са мы соз­даем спи­сок свой­ств про­цес­са _process_property в виде сло­варя и опре­деля­ем объ­ект наб­людате­ля за про­цес­сами (при этом зна­чение notify_filter может быть опре­деле­но в момент ини­циали­зации клас­са и по умол­чанию задано как «operation»). Спи­сок свой­ств про­цес­са может быть рас­ширен (более под­робно о свой­ствах про­цес­сов мож­но почитать здесь).

Ме­тод update() обновля­ет поля _process_property, ког­да про­исхо­дит событие, опре­делен­ное зна­чени­ем notify_filter, а методы event_type, caption, creation_date и process_id поз­воля­ют получить зна­чения соот­ветс­тву­ющих полей спис­ка свой­ств про­цес­са (обра­ти вни­мание, что эти методы объ­явле­ны как свой­ства клас­са с исполь­зовани­ем декора­тора @property).

Те­перь это все мож­но запус­кать в отдель­ном потоке. Для начала соз­дадим класс Monitor, нас­леду­емый от клас­са Thread (из Python-модуля threading):

При желании цикл мож­но сде­лать пре­рыва­емым, нап­ример по нажатию какого‑либо сочета­ния кла­виш (для это­го нуж­но исполь­зовать воз­можнос­ти модуля keyboard и его фун­кции is_pressed()). Вмес­то вывода резуль­татов на экран мож­но писать резуль­тат работы прог­раммы в лог‑файл, для чего при­меня­ется соот­ветс­тву­ющая фун­кция, которую сле­дует исполь­зовать вмес­то print().

Да­лее уже мож­но запус­кать монито­ринг событий про­цес­сов в отдель­ных потоках:

В ито­ге получим при­мер­но сле­дующую кар­тину.

Ре­зуль­таты работы нашего Python-скрип­та для отсле­жива­ния событий соз­дания и унич­тожения про­цес­сов

Следим за файловыми операциями
Здесь, как мы и говори­ли в начале, мож­но пой­ти дву­мя путями: исполь­зовать спе­циали­зиро­ван­ные API-фун­кции Windows или воз­можнос­ти, пре­дос­тавля­емые механиз­мом WMI. В обо­их слу­чаях монито­ринг событий луч­ше выпол­нять в отдель­ном потоке, так же как мы сде­лали при отсле­жива­нии про­цес­сов. Поэто­му для начала опи­шем базовый класс FileMonitor, а затем от него нас­леду­ем класс FileMonitorAPI, в котором будем исполь­зовать спе­циали­зиро­ван­ные API-фун­кции Windows, и класс FileMonitorWMI, в котором при­меним механиз­мы WMI.

Итак, наш базовый класс будет выг­лядеть при­мер­но так:

Здесь при ини­циали­зации так­же исполь­зует­ся параметр notify_filter (его воз­можные зна­чения опре­деля­ются в зависи­мос­ти от того, исполь­зуют­ся API или WMI) и параметр **kwargs, с помощью которо­го опре­деля­ется путь к отсле­жива­емо­му фай­лу или катало­гу, его имя, рас­ширение и про­чее. Эти зна­чения так­же зависят от исполь­зования API или WMI и будут кон­кре­тизи­рова­ны уже в клас­сах‑нас­ледни­ках. При ини­циали­зации клас­са соз­дает­ся сло­варь _event_property для хра­нения свой­ств события: имя дис­ка, путь к фай­лу, имя фай­ла, рас­ширение, мет­ка вре­мени и тип события (по ана­логии с клас­сом монито­рин­га событий про­цес­сов). Ну а с осталь­ными метода­ми нашего базово­го клас­са, я думаю, все и так понят­но: они поз­воля­ют получить зна­чения соот­ветс­тву­юще­го поля из сло­варя свой­ств события.

Используем API Windows

В осно­ву это­го вари­анта реали­зации монито­рин­га будет положе­на фун­кция WaitForSingleObject(). Упо­мяну­тая фун­кция занима­ется тем, что ждет, ког­да объ­ект (хендл которо­го передан в качес­тве пер­вого парамет­ра) перей­дет в сиг­наль­ное сос­тояние, и воз­вра­щает WAIT_OBJECT_0, ког­да объ­ект изме­нит свое сос­тояние. Помимо этой фун­кции, в Windows есть весь­ма полез­ная фун­кция ReadDirectoryChangesW(), наз­начение которой — сле­дить за изме­нени­ями фай­ла или катало­га, ука­зан­ного в одном из парамет­ров фун­кции. Так­же в про­цес­се монито­рин­га мы задей­ству­ем API CreateFile() и CreateEvent().

Итак, нач­нем.

Зна­чения парамет­ра notify_filter кор­релиру­ют с кон­стан­тами FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_DIR_NAME или FILE_NOTIFY_CHANGE_LAST_WRITE. Для их пре­обра­зова­ния мы ниже опи­шем спе­циаль­ный метод. Так­же опре­делим метод update(), с помощью которо­го и будем обновлять све­дения о про­изо­шед­шем событии.

На­пишем метод уста­нов­ки «наб­людате­ля» за событи­ями в фай­ловой сис­теме (в ней мы задей­ству­ем фун­кцию ReadDirectoryChangesW()):

Пос­коль­ку в качес­тве одно­го из парамет­ров ReadDirectoryChangesW() при­нима­ет кон­стан­ты, опре­деля­ющие тип отсле­жива­емо­го события, то опре­делим метод, пре­обра­зующий зна­чения парамет­ра notify_filter в ука­зан­ные кон­стан­ты.

Здесь для прос­тоты показа­но пре­обра­зова­ние в кон­стан­ту толь­ко одно­го зна­чения notify_filter, по ана­логии мож­но опи­сать пре­обра­зова­ние дру­гих зна­чений notify_filter в кон­стан­ты FILE_NOTIFY_CHANGE_DIR_NAME или  FILE_NOTIFY_CHANGE_LAST_WRITE.

Да­лее опре­делим методы, воз­вра­щающие сох­ранен­ные в буфере _buffer свой­ства события при нас­тупле­нии это­го события. Воз­вра­щающий тип события метод выг­лядит так:

В этом методе исполь­зует­ся кон­стан­та _ACTIONS, содер­жащая воз­можные дей­ствия с отсле­жива­емым фай­лом или катало­гом. Эта кон­стан­та опре­деле­на в виде сло­варя сле­дующим обра­зом:

Ме­тод, воз­вра­щающий путь к отсле­жива­емо­му фай­лу:

Ме­тод, воз­вра­щающий имя отсле­жива­емо­го фай­ла, которое было сох­ранено в _buffer при нас­тупле­нии события:

За­дей­ство­вать это все мож­но сле­дующим обра­зом (по ана­логии с монито­рин­гом про­цес­сов):

Используем WMI

Мо­нито­ринг событий фай­ловой сис­темы с исполь­зовани­ем WMI похож на ранее рас­смот­ренное отсле­жива­ние событий с про­цес­сами. Для того что­бы сле­дить за изме­нени­ями кон­крет­ного фай­ла, вос­поль­зуем­ся сле­дующим кодом:

Здесь вид­но, что, помимо парамет­ра notify_filter, еще переда­ются парамет­ры, опре­деля­ющие файл, события которо­го необ­ходимо отсле­живать. Обра­ти вни­мание на осо­бен­ность написа­ния парамет­ра Path с модифи­като­ром r (он нужен для того, что­бы получить тре­буемое количес­тво сле­шей‑раз­делите­лей в стро­ке).

Для отсле­жива­ния изме­нений в катало­ге, а не в фай­ле вмес­то клас­са CIM_DataFile необ­ходимо исполь­зовать класс CIM_Directory (более под­робно о работе с фай­ловой сис­темой с помощью WMI мож­но почитать здесь):

Ко­неч­но, все это желатель­но офор­мить в виде клас­са — нас­ледни­ка от нашего базово­го клас­са FileMonitor, опи­сан­ного выше, что­бы монито­ринг событий фай­ловой сис­темы мож­но было запус­тить в отдель­ном потоке. В целом пол­ную реали­зацию опи­сан­ных клас­сов по монито­рин­гу фай­ловой сис­темы мож­но пос­мотреть на моем гит­хабе.

Мониторим действия с реестром
Так же как и события фай­ловой сис­темы, события реес­тра мож­но отсле­живать либо с помощью спе­циали­зиро­ван­ных API-фун­кций, либо с исполь­зовани­ем механиз­мов WMI. Пред­лагаю, как и в слу­чае с событи­ями фай­ловой сис­темы, начать с написа­ния базово­го клас­са RegistryMonitir, от которо­го нас­ледовать клас­сы RegistryMonitorAPI и RegistryMomitorWMI:

Здесь в **kwargs переда­ются парамет­ры Hive, RootPath, KeyPath и ValueName, зна­чения которых и опре­деля­ют мес­то в реес­тре, за которым мы будем сле­дить. Зна­чение парамет­ра notify_filter, как и в пре­дыду­щих слу­чаях, опре­деля­ет отсле­жива­емые дей­ствия.

Используем API

Здесь мы так же, как и в слу­чае с фай­ловой сис­темой, исполь­зуем связ­ку API-фун­кций CreateEvent() и WaitForSingleObject(). При этом хендл отсле­жива­емо­го объ­екта получим с исполь­зовани­ем RegOpenKeyEx() со зна­чени­ем пос­ледне­го парамет­ра (которым опре­деля­ется дос­туп к жела­емо­му клю­чу реес­тра):

Фун­кция _get_hive_const() пре­обра­зует имя кус­та реес­тра в соот­ветс­тву­ющую кон­стан­ту (HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS или HKEY_CURRENT_CONFIG):

Здесь _get_notify_filter(), исхо­дя из зна­чения notify_filter, выда­ет кон­стан­ту, опре­деля­ющую событие, на которое будет реак­ция (REG_NOTIFY_CHANGE_NAME, REG_NOTIFY_CHANGE_LAST_SET), или их дизъ­юнкцию:

Ме­тод update() прак­тичес­ки пол­ностью пов­торя­ет таковой из клас­са FileMonitorAPI().

Во­обще, у API-фун­кций, пред­назна­чен­ных для отсле­жива­ния изме­нений в фай­ловой сис­теме или в реес­тре, есть осо­бен­ность, зак­люча­ющаяся в том, что зна­чение вре­мени нас­тупле­ния события сами эти фун­кции не фик­сиру­ют (в отли­чие от клас­сов WMI), и в слу­чае необ­ходимос­ти это надо делать самому (к при­меру, исполь­зуя datatime).

Дан­ный кусочек кода необ­ходимо вста­вить в метод update() (как клас­са FileMonitorAPI, так и клас­са RegistryMonitorAPI) пос­ле про­вер­ки усло­вия появ­ления события, и в перемен­ной timestamp запишет­ся соот­ветс­тву­ющее вре­мя.

Используем WMI

Здесь име­ется два отли­чия отно­ситель­но клас­са FileMonitorWMI. Пер­вое: события, свя­зан­ные с реес­тром, явля­ются внеш­ними (в то вре­мя как события, свя­зан­ные с про­цес­сами и фай­ловой сис­темой, — внут­ренние). Вто­рое: для монито­рин­га изме­нений в вет­ке реес­тра, клю­че реес­тра или зна­чении, записан­ном в какой‑либо ключ, необ­ходимо исполь­зовать раз­ные клас­сы WMI: RegistryTreeChangeEvent, RegistryKeyChangeEvent или RegistryValueChangeEvent.

Со­ответс­твен­но, уста­нов­ка «наб­людате­ля» при ини­циали­зации экзем­пля­ра клас­са RegisterMonitorWMI в дан­ном слу­чае будет выг­лядеть так:

Все осталь­ное (в том чис­ле и метод update()), в прин­ципе, очень похоже на FileMonitorWMI. Пол­ностью всю реали­зацию клас­сов RegisterMonitor, RegisterMonitorAPI и RegisterMonitorWMI мож­но пос­мотреть здесь.

Мониторим вызовы API-функций

Здесь, как мы и говори­ли, нам понадо­бит­ся WinAppDbg. Вооб­ще, с помощью это­го модуля мож­но не толь­ко перех­ватывать вызовы API-фун­кций, но и делать очень мно­го дру­гих полез­ных вещей (более под­робно об этом мож­но узнать в докумен­тации WinAppDbg). К сожале­нию, помимо того что модуль ори­енти­рован исклю­читель­но для работы со вто­рой вер­сией Python, он исполь­зует стан­дар­тный механизм отладки Windows. Поэто­му если ана­лизи­руемая прог­рамма осна­щена хотя бы прос­тей­шим модулем анти­отладки (а об этих модулях мож­но почитать, нап­ример, в стать­ях «Ан­тиот­ладка. Теория и прак­тика защиты при­ложе­ний от дебага» и «Биб­лиоте­ка анти­отладчи­ка)», то перех­ватить вызовы API не получит­ся. Тем не менее это весь­ма мощ­ный инс­тру­мент, потому знать о его сущес­тво­вании и хотя бы в минималь­ной сте­пени овла­деть его воз­можнос­тями будет весь­ма полез­но.

Итак, для перех­вата API-фун­кции будем исполь­зовать класс EventHandler, от которо­го нас­леду­ем свой класс (назовем его, к при­меру APIIntercepter). В нем мы реали­зуем нуж­ные нам фун­кции.

Как вид­но, в сос­таве клас­са EventHandler опре­делен сло­варь apiHooks, в который при опи­сании клас­са‑нас­ледни­ка необ­ходимо про­писать все перех­ватыва­емые фун­кции, не забыв про наз­вания DLL-биб­лиотек. Фор­ма записи сле­дующая:

Что­бы пра­виль­но сфор­мировать дан­ный сло­варь, нуж­но знать про­тоти­пы перех­ватыва­емых фун­кций (то есть перечень и типы переда­ваемых в фун­кции парамет­ров). Все это мож­но пос­мотреть в MSDN.

Пос­ле того как мы опре­дели­лись с переч­нем перех­ватыва­емых фун­кций, необ­ходимо для каж­дой перех­ватыва­емой API-фун­кции написать два метода: пер­вый будет сра­баты­вать при вызове фун­кции, вто­рой — при завер­шении ее работы. Для фун­кции GetProcAddress() эти методы выг­лядят так:

В метод pre_GetProcAddress пер­вым парамет­ром переда­ется объ­ект event, вто­рым — адрес воз­вра­та, треть­им и пос­леду­ющи­ми — парамет­ры перех­ватыва­емой фун­кции (здесь это прос­то перемен­ные, зна­чения которых будут записа­ны пос­ле оче­ред­ного вызова перех­ватыва­емой фун­кции, пос­ле чего их мож­но вывес­ти с помощью print). В метод post_GetProcAddress() пер­вым парамет­ром так­же переда­ется объ­ект event, вто­рым — воз­вра­щаемое перех­ватыва­емой фун­кци­ей зна­чение (реаль­ные зна­чения туда будут записа­ны пос­ле завер­шения работы перех­вачен­ной API-фун­кции).

Да­лее напишем фун­кцию, которая и уста­новит опи­сан­ный нами перех­ватчик:

И запус­тим эту фун­кцию:

В ито­ге дол­жна получить­ся при­мер­но такая кар­тина.

Пе­рех­ват фун­кции GetProcAddress (вид­но, что ана­лизи­руемый файл, ско­рее все­го, пыта­ется внед­рить что‑то в уда­лен­ный поток)
В методах, вызыва­емых при вызове и завер­шении работы API-фун­кции (в нашем слу­чае это pre_GetProcAddress() и post_GetProcAddress()), мож­но прог­рамми­ровать какие угод­но дей­ствия, а не толь­ко вывод информа­ции о вызове API-фун­кции, как это сде­лали мы.

Заключение

Исполь­зуя Python и нес­коль­ко полез­ных пакетов, мож­но получить доволь­но боль­шой объ­ем информа­ции о событи­ях, про­исхо­дящих в сис­теме при запус­ке той или иной прог­раммы. Конеч­но, все это желатель­но делать в изо­лиро­ван­ном окру­жении (осо­бен­но если ана­лизи­ровать край­не подоз­ритель­ные прог­раммы).

Пол­ностью написан­ный код клас­сов ана­лиза событий про­цес­сов, фай­ловой сис­темы и реес­тра мож­но пос­мотреть на моем гит­хабе. Он так­же при­сутс­тву­ет в PyPi, что поз­воля­ет уста­новить его коман­дой pip install pywinwatcher и исполь­зовать по сво­ему усмотре­нию.

Дима (Kozhuh)

Эксперт в кибербезопасности. Работал в ведущих компаниях занимающихся аналитикой компьютерных угроз. Анонсы новых статей в Телеграме.

Добавить комментарий