Найти тему
It-Assistant

Малварь на просвет. Используем Python для динамического анализа вредоносного кода

Оглавление

Содержание статьи

  • Отслеживаем процессы
  • Следим за файловыми операциями
  • Используем API Windows
  • Используем WMI
  • Мониторим действия с реестром
  • Используем API
  • Используем WMI
  • Мониторим вызовы API-функций
  • Заключение

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

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

INFO

Под­робнее о методи­ке ста­тичес­кого ана­лиза вре­донос­ных фай­лов ты можешь про­читать в статье «Мал­варь на прос­вет. Учим­ся быс­тро искать приз­наки вре­донос­ного кода».

Для отсле­жива­ния жиз­неде­ятель­нос­ти при­ложе­ний сущес­тву­ет целый арсе­нал готовых средств, сре­ди которых самое извес­тное — ути­лита Process Monitor из Sysinternals Suite. Эта тул­за в рек­ламе не нуж­дает­ся, она неп­лохо докумен­тирова­на и поль­зует­ся зас­лужен­ной популяр­ностью. Process Monitor спо­собен отсле­живать все изме­нения в фай­ловой сис­теме Windows, монито­рить опе­рации соз­дания и унич­тожения про­цес­сов и потоков, регис­три­ровать и отоб­ражать про­исхо­дящее в реес­тре, а так­же фик­сировать опе­рации заг­рузки DLL-биб­лиотек и драй­веров устрой­ств.

От­сле­живать вызовы API-фун­кций мож­но с помощью ути­литы API Monitor фран­цуз­ской ком­пании Rohitab. Тутори­ал по работе с этой тул­зой мож­но най­ти на сай­те прог­раммы, прав­да на англий­ском язы­ке.

-2

Са­мый глав­ный недос­таток этих ути­лит (как, впро­чем, и дру­гих широко рас­простра­нен­ных прог­рамм такого рода) имен­но в их популяр­ности. Потому что с ними отлично зна­комы не толь­ко ана­лити­ки, но и вирусо­писа­тели. Далеко не любая мал­варь поз­волит исполь­зовать подоб­ные инс­тру­мен­ты и без­наказан­но иссле­довать свое поведе­ние в сис­теме. Наибо­лее прод­винутые тро­яны фик­сиру­ют любые попыт­ки запус­ка анти­виру­сов и средств ана­лиза сос­тояния ОС, а затем либо пыта­ются все­ми прав­дами и неп­равда­ми при­бить соот­ветс­тву­ющий про­цесс, либо прек­раща­ют активные дей­ствия до луч­ших вре­мен.

Тем не менее сущес­тву­ют спо­собы перехит­рить мал­варь. Один из наибо­лее оче­вид­ных — изоб­рести собс­твен­ный инс­тру­мент, который будет уметь (хотя бы час­тично) то же самое, что дела­ют 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()

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

-3

СЛЕДИМ ЗА ФАЙЛОВЫМИ ОПЕРАЦИЯМИ

Здесь, как мы и говори­ли в начале, мож­но пой­ти дву­мя путями: исполь­зовать спе­циали­зиро­ван­ные 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:])

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

-4

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

ЗАКЛЮЧЕНИЕ

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

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

Источник