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

Про взлом паролей 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#)

int lmhashOffset = userVblock[0x9c] + userVblock[0x9d] * 0x100 + 4 + 0xcc;
int nthashOffset = userVblock[0xa8] + userVblock[0xa9] * 0x100 + 4 + 0xcc;
int lmhashSize = userVblock[0xa0] + userVblock[0xa1] * 0x100 - 4;
int nthashSize = userVblock[0xac] + userVblock[0xad] * 0x100 - 4;
int usernameOffset = userVblock[0xc] + userVblock[0xd] * 0x100 + 0xcc;
int usernameLen = userVblock[0x10] + userVblock[0x1a] * 0x100;

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]];

В нашем примере получится массив:

key[] = {0x4d,0x38,0xe0,0x3c,0x49,0x18,0x19,0xdb,0x46,0x7d,0x00,0x04,0xed,0x34,0x03,0x24 };

этот массив и есть так называемый bootkey. Только в шифровании паролей будет учавствовать не он а некий хэш на основе bootkey, фрагментов f-блока и некоторых констант. Назовем его Hashed bootkey.

Hashed bootkey


для получения Hashed bootkey нам понадобятся 2 строковые константы (ASCII):

string aqwerty = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0";
string anum = "0123456789012345678901234567890123456789\0";

Также понадобится 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 строковые константы —

string almpassword = "LMPASSWORD";
string antpassword = "NTPASSWORD";


а так же 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
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 30
  • +13
    Хулиганьё!
    • +1
      Интересное исследование, положил в закладки. Глядишь и пригодится…
      • +1
        Спасибо огромное. Косвенно сталкивался, но такие подробности лучше сохранить.
        • +9
          Ну не знаю… как вообще в подобном можно разобраться ) Очень сильно!
          • +2
            согласен, товарищи из Microsoft намудрили неплохо, правда толку от этого немного, если учесть что алгоритмы практически не менялись как минимум 10 лет.
            Возможно попозже попробую прилепить некую схемку, в которой наглядно будет что к чему.
            • 0
              Я уже долгое время ищу того, кто знает как работает LSASS изнутри, может вы знаете?
              • 0
                я точно знаю кто знает ) — для начала посмотрите в книжке Марка Руссиновича. Там конечно не все, но это поможет в исследовании. А если окажется мало — windbg + винда в режиме отладки на виртуальной машине: непросто, но зато самый эффективный способ изучения
              • 0
                Не подскажете, в семерке механизм тот же?
                • 0
                  да, механизм проверен на xp, висте и 7
              • 0
                Непонятно в чем польза всех этих наворотов, если в конечном счете все сводится к брутфорсу md4.
                • +1
                  ну на самом деле некая польза от наворотов была бы если бы они хотя бы раз в 2-3 года что-нибудь меняли бы. Секреты Syskey, если я не ошибаюсь, были раскрыты совсем не сразу, т.е некоторое время польза точно была.
                • 0
                  Я думал всё намного проще реализовано
                  • +1
                    В винде, проще? Это фантастика :)
                  • +4
                    Огромное спасибо за статью — читал с интересом и почерпнул немало интересных вещей. Мне она очень пригодилась — я сейчас активно занимаюсь изучением криптографии в целом и пространства имен System.Security.Cryptography в .NET. И вот что меня больше всего поражает, так это количество пишущих комментарии наподобие «оказывается все довольно фигово сделано», «я думал что все навмного проще реализовано». Не в обиду комментирующим конечно, но криптография — это сложная прикладная дисциплина. Я вовсе не пытаюсь поучать или «академничать» тут. Просто я сейчас решаю вполне конкретные задачи по защите своего ПО от несанкционированной модификации и распространения — и уже то, что я успел узнать, впечатляет. Поэтому не стоит критиковать людей, которые реализовывали подсистему безопасности Windows и называть ее устаревшей, неактуальной, слишком простой/сложной — она работает. Уверен — много копий было поломано об эту систему, много тысяч человека-чсасов затрачено на ее разработку и поддержку. И она работает. Работает уже много лет, такая, какая есть — с недостатками, дырками, заплатками, новыми дырками и новыми заплатками… Не стоит недооценивать труд других людей.
                    • +5
                      ну я на самом деле согласен лишь отчасти. Мне сложно понять, например почему они не перешли на md5? почему не добавляют соль к хэшам? Ведь это совсем не сложно, а защита сразу улучшается в разы.
                      • +4
                        ну как же — обратная совместимость со старыми версиями l0phtcrack
                    • 0
                      Самое веселое, что перебирать эти хеши часто никакой необходимости и нет. Аутентификаций по NTLM оперирует только хешами — знаешь хеш, можешь авторизоваться.
                      • 0
                        Ну это если авторизироваться через Active directory. Для локального пользователя все же придется поковырять. В прочем если есть, например, хорошая база rainbow tables это довольно простое занятие
                      • +2
                        помним, как-же!
                        на атлоне 1.6 пароль под винхп брутился за 10-15 минут.
                        • 0
                          Это как ни крути зависит от длины пароля, а вообще основная масса паролей пользователей брутится по словарю ;)
                          • 0
                            два кусочка по 7 символов? да хоть и не два, они параллельно брутятся же.
                            • 0
                              Насколька я понял надо ведь чтоб не тока они (сами кусочки), но и разбиение совпало, дало два необходимых кусочка…
                              • 0
                                нет, все хуже. к примеру, если установить пароль «0zd4gU7a3», то уже в первую секунду будет видно что конец пароля «а3» а остальная часть будет какое-то время брутится.
                                потому что восьмой и девятый символы во второй части, и длинна этой самой второй части очень мала.
                                • 0
                                  Да, извиняюсь, я не прочитал что оно сначала делится, а потом уже шифруется :)

                                  Я думал что наоборот, сначала шифруется, а потом разделяется :) :) :)

                                  Тогда я просто не понимаю нахера такой ИДИОТИЗМ было выдумывать ;) ;) это ведь надо было специально придумывать такое… :) Да еще и в верхний регистр… ужость…
                        • +3
                          Мне кажется многие пишущие про «система устарела» не совсем понимают о чем идёт речь. Как автор уже заметил в статье, NT хэш не сохраняется в современных системах по умолчанию. Он правда не упомянул что в современных системах используются более «новые» алгоритмы — NTLM, NTLM2. Да, разумеется старый NT тоже остаётся и безопасность сводится к безопасности самого слабого звена, но надо понимать что сделано это для совместимости, для того чтобы с переходом на новую версию у вас не перестали работать например принтеры которые не поддерживают этих «новинок» (которым тоже уже немало лет). Даже отключение сохранения LM хэша было нелегким делом…
                          А те кто действительно печется о безопасности, должны знать о том что рекомендуется рекомендуется отключить сохранение хэшей устаревших алгоритмов если в вашей инфраструктуре это возможно.
                          • +2
                            ой, читать NT как LM в первых двух упоминаниях конечно :) Только проснулся…
                            • +1
                              Да, кстати еще надо понимать что хэш — с точки зрения безопасности почти эквивалентен паролю. Если злоумышленник получил такой доступ к системе что смог вытащить хэш — система _уже_ его, и никакие пароли ему вобщем то не нужны. Более того, всякий кто залогинился на такой скомпроментированной системе — тоже уже скомпроментирован.
                              • +3
                                Вы бы написали статейку про то как сделать Винду максимально секурной, что отключить, что включить.
                                • +1
                                  Уже написано гораздо более компетентными людьми чем я. Вот например официальный Security Guide по Vista — technet.microsoft.com/en-us/library/cc507874.aspx По 7 примерно то же самое, но обновление скоро будет.
                                  В «максимально секурной» системе будет невозможно работать :) Вы наверное знаете что она отключена от сети, и закопана в землю под несколько метров бетона :)
                                  Безопасность это всегда компромис. Настройки Windows Vista/7 по умолчанию — прекрасное решение для большинства пользователей (причем даже часто смещенное в сторону безопасности). Если хочется большего — возможностей полно. Всевозможным военным и госорганизациям США например вполне хватает, а мне почему то кажется что там IT спецы не хуже наших…
                              • +3
                                Ну на самом деле NTLM и NTLMv2 — это протоколы сетевой аутентификации, в основе которых лежат все те же хаши — LM и NT

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое