Статья ASM. Демоны в Windows (1) – сессии, станции, рабочие столы

В системах класса Win, одним из привлекательных направлений является программирование сервисов. Связано это с тем, что сервисы имеют приоритет над обычными приложениями, поскольку предназначены для обслуживания работы самой ОС и запускаются от имени системы. По этой причине, их ещё называют системными службами. Если провести аналогию с Linux, то сервисы Win можно сравнить с демонами, которые присутствуют всегда, но не выползая в свет работают в фоне. В данном треде попробуем разобраться с технической их стороной и посмотрим, какую они представляют угрозу безопасности. Материал получился обширным, а потому пришлось разделить его на две части.

Оглавление:


1. Вводная часть
2. Учётные записи: System, LocalService, NetworkService
3. Сессии входа в систему
4. Оконные станции
5. Рабочие столы
6. Общая картина
7. Практика – вывод инфраструктуры
8. Заключение.



1. Введение (в заблуждение)

Работа сервисов тесно связана с такими понятиями как: сессии входа в систему, оконные\терминальные станции, и рабочие столы "Desktop". По сути, эта триада представляет собой рабочее окружение всей исполняемой среды Win, так-что для полноты картины имеет смысл дать ей определение. Здесь нужно отметить, что тема довольно мутная, а её разбор и понимание усугубляет то, что начиная с Висты внутренняя архитектура претерпела огромные изменения. Более того, некоторые сюрпризы в этом плане подвезли и в десятку (например виртуальные десктопы Win10), а значит и новшества Висты уже устарели.

Но не всё так плохо, поскольку общие принципы и фундамент остался прежним – из крана с идеями уже не бьёт как прежде струя, и нам будут подкидывать мелкие крапаля ещё довольно долго. Просто Microsoft в своём стиле навешивает на старый позвоночник никому не нужные аппендиксы, выдавая их за нечто новое (а как иначе продать товар?). Далее по тексту, отличительные особенности разных ОС будут выноситься в сноски, оставляя свободу основным моментам организации исполняемой среды.


2. Учётные записи пользователей

Windows изначально проектировалась как много-пользовательская ОС. Пользователь считается легальным, если в разделе реестра HKEY_USERS для него создана своя учётная запись. Отвечающий за безопасность компонент ядра LSASS не использует имена юзеров в привычном нам строковом виде, а при вводе данных аккаунта в окне WinLogon, назначает им идентификатор безопасности SID (Security Identifier), после чего формирует для данного SID маркер-доступа к различным объектам ACCESS_TOKEN (защищённые объекты ожидают пользовательский SID в своих списках DACL). Таким образом, каждый из прошедших проверку пользователей, должен иметь свою учётную запись, SID и токен.

В контексте данной статьи мы сконцентрируем своё внимание на токене, поскольку именно он определяет привилегии, а так-же служит мандатом при попытке доступа к буквально всем объектам системы, например службам, процессам, файлам, и т.д. Если повезёт утянуть токен у пользователя System, мы получим полную власть над системой, оставив даже админа нервно курить в сторонке. Для этого, открывается какой-либо системный процесс функцией OpenProcess(), далее запрашивается его маркер безопасности через OpenProcessToken(), и с полученного токена снимается ксерокопия DuplicateTokenEx() с флагом "TOKEN_PRIMARY". Поскольку дескрипторы процессов наследуются от родителей, теперь мы сможем запустить любой код от имени пользователя System функцией CreateProcessAsUser(), которая требует в первом аргументе именно токен юзверя.

Помимо знакомых большинству из нас учётных записей типа Admin, User, Guest (им назначается SID = S-1-5-21-x-y-z), в системе имеются ещё как-минимум 4 перечисленных ниже скрытых пользователя, которые заслужено привлекают к себе внимание (для них последние числа 7,18,19,21 и т.д. известны как RID, или относительный RelativeID):


1. Пользователь System с неограниченными правами (иногда LocalSystem): SID = S-1-5-18;
2. LocalService, под которой исполняются все сервисы на локальном узле: SID = S-1-5-19;
3. NetworkService, для обслуживания сети и удалённого доступа к системе: SID = S-1-5-20;
4. Anonymous, анонимный вход по сети без указания логин\пароля, а потому ограниченный: SID = S-1-5-7;

Если зайти в раздел реестра HKEY_USERS, можно обнаружить там подразделы для этих учётных записей.
Что касается Win-10, то в ней к уже имеющимся, присавокупились ещё две учётки:


5. DWM, компонент "диспетчера окон рабочего стола" и его системных служб (Desktop Window Manager, SID = S-1-5-90);
6. UMFD, что подразумевает "User Mode Font Driver" (компонент среды разработки DriverFramework, SID = S-1-5-96).

Более развёрнутую информацию по теме можно найти на сайте MSDN по этой ссылке.

