В сегодняшней статье я покажу, как написать программу на Python для анализа вирусов и других вредоносных программ.
Еще по теме:
- Сбор информации о системе удаленно с помощью Python
- Как написать шифровальщик на Python
- Как написать троян на Python
Отслеживать процессы будем с помощью механизма WMI. Это делается достаточно просто:
1 2 3 4 5 6 7 |
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 (более подробно об этом формате можно почитать здесь), поэтому для вывода времени в более привычной форме можно написать нечто вроде функции преобразования формата времени:
1 2 3 4 5 6 7 8 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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().
Далее уже можно запускать мониторинг событий процессов в отдельных потоках:
1 2 3 4 5 6 |
# Отслеживаем события создания процессов mon_creation = Monitor('creation') mon_creation.start() # Отслеживаем события уничтожения процессов mon_deletion = Monitor('deletion') mon_deletion.start() |
В итоге получим примерно следующую картину.
Результаты работы нашего Python-скрипта для отслеживания событий создания и уничтожения процессов
Следим за файловыми операциями
Здесь, как мы и говорили в начале, можно пойти двумя путями: использовать специализированные API-функции Windows или возможности, предоставляемые механизмом WMI. В обоих случаях мониторинг событий лучше выполнять в отдельном потоке, так же как мы сделали при отслеживании процессов. Поэтому для начала опишем базовый класс FileMonitor, а затем от него наследуем класс FileMonitorAPI, в котором будем использовать специализированные API-функции Windows, и класс FileMonitorWMI, в котором применим механизмы WMI.
Итак, наш базовый класс будет выглядеть примерно так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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().
Итак, начнем.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# Подключим все необходимые модули 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(), с помощью которого и будем обновлять сведения о произошедшем событии.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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()):
1 2 3 4 5 6 7 8 9 |
def _set_watcher(self): win32file.ReadDirectoryChangesW( self._directory, self._buffer, True, self._get_notify_filter_const(), self._overlapped, None ) |
Поскольку в качестве одного из параметров ReadDirectoryChangesW() принимает константы, определяющие тип отслеживаемого события, то определим метод, преобразующий значения параметра notify_filter в указанные константы.
1 2 3 4 |
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 свойства события при наступлении этого события. Возвращающий тип события метод выглядит так:
1 2 3 4 5 6 |
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, содержащая возможные действия с отслеживаемым файлом или каталогом. Эта константа определена в виде словаря следующим образом:
1 2 3 4 5 6 7 8 |
_ACTIONS = { 0x00000000: 'Unknown action', 0x00000001: 'Added', 0x00000002: 'Removed', 0x00000003: 'Modified', 0x00000004: 'Renamed from file or directory', 0x00000005: 'Renamed to file or directory' } |
Метод, возвращающий путь к отслеживаемому файлу:
1 2 3 4 5 6 7 8 |
def _get_path(self): result = '' if self._num_bytes_returned != 0: result = win32file.GetFinalPathNameByHandle( self._directory, win32con.FILE_NAME_NORMALIZED ) return result |
Метод, возвращающий имя отслеживаемого файла, которое было сохранено в _buffer при наступлении события:
1 2 3 4 5 6 |
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 |
Задействовать это все можно следующим образом (по аналогии с мониторингом процессов):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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 похож на ранее рассмотренное отслеживание событий с процессами. Для того чтобы следить за изменениями конкретного файла, воспользуемся следующим кодом:
1 2 3 4 5 6 7 8 9 10 11 |
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 можно почитать здесь):
1 2 3 |
directory_watcher = wmi.WMI().CIM_Directory.watch_for( notify_filter, Drive = 'e:', Path=r'\\example_dir\\' ) |
Конечно, все это желательно оформить в виде класса — наследника от нашего базового класса FileMonitor, описанного выше, чтобы мониторинг событий файловой системы можно было запустить в отдельном потоке. В целом полную реализацию описанных классов по мониторингу файловой системы можно посмотреть на моем гитхабе.
Мониторим действия с реестром
Так же как и события файловой системы, события реестра можно отслеживать либо с помощью специализированных API-функций, либо с использованием механизмов WMI. Предлагаю, как и в случае с событиями файловой системы, начать с написания базового класса RegistryMonitir, от которого наследовать классы RegistryMonitorAPI и RegistryMomitorWMI:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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() со значением последнего параметра (которым определяется доступ к желаемому ключу реестра):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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), или их дизъюнкцию:
1 2 3 4 5 6 7 8 9 10 |
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().
1 2 3 4 5 6 7 8 9 |
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).
1 2 3 |
timestamp = datetime.datetime.fromtimestamp( datetime.datetime.utcnow().timestamp() ) |
Данный кусочек кода необходимо вставить в метод update() (как класса FileMonitorAPI, так и класса RegistryMonitorAPI) после проверки условия появления события, и в переменной timestamp запишется соответствующее время.
Используем WMI
Здесь имеется два отличия относительно класса FileMonitorWMI. Первое: события, связанные с реестром, являются внешними (в то время как события, связанные с процессами и файловой системой, — внутренние). Второе: для мониторинга изменений в ветке реестра, ключе реестра или значении, записанном в какой‑либо ключ, необходимо использовать разные классы WMI: RegistryTreeChangeEvent, RegistryKeyChangeEvent или RegistryValueChangeEvent.
Соответственно, установка «наблюдателя» при инициализации экземпляра класса RegisterMonitorWMI в данном случае будет выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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). В нем мы реализуем нужные нам функции.
1 2 3 4 5 6 7 8 9 10 |
# Не забудем подключить нужные модули 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-библиотек. Форма записи следующая:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
'<имя 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() эти методы выглядят так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# Вызывается при вызове 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-функции).
Далее напишем функцию, которая и установит описанный нами перехватчик:
1 2 3 4 5 6 7 8 |
def set_api_interceptor(argv): # Создаем экземпляр объекта Debug, передав ему экземпляр APIIntercepter with Debug(APIIntercepter(), bKillOnExit=True) as debug: # Запустим анализируемую программу в режиме отладки # Путь к анализируемой программе должен быть в argv debug.execv(argv) # Ожидаем, пока не закончится отладка debug.loop() |
И запустим эту функцию:
1 2 |
import sys set_api_interceptor(sys.argv[1:]) |
В итоге должна получиться примерно такая картина.
Перехват функции GetProcAddress (видно, что анализируемый файл, скорее всего, пытается внедрить что‑то в удаленный поток)
В методах, вызываемых при вызове и завершении работы API-функции (в нашем случае это pre_GetProcAddress() и post_GetProcAddress()), можно программировать какие угодно действия, а не только вывод информации о вызове API-функции, как это сделали мы.
Заключение
Используя Python и несколько полезных пакетов, можно получить довольно большой объем информации о событиях, происходящих в системе при запуске той или иной программы. Конечно, все это желательно делать в изолированном окружении (особенно если анализировать крайне подозрительные программы).
Полностью написанный код классов анализа событий процессов, файловой системы и реестра можно посмотреть на моем гитхабе. Он также присутствует в PyPi, что позволяет установить его командой pip install pywinwatcher и использовать по своему усмотрению.