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

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

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

Еще по теме:

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

import wmi
notify_filter = "creation"
process_watcher = wmi.WMI().Win32_Process.watch_for(notify_filter)
while True:
    new_process = process_watcher()
    print(new_process.Caption)
    print(new_process.CreationDate)

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

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

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

def date_time_format(date_time):
    year = date_time[:4]
    month = date_time[4:6]
    day = date_time[6:8]
    hour = date_time[8:10]
    minutes = date_time[10:12]
    seconds = date_time[12:14]
    return '{0}/{1}/{2} {3}:{4}:{5}'.format(day, month, year, hour, minutes, seconds)

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

class ProcessMonitor():
    def __init__(self, notify_filter='operation'):
        self._process_property = {
            'Caption': None,
            'CreationDate': None,
            'ProcessID': None,
        }
        self._process_watcher = wmi.WMI().Win32_Process.watch_for(
            notify_filter
        )
    def update(self):
        process = self._process_watcher()
        self._process_property['EventType'] = process.event_type
        self._process_property['Caption'] = process.Caption
        self._process_property['CreationDate'] = process.CreationDate
        self._process_property['ProcessID'] = process.ProcessID
    @property
    def event_type(self):
        return self._process_property['EventType']
    @property
    def caption(self):
        return self._process_property['Caption']
    @property
    def creation_date(self):
        return date_time_format(self._process_property['CreationDate'])
    @property
    def process_id(self):
        return self._process_property['ProcessID']

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

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

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

from threading import Thread
import wmi
import pythoncom
...
# Не забываем вставить здесь описание класса ProcessMonitor
...
class Monitor(Thread):
    def __init__(self, action):
        self._action = action
        Thread.__init__(self)
    def run(self):
        pythoncom.CoInitialize()
        proc_mon = ProcessMonitor(self._action)
        while True:
            proc_mon.update()
            print(
                proc_mon.creation_date,
                proc_mon.event_type,
                proc_mon.name,
                proc_mon.process_id
            )
        pythoncom.CoUninitialize()

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

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

# Отслеживаем события создания процессов
mon_creation = Monitor('creation')
mon_creation.start()
# Отслеживаем события уничтожения процессов
mon_deletion = Monitor('deletion')
mon_deletion.start()

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

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

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

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

class FileMonitor:
    def __init__(self, notify_filter, **kwargs):
        self._notify_filter = notify_filter
        self._kwargs = kwargs
        self._event_properties = {
            'Drive': None,
            'Path': None,
            'FileName': None,
            'Extension': None,
            'Timestamp': None,
            'EventType': None,
        }
    @property
    def drive(self):
        return self._event_properties['Drive']
    @property
    def path(self):
        return self._event_properties['Path']
    @property
    def file_name(self):
        return self._event_properties['FileName']
    @property
    def extension(self):
        return self._event_properties['Extension']
    @property
    def timestamp(self):
        return self._event_properties['Timestamp']
    @property
    def event_type(self):
        return self._event_properties['EventType']

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

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

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

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

# Подключим все необходимые модули
import pywintypes
import win32api
import win32event
import win32con
import win32file
import winnt
class FileMonitorAPI(FileMonitor):
    def __init__(self, notify_filter = 'FileNameChange', **kwargs):
        # Здесь в качестве **kwargs необходимо указывать
        # путь к файлу или директории
        # в виде "Path=r'e:\\example\\file.txt'"
        FileMonitor.__init__(self, notify_filter, **kwargs)
        # Получаем хендл нужного файла или каталога
        self._directory = win32file.CreateFile(
            self._kwargs['Path'],
            winnt.FILE_LIST_DIRECTORY,
            win32con.FILE_SHARE_READ |
            win32con.FILE_SHARE_WRITE,
            None,
            win32con.OPEN_EXISTING,
            win32con.FILE_FLAG_BACKUP_SEMANTICS |
            win32con.FILE_FLAG_OVERLAPPED,
            None
        )
        # Инициализируем структуру типа OVERLAPPED
        self._overlapped = pywintypes.OVERLAPPED()
        # и поместим в нее хендл объекта «событие» в сброшенном состоянии
        self._overlapped.hEvent = win32event.CreateEvent(
            None,
            False,
            False,
            None
        )
        # Выделим память, куда будет записана информация
        # об отслеживаемом файле или каталоге
        self._buffer  = win32file.AllocateReadBuffer(1024)
        # Здесь будет число байтов сведений о файле или каталоге,
        # записанных при наступлении события
        self._num_bytes_returned = 0
        # Установим «наблюдатель» за событиями (его мы опишем ниже)
        self._set_watcher()

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

def update(self):
    while True:
        # Ждем наступления события (поскольку второй параметр 0, то время
        # ожидания бесконечно)
        result = win32event.WaitForSingleObject(self._overlapped.hEvent, 0)
        if result == win32con.WAIT_OBJECT_0:
            # Если событие произошло, то получим размер сохраненных сведений о событии
            self._num_bytes_returned = win32file.GetOverlappedResult(
                self._directory,
                self._overlapped,
                True
            )
            # Поместим информацию о событии в _event_properties
            self._event_properties['Path'] = self._get_path()
            self._event_properties['FileName'] = self._get_file_name()
            ...
            self._set_watcher()
            break

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

