А АSaturday 25 February 2023

Влияем на MS ATA через функцию отражения.

Всем привет.

Установка ведения журналов блоков сценариев доставляется в компьютеры Windows через настроенные в контроллере домена глобальные параметры: параметры объекта Групповой политики (GPO).

При каждом исполнении команды интерпретатором PowerShell ведение журналов блоков сценариев проверяет соответствующий объект Групповой политики в ключе реестра и принимает решение о регистрации этой команды или её игнорировании. 

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

Эта переменная, или чтобы быть более точным свойство определяется в загружаемом DLL System.Management.Automation внутреннем статическом классе Utils. 

Ведение журнала блоков сценариев включено когда cachedGroupPolicySettings установлено в определённое значение. Если оно не определено, ведение журналов блоков сценариев отключено - во всех его смыслах и проявлениях.

Можем ли мы считать это пространство памяти и обойти имеющиеся правила объектно- ориентированного программирования, которые гласят, что мы не можем получать доступ к закрытым переменным вне их классов? Да, и ещё раз: да! Более того, мы способны перекрыть это поле в памяти чтобы отключить ведение журнала для своего конкретного экземпляра PowerShell. В конце концов, любой загружаемый обычным процессом пользователя код DLL пребывает в пространстве пользователя, а переменные этой DLL обычно находятся в страницах памяти для чтения/записи. Поэтому мы можем отключать ведение журналов не обладая полномочиями администратора. Чтобы реализовать такой трюк вуду мы полагаемся на функцию, носящую название отражения (reflection).

В исполняемых файлах .NET отражение (reflection) позволяет фрагменту кода считывать код данного исполняемого кода извлекать методы и участников и изменять их во время выполнения. В основном это допустимо по причине того, что исполняемый файл .NET не содержит реального собственного машинного кода. Вместо этого он состоит из промежуточного кода с названием Microsoft Intermediate Language (MSIL, Промежуточного языка Microsoft), который компилятор транслирует из кода верхнего уровня, как правило C#. Во время исполнения код MSIL на лету компилируется в аппаратный код средой Windows Common Language Runtime (CLR, времени исполнения общего языка).

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

Традиционно с языке без управления, таком как C++, для доступа к внутренним методам и свойствам класса мы бы пользовались функциями getter и setter (получения и установки). Единственная цель этих функций - после их реализации - позволять программистам мягко нарушать правило границ ООП и получать (при помощи getter) или устанавливать (через setter) конкретные значения внутренних переменных извне своих классов.

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

В PowerShell для загрузки общедоступного класса .NET, такого как Console, мы просто заключаем его в квадратные скобки и напрямую получаем доступ к его методам:

PS C:\Lab> [console]

IsPublic     IsSerial       Name     BaseType

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

True         False          Console  System.Object

PS C:\Lab> [Console]::WriteLine("static method WriteLine")

static method WriteLine

Однако, как мы видели ранее, System.Management.Automation.Utils объявлен как внутренний класс. Это означает что мы не можем непосредственно его вызывать из интерпретатора PowerShell. Тем не менее, благодаря отражению мы имеем возможность динамически загружать тот файл DLL, который его содержит, и принудительно выделять ссылку на его внутренний класс Utils. На практике PowerShell всегда загружает файл System.Management.Automation.dll и превращает его в доступный через свойство Assembly общедоступного класса System.Management.Automation.PSReference:

PS C:\Lab> [System.Management.Automation.PSReference].Assembly

GAC: True

Version: v4.0.30319

Location: C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll

Для удобства [System.Management.Automation.PSReference] можно сократить до [ref], а потому в качестве альтернативы просто выполните:

PS C:\Lab> [ref].Assembly

Теперь у нас имеется объект, который указывает на необходимую DLL, поэтому мы можем вызвать функцию GetType, которая благодаря отражению выполняет выборку дескриптора для необходимого внутреннего класса Utils. Его мы сохраняем в качестве более удобной переменной $utils:

PS C:\Lab> $utils = [ref].Assembly.GetType('System.Management.Automation.Utils')

PS C:\Lab> $utils

IsPublic     IsSerial       Name     BaseType

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

False        False          Utils  System.Object

Применяя этот дескриптор мы вызываем метод GetField для выборки внутри этого класса поля cachedGroup PolicySettings, снабжая нас контролем теми атрибутами, которые будут запрещены ведением регистрации блоков сценариев. Метод GetField ожидает значения переменной имени и сведения о его типе - по- другому именуемых флагами связывания - таких как Public, NonPublic, Static, Instance и тому подобных. На Рисунке 8.2, после декомпиляции необходимой DLL при помощи .NET Reflector, мы видим что значение свойства cached GroupPolicySettings объявляются как Private и Static, поэтому для его выборки мы пользуемся таким кодом:

PS C:\Lab> $dict = $utils.GetField("cachedGroupPolicySettings","NonPublic,Static")

PS C:\Lab> $dict

Name        : cachedGroupPolicySettings

FieldHandle : System.RuntimeFieldHandle

Attributes  : Private, Static

FieldType   : Collections.Concurrent.ConcurrentDictionary...

Этот тип поля указывает что, как ожидалось, мы имеем дело со словарём. Мы можем отобразить его значения при помощи метода GetValue. Помните, что подобные GetValue, GetType и GetField функции работают исключительно благодаря отражению на самом первом месте:

PS C:\Lab> $dict.getValue("")

Key : HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging

Value : {[EnableScriptBlockLogging, 1]}

Вот оно - мы можем ясно видеть, что отвратительный EnableScriptBlockLogging установлен в 1, который нам следует заменить на 0 для обмана PowerShell, чтобы он молча отбрасывал все последующие команды, выдаваемые этим конкретным экземпляром PowerShell:

PS C:\Lab> $key = "HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging"

PS C:\Lab> $scriptBlockLogging = $dict.getValue("")[$key]

PS C:\Lab> $scriptBlockLogging['EnableScriptBlockLogging'] = 0

После выполнения этого сценария в нашей целевой машине нам нет нужды  беспокоиться относительно MS ATA, так как эти команды уже не вовлечены ни в какое сетевое взаимодействие со своим контроллером домена.

По материалам книги Спарка Флоу "Как заниматься взломом словно легенда. Прорываемся в Windows", Copyright (С) 2022 Sparc Flow, No Starch Press, Inc.


No comments:

Post a Comment

А что вы думаете по этому поводу?

Версия на печать

Популярное