Статья ASM – Безопасность Win. [2]. Токен и авторизация пользователей

..продолжим первую часть статьи, где были представлены общие сведения о системных объектах Win. Чтобы не потерять логическую нить, предыдущая рекомендуется к прочтению, поскольку здесь ставка делается на то, что вы уже знакомы с понятиями: идентификатор пользователя SID, дескриптор безопасности объекта и его список DACL. Организовать качественную подсистему безопасности можно лишь в том случае, когда есть чёткая модель того, что нужно защищать и кому/что разрешается делать. В этой области инженерами Microsoft проделан огромный объём работы, так-что сконцентрируем своё внимание исключительно на основных моментах. За роялем сегодня токен юзера, его права и привилегии.

Содержание:

1. Аутентификация пользователей в системе;
2. Маркер доступа к объектам (токен);
3. Права и привилегии пользователей;
4. Практика – информация о токенах и дескрипторах(SD) процесса;
5. Подводим итоги.
------------------------------------------------

1. Регистрация пользователя в системе

Ядром подсистемы безопасности является служба Lsass.exe, или "Local Security Authority Sub-System". В автозагрузке Win лежит процесс входа пользователей Winlogon.exe, который как швейцар проводит фейс-контроль вновь прибывших и открывает им двери в систему. Winlogon является критическим важным процессом (если убить, то рухнет вся ОС), поэтому провайдер аутентификации Authui.dll и интерфейсное окно входа в систему запускаются внутри дочернего процесса Winlogon, под названием LogonUI.exe (см.папку system32). После того-как пользователь вводит свои учётные данные, процесс LogonUI завершается, а Winlogon остаётся активным, чтобы в фоне отлавливать комбинацию клавиш юзера [Ctrl+Alt+Delete] для смены пользователей. При обнаружении таковой, Winlogon опять зовёт LogonUI и так в бесконечном цикле.

На следующем этапе, Winlogon передаёт имя и пароль юзера в службу безопасности LSA, которая обращается к Authui.dll для аутентификации пользователя. Если всё в порядке и юзер с таким именем существует, провайдер вычисляет NTLM-хеш введённого пасворда и сравнивает его с дайджестом (хешем) из базы данных "диспетчера учётных записей" SAM – Security Account Manager. Если проверка подтвердит пользователя и не найдёт оснований, чтобы выпроводить его за порог, провайдер формирует и возвращает в Lsass уже знакомый нам идентификатор безопасности пользователя SID (Security Identifier). В противном случае, на территории охранной зоны включается ревун, и на незадачливого юзверя направляются сразу несколько ярких прожекторов.

Теперь получив SID, подсистема Lsass вызывает функцию NtCreateToken() в мониторе безопасности SRM, чтобы создать т.н. маркер-доступа клиента "AccessToken". На системах х86-32, структура токена имеет размер 480-байт и в неё заложен профиль безопасности пользователя. Помимо прочего, он содержит в себе SID'ы юзера и группы в которую он входит, а так-же его привилегии. Остальные поля в токене носят информационный характер и в данном случае не представляют для нас интереса. Примерно так выглядит взаимодействие субъектов в этом сообществе:

MainScheme.png


Обратите внимание, что создавать токены доступа к объектам может не только служба безопасности Lsass, но и мы и с вами, как рядовые пользователи. Благодаря именно этой возможности консольной утилите "runas.exe" удаётся запускать процессы выдавая себя за админа системы. Она использует функцию из библиотеки advapi32.dll LogonUser(), которая пытается зарегать нового юзверя на локальном узле. При удачных обстоятельствах, функция создаёт токен с настройками по умолчанию, и теперь он будет представлять новорождённого юзера во-всех его начинаниях, то как старт процессов и прочее. Если функция нарвётся на шипы подсистемы безопасности и по какой-либо причине не сможет выполнить свою задачу, то в EAX вернёт логический нуль. Вот её прототип:

C-подобный:
BOOL LogonUserA(
  LPCSTR  lpszUsername,      ;// имя юзера = произвольное
  LPCSTR  lpszDomain,        ;// имя домена/сервера = GetComputerName()
  LPCSTR  lpszPassword,      ;// пароль юзера = 0
  DWORD   dwLogonType,       ;// тип входа в систему = LOGON32_LOGON_NEW_CREDENTIALS
  DWORD   dwLogonProvider,   ;// провайдер обеспечения входа = LOGON32_PROVIDER_DEFAULT (Authui.dll)
  PHANDLE phToken )          ;// сюда вернётся дескриптор токена нового пользователя!

Далее, установив в этом токене нужные привилегии, можно будет вызывать процессы от чужого имени, посредством функции CreateProcessAsUser(). В качестве альтернативы, имеется ещё одна обёртка, сочетающая в себе сразу две функции выше под названием CreateProcessWithLogonW(). Одним выстрелом дробью она и создаёт нового юзера, и сразу запускает указанный в аргументе процесс. В общем выбор у нас широкий, правда в клинических случаях нам как "художникам" самим потребуются некоторые привилегии, но это уже дело техники и всегда можно найти забытый инженерами люк (например использовать не первичный токен, а токен олицетворения, или ограниченный токен).


2. Маркер доступа к объектам "AccessToken"

Токен сам является системным объектом, который хранит SID и привилегии пользователя. Он создаётся локальным органом безопасности LSA после того-как идентификация юзера успешно пройдёт цензуру. В последующем, каждый процесс, который так или иначе запускается от имени данного пользователя, сопровождается копией его токена. Токен используется системой исключительно для предоставления процессу доступа к защищённым объектам, и в зависимости от указанных в нём привилегий позволяет выполнять различные системные операции. Уже упоминалось, что защищённые объекты ожидают пользовательский SID в своих списках DACL, который как-раз и берётся из токена пользователя. В отличии от имени и пароля в базе реестра SAM, токен нигде не сохраняется, а при каждой загрузке системы создаётся вновь.

Алгоритм проверки прав доступа к объекту со стороны нашего процесса выглядит так:

1. Если SID из токена не совпадает с SID в элементе ACE объекта, то осуществляется переход к следующему ACE в цепочке, иначе переход к п.2.​
2. Если элемент ACE запрещает доступ к объекту обладателю данного SID, но он является владельцем объекта (и запрос только на чтение), доступ к объекту разрешается, иначе переход к п.3.​
3. Если достигнут конец списка DACL – попытка доступа отклоняется.​
4. Если DACL объекта пуст, доступ к нему запрещён всем субъектам, за исключением владельца, которому разрешены чтение и изменение списка DACL (право WRITE_DAC).​
5. Если у объекта нет дескриптора безопасности SD (например, у файлов на диске FAT), то все могут получить любые права-доступа к данному объекту.​

Обсуждать коня в вакууме как-то не комильфо, поэтому проведём небольшой эксперимент в отладчике WinDbg, чтобы посмотреть на токен реального приложения. Значит даём команду !process 0 0 для сбора инфы об активных процессах, и находим среди них адрес окружения своего – я получил 0x8a058d20. Теперь передаю этот адрес той-же команде !process, чтобы она отфильтровала лог и вернула информацию только о моём процессе, забросив в топку все остальные. Объём выводимой инфы можно контролировать параметром в диапазоне 0-7, где нуль минимум, а 7 макс.инфы – нам достаточно единицы. В выхлопе отладчика будет красоваться адрес структуры принадлежащего нам токена = 0xa5e41030:

C-подобный:
lkd> !process 8a058d20 1
PROCESS 8a058d20  SessionId: 1  Cid: 0ab0    Peb: 7ffdf000  ParentCid  : 0abc
                  DirBase: 5f524a40  ObjectTable: a8c2b388  HandleCount: 33.
    Image: EXAMPLE.EXE

    Token                             a5e41030      ;//<------ Адрес структуры _TOKEN
    ElapsedTime                       00:00:34.834
    QuotaPoolUsage[PagedPool]         0
    Working Set Sizes (now,min,max)  (387, 50, 345) (1548KB, 200KB, 1380KB)
    PeakWorkingSetSize                387
    VirtualSize                       10 Mb
    PageFaultCount                    383
    MemoryPriority                    BACKGROUND
    BasePriority                      8
    CommitCharge                      82

Остаётся запросить токен командой dt (DisplayType), и в качестве аргумента передать ей полученный на предыдущем этапе линк 0xa5e41030. Как видим, основная структура "TOKEN" имеет не только переменные, но и множество вложенных структур, где могут быть припрятаны весьма интересные вещи. Для их отображения в WinDbg предусмотрен специальный ключ(-r), который подразумевает рекурсию. Добавьте его в хвост команды ниже, и отладчик вернёт лог полностью в развёрнутом виде, вместе со-значениями всех вложенных структур. Кстати этому ключу можно передавать и уровень рекурсии в числовом виде, например –r1 или –r2. Наиболее важные поля я выделил здесь стрелками:

C-подобный:
lkd> dt _token a5e41030
nt!_TOKEN              ;//<---//------------------ размер 480 байт = 0x1e0
   +0x000 TokenSource         : _TOKEN_SOURCE
   +0x010 TokenId             : _LUID
   +0x018 AuthenticationId    : _LUID
   +0x020 ParentTokenId       : _LUID
   +0x028 ExpirationTime      : _LARGE_INTEGER 0x7fffffff`ffffffff
   +0x030 TokenLock           : 0x826d2860 _ERESOURCE
   +0x034 ModifiedId          : _LUID
   +0x040 Privileges          : _SEP_TOKEN_PRIVILEGES   ;//<-----------
   +0x058 AuditPolicy         : _SEP_AUDIT_POLICY
   +0x074 SessionId           : 1
   +0x078 UserAndGroupCount   : 0x10
   +0x07c RestrictedSidCount  : 0
   +0x080 VariableLength      : 0x1fc
   +0x084 DynamicCharged      : 0x400
   +0x088 DynamicAvailable    : 0
   +0x08c DefaultOwnerIndex   : 5
   +0x090 UserAndGroups       : 0xa5e41210 _SID_AND_ATTRIBUTES    ;//<-----------
   +0x094 RestrictedSids      : (null)
   +0x098 PrimaryGroup        : 0xb08ff468 Void                   ;//<-----------
   +0x09c DynamicPart         : 0xb08ff468  -> 0x501
   +0x0a0 DefaultDacl         : 0xb08ff484 _ACL                   ;//<-----------
   +0x0a4 TokenType           : 1 ( TokenPrimary )                ;//<-----------
   +0x0a8 ImpersonationLevel  : 0 ( SecurityAnonymous )
   +0x0ac TokenFlags          : 0x2000
   +0x0b0 TokenInUse          : 0x1 ''
   +0x0b4 IntegrityLevelIndex : 0xf
   +0x0b8 MandatoryPolicy     : 3
   +0x0bc LogonSession        : 0x8df64310 _SEP_LOGON_SESSION_REFERENCES
   +0x0c0 OriginLogonSession  : _LUID
   +0x0c8 SidHash             : _SID_AND_ATTRIBUTES_HASH          ;//<-----------
   +0x150 RestrictedSidHash   : _SID_AND_ATTRIBUTES_HASH
   +0x1d8 pSecurityAttributes : 0xae185238 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
   +0x1dc SessionObject       : 0x86628c68 Void
   +0x1e0 VariablePart        : 0xa5e41290

А что, если нужно просмотреть не все вложенные структуры, а в избирательном порядке одну.., к примеру первую "TOKEN --> TOKEN_SOURCE"? Для этого, в конце команды указываем имя интересующего нас поля, и завершаем команду жирной точкой. Можно даже связывать отображение вложенных структур по типу:
dt _token a5e41030 TokenSource.SourceIdentifier. (отобразит локальный идентификатор LUID):

C-подобный:
lkd> dt _token a5e41030 TokenSource.
nt!_TOKEN
   +0x000 TokenSource :
      +0x000 SourceName       : [8] "User32 "
      +0x008 SourceIdentifier : _LUID

2.1. Типы токенов

Обратите внимание на поле по смещению 0x0a4 под названием "TokenType" – это тип токена. Здесь оно хранит константу(1) = TOKEN_PRIMARY. Дело в том, что в природе мирно сосуществуют две разновидности токенов: первичный и наследуемый.

• Первичный токен: Token_Primary = 1
Это основной токен процесса, и создается он только ядром Win. Содержимое представляет информацию о безопасности по умолчанию. Функция UserLogon() и подсистема Lsass всегда создаёт только первичный токен, хэндл которого можно получить вызовом OpenProcessToken(). Ещё одна функция CreateRestrictedToken() тоже создаёт его, но только урезанный в привилегиях вариант, о чём собственно и говорит название "Restricted" (ограниченный). На вход ей нужно подать первичный токен, а на выходе получим только шелуху от него. Такие токены Lsass обычно "дарит" гостям системы.

• Токен олицетворения: Token_Impersonation = 2
Токен данного типа заимствует права первичного токена, чтобы олицетворять клиентский процесс на сервере. К примеру токены олицетворения передаются от процессов к каждому из его потоков, так-что получить дескриптор можно функцией OpenThreadToken(). Однако в полной мере достоинства наследуемых токенов раскрываются в клиент-серверных приложениях, когда серверы предоставляют клиентам такие расшаренные объекты как: файлы, принтеры, базы-данных и прочее.

Получив запрос сервер должен убедиться, что у клиента есть разрешение на доступ к запрашиваемому объекту – для этого ему нужен SID учётной записи клиента, который и передаётся в токене олицетворения механизмом заимствования прав. Меняет тип токена с первичного на токен-олицетворения функция ImpersonateLoggedOnUser(), а обратную операцию проводят RevertToSelf() или DuplicateTokenEx().


3. Привилегии пользователей

Рассмотрим некоторые вопросы лингвистики..
Знаете-ли вы, какая разница между привилегией и правом пользователя? На сайте MSDN привилегией называют расширенные права юзеров, что собственно ничуть не проясняет ситуацию. Однако если уйти в доки с головой, то найти тонкую грань между двумя этими терминами всё-таки можно.

Права "Rights" (так-же известные как разрешения) всегда связаны с определенным объектом системы, который выдаёт нам право на открытие файла, запись, чтение из него и прочее. Монитор безопасности SRM разграничивает права пользователей на доступ к объекту, просматривая его список DACL. Обычно DACL'ы и их записи АСЕ сохраняются вместе с объектом. Например файловая запись АСЕ лежит прямо рядом с ним на диске – атрибут безопасности файла в NTFS равен 50h (см.спеки Ntfs):

NTFS_attr.png


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

Добавляет привилегий в токен функция AdjustTokenPrivileges(), но перед этим не помешало-бы узнать полный их список в системе. Проблема в том, что за время своего существования Win не однократно редактировала этот лист и может получиться так, что поддержка требуемой нам привилегии просто прекратилась и её место заняла какая-нибудь новая. Например начиная с Win-7, широко известной привилегии SeTcbPrivilege с идентификатором(7) уже не существует, и можно долго гадать, почему-же она не выставляется.

Функции для работы с подсистемой безопасности Lsass сосредоточены в библиотеке пользовательского уровня Advapi32.dll (advanced, расширенные API). Среди них имеются две интересные функции Set/GetTokenInformation(), которые как и следует из названий оперируют информацией в токене. Вот их прототип:

C-подобный:
BOOL GetTokenInformation
  TokenHandle             dd  0  ;// дескриптор токена
  TokenInformationClass   dd  0  ;// класс запрашиваемой информации
  TokenInformation        dd  0  ;// линк на буфер под инфу
  TokenInformationLength  dd  0  ;// размер буфера
  ReturnLength            dd  0  ;// переменная, куда вернётся кол-во байт

Значит в первый аргумент передаём дескриптор токена, который возвращает OpenProcessToken(). Во-втором аргументе нужно указать класс/тип запрашиваемой информации – поскольку мы планируем получить список системных привилегий, то ставим константу(3) "TokenPrivileges". Для этих функции всего предусмотрено 48 разнообразных классов, и соответственно такое-же количество информации о токене они способны вернуть. В следующих аргументах указываем адрес и размер буфера, а так-же адрес переменной, куда функция вернёт требуемый размер буфа для конкретного типа инфы.

C-подобный:
;//--- Класс запросов для Set/Get TokenInformation() -----
;// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-token_information_class

TokenUser                    =  1    ;// SID пользователя (атрибуты не определены)
TokenGroups                  =  2    ;// массив SID групп (и их атрибуты)
TokenPrivileges              =  3    ;// массив LUID привилегий (и их атрибуты)
TokenOwner                   =  4    ;// владелец
TokenPrimaryGroup            =  5    ;// группа пользователя токена
TokenDefaultDacl             =  6    ;// DACL, который будет назначен всем объектам, использующим этот токен
TokenSource                  =  7    ;// текстовый (и числовой) ID создателя токена
TokenType                    =  8    ;// тип
TokenImpersonationLevel      =  9    ;// уровень олицетворения
TokenStatistics              =  10   ;// общая информация о токене
TokenRestrictedSids          =  11   ;// ограниченный SID
TokenSessionId               =  12   ;// SID сессии Logon

TokenGroupsAndPrivileges     =  13 
TokenSessionReference        =  14
TokenSandBoxInert            =  15
TokenAuditPolicy             =  16
TokenOrigin                  =  17
TokenElevationType           =  18
TokenLinkedToken             =  19
TokenElevation               =  20
TokenHasRestrictions         =  21
TokenAccessInformation       =  22
TokenVirtualizationAllowed   =  23
TokenVirtualizationEnabled   =  24
TokenIntegrityLevel          =  25
TokenUIAccess                =  26
TokenMandatoryPolicy         =  27
TokenLogonSid                =  28
TokenIsAppContainer          =  29
TokenCapabilities            =  30
TokenAppContainerSid         =  31
TokenAppContainerNumber      =  32
TokenUserClaimAttributes     =  33
TokenDeviceClaimAttributes   =  34
TokenRestrictedUserClaimAttributes    =  35
TokenRestrictedDeviceClaimAttributes  =  36
TokenDeviceGroups            =  37
TokenRestrictedDeviceGroups  =  38
TokenSecurityAttributes      =  39
TokenIsRestricted            =  40
TokenProcessTrustLevel       =  41
TokenPrivateNameSpace        =  42
TokenSingletonAttributes     =  43
TokenBnoIsolation            =  44
TokenChildProcessFlags       =  45
TokenIsLessPrivilegedAppContainer     =  46
TokenIsSandboxed             =  47
MaxTokenInfoClass            =  48

Если функция отработает успешно, вернёт в EAX логическую единицу и в буфере получим данные, которые описывает структура "TOKEN_PRIVILEGES". Первое поле в ней "PrivilegeCount" – это общее число поддерживаемых привилегий, а дальше следует массив вложенных структур "LUID_AND_ATTRIBUTES" с описанием каждой привилегии в отдельности. Таким образом, первое поле хранит кол-во вложенных структур, что совпадает с кол-вом зареганных в системе привилегий. Чтобы узнать, какие из общего списка назначены нам, нужно будет проверить поле с атрибутом во-вложенных структурах.

C-подобный:
struct TOKEN_PRIVILEGES
  PrivilegeCount   dd  0
  Privileges       LUID_AND_ATTRIBUTES
ends
;//--------------------
struct LUID_AND_ATTRIBUTES
  Luid             dq  0   ;// ID привилегии (8-байт)
  Attributes       dd  0   ;// откл(0), вкл.в дефолте(1), вкл.программно(2)
ends

Так мы получим в буфере идентификаторы LUID всех привилегий (Local Identifier), и чтобы привести их в дружелюбный текстовый вид, нужно будет позвать функцию LookupPrivilegeName(). Она ожидает на входе идентификатор, и возвращает соответствующую ему строку. Родственная ей функция LookupPrivilegeValue() проводит обратную операцию, т.е. принимая имя, возвращает LUID. Завернём всё вышесказанное в код:

C-подобный:
format pe console
entry  start
;//------------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\advapi32.inc'
;//------------
.data
szEnabled    db  '      <---// Enabled',0
szDefault    db  '      <---// Enabled by default',0

hToken       dd  0
pReturn      dd  0

align 16
privStr      rb  128
strSize      dd  0
buff         db  0
;//------------
.code
start:   invoke  SetConsoleTitle,<'*** System Privileges List ***',0>

;//----- Открываем первичный токен своего процесса
         invoke  GetCurrentProcess
         invoke  OpenProcessToken,eax,TOKEN_QUERY,hToken

;//----- Запрос списка привелегий в токене
;//----- (первый вызов возвращает требуемый размер буфера)
         invoke  GetTokenInformation,[hToken],TokenPrivileges,buff,0,pReturn
         invoke  GetTokenInformation,[hToken],TokenPrivileges,buff,[pReturn],pReturn

        cinvoke  printf,<10,10,' Total privileges in system = %d',\
                         10,10,'  Id   Name',\
                            10,' *************************************',0>,dword[buff]

;//----- В буфере лежат все привилегии - перебираем их..
align 16
         mov     ecx,dword[buff]   ;// PrivilegeCount
         mov     esi,buff+4        ;// начало массива "LUID_AND_ATTRIBUTES"
@@:      push    esi ecx esi
         mov     [strSize],128     ;// обновить длину строки
         mov     eax,[esi]         ;// LUID очередной привилегии
         push    eax
         invoke  LookupPrivilegeName,0,esi,privStr,strSize   ;// в строку его,
         pop     eax
        cinvoke  printf,<10,'  %02d.  %-25s',0>,eax,privStr  ;// ..и на консоль
         pop     eax
         mov     eax,[eax+8]       ;// взять атрибут
         or      eax,eax           ;// если нуль (отключена),
         je      @next             ;//  ..пропустить.

         mov     esi,szEnabled     ;// иначе: попалась активная привилегия!
         cmp     eax,SE_PRIVILEGE_ENABLED  ;// программно включена?
         je      @prn
         mov     esi,szDefault     ;// значит идёт в дефолте.
@prn:   cinvoke  printf,<'%s',0>,esi

@next:   pop     ecx esi
         add     esi,sizeof.LUID_AND_ATTRIBUTES  ;// перейти в массиве на сл.привилегию
         dec     ecx               ;// счётчик -1
         jnz     @b                ;// повторить, если не нуль..

@exit:  cinvoke  _getch
        cinvoke  exit,0
;//------------
section '.idata' import data readable
library  kernel32,'kernel32.dll',msvcrt,'msvcrt.dll',advapi32,'advapi32.dll'

include  'api\kernel32.inc'
include  'api\msvcrt.inc'
include  'api\advapi32.inc'

PrivList.png


Если верить функции GetTokenInformation() (а не верить ей у нас нет причин), то семёрка поддерживает всего 24 привилегии, причём по-умолчанию у меня (как входящего в группу админов) включены только три из них. "ChangeNotify" имеют все пользователи, "Impersonate" позволяет олицетворять себя с другими лицами, а привилегию "CreateGlobal" система назначает только админам, что даём им право создавать глобальные объекты в пространстве имён. Список всех привилегий и заложенный в них глубокий смысл можно найти в репозитории MSDN по этой ссылке.

Обратите внимание на идентификаторы – первые четыре привилегии(0,1,2,3) под семёркой уже отправлены в утиль, причём и дальше порядок не последовательный. Более того, содержимое листа привязано и к версии операционной системы. Так, запустив этот код на своём буке с Win-10 я обнаружил, что в ней пропала привилегия с идентификатором(4), зато появилась новая с LUID=36, под громким названием "SeDelegateSessionUserImpersonatePrivilege". На сайте RSDN есть статья
"Что такое привилегии?" – можете почитать на досуге.


3.1. Включение привилегий в токене

Ладно, лист поддержки получили.. теперь попробуем повысить себя в ранге и добавить парочку привилегий в токен функцией AdjustTokenPrivileges(). Она может как включать, так и отключать ненужные привилегии. Данная операция требует, чтобы токен был открыт с доступом TOKEN_QUERY + TOKEN_ADJUST_PRIVILEGES. Вот её описание:


C-подобный:
BOOL AdjustTokenPrivileges
  TokenHandle             dd  0   ;// хендл токена безопасности
  DisableAllPrivileges    dd  0   ;// 1 = отмена всех привилегий, 0 = добавить в токен
  NewState                dd  0   ;// нужные привилегии в виде структуры TOKEN_PRIVILEGES
  BufferLength            dd  0   ;// размер буфера (опционально)
  PreviousState           dd  0   ;// лист предыдущих (опционально)
  ReturnLength            dd  0   ;// размер PreviousState (опционально)

Если во-второй аргумент запихать логическую единицу, функция превратит токен в ограниченный "Resticted", удалив из него все привилегии кроме "SeChangeNotify". Особого внимания заслуживает здесь аргумент "NewState", который является указателем на структуру "TOKEN_PRIVILEGES". Как упоминалось выше, в первом поле этой структуры лежит счётчик вложенных структур "LUID_AND_ATTRIBUTES", за которым следует и сам массив. Значит чтобы добавить в токен, например, четыре какие-нибудь дополнительные привилегии, можно воспользоваться такой конструкцией в секции-данных программы:

C-подобный:
align 16
newState:       ;//<-----<---- Структура "TOKEN_PRIVILEGES"
PrivilegeCount  dd  4
Luid1           dq  0
Attribute1      dd  SE_PRIVILEGE_ENABLED
Luid2           dq  0
Attribute2      dd  SE_PRIVILEGE_ENABLED
Luid3           dq  0
Attribute3      dd  SE_PRIVILEGE_ENABLED
Luid4           dq  0
Attribute4      dd  SE_PRIVILEGE_ENABLED

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

C-подобный:
format pe console
entry  start
;//------------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\advapi32.inc'
;//------------
.data
szEnabled    db  '      <---// Enabled',0
szDefault    db  '      <---// Enabled by default',0

hToken       dd  0
pReturn      dd  0

align 16
;//---- Структура "TOKEN_PRIVILEGES"
newState:
PrivilegeCount  dd  4
Luid1           dq  0
Attribute1      dd  SE_PRIVILEGE_ENABLED
Luid2           dq  0
Attribute2      dd  SE_PRIVILEGE_ENABLED
Luid3           dq  0
Attribute3      dd  SE_PRIVILEGE_ENABLED
Luid4           dq  0
Attribute4      dd  SE_PRIVILEGE_ENABLED

align 16
privStr      rb  128
strSize      dd  0
buff         db  0
;//------------
.code
start:   invoke  SetConsoleTitle,<'*** System Privileges List ***',0>
        cinvoke  printf,<10,' Set Privileges: SeDebugPrivilege',\
                         10,'                 SeBackupPrivilege',\
                         10,'                 SeRestorePrivilege',\
                         10,'                 SeLoadDriverPrivilege',0>

;//----- Открываем первичный токен своего процесса
         invoke  GetCurrentProcess
         invoke  OpenProcessToken,eax,\
                                  TOKEN_QUERY + TOKEN_ADJUST_PRIVILEGES,\
                                  hToken
         or      eax,eax
         jnz     @f
        cinvoke  printf,<10,' Error set AdjustPrivileges flag!',0>
         jmp     @exit

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

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

;//----- Запрос списка привелегий
;//----- (первый вызов возвращает требуемый размер буфера)
@@:      invoke  GetTokenInformation,[hToken],TokenPrivileges,buff,0,pReturn
         invoke  GetTokenInformation,[hToken],TokenPrivileges,buff,[pReturn],pReturn

        cinvoke  printf,<10,10,' Total privileges in system = %d',\
                         10,10,'  Id   Name',\
                            10,' *************************************',0>,dword[buff]
;//..............
;//<----- здесь всё остаётся без изменений

Adjust.png


Привилегии, которые мы назначаем сами должны иметь атрибут(2), а которые идут по-умолчанию(1). На случай, если нам нужно наоборот отключить какую-нибудь привилегию оставив все остальные, предусмотрен атрибут(4) "SE_PRIVILEGE_REMOVED". Так они представлены в сишном хидере Winnt.h:

C-подобный:
;//--- Атрибуты привилегий -------------------
SE_PRIVILEGE_ENABLED_BY_DEFAULT  =  0x00000001  ;// по умолчанию
SE_PRIVILEGE_ENABLED             =  0x00000002  ;// предоставлена по запросу
SE_PRIVILEGE_REMOVED             =  0x00000004  ;// удалена

Теперь мой процесс может без проблем копировать системные файлы привилегией "SeBackup" (даже если этого запрещает DACL файла, поскольку привилегии имеют приоритет над правом), производить в них запись посредством "SeRestore", и захватывать чужие процессы, для чего и предназначена "SeDebug". Следуя этикету нужно отметить, после того-как мы выполним задачу, нужно восстановить свои дефолтные привилегии (см.аргумент PreviousState в AdjustTokenPrivileges). В противном случае, даже после закрытия софта установленные привилегии остаются в нашем токене, и будут действительны вплоть до окончания текущего сеанса (т.е. до следующей перезагрузки системы). В некоторых случаях это может создать проблемы, например оставив лазейку для хакеров.


4. Сбор информации о токенах, и списках DACL

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

GetTokenInformation() – запрашивает 48-типов информации из токена;
LookupPrivilegeName() – принимая LUID возвращает привилегию в виде строки;
ConvertSidToStringSid() – конвертирует двоичный SID в строку.

Можно было вывести намного больше данных, но лист не вместился уже в окно консоли, поэтому минимум.:

C-подобный:
format pe console
entry  start
;//------------
section '.inc' data readable
include 'win32ax.inc'
include 'equates\advapi32.inc'
;//------------
.data
hToken       dd  0
pReturn      dd  0
counter      dd  0
number       dd  0
offset       dd  0

align 16
privStr      rb  256
strSize      dd  0
sidBuff      rb  128

align 16
buff         db  0
;//------------
.code
start:   invoke  SetConsoleTitle,<'*** Current Token Info ***',0>

         invoke  OpenProcessToken,-1,TOKEN_QUERY,hToken

;//----- SID юзера в системе ----------
         invoke  GetTokenInformation,[hToken],TokenUser,buff,0,pReturn
         invoke  GetTokenInformation,[hToken],TokenUser,buff,[pReturn],pReturn
         mov     eax,dword[buff]
         invoke  ConvertSidToStringSid,eax,buff
         mov     eax,dword[buff]
        cinvoke  printf,<10,' User System Sid..:  %s',0>,eax

;//----- Получим SID из токена ----------
         invoke  GetTokenInformation,[hToken],TokenGroups,buff,0,pReturn
         invoke  GetTokenInformation,[hToken],TokenGroups,buff,[pReturn],pReturn

         mov     ecx,dword[buff]
         mov     esi,buff+4
@@:      mov     eax,[esi + SID_AND_ATTRIBUTES.Attributes]
         and     eax,SE_GROUP_LOGON_ID
         cmp     eax,SE_GROUP_LOGON_ID
         je      @found
         add     esi,sizeof.SID_AND_ATTRIBUTES
         dec     ecx
         jnz     @b
        cinvoke  printf,<10,' ERROR! SID not found!',0>
         jmp     @exit

@found:  mov     eax,dword[esi]
         invoke  ConvertSidToStringSid,eax,buff
         mov     eax,dword[buff]
        cinvoke  printf,<10,' User Logon  Sid..:  %s',0>,eax

;//----- Перечислим привелегии в токене ----------
         invoke  GetTokenInformation,[hToken],TokenPrivileges,buff,0,pReturn
         invoke  GetTokenInformation,[hToken],TokenPrivileges,buff,[pReturn],pReturn

        cinvoke  printf,<10,10,' Total privileges in system = %d **********',\
                            10,' ******************************************',\
                            10,' User privileges: ',0>,dword[buff]

         mov     ecx,dword[buff]
         mov     esi,buff+4
@@:      cmp     [esi + LUID_AND_ATTRIBUTES.Attributes],0
         jz      @fuck
         push    esi ecx
         mov     [strSize],128
         invoke  LookupPrivilegeName,0,esi,privStr,strSize
        cinvoke  printf,<10,'      %s',0>,privStr
         pop     ecx esi
@fuck:   add     esi,sizeof.LUID_AND_ATTRIBUTES
         dec     ecx
         jnz     @b

;//----- Общая статистика токена ----------
;         invoke  GetTokenInformation,[hToken],TokenStatistics,buff,0,pReturn
;         invoke  GetTokenInformation,[hToken],TokenStatistics,buff,[pReturn],pReturn
;        cinvoke  printf,<10,' Stat:  %x',0>,eax

;//**************************************************************
        cinvoke  printf,<10,10,' Default Security Descriptor **************',\
                            10,' ******************************************',0>

;//----- Owner (владелец).
;//----- SID, который будет записан как владелец любых объектов,
;//----- созданных процессом с этим токеном доступа.
         invoke  GetTokenInformation,[hToken],TokenOwner,buff,0,pReturn
         invoke  GetTokenInformation,[hToken],TokenOwner,buff,[pReturn],pReturn
         mov     eax,dword[buff]
         invoke  ConvertSidToStringSid,eax,buff
         mov     eax,dword[buff]
        cinvoke  printf,<10,' Owner.......:  %s',0>,eax

;//----- PrimaryGroup.
;//----- SID, который будет записан как основная группа любых объектов,
;//----- созданных процессом с этим токеном доступа.
         invoke  GetTokenInformation,[hToken],TokenPrimaryGroup,buff,0,pReturn
         invoke  GetTokenInformation,[hToken],TokenPrimaryGroup,buff,[pReturn],pReturn
         mov     eax,dword[buff]
         invoke  ConvertSidToStringSid,eax,buff
         mov     eax,dword[buff]
        cinvoke  printf,<10,' Group.......:  %s',0>,eax

;//----- DefaultDacl.
;//----- DACL, который будет назначен любым объектам,
;//----- созданным процессом c этим токеном, если не указан явный ACL.
         invoke  GetTokenInformation,[hToken],TokenDefaultDacl,buff,0,pReturn
         invoke  GetTokenInformation,[hToken],TokenDefaultDacl,buff,[pReturn],pReturn

         mov     esi,dword[buff]
         push    esi
         movzx   eax,[esi + DACL_HEADER.AclRevision]
         movzx   ebx,[esi + DACL_HEADER.AclSize]
         movzx   ecx,[esi + DACL_HEADER.AceCount]
         mov     [counter],ecx
        cinvoke  printf,<10,10,' DACL ver....:  %d',\
                            10,' DACL size...:  %d byte',\
                            10,'  ACE count..:  %d',0>,eax,ebx,ecx

         pop     esi
         add     esi,sizeof.DACL_HEADER
         mov     ecx,[counter]
@@:      push    esi ecx
         movzx   eax,[esi + ACCESS_ALLOWED_ACE.AceFlags]
         movzx   ebx,[esi + ACCESS_ALLOWED_ACE.AceSize]
         mov     ecx,[esi + ACCESS_ALLOWED_ACE.Mask]
         mov     [offset],ebx
         push    esi
        cinvoke  printf,<10,10,'         ACE.%d   Size:  %d byte',\
                            10,'         ACE.%d  Flags:  0x%x',\
                            10,'         ACE.%d   Mask:  0x%08X',0>,[number],ebx,\
                                                                    [number],eax,\
                                                                    [number],ecx
         pop     esi
         add     esi,ACCESS_ALLOWED_ACE.SidStart
         invoke  ConvertSidToStringSid,esi,sidBuff
         mov     eax,dword[sidBuff]
        cinvoke  printf,<   10,'         ACE.%d    SID:  %s',0>,[number],eax

         pop     ecx esi
         inc     [number]
         add     esi,[offset]
         dec     ecx
         jnz     @b

@exit:  cinvoke  _getch
        cinvoke  exit,0
;//------------

;//------------
section '.idata' import data readable
library  kernel32,'kernel32.dll',msvcrt,'msvcrt.dll',advapi32,'advapi32.dll'

include  'api\kernel32.inc'
include  'api\msvcrt.inc'
include  'api\advapi32.inc'

Result.png



5. Под занавес..

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

Вложения

  • TOKEN.ZIP
    7,8 КБ · Просмотры: 151
Последнее редактирование модератором:

igtech

New member
30.09.2021
1
0
Всем привет, немного, наверное, простой вопрос, но почему не срабатывает !process 0 0?

************* Path validation summary **************
Response Time (ms) Location
OK D:\test\symbols
0:000> !process 0 0
kdexts!DebugExtensionInitialize failed with 0x80004005
The !process extension command was not found in loaded extensions, but is
available as part of the kdexts extension. Run .update kdexts to install
No export process found
0:000> .load kdexts
kdexts!DebugExtensionInitialize failed with 0x80004005


0:000> .load D:\test\kdexts.dll

**** WARNING loaded *kernel* extension dll for usermode

0:000> !process 0 0

**** WARNING loaded *kernel* extension dll for usermode

**** NT ACTIVE PROCESS DUMP ****
unable to get nt!MmUserProbeAddress
NT symbols are incorrect, please fix symbols

...
как заставить работать комманду?
 

Marylin

Mod.Assembler
Red Team
05.06.2019
291
1 267
попробуйте запустить отладчик в режиме ядра CTRL+K, и загрузите символы
**** NT ACTIVE PROCESS DUMP ****
невозможно получить nt! MmUserProbeAddress
Символы NT неверны, исправьте символы
 
Мы в соцсетях: