Эта статья - продолжение серии статей "Привет Emotet!", заключительная её часть
Первую и вторую статьи вы можете найти здесь и здесь.

В этой статье, мы решили убрать в сторону плагины volalatility, автоматизирующие нашу работу и прогуляться по узким коридорам памяти, в поисках артефактов. Давайте начнём.


Для этого запустим плагин 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) 

На выходе мы получим адреса все следующих и предыдущих активных процессов. (рисунок 14)

Рис 14 - указатели всех активных процессов и адреса их структуры ActiveProcessLinks
Рис 14 - указатели всех активных процессов и адреса их структуры ActiveProcessLinks

Цепочка большая, да и все процессы и их адреса нам не понадобятся, нас интересует адрес процессов, которые расположены впереди и после скрытого, чтобы ответить на следующий вопрос ‘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):

Рисунок 15 - итоговая цепочка со скрытым процессом vds_ps.exe
Рисунок 15 - итоговая цепочка со скрытым процессом vds_ps.exe

Обратите внимание, что ни одна прямая (Flink) и обратная (Blink) ссылка, не указывает на структуру ActiveProcessLinks скрытого процесса vds_ps.exe (PID:2448), это и делает его скрытым. Это простая и довольно распространённая атака Direct Kernel Object Manipulation (DKOM), в ходе которой для того, чтобы скрыть процесс, необходимо изменить прямую ссылку стоящего перед и следующего, за скрытым, процессов, тем самым исключив его из цепочки.

Здесь мы видим что прямая ссылка ведёт на процесс SearchIndexer.exe.

Давайте ещё пройдёмся по структуре и заглянем в некоторые объекты, к примеру в Process Environment Block (Peb.ProcessParameters) на рисунке 16:

Рисунок 16 - объект PEB.ProcessParameters структуры _EPROCESS.
Рисунок 16 - объект PEB.ProcessParameters структуры _EPROCESS.

Здесь мы обнаружим путь исполняемого файла (как раз тот чуть более трудный путь чем использовать 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 рисунка структуры ниже.

Рисунок 17 – заголовки объектов в Windows x64 (серым помечены опциональные, т.е. необязательные заголовки)
Рисунок 17 – заголовки объектов в Windows x64 (серым помечены опциональные, т.е. необязательные заголовки)
Рисунок 18 – исполняемый объект в пуле.
Рисунок 18 – исполняемый объект в пуле.

Для поиска дополнительных заголовков нам необходимо обратиться к значению в разделе InfoMask, который содержит маску бит для каждого из необязательных заголовков объекта.

Отвечая на вопрос сканируемого профиля ОС (рисунок 3) мы уже выяснили, что работаем с 64-разрядной Windows 7, для неё характерны следующие дополнительные заголовки и их Bit Mask, более подробную информацию можно найти здесь. (Рисунок 19)

Рисунок 19 – доп. Заголовки Win7x64.
Рисунок 19 – доп. Заголовки Win7x64.

Чтобы узнать значение в InfoMask нам нужно перейти по адресу OBJECT_HEADER->InfoMask, для этого вычтем из адреса структуры _EPROCESS адрес _OBJECT_HEADER:

dt(‘_OBJECT_HEADER’, 0x000000007d336950 – 0x30)

Рисунок 20 – значение InfoMask.
Рисунок 20 – значение InfoMask.

Согласно таблице из рисунка 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)

Рисунок 21 – pooltag.
Рисунок 21 – pooltag.

Выше я перевёл значение из десятичного в шестнадцатеричный формат, поменял байты местами (т.к. в памяти значение расположено от младших к старшим байтам, то есть привёл от LittleEndian к BigEndian формату) и конвертировал в char. На выходе получили «R0ot».

Давайте выведем байты и посмотрим, что находится по этому адресу:
db(0xFFFFFA80045368F0,1000)

Рисунок 22 – содержимое от адреса 0xFFFFFA80045368F0.
Рисунок 22 – содержимое от адреса 0xFFFFFA80045368F0.

Ответим на последний вопрос: What is the physical address of the hidden executable's pooltag? (HINT: use volshell). Выше мы уже разобрались, что PoolTag = 4 байтам, поэтому к начальному адресу 0xFFFFFA80045368F0 мы просто добавим 4 байта (хотя это уже было видно на рисунке выше).

Рисунок 23 – PoolTag.
Рисунок 23 – PoolTag.

В предыдущих статьях мы ознакомились с практическими способами исследования Microsoft Office документов на примере реального сэмпла. Узнали об атаке DKOM, цепочке процессов и как вредоносному ПО удаётся скрыть процессы, поработали с дампом оперативной памяти и volatility.

В заключительной части мы рассмотрели некоторые объекты и структуры ядра, нашли в памяти артефакты, на реальном примере рассмотрели цепочку процессов со скрытым процессом внутри, поближе познакомились со способами обнаружения таких процессов в памяти. Спасибо!

Комментарии (2)


  1. vesper-bot
    14.11.2022 09:42

    А как на работающей винде найти подобные скрытые процессы?


    1. AntonyN0p Автор
      14.11.2022 11:30

      Подобные атаки нацелены на объекты ядра ОС, поэтому выявить на работающей ОС очень трудно, здесь несколько решений:
      1. WinDbg;
      2. Снять дамп и уже исследовать его.