Хранение и шифрование паролей Microsoft Windows

  1. ozs
    Про взлом паролей windows было написано немало статей, но все они сводились к использованию какого-либо софта, либо поверхностно описывали способы шифрования LM и NT, и совсем поверхностно описывали syskey. Я попытаюсь исправить этот неодостаток, описав все подробности о том где находятся пароли, в каком виде, и как их преобразует утилита syskey.

    Существует 2 возможности получения пароля — через реестр, или получив прямой доступ к файлам-кустам реестра. В любом случае нужны будут либо привелегии пользователя SYSTEM, либо хищение заветных файлов, например, загрузившись из другой ОС. Здесь я не буду описывать возможности получения доступа, но в целях исследования нагляднее будет выбрать первый вариант, это позволит не заострять внимание на структуре куста реестра. А запуститься от системы нам поможет утилита psExec от sysinternals. Конечно, для этих целей можно использовать уязвимости windows, но статья не об этом.

    V-блок

    Windows до версии Vista по умолчанию хранила пароль в двух разных хэшах — LM и NT. В висте и выше LM-хэш не хранится. Для начала посмотрим где искать эти хэши, а потом разберемся что из себя они представляют.

    Пароли пользователей, а так же много другой полезной информации хранится в реестре по адресу HKLM\SAM\SAM\Domains\Account\users\[RID]\V
    , известном как V-блок. Раздел SAM находится в соответствующем файле c:\Windows\System32\config\SAM. RID — уникальный идентификатор пользователя, его можно узнать, например заглянув в ветку HKLM\SAM\SAM\Domains\Account\users\names\<имя пользователя> (параметр Default, поле — тип параметра). Например, RID учетной записи «Администратор» всегда 500 (0x1F4), а пользователя «Гость» — 501 (0x1f5). Доступ к разделу SAM по умолчанию возможен только пользователю SYSTEM, но если очень хочется посмотреть — запускаем regedit c правами системы:

    Код:
    PsExec.exe -s -i -d regedit.
    Чтобы наблюдать V-блок в удобном виде можно, например, экспортировать его в текстовый файл (File-Export в Regedit).
    Вот что мы там увидим:

    От 0x0 до 0xCC располагаются адреса всех данных, которые находятся в V-блоке, их размеры и некоторая дополнительная информация о данных. Чтобы получить реальный адрес надо к тому адресу, что найдем прибавить 0xCC. Адреса и размеры хранятся по принципу BIG ENDIAN, т.е понадобится инвертировать байты. На каждый параметр отводится по 4 байта, но фактически все параметры умещаются в одном-двух байтах. Вот где искать:

    Адрес имени пользователя — 0xС
    Длина имени пользователя — 0x10
    Адрес LM-хэша — 0x9с
    Длина LM-хэша — 0xa0
    Адрес NT-хэша — 0xa8
    длина NT-хэша — 0xac

    В данном случае имя пользователя найдется по смещению 0xd4 + 0xcc и его длина будет 0xc байт.
    NT-хэш будет располагаться по смещению 0x12c + 0xcc и его размер (всегда один и тот же) = 0x14.

    Еще одна деталь, касающаяся хранения паролей — как к NT- так и к LM-хэшу всегда добавляются спереди 4 байта, назначение которых для меня загадка. Причем 4байта будут присутствовать даже если пароль отключен. В данном случае видно, что длина LM хэша =4 и если посмотреть на его адрес, можно эти 4 байта увидеть несмотря на то что никакого LM-хэша нет.
    Поэтому при поиске смещений хэшей смело прибавляем 4 байта к адресу, а при учете размеров — вычитаем. Если удобнее читать код — вот примерно так будет выглядеть поиск адресов с учетом инверсии, лишних четырех байтов и прибавления стартового смещения 0xcc (код C#)
    userVblock — значение HKLM\SAM\SAM\Domains\Account\users\\V в виде массива байт.
    Еще про V-блок можно почитать тут.

    Алгоритмы

    Теперь разберемся в алгоритмах шифрования.
    Формирование NT-хэша:
    1. Пароль пользователя преобразуется в Unicode-строку.
    2. Генерируется MD4-хэш на основе данной строки.
    3. Полученный хэш шифруется алгоритмом DES, ключ составляется на основе RID пользователя.
    Формирование LM-хэша:
    1. Пароль пользователя преобразуется в верхний регистр и дополняется нулями до длины 14 байт.
    2. Полученная строка делится на две половинки по 7 байт и каждая из них по отдельности шифруется алгоритмом DES. В итоге получаем хэш длиной 16 байт (состоящий из двух независимых половинок длиной по 8 байт).
    3. Полученный хэш шифруется алгоритмом DES, ключ составляется на основе RID пользователя.
    4. В windows 2000 и выше оба полученых хэша дополнительно шифруются алоритмом RC4 с помощью ключа, известного как «системный ключ» или bootkey, сгенерированого утилитой syskey, и шифруются довольно хитрым образом.
    Рассмотрим общую последовательность действий для получения исходного пароля и каждый шаг в отдельности
    1. Получаем bootkey, генерируем на его основе ключи для RC4, расшифровываем хэши с помощью RC4
    2. Получаем ключи для DES из RID'ов пользователей, расшифровываем хэши DES'ом
    3. Полученые хэши атакуем перебором.

    Bootkey
    Системный ключ (bootkey) разбит на 4 части и лежит в следующих разделах реестра:

    HKLM\System\CurrentControlSet\Control\Lsa\JD
    HKLM\System\CurrentControlSet\Control\Lsa\Skew1
    HKLM\System\CurrentControlSet\Control\Lsa\GBG
    HKLM\System\CurrentControlSet\Control\Lsa\Data


    Раздел system находится в файле c:\Windows\System32\config\system

    Следует отметить, что раздел CurrentControlSet является ссылкой на один из разделов controlset и создается в момент загрузки системы. Это значит что не получится его найти в файле system, если система неактивна. Если вы решили искать ключ в файле — необходимо узнать значение ContolSet по умолчанию в HKLM\SYSTEM\Select\default.
    например если HKLM\SYSTEM\Select\default = 1 — вместо HKLM\System\CurrentControlSet\ ищем в HKLM\System\controlset001\
    У каждого ключа реестра есть некий скрытый атрибут, известный как «class». Regedit его так просто не покажет, однако его можно увидеть, например, если экспортировать эти ключи реестра в текстовые файлы. В winapi для получения этого атрибута есть функция RegQueryInfoKey.
    Фрагменты хранятся в строковом представлении шестнадцатеричных чисел, причем по принципу BIG ENDIAN (т.е не строка задом наперед, а число).
    Например мы обнаружили вот такие записи:
    Код:
    Key Name: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\JD
    Class Name: 46003cdb = {0xdb,0x3c,0x00,0x46}
    Key Name: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Skew1
    Class Name: e0387d24 = {0x24,0x7d,0x38,0xe0}
    Key Name: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\GBG
    Class Name: 4d183449 = {0x49,0x34,0x18,0x4d}
    Key Name: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\Data
    Class Name: 0419ed03 = {0x03,0xed,0x19,0x04}
    Собраный из четырех частей ключ будет массивом байт:
    Код:
    scrambled_key = {0xdb,0x3c,0x00,0x46,0x24,0x7d,0x38,0xe0,0x49,0x34,0x18,0x4d,0x03,0xed,0x19,0x04}; 
    Далее элементы этого массива переставляются на основе некоторого константного массива p
    Код:
    int[] p = { 0xb, 0x6, 0x7, 0x1, 0x8, 0xa, 0xe, 0x0, 0x3, 0x5, 0x2, 0xf, 0xd, 0x9, 0xc, 0x4 };
    Элементы в этом массиве определяют позиции для перестановок, т.е.
    Код:
    key[i] = scrambled_key[p[i]];
    В нашем примере получится массив:
    этот массив и есть так называемый bootkey. Только в шифровании паролей будет учавствовать не он а некий хэш на основе bootkey, фрагментов f-блока и некоторых констант. Назовем его Hashed bootkey.

    Hashed bootkey

    Для получения Hashed bootkey нам понадобятся 2 строковые константы (ASCII):
    Также понадобится F-блок пользователя (HKLM\SAM\SAM\Domains\Account\users\\F), а именно его 16 байт: F[0x70:0x80]
    На основе этих значений, склееных в один большой массив формируем MD5 хэш, который будет являться ключем для шифрования RC4
    Код:
    rc4_key = MD5(F[0x70:0x80] + aqwerty + bootkey + anum).
    Последним шагом для получения hashed bootkey будет rc4 шифрование( или дешифрование — в rc4 это одна и та же функция) полученым ключем фрагмента F-блока F[0x80:0xA0];
    Код:
    hashedBootkey = RC4(rc4_key,F[0x80:0xA0])
    Hashed bootkey у нас в руках, осталось научиться с ним правильно обращаться.

    Дешифруем пароли с помощью Hashed Bootkey


    Для паролей LM и NT нам понадобятся еще 2 строковые константы
    а так же RID пользователя в виде 4х байт (дополненый нулями) и первая половина Hashed Bootkey (hashedBootkey[0x0:0x10]);
    Все это склеивается в один массив байт и считается MD5 по правилам:
    Код:
    rc4_key_lm = MD5(hbootkey[0x0:0x10] +RID + almpassword);
    rc4_key_nt = MD5(hbootkey[0x0:0x10] +RID + antpassword);
    полученый md5 хэш — ключ для rc4, которым зашифрованы LM и NT хэши в V-блоке пользователя
    Код:
    userLMpass = RC4(rc4_key_lm,userSyskeyLMpass);
    userNTpass = RC4(rc4_key_lm,userSyskeyNTpass);
    На этом этапе мы получили пароли пользователя в том виде в каком они хранились бы без шифрования syskey, можно сказать, что самое сложное позади. Переходим к следующему шагу

    DES

    На основе четырех байт RID'а пользователя с помощью некоторых перестановок и побитовых операций создаем 2 ключа DES. Вот функции, которые осуществляют обфускацию (С#):
    Код:
    private byte[] str_to_key(byte[] str) {
    byte[] key = new byte[8];
    key[0] = (byte)(str[0] >> 1);
    key[1] = (byte)(((str[0] & 0x01) << 6) | (str[1] >> 2));
    key[2] = (byte)(((str[1] & 0x03) << 5) | (str[2] >> 3));
    key[3] = (byte)(((str[2] & 0x07) << 4) | (str[3] >> 4));
    key[4] = (byte)(((str[3] & 0x0F) << 3) | (str[4] >> 5));
    key[5] = (byte)(((str[4] & 0x1F) << 2) | (str[5] >> 6));
    key[6] = (byte)(((str[5] & 0x3F) << 1) | (str[6] >> 7));
    key[7] = (byte)(str[6] & 0x7F);
    for (int i = 0; i < 8; i++) {
    key[i] = (byte)(key[i] << 1);
    }
    des_set_odd_parity(ref key);
    return key;
    }
    
    private byte[] sid_to_key1(byte[] rid) {
    byte[] s = new byte[7];
    s[0] = (byte)(rid[0] & 0xFF);
    s[1] = (byte)(rid[1] & 0xFF);
    s[2] = (byte)(rid[2] & 0xFF);
    s[3] = (byte)(rid[3] & 0xFF);
    s[4] = s[0];
    s[5] = s[1];
    s[6] = s[2];
    
    return str_to_key(s);
    }
    
    private byte[] sid_to_key2(byte[] rid) {
    byte[] s = new byte[7];
    s[0] = (byte)((rid[3]) & 0xFF);
    s[1] = (byte)(rid[0] & 0xFF);
    s[2] = (byte)((rid[1]) & 0xFF);
    s[3] = (byte)((rid[2]) & 0xFF);
    s[4] = s[0];
    s[5] = s[1];
    s[6] = s[2];
    
    return str_to_key(s);
    }
    Ну здесь особо комментировать нечего, кроме функции des_set_odd_parity(ref key) — это одна из функций библиотеки openssl, задача которой добавить некоторые «биты нечетности», используется для повышения стойкости ключа к атакам.
    Далее разбиваем NT (или LM) хэш на 2 части по 8 байт и дешифруем DES'ом -одна половина зашифрована ключем сформированым функцией sid_to_key1, вторая — sid_to_key2.
    Код:
    obfskey_l = userNTpass[0x0:0x7]
    obfskey_r = userNTpass[0x8:0xF]
    byte[] deskey1 = sid_to_key1(RID);
    byte[] deskey2 = sid_to_key2(RID);
    byte[] md4hash_l = DES(obfskey_l, deskey1);
    byte[] md4hash_r = DES(obfskey_r, deskey2);
    После склеивания двух половин мы получим md4 хэш -в случае NT, или LanMan (DES) — в случае LM. Полученый хэш полностью готов к атаке перебором.
    Кстати, md4 Хэш от пустого пароля — 31d6cfe0d16ae931b73c59d7e0c089c0

    Исследование проведено на основе исходного кода ophcrack-3.3.1, а так же статьи Push the Red Button:SysKey and the SAM

    (c)habrahabr.ru