C-подобный:
lkd> dt _token
nt!_TOKEN
   +0x000 TokenSource             : _TOKEN_SOURCE
   +0x010 TokenId                 : _LUID
   +0x018 AuthenticationId        : _LUID
   +0x020 ParentTokenId           : _LUID
   +0x028 ExpirationTime          : _LARGE_INTEGER
   +0x030 TokenLock               : Ptr32 _ERESOURCE
   +0x034 ModifiedId              : _LUID
   +0x040 Privileges              : _SEP_TOKEN_PRIVILEGES
   +0x058 AuditPolicy             : _SEP_AUDIT_POLICY
   +0x074 SessionId               : Uint4B
   +0x078 UserAndGroupCount       : Uint4B
   +0x07c RestrictedSidCount      : Uint4B
   +0x080 VariableLength          : Uint4B
   +0x084 DynamicCharged          : Uint4B
   +0x088 DynamicAvailable        : Uint4B
   +0x08c DefaultOwnerIndex       : Uint4B
   +0x090 UserAndGroups           : Ptr32 _SID_AND_ATTRIBUTES
   +0x094 RestrictedSids          : Ptr32 _SID_AND_ATTRIBUTES
   +0x098 PrimaryGroup            : Ptr32 Void
   +0x09c DynamicPart             : Ptr32 Uint4B
   +0x0a0 DefaultDacl             : Ptr32 _ACL
   +0x0a4 TokenType               : _TOKEN_TYPE
   +0x0a8 ImpersonationLevel      : _SECURITY_IMPERSONATION_LEVEL
   +0x0ac TokenFlags              : Uint4B
   +0x0b0 TokenInUse              : UChar
   +0x0b4 IntegrityLevelIndex     : Uint4B
   +0x0b8 MandatoryPolicy         : Uint4B
   +0x0bc LogonSession            : Ptr32 _SEP_LOGON_SESSION_REFERENCES
   +0x0c0 OriginatingLogonSession : _LUID
   +0x0c8 SidHash                 : _SID_AND_ATTRIBUTES_HASH
   +0x150 RestrictedSidHash       : _SID_AND_ATTRIBUTES_HASH
   +0x1d8 pSecurityAttributes     : Ptr32 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
   +0x1dc SessionObject           : Ptr32 Void
   +0x1e0 VariablePart            : Uint4B


3. Сессии входа в систему

В силу того, что каждый зареганый пользователь имеет свой SID, привилегии, и права на доступ к объектам, подсистема безопасности Win не может стричь всех под одну гребёнку – ей обязательно нужен глобальный механизм разграничения прав. Для этого, с каждым пользователем связывается такое понятие как "Сессия входа в систему", которая помещает каждого юзера в свой запечатанный "пузырь". Иными словами сессия – это граница безопасности пользователя, его собственный периметр с каменной стеной.

Например когда при вкл.машины в окне WinLogon админ указывает данные своего аккаунта (логин\пароль), ему назначается сессия(1), обычному юзеру сессия(2), гостю сессия(3), и т.д. Вообще-то сессии нумеруются по правилу "кто первым встал, того и тапки". То-есть обладателем сессии(1) становится тот, кто первым вошёл в систему, и это не обязательно должен быть админ.

Windows организована так, что в любой момент только один из пользователей может быть активным, а остальные уходят в фон на скамейку запасных. Чтобы переключиться на другую сессию и сменить пользователя, необходимо нажать комбинацию клавиш Ctrl+Alt-Delete (или Win+L), что пробудит процесс Winlogon.exe, и он тут-же отзовётся своим окном Logon. При этом активная сессия отправится на покой, а её место займёт сессия выбранной учётной записи.

Однако начиная с Висты, в архитектуре ОС появилась изолированная от всех сессия(0).
При загрузке системы, процесс Wininit.exe сначала создаёт нулевую сессию, внутри которой запускает все системные службы с флагом "AutoRun". Она отводится для уже знакомых нам учётных записей: System, LocalService, NetworkService, и драйвера шрифтов UMFD. В доках сессию(0) называют ещё служебной, и доступ к ней имеют только системные процессы Win.

Далее создаётся сессия(1) и управление принимает процесс WinLogon.exe, для вывода окна-входа в систему и регистрации смертных пользователей. Таким образом, в Виста+ системные службы и драйвера полностью изолированы от наших приложений уровня Win32, поскольку находятся в недоступной нам сессии(0).


Примечание:
Изоляция сеанса(0) отсутствует в более ранних версиях ОС, включая Win-XP и сервер 2003.
В них до служб можно было дотянуться рукой, поскольку работали они внутри одной сессии с юзермодными приложениями.
Это было огромной дырой, и угрожало всей подсистеме безопасности Windows
.

Когда требуется диалог Win32-приложений с работающей в сессии(0) службой или драйвером, нам доступны только три варианта – это использование именованных каналов "Pipe", системных сокетов, и глобальных событий "Event". Это означает, что при взаимодействии между сессиями, мы лишены возможности посылать привычные нам оконные сообщения при помощи SendMessage(). На Виста+ сами по себе отваливаются и меж-сессионные хуки, которые часто применялись в кейлогерах ХР, чтобы перехватить ввод паролей в окне WinLogon. Так-что от изоляции сеанса\сессии(0) одни плюсы, хотя это зависит от того, на чьей стороне мы воюем.

На рис.ниже представлен скрин программы "ProcessHacker" где видно, что на моей системе 3 зареганых пользователя, и соответственно 4 сессии, включая изолированную(0). Именно в неё загружаются все системные файлы от имени учётных записей System, Service и Network (чтобы просмотреть файлы последних двух учёток, раскройте диспетчер служб Services.exe). В столбце "UserName" указывается имя в формате Domain\User, где NT_AUTHORITY есть домен (для сессий 1,2,3 он будет совпадать с именем компьютера). Обратите внимание, что системные файлы WinLogon и Csrss.exe копируются из ядра в каждую сессию, а токен их принадлежит учётки System. Это даёт возможность продублировать токены через DuplicateTokenEx() и попытаться запустить свой процесс с их правами CreateProcessAsUser():


ProcessHacker.png


Для сбора информации о сессиях можно воспользоваться функцией LsaEnumerateLogonSessions() из либы secur32.dll, с последующим вызовом в цикле LsaGetLogonSessionData(). Последняя возвращает данные в структуру "SECURITY_LOGON_SESSION_DATA" с таким содержимым:

C-подобный:
struct SECURITY_LOGON_SESSION_DATA
  Size                     dd  0                ;// размер этой структуры (sizeof.SECURITY_LOGON_SESSION_DATA)
  LogonId                  dq  0                ;// LUID (64-бит LocalUniqueID) сеанса входа в систему
  UserName                 LSA_UNICODE_STRING   ;// имя учетной записи, которому принадлежит сеанс входа в систему
  LogonDomain              LSA_UNICODE_STRING   ;// имя домена, для аутентификации владельца сеанса
  AuthenticationPackage    LSA_UNICODE_STRING   ;// имя пакета проверки подлинности, для проверки владельца сеанса
  LogonType                dd  0                ;// метод входа в систему (см.SECURITY_LOGON_TYPE)
  Session                  dd  0                ;// идентификатор сеанса\сессии (0,1,2,3)
  Sid                      dd  0                ;// указатель на SID пользователя
  LogonTime                dq  0                ;// время, когда владелец сеанса вошёл в систему (тип FILETIME)
  LogonServer              LSA_UNICODE_STRING   ;// имя сервера, используемого для аутентификации владельца
  DnsDomainName            LSA_UNICODE_STRING   ;// DNS-имя владельца сеанса
  Upn                      LSA_UNICODE_STRING   ;// имя участника-пользователя (UPN) для владельца сеанса
  UserFlags                dd  0                ;// пользователь отмечает сеанс входа
  LastLogonInfo            LSA_LAST_INTER_LOGON_INFO   ;// прочая инфа
  LogonScript              LSA_UNICODE_STRING   ;//
  ProfilePath              LSA_UNICODE_STRING   ;//
  HomeDirectory            LSA_UNICODE_STRING   ;//
  HomeDirectoryDrive       LSA_UNICODE_STRING   ;//
  LogoffTime               dq  0                ;// время, когда владелец сеанса вышел из системы
  KickOffTime              dq  0                ;// время, в течение которого должен завершиться сеанс входа в систему.
  PasswordLastSet          dq  0                ;// время последней смены пользователем пароля
  PasswordCanChange        dq  0                ;// пароль можно изменить во время сеанса входа в систему
  PasswordMustChange       dq  0                ;// пароль должен быть изменен во время сеанса входа в систему
ends


4. Оконные станции

Теперь мы знаем, что в иерархии среды исполнения Win первыми стоят "сессии входа в систему", нулевая из которых изолирована от всех остальных. Однако чтобы у юзера была возможность отображать консольные и графические окна, одной сессии недостаточно – сессия должна иметь хотя-бы одну "оконную станцию" Window Station. Её нужно воспринимать как бокс безопасности внутри сессии, куда помещаются рабочие столы "Desktop", а в десктопах исполняются уже процессы. Тогда получаем матрёшку – сессии (сеансы) содержат одну или несколько оконных станций, и каждая станция может иметь по несколько рабочих столов: сессия --> станция --> рабочий стол.

Во-всей линейке Win предусмотрены два типа станций:


1. Интерактивные, которые могут отображать окна и прочий гуй на своём рабочем столе, а так-же принимать клавиатурный ввод от пользователя. Как правило, для неё всегда отводится статичное имя "WinSta0". Каждая сессия входа в систему получает в своё распоряжение одну WinSta0 - она содержит клавиатуру, мышь и устройство отображения.
2. Не интерактивные станции. Этот тип не способен отображать вообще никакие окна, и обрабатывать какой-бы то ни было пользовательский ввод. Попытка вывести, например, MessageBox() в неинтерактивную станцию заранее обречена на провал, т.к. диспетчер окон тут-же посылает окну сообщение ID_OK, и не успев даже сделать вздох окно закрывается. Так повелось, что клиентами подобного рода станций являются системные службы – они изначально не расположены на общение с юзером, и сосредоточены чисто на узкопрофильных своих задачах.

К примеру, большинство сервисов и приложений загружаются под неинтерактивной станцией Service-0x0-3e7$ в изолированной сессии(0) учётной записи System. В то-же время для юзверя LocalService отводится своя станция Service-0x0-3e5$ в той-же сессии(0), а на растерзание службам сетевого аккаунта Network отдаётся Service-0x0-3e4$. Тогда получается, что внутри служебной сессии(0) живут аж 5 самостоятельных станций, и лишь одна из них "WinSta0" интерактивна. Вот что думает на этот счёт вьювер объектов от крутого прогера Four-F (на сайте wasm.in можно найти массу его статей):

WinObjStation.png


Аналогичную картину можно наблюдать и в отладчике WinDbg, потянув за его расширение !object.
На скрине выше видно, что сначала нужно запросить объект корневого каталога \Windows, и по адресу просмотреть в нём единственно вложенный дир:


Код:
lkd> !object \Windows
Object: 85fad8d8  Type: (847ca778) Directory
    ObjectHeader: 85fad8c0 (new version)
    HandleCount : 1         PointerCount: 6
    ObjectDir   : 85e05f50  Name: Windows

    Hash Address   Type          Name
    ---- -------   ----          ----
     04  8665ead0  ALPC Port     SbApiPort
     09  8665ef00  ALPC Port     ApiPort
     14  91a68980  Section       SharedSection
     32  8b1a8f50  Directory     WindowStations  <----- Наш клиент


lkd> !object 8b1a8f50
Object: 8b1a8f50  Type: (847ca778) Directory
    ObjectHeader: 8b1a8f38 (new version)
    HandleCount : 1         PointerCount: 7
    ObjectDir   : 85fad8d8  Name: WindowStations

    Hash Address  Type          Name
    ---- -------  ----          ----
     01  86727390 WindowStation Service-0x0-3e4$
     04  8674e690 WindowStation Service-0x0-3e5$
     06  8a133db8 WindowStation msswindowstation
     11  866a77c8 WindowStation Service-0x0-3e7$
     27  86676c68 WindowStation WinSta0

Теперь зададимся вопросом, что делает внутри изолированной сессии(0) интерактивная станция "WinSta0", которая способна выводить окна и обслуживать ввод? Дело в том, что некоторым (отбившимся от стада) службам может понадобится вывести инфу в диалоговое окно – такой поворот событий предусмотрен даже в аргументе "dwServiceType" функции CreateService(). Если у службы имеется веская причина для взаимодействия с пользователем, в этом аргументе нужно указать "SERVICE_INTERACTIVE_PROCESS". Тогда диспетчер загрузит службу как и прежде в станцию Service-0x0-3e7$ сессии зеро, но в момент отображения ею окна, переключит станцию на нашу интерактивную WinSta0 (для этого должен быть активным сервис UI0Detect.exe, см.ProcessHacker или диспетчер задач).

Для работы с оконными станциями имеются стандартные функции из либы User32.dll типа Open\EnumWindowStations(). Нам позволено создавать доп.станции, переключаться между ними, и всё остальное (см.список), но только внутри своей сессии входа в систему. Штатными средствами, чужая сессия закрыта для нас на амбарный замок! Более того, Win комплектуется бронебойной библиотекой расширенных возможностей Winsta.dll, с порядка 140 функциями на борту. Правда на MSDN она не документирована от слова вообще, но в репозиториях ReactOS можно найти вполне вменяемый перечень аргументов её основных функций.


5. Рабочие столы "Desktop"

Прихлопнем теорию разговором о десктопах..
Под оконной станцией "Winsta0" загружаются три рабочих стола – это Winlogon (экран входа в систему), Default (рабочий стол пользователя) и если включена защищённая экранная заставка, то ScreenSaver. Каждый из них имеет свой логический дисплей, в результате чего дефолтный стол исчезает, когда мы блокируем рабочую станцию комбинацией Win+L, или жмём аккорд Ctrl+Alt+Del. Блокирование станций приводит к тому, что дисплей переключается с Default на Winlogon, катапультируя нас с одного измерения, в другое.

Объекты типа "Desktop" являются защищаемыми (в дескрипторах), и содержат в себе: консольные\графические окна, меню ОС, перехватчики Hook, а так-же "кучу" виртуальной памяти Heap. Примечательным является то, что функцией CreateDesktop() мы можем создавать доп.рабочие столы, и переключаться между ними при надобности. Например на одном столе можно собрать все ярлыки для документов, на другом – связанные с Интернетом линки и т.д. Кстати после того-как рабочий стол создан, необходимо запустить на нём процесс Explorer.exe, иначе получим абсолютное пустое окно, без панели задач и любых признаков жизни.


Примечание:
Win10 принесла с собой виртуальные рабочие столы.
Они отличаются тем, что стол фактически не создаётся функцией CreateDesktop(),
а драйвер графической подсистемы Win32k.sys просто манипулирует видимостью окон.
В отличие от физ.десктопов Висты, бутафорские в Win10 предназначены не для обеспечения безопасности, а для удобства.
Инструмент под названием "Desktops" из пакета Sysinternals позволяет создавать до 4-х физ.столов.

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

В любое время может быть активным только один стол, а для прыжков на другой предусмотрена функция SwitchDesktop() из либы User32.dll. Запускать процессы на произвольных столах позволяет так-же CreateProcess(), ожидая его имя в поле "lpDesktop" структуры STARTUPINFO.

Рабочий стол Winlogon остаётся активным на время аутентификации пользователя, после чего система переключает дисплей на стол Default. Дескриптор безопасности стола Winlogon запрещает обращаться к себе буквально всем учётным записям, включая System, Service и Network. Соответственно из кода своих приложений мы не сможем вызвать окно входа в систему. Переключиться на него можно только комбинациями клавиш Win+L или Ctrl+Alt+Del, которые перехватывает процесс подсистемы безопасности Lsass.exe.

Если рассматривать Desktop как системный объект (каковым он и является на самом деле), то для одного объекта выделяется одна куча Heap рабочего стола. В ней и хранятся основные элементы пользовательского интерфейса типа: окна, меню и хуки. Каждый из этих элементов требует ресурсов памяти из кучи.


Примечание:
На системах ХР, для кучи стола отводилось в ядре фиксированное пространство размером 48 МБ.
Поскольку данная память была общим пулом для всех десктопов внутри одной станции,
это накладывало отпечаток на общее кол-во создаваемых рабочих столов функцией CreateDesktop().
В системах на ядре NT6 (от Виста до Win8) память для кучи столов выделяется динамически, и ограничения в 48 МБ уже нет.

В ветке реестра: HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems присутствует ключ Windows, со-строковым параметром "SharedSection". Три значения в нём определяют, сколько памяти в КБайтах выделяется куче. На своей Win7 я получил следующий лимит: 1024,12288,512.

• 1024 – это общий для всех столов размер кучи (внутри одной станции). Отводится под глобальную таблицу дескрипторов и общих настроек.
• 12288 – размер кучи рабочего стола Default в интерактивной станции "WinSta0". Здесь уже хранятся объекты юзера, типа хуки, меню, и окна.
• 512 – размер кучи для каждого стола в неинтерактивных оконных станциях, изолированной сессии(0).

Базовые функции для работы с десктопами сосредоточены в библиотеке User32.dll.
В частности EnumDesktop() возвращает список всех рабочих столов на активной станции, а помещённая внутрь её колбека функция EnumDesktopWindows() перечисляет все окна на найденном десктопе.


6. Общая картина

Чтобы зрительно сформировать законченную фейс, посмотрим на два рисунка ниже..
Значит после загрузки ОС появляется окно входа в систему Winlogon, куда юзер вводит данные своего аккаунта (в данном случае админ). Если аутентификация проходит успешно, админ получает от системы свою сессию с номером >=1. Внутри этой сессии находится обязательная интерактивная оконная станция WinSta0, и могут быть ещё несколько доп.станций. Например все браузеры на базе Хрома, создают себе станции для песочниц "Sandbox", о чём свидетельствует имя её рабочего стола. На системах Виста+ нас не ограничивают в кол-ве рабочих столов, но у сессии(1) всегда присутствуют столы Winlogon и Default.


Session1.png


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

Значит SID и токен ограничивает доступ к сессии(0), которая имеет в своей тушке аж 5 терминальных станций. Каждая из них принадлежит своей учётной записи – это System(3e7$), Services(3e5$) и Network(3e4$). Интерактивная станция WinSta0 предусмотрена для исключительных случаев, когда службе приспичит взаимодействовать с пользователем. Плохо документированная станция "mswindowstation" (судя по названию её стола) является ограниченной Restricted, например для тех служб, которые доступны анонимным юзерам (входят по сети без логин\пароля).


Session0.png



7. Практика – вывод инфраструктуры

Теперь немного попрактикуемся и напишем софт, который соберёт всё вышеизложенное под один капот.
Для этого задействуем следующие функции:

1. LsaEnumerateLogonSessions() из либы Secur32.dll – в аргумент "sList" возвращает массив LUID всех активных "сессий входа в систему". В аргументе "sCount" получим общее кол-во элементов в массиве. Работает только под админом, поэтому..

2. IsUserAnAdmin() из Shell32.dll – проверяет нашу тушку на админа. Возвращает EAX=1, если мы являемся таковыми.

3. LsaGetLogonSessionData() – принимая LUID от предыдущей функции, в структуру "SECURITY_LOGON_SESSION_DATA" сбрасывает общую информацию о сессии (см.описание структуры выше). Требует организации цикла, кол-во итераций которого берём из аргумента "sCount".

4. OpenProcessToken() + GetTokenInformation() из Advapi32.dll – парсим токен на идентификатор сессии(1,2,N), и кому он принадлежит (User\System).

5. NetWkstaUserGetInfo() из Netapi32.dll – возвращает в буфер имя и домен юзера.

6. GetProcessWindowStation() + GetUserObjectInformation() из User32.dll – в данном случае предоставляет SID сессии (меняется при каждой перезагрузки системы), и размер кучи стола. Последняя требует указания типа запрашиваемой инфы:


C-подобный:
UOI_FLAGS      =  1  ;// см.структуру USEROBJECTFLAGS
UOI_NAME       =  2  ;// имя объекта в виде строки
UOI_TYPE       =  3  ;// имя типа-объекта в виде строки
UOI_USER_SID   =  4  ;// SID пользователя, который в данный момент связан с указанным объектом
UOI_HEAPSIZE   =  5  ;// размер кучи рабочего стола в КБ в виде значения ULONG
UOI_IO         =  6  ;// TRUE, если станция\раб.стол интерактивный

7. EnumWindowStations() + EnumDesktops() из User32.dll – перечисляет оконные станции и десктопы внутри текущей сессии. Обе возвращают инфу в свои процедуры колбек.

Как видно из краткого описания функций, LsaEnumerateLogonSessions() не дружит с юзером, а потому чтобы получить полную информацию, код нужно запускать с правами админа. Вот собственно и пример возможной реализации:


C-подобный:
format   pe console
include 'win32ax.inc'
entry    start
;//----------
.data
logonTable   dd  lt00,lt01,lt02,lt03,lt04,lt05,lt06
             dd  lt07,lt08,lt09,lt10,lt11,lt12,lt13
lt00         db  '0 (SYSTEM)',0
lt01         db  '1 (Undefined)',0
lt02         db  '2 (Interactive)',0
lt03         db  '3 (Network)',0
lt04         db  '4 (Batch)',0
lt05         db  '5 (Service)',0
lt06         db  '6 (Proxy)',0
lt07         db  '7 (Unlock)',0
lt08         db  '8 (NetworkCleartext)',0
lt09         db  '9 (NewCredentials)',0
lt10         db  '10 (RemoteInteractive)',0
lt11         db  '11 (CachedInteractive)',0
lt12         db  '12 (CachedRemoteInteractive)',0
lt13         db  '13 (CachedUnlock)',0

align 16
userName        rb  32
source          rb  16
pcbBytesNeeded  dd  0
sCount          dd  0
sList           dd  0
hToken          dd  0
sessionId       dd  0
buff            db  0

;//----------
.code
;//*********************************************************
;//---------- CALLBACK ПРОЦЕДУРЫ ---------------------------
;//*********************************************************
proc     WinStaCallback lpszWinSta,lParam
        cinvoke  printf,<10,10,'    Station:  %s',0>,[lpszWinSta]
         invoke  OpenWindowStation,[lpszWinSta], FALSE, READ_CONTROL + WINSTA_ENUMDESKTOPS
         push    eax
         invoke  EnumDesktops,eax,EnumDeskCallback,0
         pop     eax
         invoke  CloseWindowStation,eax
         ret
endp
proc     EnumDeskCallback lpszDesktop,lParam
        cinvoke  printf,<10,17 dup(' '),'|----> Desktop: \%-10s',0>,[lpszDesktop]
         invoke  OpenDesktop,[lpszDesktop],0,FALSE,GENERIC_READ
         push    eax
         mov     dword[buff],0
         invoke  GetUserObjectInformation,eax,UOI_HEAPSIZE,buff,4,pcbBytesNeeded
        cinvoke  printf,<' --> Heap: %d Kb',0>,dword[buff]
         pop     eax
         invoke  CloseDesktop,eax
         ret
endp
;//*********************************************************
;//---------- ТОЧКА ВХОДА В ПРОГРАММУ ----------------------
;//*********************************************************
align    16
start:   invoke  SetConsoleTitle,<'*** Session info ***',0>

         invoke  LsaEnumerateLogonSessions,sCount,sList   ;// EAX=1 ошибка
         or      eax,eax
         je      @f
        cinvoke  printf,<10,' ERROR LsaEnumerateLogonSession()...',0>
         jmp     @exit

@@:      push    [sList]
        cinvoke  printf,<10,'              LUID       SecurRID   LogonType         UserName       ',\
                            '       LogonDomain           AuthPack    LogonTime',\
                         10,14 dup(' '),105 dup('-'),0>