def _set_watcher(self):
        win32file.ReadDirectoryChangesW(
            self._directory,
            self._buffer,
            True,
            self._get_notify_filter_const(),
            self._overlapped,
            None
        )

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

def _get_notify_filter_const(self):
    if self._notify_filter == 'FileNameChange':
        return win32con.FILE_NOTIFY_CHANGE_FILE_NAME
    ...

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

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

def _get_event_type(self):
    result = ''
    if self._num_bytes_returned != 0:
        result = self._ACTIONS.get(win32file.FILE_NOTIFY_INFORMATION(
            self._buffer, self._num_bytes_returned)[0][0], 'Uncnown')
    return result

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

_ACTIONS = {
    0x00000000: 'Unknown action',
    0x00000001: 'Added',
    0x00000002: 'Removed',
    0x00000003: 'Modified',
    0x00000004: 'Renamed from file or directory',
    0x00000005: 'Renamed to file or directory'
}

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

def _get_path(self):
    result = ''
    if self._num_bytes_returned != 0:
        result = win32file.GetFinalPathNameByHandle(
            self._directory,
            win32con.FILE_NAME_NORMALIZED
        )
    return result

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

def _get_file_name(self):
    result = ''
    if self._num_bytes_returned != 0:
        result = win32file.FILE_NOTIFY_INFORMATION(
            self._buffer, self._num_bytes_returned)[0][1]
    return result

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

from threading import Thread
import pywintypes
import win32api
import win32event
import win32con
import win32file
import winnt
...
# Не забываем вставить здесь описание классов FileMonitor и FileMonitorAPI
...
# Опишем класс Monitor, наследуемый от Thread
class Monitor(Thread):
    def __init__(self):
        Thread.__init__(self)
    def run(self):
        # Используем значение notify_filter по умолчанию
        file_mon = pymonitor.FileMonitorAPI(Path=r'e:\\example')
        while True:
            file_mon.update()
            print(file_mon.timestamp,
                file_mon.path,
                file_mon.file_name,
                file_mon.event_type
            )
# Создадим экземпляр класса Monitor
mon = Monitor()
# Запустим процесс мониторинга
mon.start()

Используем WMI

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

import wmi
notify_filter = "creation"
# «Наблюдатель» за изменениями в файле
file_watcher = wmi.WMI().CIM_DataFile.watch_for(
    notify_filter, Drive = 'e:', Path=r'\\example_dir\\', FileName='example_file', Extension = 'txt'
)
while True:
    # Выводим информацию о событии с файлом
    new_file = file_watcher()
    print(new_file.timestamp)
    print(new_file.event_type)

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

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

directory_watcher = wmi.WMI().CIM_Directory.watch_for(
    notify_filter, Drive = 'e:', Path=r'\\example_dir\\'
)

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

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

class RegistryMonitor:
    def __init__(self, notify_filter, **kwargs):
        self._notify_filter = notify_filter
        self._kwargs = kwargs
        self._event_properties = {
            'Hive': None,
            'RootPath': None,
            'KeyPath': None,
            'ValueName': None,
            'Timestamp': None,
            'EventType': None,
        }
    @property
    def hive(self):
        return self._event_properties['Hive']
    @property
    def root_path(self):
        return self._event_properties['RootPath']
    @property
    def key_path(self):
        return self._event_properties['KeyPath']
    @property
    def value_name(self):
        return self._event_properties['ValueName']
    @property
    def timestamp(self):
        return self._event_properties['Timestamp']
    @property
    def event_type(self):
        return self._event_properties['EventType']

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

Используем API

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

class RegistryMonitorAPI(RegistryMonitor):
    def __init__(self, notify_filter='UnionChange', **kwargs):
        RegistryMonitor.__init__(self, notify_filter, **kwargs)
        # Создаем объект «событие»
        self._event = win32event.CreateEvent(None, False, False, None)
        # Открываем нужный ключ с правами доступа на уведомление изменений
        self._key = win32api.RegOpenKeyEx(
            self._get_hive_const(),
            self._kwargs['KeyPath'],
            0,
            win32con.KEY_NOTIFY
        )
        # Устанавливаем наблюдатель
        self._set_watcher()

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

def _get_hive_const(self):
    if self._kwargs['Hive'] == 'HKEY_CLASSES_ROOT':
        return win32con.HKEY_CLASSES_ROOT
    ...
    if self._kwargs['Hive'] == 'HKEY_CURRENT_CONFIG':
        return win32con.HKEY_CURRENT_CONFIG

Сам же «наб­людатель» реали­зуем с помощью API-фун­кции RegNotifyChangeKeyValue():
def _set_watcher(self):
    win32api.RegNotifyChangeKeyValue(
        self._key,
        True,
        self._get_notify_filter_const(),
        self._event,
        True
    )

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

def _get_notify_filter_const(self):
    if self._notify_filter == 'NameChange':
        return win32api.REG_NOTIFY_CHANGE_NAME
    if self._notify_filter == 'LastSetChange':
        return win32api.REG_NOTIFY_CHANGE_LAST_SET
    if self._notify_filter == 'UnionChange':
        return (
            win32api.REG_NOTIFY_CHANGE_NAME |
            win32api.REG_NOTIFY_CHANGE_LAST_SET
        )

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

def update(self):
    while True:
        result = win32event.WaitForSingleObject(self._event, 0)
        if result == win32con.WAIT_OBJECT_0:
            self._event_properties['Hive'] = self._kwargs['Hive']
            self._event_properties['KeyPath'] = self._kwargs['KeyPath']
            ...
            self._set_watcher()
            break

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

timestamp = datetime.datetime.fromtimestamp(
    datetime.datetime.utcnow().timestamp()
)

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

Используем WMI

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

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

def __init__(self, notify_filter='ValueChange', **kwargs):
    RegistryMonitor.__init__(self, notify_filter, **kwargs)
    # Подключаем пространство имен с классами внешних событий
    wmi_obj = wmi.WMI(namespace='root/DEFAULT')
    # Мониторим изменения ветки реестра
    if notify_filter == 'TreeChange':
        self._watcher = wmi_obj.RegistryTreeChangeEvent.watch_for(
            Hive=self._kwargs['Hive'],
            RootPath=self._kwargs['RootPath'],
        )
    # Мониторим изменения ключа реестра
    elif notify_filter == 'KeyChange':
        self._watcher = wmi_obj.RegistryKeyChangeEvent.watch_for(
                Hive=self._kwargs['Hive'],
                KeyPath=self._kwargs['KeyPath'],
            )
    # Мониторим изменения значения
    elif notify_filter == 'ValueChange':
        self._watcher = wmi_obj.RegistryValueChangeEvent.watch_for(
                Hive=self._kwargs['Hive'],
               KeyPath=self._kwargs['KeyPath'],
               ValueName=self._kwargs['ValueName'],
           )

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

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

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

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

# Не забудем подключить нужные модули
from winappdbg import Debug, EventHandler
from winappdbg.win32 import *
class APIIntercepter(EventHandler):
    # Будем перехватывать API-функцию GetProcAddress из kernel32.dll
    apiHooks = {
        'kernel32.dll' : [
            ('GetProcAddress', (HANDLE, PVOID)),
        ],
    }

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

'<имя DLL-библиотеки_1>' : [
    ('<имя API-функции_1>', (<параметр_1>, <параметр_2>, <параметр_3>, ...),
    ('<имя API-функции_2>', (<параметр_1>, <параметр_2>, <параметр_3>, ...),
    ('<имя API-функции_3>', (<параметр_1>, <параметр_2>, <параметр_3>, ...),
    ...
],
'<имя DLL-библиотеки_2>' : [
    ('<имя API-функции_1>', (<параметр_1>, <параметр_2>, <параметр_3>, ...),
    ('<имя API-функции_2>', (<параметр_1>, <параметр_2>, <параметр_3>, ...),
    ('<имя API-функции_3>', (<параметр_1>, <параметр_2>, <параметр_3>, ...),
    ...
],
...

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

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

# Вызывается при вызове GetProcAddress
def pre_GetProcAddress(self, event, retaddr, hModule, lpProcName):
    # Выводим информацию при запуске функции
    # Получаем имя переданной в GetProcAddress в качестве параметра функции
    string = event.get_process().peek_string(lpProcName)
    # Получаем ID потока, в котором произошел вызов GetProcAddress
    tid    = event.get_tid()
    # Выводим это все на экран
    print "%d: Call GetProcAddress: %s" % (tid, string)
# Вызывается при завершении GetProcAddress
def post_GetProcAddress(self, event, retval):
    # Выводим информацию по завершении функции
    # Получаем ID потока, в котором произошел вызов GetProcAddress
    tid = event.get_tid()
    # Проверяем наличие возвращаемого значения
    if retval:
        # Если возвращаемое значение не None, выводим его на экран
        print "%d: Success. Return value: %x" % (tid, retval)
    else:
        print "%d: Failed!" % tid

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

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

def set_api_interceptor(argv):
    # Создаем экземпляр объекта Debug, передав ему экземпляр APIIntercepter
    with Debug(APIIntercepter(), bKillOnExit=True) as debug:
        # Запустим анализируемую программу в режиме отладки
        # Путь к анализируемой программе должен быть в argv
        debug.execv(argv)
        # Ожидаем, пока не закончится отладка
        debug.loop()

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

import sys
set_api_interceptor(sys.argv[1:])

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

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

Заключение

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

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

ВКонтакте
OK
Telegram
WhatsApp
Viber

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *