Содержание статьи
- Отслеживаем процессы
- Следим за файловыми операциями
- Используем API Windows
- Используем WMI
- Мониторим действия с реестром
- Используем API
- Используем WMI
- Мониторим вызовы API-функций
- Заключение
Многие вредоносные программы сопротивляются отладке: они отслеживают и блокируют запуск популярных утилит для мониторинга файловой системы, процессов и изменений в реестре Windows. Чтобы обхитрить такую малварь, мы напишем на Python собственный инструмент для исследования образцов вредоносных программ.
Статический анализ, как ты знаешь, подразумевает исследование исполняемого файла без его запуска. Динамический куда увлекательнее: в этом случае образец запускают и отслеживают все происходящие при этом в системе события. Для исследователя интереснее всего операции с файловыми объектами, с реестром, а также все случаи создания и уничтожения процессов. Для получения более полной картины неплохо было бы отслеживать вызовы API-функций анализируемой программой. Разумеется, экспериментировать с вредоносом нужно в изолированной среде с использованием виртуальной машины или песочницы — иначе он может натворить бед.
INFO
Подробнее о методике статического анализа вредоносных файлов ты можешь прочитать в статье «Малварь на просвет. Учимся быстро искать признаки вредоносного кода».
Для отслеживания жизнедеятельности приложений существует целый арсенал готовых средств, среди которых самое известное — утилита Process Monitor из Sysinternals Suite. Эта тулза в рекламе не нуждается, она неплохо документирована и пользуется заслуженной популярностью. Process Monitor способен отслеживать все изменения в файловой системе Windows, мониторить операции создания и уничтожения процессов и потоков, регистрировать и отображать происходящее в реестре, а также фиксировать операции загрузки DLL-библиотек и драйверов устройств.
Отслеживать вызовы API-функций можно с помощью утилиты API Monitor французской компании Rohitab. Туториал по работе с этой тулзой можно найти на сайте программы, правда на английском языке.
Самый главный недостаток этих утилит (как, впрочем, и других широко распространенных программ такого рода) именно в их популярности. Потому что с ними отлично знакомы не только аналитики, но и вирусописатели. Далеко не любая малварь позволит использовать подобные инструменты и безнаказанно исследовать свое поведение в системе. Наиболее продвинутые трояны фиксируют любые попытки запуска антивирусов и средств анализа состояния ОС, а затем либо пытаются всеми правдами и неправдами прибить соответствующий процесс, либо прекращают активные действия до лучших времен.
Тем не менее существуют способы перехитрить малварь. Один из наиболее очевидных — изобрести собственный инструмент, который будет уметь (хотя бы частично) то же самое, что делают Process Monitor, API Monitor и им подобные программы. Чем мы, благословясь, и займемся.
Для работы мы будем использовать Python (не зря же он считается одним из самых хакерских языков программирования). Отслеживать интересующие нас события, связанные с реестром, файловой системой или процессами, можно двумя путями: используя специализированные API-функции Windows и при помощи механизмов WMI (Windows Management Instrumentation, или инструментарий управления Windows).
То есть, помимо Python, нам понадобятся модуль pywin32 и модуль WMI. Установить их очень просто (на самом деле достаточно поставить только пакет WMI, а он уже самостоятельно подгрузит pywin32):
pip install pywin32
pip install wmi
Чтобы отследить вызовы API-функций, понадобится модуль WinAppDbg. Этот модуль работает только со второй версией Python (если говорить точнее, то потребуется 2.5, 2.6 или 2.7), поэтому старый Python рано окончательно списывать в утиль. Тем более что автор WinAppDbg пока не планирует переписывать модуль под третью версию в связи с необходимостью рефакторинга большого объема кода, о чем прямо говорит в документации. Установить модуль можно через pip:
pip install winappdbg
Cкачав и установив все необходимые модули, приступим к таинству написания собственного инструмента для динамического анализа малвари.
ОТСЛЕЖИВАЕМ ПРОЦЕССЫ
Отслеживать процессы будем с помощью механизма 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()
В итоге получим примерно следующую картину.
СЛЕДИМ ЗА ФАЙЛОВЫМИ ОПЕРАЦИЯМИ
Здесь, как мы и говорили в начале, можно пойти двумя путями: использовать специализированные 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:])
В итоге должна получиться примерно такая картина.
В методах, вызываемых при вызове и завершении работы API-функции (в нашем случае это pre_GetProcAddress() и post_GetProcAddress()), можно программировать какие угодно действия, а не только вывод информации о вызове API-функции, как это сделали мы.
ЗАКЛЮЧЕНИЕ
Как видишь, используя Python и несколько полезных пакетов, можно получить довольно большой объем информации о событиях, происходящих в системе при запуске той или иной программы. Конечно, все это желательно делать в изолированном окружении (особенно если анализировать крайне подозрительные программы).
Полностью написанный код классов анализа событий процессов, файловой системы и реестра можно посмотреть на моем гитхабе. Он также присутствует в PyPi, что позволяет установить его командой pip install pywinwatcher и использовать по своему усмотрению.