;//----- Тест на админа! -------------
         invoke  IsUserAnAdmin     ;// админ: EAX=1
         or      eax,eax
         jz      @user

;//----- Запрашиваем данные очередной сессии ------------
@@:      invoke  LsaGetLogonSessionData,[sList],buff
         mov     esi,dword[buff]
         mov     eax,[esi + SECURITY_LOGON_SESSION_DATA.UserName+4]
         cmp     word[eax],0
         je      @fuck
         push    esi
         invoke  CharToOemW,eax,buff   ;// для вывода кирилицы на консоль
         pop     esi
         mov     eax,[sList]
         mov     eax,[eax]
         mov     ebx,[esi + SECURITY_LOGON_SESSION_DATA.LogonType]
         shl     ebx,2
         mov     edi,logonTable
         add     edi,ebx
         mov     ebx,[edi]
         mov     ecx,[esi + SECURITY_LOGON_SESSION_DATA.Sid]
         push    esi esi eax ebx ecx
         invoke  ConvertSidToStringSid,ecx,ecx   ;// SID в строку
         pop     ecx ebx eax esi
        cinvoke  printf,<10,' Session(%d):  %08x   %-8.8s   %-16s  %-20s  %-20ls  %-11ls',0>,\
                         dword[esi + SECURITY_LOGON_SESSION_DATA.Session],eax,dword[ecx],ebx,buff,\
                         dword[esi + SECURITY_LOGON_SESSION_DATA.LogonDomain+4],\
                         dword[esi + SECURITY_LOGON_SESSION_DATA.AuthenticationPackage+4]
         pop     esi
         add     esi,SECURITY_LOGON_SESSION_DATA.LogonTime
         invoke  FileTimeToSystemTime,esi,buff
         movzx   eax,word[buff+8]
         movzx   ebx,word[buff+10]
         movzx   ecx,word[buff+12]
        cinvoke  printf,<' %02d:%02d:%02d',0>,eax,ebx,ecx  ;//<----- время входа в сессию

@fuck:   add     [sList],8    ;// следующий 64-бит LUID из массива..
         dec     [sCount]     ;// весь массив обошли?
         jnz     @b

         pop     eax
         invoke  LsaFreeReturnBuffer,eax
        cinvoke  printf,<10,14 dup(' '),105 dup('-'),0>

;//----- Если нет админских прав, блок выше не отображается ---------
@user:   mov     ecx,128
         mov     edi,buff
         xor     eax,eax
         rep     stosd       ;// Очистить служебный буфер

;//*********************************************************
;//---------- ИНФОРМАЦИЯ О ТЕКУЩЕЙ СЕССИИ ------------------
;//*********************************************************
         invoke  OpenProcessToken,-1,GENERIC_READ,hToken
         invoke  GetTokenInformation,[hToken],TokenSessionId,sessionId,4,pcbBytesNeeded
         invoke  GetTokenInformation,[hToken],TokenSource,source,16,pcbBytesNeeded

         invoke  NetWkstaUserGetInfo,0,1,buff
         mov     esi,dword[buff]
         mov     eax,[esi + WKSTA_USER_INFO_1.username]
         invoke  CharToOemW,eax,userName
         mov     ebx,[esi + WKSTA_USER_INFO_1.logon_domain]
        cinvoke  printf,<10,10,' Active session info...',\
                            10,'         ID:  %d',\
                            10,'      Logon:  %s \ %ls',0>,[sessionId],userName,ebx

         invoke  GetProcessWindowStation
         invoke  GetUserObjectInformation,eax,UOI_USER_SID,buff,128,pcbBytesNeeded
         invoke  ConvertSidToStringSid,buff,buff
        cinvoke  printf,<   10,'        SID:  %s',\
                            10,'      Token:  %s',0>,dword[buff],source

;//----- Перечисляем станции и их рабочие столы -----------------------
         invoke  EnumWindowStations,WinStaCallback,0

@exit:  cinvoke  _getch     ;// клавиша
        cinvoke  exit,0     ;// выход!
;//----------
section '.idata' import data readable
library  msvcrt,'msvcrt.dll',kernel32,'kernel32.dll',netapi32,'Netapi32.dll',\
         user32,'user32.dll',secur32,'secur32.dll',advapi32,'advapi32.dll',shell32,'shell32.dll'
import   shell32,IsUserAnAdmin,'IsUserAnAdmin'
import   secur32,LsaEnumerateLogonSessions,'LsaEnumerateLogonSessions',\
                 LsaGetLogonSessionData,'LsaGetLogonSessionData',\
                 LsaFreeReturnBuffer,'LsaFreeReturnBuffer'
import   netapi32,NetWkstaUserGetInfo,'NetWkstaUserGetInfo'
include  'api\msvcrt.inc'
include  'api\kernel32.inc'
include  'api\user32.inc'
include  'api\advapi32.inc'
include  'equates\scm.inc'
include  'equates\advapi32.inc'

ResWin7.png


Рассмотрим этот лог под микроскопом..
Значит на моей системе Win7-SP1 имеется три интерактивных пользователя – Админ, Юзер и Гость. А вот сессий входа уже 4, включая изолированную(0). В этой "обители зла" тусуются все служебные учётки, типа System, LocalService и NetworkService. Каждой сессии назначается локальный идентификатор LUID, последние три цифры в котором совпадают с именами служебных станций "Service-0x0-3e7$" и двух остальных (см.предыдущий скрин).

Во-втором и следующих столбцах видим RID учётных записей, метод входа в систему "LogonType", и дальше имя\домен пользователя. Обратите внимание как система обзывает своих\системных юзеров в столбце "UserName" – эти имена совпадают с названием домена, который сопровождается символом($) в конце.

Далее идёт "AuthPack", что подразумевает используемый пакет авторизации. На данный момент имеется всего два варианта – это NTLM и Kerberos. Если в этом поле указывается "Negotiate", то система сама выбирает из этих двух предпочтительный вариант, исходя из своей модели безопасности. Ну и в последнем столбце лежит время, когда юзер вошёл в свою сессию. Сначала подгружается сессия(0), а за ней и все остальные.

Теперь, внутри активной сессии(1) видим две оконные станции – в данном случае всегда присутствующая интерактивная "WinSta0", и станция браузера Хром с именем "Service-0x0-27f3d$" (последнее значение меняется при каждом ребуте). В свою очередь, к WinSta0 цепляются 4 рабочих стола, 2 из которых Default и Winlogon присутствуют всегда, а 2 остальных породил софт Марка Руссиновича "Desktops". Как видим, куча Heap выделяется системой всем столам в этой станции, кроме Winlogon. Это потому, что он не имеет меню, панели задач, и вообще никаких окон, кроме окна авторизации.

А вот картинка от Win10..
Тут уже появились ещё две учётки: UMFD-х и диспетчера окон DWM.
Обратите внимание на размер хипа, который вырос с 12-ти, до ~20 МБ. Причина тому - перегруженный интерфейс пользователя на рабочем столе:


ResWin10.png



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

Знакомство с внутренней организацией подсистемы безопасности открывает огромные перспективы. На фоне рассмотренных здесь пользовательских сессий, наружу всплывают токены, а это уже абсолютно другой уровень взаимодействия с OC. В следующей части будет предпринята попытка грабежа этих токенов у системных процессов, с целью модификации их под себя. При удачных обстоятельствах, это позволит запускать свои процессы от имени системы, что откроет доступ к изолированной сессии(0). Не останутся в стороне и службы, на которые сделаем основную ставку. Держите руку на пульсе..

В скрепку кладу исполняемый файл для тестов, и необходимые для сборки исходника, пару инклудов.
Всем удачи, пока!
 

Вложения

  • Session.zip
    14,2 КБ · Просмотры: 95

Vertigo

Lex mea est Vulgate Linux
Gold Team
15.02.2017
1 318
3 996
Бро,ну конкурсная статья-то)).Ладно,не умеешь писать плохие статьи,))Респект как всегда.
 
  • Нравится
Реакции: DarthVader и Marylin

Mikl___

Green Team
01.09.2019
25
41
С Днем Рождения, Тимур! Огромное спасибо за статьи и знания, которыми ты с нами делишься!
zlLV7zuIiXo[1].jpg
 
  • Нравится
Реакции: Evgeny D, Mogen и Marylin
27.02.2020
2
1
В следующей части будет предпринята попытка грабежа этих токенов у системных процессов
Спасибо за первую статью Огромное!
Теперь ждем "как ждут зимой весны" вторую статью о том как "по братски"
поделиться полномочиями с системными процессами!

Ждем без терпения!
:)
 
  • Нравится
Реакции: Mikl___

Mikl___

Green Team
01.09.2019
25
41
Тимур, поздравляю с "серебром" на конкурсе статей! :)
 
  • Нравится
Реакции: Marylin

SearcherSlava

Red Team
10.06.2017
913
1 232
Братья и Сестры, здравы будьте!
О нолях и единицах не забудьте!

Машинный язык
Значим и велик
Тот высот достиг
Кто учил язык

Maybe you a dreamer
And you life seems out of place
But you can’t relax this fever
And the need for open space
And you heart seems in the right place
Though sometimes you head can stray
And take for granted all those things
That keep you warm and safe
Like the ones who stood beside you
And who taught you wrong from right
To believe in what’s inside you
And to never give up the fight
For the lesson’s in the learning
Don’t you dwll on what has been
It’s because the ons who hold out
Yes, become the ones who win
Don’t stop assembler
'cause you’re not the only one believes in ASM
Don’t stop assembler
'cause you’re not the only one believes in ASM
 
  • Нравится
Реакции: Marylin
Мы в соцсетях: