Привет_emotet!(part2)
Привет Emotet! Исследуем дамп оперативной памяти заражённый Emotet
Всем привет, как я обещал в первой части статьи «Привет Emotet!», в следующей статье мы приступим к расследованию инцидента в дампе оперативной памяти и заодно пройдём задание от cyberdefenders.org ответив на 10 несложных вопросов, а также узнаем как вредонос скрывает свою активность в скрытых процессах и как ему удаётся обходить средства защиты информации. Разберём распространённую атаку Direct Kernel Object Manipulation (DKOM), которая часто используется Rootkit’ами.
В статье я постараюсь объяснить как устроены некоторые стурктуры в памяти, в том числе: структура ядра и исполняемых процессов. Но на подробное объяснение не хватит ни одной статьи, поэтому если у вас есть желание узнать более подробную информацию о структурах: ядра, процессов и расследовании инцидентов в дампах оперативной памяти, рекомендую обзавестись книгой «The Art of Memory Forensics» by Michael Hale Ligh, Andrew Case, Jamie Levy, AAron Walters. В книге изложена вся необходимая базовая информация о структуре ОС, ядра, процессов и.т.д, не только Windows, но и Linux, а также Mac. В связи с изменениями версий ОС информация несколько устарела, на базовый материал в книге – определённо мастхэв для изучения.
Правильно подобранный набор утилит и хорошо подготовленный стенд — залог успеха в расследовании инцидентов, поэтому начнем именно с этого. Я буду использовать SIFT (by SANS), т.к. он уже содержит набор необходимых утилит и плагинов. Основная утилита, которой мы сегодня будем пользоваться это, конечно многим знакомая, volatility на python2.
Часть 2 серии статей «Привет Emotet»!
В первую очередь, загрузим дамп оперативной памяти с трояном внутри и разархивируем его на SIFT (рисунок 1)
Чтобы не обращаться к нему постоянно с помощью “-f dump.vmem”, добавим в переменную окружения расположение файла дампа оперативной памяти:
export VOLATILITY_LOCATION=file:///home/sansforensics/Desktop/Emotet_dump/banking-malware.vmem (рисунок 2)
Теперь нам необходимо узнать наименование профиля ОС, чтобы корректно работать с содержимым (процессами, файлами и.т.д) и заодно ответить на первый вопрос What profile should you use for this memory sample?
Для этого, мы воспользуемся плагином kdbgscan, которые находит Kernel Debugger Data Block (KDBG), результат на рисунке 3.
Получим исчерпывающую информацию о структуре ОС, в том числе указание профиля и предложенные профили (их 8). Определить, что профиль выбран верно можно с помощью следующих плагинов:
-
pslist;
-
psscan;
-
filescan;
-
hivelist.
Если профиль выбран неверно, вы получите нечитаемый или искажённый результат после выполнения этих плагинов.
Здесь же мы обнаружим виртуальный и физический адреса и сразу ответим на второй вопрос (рис 4):
What is the KDBG virtual address of the memory sample?
Также, вы можете воспользоваться плагином imageinfo, но с ним вы получите чуть меньше информации. Для удобства, чтобы не передавать в каждом вызове volatility указание профиля (–profile=’win7sp1x64_2400’) мы экспортируем наименование профиля в переменную окружения:
export VOLATILITY_PROFILE='Win7SP1x64_24000'
Готово, теперь мы, запустив vol.py, можем сразу использовать плагины, без указания месторасположения и профиля, например получить список активных процессов:
vol.py pslist
В следующем вопросе There is a malicious process running, but it’s hidden. What’s its name? нас просят найти имя скрытого процесса. Что это за процесс, почему и от кого он скрыт? Kernel Debugger Data Block (KDBG) имеет указатель на структуру _EPROCESS block list, где каждый блок указывает на объекты: handles, VAD Tree, Access Tokens, Threads, Process Environment Block (PEB для понимания, сюда входят исполняемые файлы, dll и.т.д). Также эта структура содержит всю остальную информацию о процессе, директории из которой был запущен исполняемый файл, пользователь, который его запустил, время и многое другое. По сути, с этой структурой работает большинство плагинов Volatility. И мы с вами с этими объектами будем работать. Визуально схема описанная выше выглядит следующим образом:
Также в структуре _EPROCESS есть цепочка процессов и ссылок ActiveProcessLinks (на неё указывает PsActiveProcessHead из KDBG), данная цепочка называется doublelinked и выглядит как на рисунке 5:
Forward Link (Flink) или прямая ссылка, указывает на адрес структуры ActiveProcessLink следующего процесса, а Backward Link (Blink) указывает на адрес структуры ActiveProcessLink предыдущего процесса.
В случае со скрытым процессом, Forward Link буквально перепрыгивает через впереди стоящий процесс и указывает на адрес структуры ActiveProcessLink следующего процесса, то же самое производится по отношению к Backward Link, таким образом процесс становится скрытым. Это означает, что при просмотре запущенных процессов в диспетчере задач и через консоль tasklist, а также process explorer, мы его не увидим. Данный принцип лежит в основе атаки DKOM, в ходе которой вредоносное ПО меняет объекты ядра непосредственно в памяти. Такие изменения не оставляют следов на диске и позволяют обходить средства защиты: АВЗ и другие.
Как выглядит цепочка со скрытым процессом Process 2 отобразил на рисунке 6.
Как мы можем заметить, на его структуру ActiveProcessLinks не указывает ни один процесс. Flink Process 1 указывает на ActiveProcessLinks процесса N, в то время как Blink процесса N указывает на структуру ActiveProcessLinks процесса 1, вместо указания структуры Process 2. Пометил красным изменённые указатели в сравнении со схемой на рисунке 5.
С краткой теорией мы покончили, теперь продолжим нашу практику. И для ответа на вопрос, напомню: There is a malicious process running, but it’s hidden. What’s its name? мы воспользуемся плагином psxview, который отобразит нам, что один из процессов был скрыт (рисунок 7), в таком случае плагины pslist и psscan его не отобразят, т.к. они проходят по цепочке тех самых ссылок Flink & Blink, аналогично работают: Диспетчер задач, Process Explorer, tasklist.
Плагин psxview позволяет находить скрытые процессы и отображает имя, PID, физический адрес и другую информацию о процессах. Это достигается благодаря тому, что в отличие от плагина pslist, которые читает _EPROCESS double linked list, данный плагин не сканирует только цепочку ссылок которая завязана на ActiveProcessLinks адреса, а совмещает в себе сразу несколько плагинов:
- pslist - читает _EPROCESS double linked список;
- psscan – считывает структуры _EPROCESS, отображает завершённые процессы, иногда помогает находить руткиты;
- thrdproc – процессы и потоки;
- pspeid – Таблица PspCid отслеживает процессы и потоки;
- csrss – хранит дескриптор процесса, который запустился после (True) или до(False);
- session – список процессов, принадлежащих каждому сеансу входа в систему;
- deskthrd – определение процессов по потокам, которые прикреплены к каждому рабочему столу.
Таким образом, мы определили, что процесс с именем vds_ps.exe, PID:2448 и физическим адресом 0x000000007d336950 скрыт, т.к. имеет значение False для pslist & psscan. Что позволяет нам ответить на следующий вопрос об адресе процесса: What is the physical offset of the malicious process? (нашли ранее). Для ответа на вопрос о пути процесса: What is the full path (including executable name) of the hidden executable? мы можем пойти сразу несколькими путями. Сейчас я покажу простой способ поиска пути и далее в статье расскажу более сложный способ. Итак, после того, как мы определили адрес процесса, давайте посмотрим его путь, а также все dll которые он использует (рисунок 8), разумеется информацию о местоположении запущенного исполняемого файла можно также обнаружить в Prefetch и ShimCahe, но я пойду через dlllist:
vol.py --offset=0x000000007d336950 dlllist
Давайте сразу сдампим процесс в текущую директорию и посмотрим, какая информация есть по известным сигнатурам на virustotal (рисунки 9 и 10):
vol.py procdump --offset=0x000000007d336950 -D .
Перед нами Emotet, неожиданно, конечно, учитывая что задание называет Emotet. Cразу ответим на следующий вопрос Which malware is this?
Для ответа на вопрос The malicious process had two PEs injected into its memory. What’s the size in bytes of the Vad that contains the largest injected PE? Answer in hex, like: 0xABC воспользуемся плагином malfind, который позволяет найти следы для большинства техник типа process injection (рисунок 11) здесь мы сразу и сдампим найденный плагином внедрённый код в текущую директорию:
vol.py malfind --offset=0x000000007d336950 -D .
Обратите внимание, что тип защиты памяти PAGE_EXECUTE_READWRITE для исполняемых файлов, которые загружены естественным путём – не нормальный (красные стрелки). Нормальное состояние защиты памяти для легитимно загруженных исполняемых файлов – PAGE_EXECUTE_WRITECOPY. Это говорит нам о том, что процесс был внедрён (injected), т.к. при этом изменяется тип памяти, ответственную за это функцию Windows API мы найдём ниже. Давайте посмотрим на функции, которые используются вредоносным процессом при помощи утилиты strings (Рисунок 12):
strings process.0xfffffa8004536950.0x220000.dmp
Здесь мы видим «полный набор»:
-
Process32First/Next и CreateToolhelp32Snapshot позволяют находить необходимый процесс (часто функции используются для внедрения DLL).
-
VirtualProtect позволяет изменять тип защиты отдельных участков памяти.
-
GetProcAdddress и др. функции из библиотеки kernel32.dll.
Чтобы ответить на вопрос нам всего то нужно взять последний используемый адрес в памяти у самого большого по размеру дампа: process.0xfffffa8004536950.0x2a80000.dmp.
Для этого я воспользуюсь ndisasm в REMnux (в SIFT) утилита отсутствует и выведу последние несколько дизассемблированных строк (рис 13) :
Запустим плагин volshell и после передадим параметры процесса, который мы хотим изучить, физический адрес процесса мы знаем. Для передачи адреса в change current shell context метод cc(), нужно по дефолту использовать виртуальный адрес памяти, решим это, указав physical=True, итого:
cc(offset=0x000000007d336950, physical=True)
Теперь мы сможем более детально проанализировать структуру процесса, к примеру посмотреть описание структуры _EPROCESS, которая упоминалась в начале статьи и узнать её содержимое для конкретного процесса, выполнив describe an object or show type info – dt(“_EPROCESS”, 0xfffffa8004536950), аналогичный результат мы с вами сможем получить вызвав метод proc().
Чтобы построить цепочку ссылок, о которой мы говорили ранее (на рисунке 5), давайте вытащим адрес структуры ActiveProcessLinks, для скрытого процесса, выполнив proc().ActiveProcessLinks:
[ _LIST_ENTRY ActiveProcessLinks] @ 0xFFFFFA8004536AD8
Очень хорошо, найдём таким же способом адреса Flink и Blink, на которые они указывают:
>>> proc().ActiveProcessLinks.Flink
<_LIST_ENTRY pointer to [0xFFFFFA800397DC88]>
>>> proc().ActiveProcessLinks.Blink
<_LIST_ENTRY pointer to [0xFFFFFA80045FC568]>
Отлично, получается, что нам ничего не мешает проделать аналогичные действия для всех активных процессов, которые можно получить списком ps(), перебрать их в цикле, и получить 3 адреса, которые мы получили для скрытого процесса выше, давайте сделаем этого, написав небольшой цикл:
>>> for pid_offset in self.getpidlist():
... cc(offset=str(pid_offset))
... print "ActiveProcessAddr = ", proc().ActiveProcessLinks
... print "Flink = ", hex(proc().ActiveProcessLinks.Flink)
... print "Blink = ", hex(proc().ActiveProcessLinks.Blink)
На выходе мы получим адреса все следующих и предыдущих активных процессов.
Цепочка большая, да и все процессы и их адреса нам не понадобятся, нас интересует адрес процессов, которые расположены впереди и после скрытого, чтобы ответить на следующий вопрос ‘This process was unlinked from the ActiveProcessLinks list. Follow its forward link. Which process does it lead to? Answer with its name and extension’ и иметь более полную картину. Если мы отобразим схематично те несколько процессов, которые нас интересуют, то получим такую схему (рис 15):
Обратите внимание, что ни одна прямая (Flink) и обратная (Blink) ссылка, не указывает на структуру ActiveProcessLinks скрытого процесса vds_ps.exe (PID:2448), это и делает его скрытым. Это простая и довольно распространённая атака Direct Kernel Object Manipulation (DKOM), в ходе которой для того, чтобы скрыть процесс, необходимо изменить прямую ссылку стоящего перед и следующего, за скрытым, процессов, тем самым исключив его из цепочки.
Здесь мы видим что прямая ссылка ведёт на процесс SearchIndexer.exe.
Давайте ещё пройдёмся по структуре и заглянем в некоторые объекты, к примеру в Process Environment Block (Peb.ProcessParameters):
Здесь мы обнаружим путь исполняемого файла (как раз тот чуть более трудный путь чем использовать dlllist и другие плагины) и путь для dll. Теперь читатель сможет дальше пройтись по структуре _EPROCESS и найти много полезной информации для себя, включая время запуска и остановки и другую информацию.
Давайте ответим на оставшиеся два вопроса и завершим наше исследование. В следующем вопросе нас просят найти и преобразовать в ascii формат адрес PoolTag: What is the pooltag of the malicious process in ascii? (HINT: use volshell).
Пул ядра – это область памяти, которая может быть разделена на более мелкие блоки для хранения любого типа данных, которые запрашивает компонент в режиме ядра. Каждый блок имеет заголовок POOL_HEADER, в заголовке содержится отладочная информация, с помощью неё можно отследить повреждения и утечки. Подробнее о Kernel Pool можно почитать здесь.
Перед созданием любого объекта (исполняемого или любого другого) необходимо выделить пул памяти, для хранения тела этого объекта и всех заголовков, включая POOL_HEADER. Для этого в Windows API предусмотрена процедура (подробнее) с уникальным тегом пула внутри:
PVOID ExAllocatePoolWithTag(
[in] __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType,
[in] SIZE_T NumberOfBytes,
[in] ULONG Tag
);
PoolTag хранит уникальное 4-х байтовое значение, обычно в ASCII формате значение каждого символа должно быть от 0 до 127, тег указывается драйвером при выделении памяти. Помогает определить источник утечек памяти, повреждения и незаменим в расследовании т.к. ведёт к драйверам. В Windows Driver Kit (DDK) можно найти файл pooltag.txt, в котором содержится информация о драйверах и их тегах в %systemroot%\system32\drivers.
К примеру:
-
8042 - i8042prt.sys - PS/2 kb и мышь;
-
AdSv - vmsrvc.sys - Служба дополнений виртуальных машин;
-
ARPC - atmarpc.sys - ATM ARP Client;
-
ATMU - atmuni.sys - ATM UNI Call Manager;
-
ACPI - acpi.sys – ACPI;
-
Afd? - afd.sys - объекты AFD;
-
AfdA - afd.sys - буфер Afd EA;
и.т.д;
Вернёмся к нашей структуре _EPROCESS по адресу 0x000000007d336950, то есть, согласно схемы из рисунка 17 находящейся в Object Body. Нашей с вами целью является PoolTag (выделяется в процедуре ExAllocatePoolWithTag) и хранится_POOL_HEADER, которая, как мы видим на рисунке 18 располагается после _EPROCESS и заголовков. Чтобы узнать её адрес нам нужно вычесть адрес структуры _OBJECT_HEADER = 0x30 и узнать, есть ли дополнительные заголовки (optional headers), на рисунке 17 выделены серым, если они есть, то их размер также необходимо учитывать. Дополнительные заголовки могут содержать информацию, которая необходима для описания объекта, подробную информацию можно найти в книге «The Art of Memory Forensics», оттуда позаимствованы 3 рисунка структуры ниже.
Для поиска дополнительных заголовков нам необходимо обратиться к значению в разделе InfoMask, который содержит маску бит для каждого из необязательных заголовков объекта.
Отвечая на вопрос сканируемого профиля ОС (рисунок 3) мы уже выяснили, что работаем с 64-разрядной Windows 7, для неё характерны следующие дополнительные заголовки и их Bit Mask, более подробную информацию можно найти здесь. (Рисунок 19)
Чтобы узнать значение в InfoMask нам нужно перейти по адресу OBJECT_HEADER->InfoMask, для этого вычтем из адреса структуры _EPROCESS адрес _OBJECT_HEADER:
dt(‘_OBJECT_HEADER’, 0x000000007d336950 – 0x30)
Согласно таблице из рисунка 19, маска 0x8 соответствует наличию дополнительного заголовка _OBJECT_HEADER_QUOTA_INFO с размером 32 байта, что при переводе в шестнадцатеричный формат даёт 0x20. Также необходимо учесть размер заголовка _POOL_HEADER = 16 байт => 0x10 в шестнадцатеричном формате чтобы попасть на нужный нам адрес.
Итого, нас отделяют от начального адреса _POOL_HEADER:
_OBJECT_HEADER = 0x30;
_OBJECT_HEADER_QUOTA_INFO = 0x20;
_POOL_HEADER = 0x10.
Посмотрим, какое значение находится в PoolTag (рис 21):
dt(‘_OBJECT_HEADER’, 0x000000007d336950 – 0x30 – 0x20 – 0x10)
Выше я перевёл значение из десятичного в шестнадцатеричный формат, поменял байты местами (т.к. в памяти значение расположено от младших к старшим байтам, то есть привёл от LittleEndian к BigEndian формату) и конвертировал в char. На выходе получили «R0ot».
Давайте выведем байты и посмотрим, что находится по этому адресу:
db(0xFFFFFA80045368F0,1000)
Ответим на последний вопрос: What is the physical address of the hidden executable’s pooltag? (HINT: use volshell). Выше мы уже разобрались, что PoolTag = 4 байтам, поэтому к начальному адресу 0xFFFFFA80045368F0 мы просто добавим 4 байта (хотя это уже было видно на рисунке выше).
В предыдущих статьях мы ознакомились с практическими способами исследования Microsoft Office документов на примере реального сэмпла. Узнали об атаке DKOM, цепочке процессов и как вредоносному ПО удаётся скрыть процессы, поработали с дампом оперативной памяти и volatility.
В заключительной части мы рассмотрели некоторые объекты и структуры ядра, нашли в памяти артефакты, на реальном примере рассмотрели цепочку процессов со скрытым процессом внутри, поближе познакомились со способами обнаружения таких процессов в памяти. Спасибо!