Kolejna część naszych zabaw z DPAPI i jeszcze głębsze wejrzenie w mechanizmy nim rządzące.
W poprzednim odcinku znęcaliśmy się nieco nad PowerShellem, z którego pomocą wygenerowaliśmy kilka plików z zaszyfrowanymi danymi. Celowo poprzednio zamieściłem na początku tekstu zrzuty dwóch moich danych, dorzućmy do tej potrawy dodatkowo zaszyfrowany ciąg z przykładu ze strony MSDN i zamieszajmy nieco :)
Przypomnę zatem zaszyfrowane dane z poprzedniego wpisu:
1.
01000000d08c9ddf0115d1118c7a00c04fc297eb010000008727d6117f2b0c429d84f2b9f7e9ea35
0000000002000000000003660000a8000000100000009403327f1497e1a4266ca7d45f84266a0000
000004800000a000000010000000ed0b8a5247be59846e80ca735a1a3f9720000000a5025c6e7ee4
cad037fcd99a5cbbe4aef20d54e685352fa67c1deec0999779c7140000002f96e55035c6760aba6f
4bb9aeffc52616cb300c
2.
01000000d08c9ddf0115d1118c7a00c04fc297eb010000008727d6117f2b0c429d84f2b9f7e9ea35
0000000002000000000003660000a80000001000000037f547b0feab4a12ab792d809a63fd0a0000
000004800000a000000010000000426ad5282726129ddce3675e82c68ca018000000b00bf7525cca
296dc3753d4aaaf867deec089bcbc94c73c21400000091011d6139ce2150b304f0cabaa7dc8b21fb
65d3
3.
01000000d08c9ddf0115d1118c7a00c04fc297eb010000001a114d45b8dd3f4aa11ad7c0abdae980
0000000002000000000003660000a8000000100000005df63cea84bfb7d70bd6842e7efa79820000
000004800000a000000010000000f10cd0f4a99a8d5814d94e0687d7430b100000008bf11f196015
8405b2779613e9352c6d14000000e6b7bf46a9d485ff211b9b2a2df3bd6eb67aae41
Po chwili bliższego przyglądania się, możemy powyższe ciągi podzielić na kilka grup:
A. pierwsze 48 znaków jest identyczne w przypadku wszystkich trzech zrzutów:
01000000d08c9ddf0115d1118c7a00c04fc297eb01000000
B. następne 32 znaki są identyczne w przypadku moich zrzutów i różne od przykładowych z MSDN.
8727d6117f2b0c429d84f2b9f7e9ea35
C. kolejne 44 znaki są identyczne w przypadku wszystkich 3 zrzutów
0000000002000000000003660000a800000010000000
D. dalej kolejne 32 znaki różne we wszystkich 3 przypadkach.
9403327f1497e1a4266ca7d45f84266a
E. następne 32 znaki są identyczne we wszystkich 3 zrzutach
0000000004800000a000000010000000
F. i potem następuje dosyć długi ciąg różnej długości dla wszystkich 3 zrzutów.
ed0b8a5247be59846e80ca735a1a3f9720000000a5025c6e7ee4cad037fcd99a5cbbe4aef20d54e6
85352fa67c1deec0999779c7140000002f96e55035c6760aba6f4bb9aeffc52616cb300c
Spróbujmy rzucić nieco światła na powyższe dane. Zacznijmy nasze poszukiwania jakichś wskazówek od zajrzenia do katalogów zawierających klucze MasterKey. W moim przypadku mogę tam znaleźć pliki o następujących nazwach:
11d62787-2b7f-420c-9d84-f2b9f7e9ea35
298da325-e2d2-454a-9a76-5846710f328e
fabbaf43-0835-4f00-9469-26f28b767c34
[CIACH pozostałe pliki]
Plik Prefered zawiera ciąg bajtów:
87 27 D6 11 7F 2B 0C 42 9D 84 F2 B9 F7 E9 EA 35 C0 BD E3 CB 49 0B CC 01
który zgodnie z tym, o czym pisałem w poprzednim odcinku, zawiera GUID klucza Master, którego ma aktualnie używać DPAPI + dodatkowe informacje.
I tak, pierwsze 16 bajtów to ciąg:
87 27 D6 117F 2B0C 429D 84F2 B9 F7 E9 EA 35
który odpowiada GUIDowi:
11d62787-2b7f-420c-9d84-f2b9f7e9ea35
W tym momencie bez trudu stwierdzamy, który plik z katalogu kluczy zawiera preferowany MasterKey. Bez większych problemów ustalamy również, że blok B z naszego zaszyfrowanego ciągu to po prostu GUID MasterKey użytego do zaszyfrowania danych.
Dochodzimy do miejsca, w którym głos należy oddać dwóm panom: Elie Bursztein oraz Jean-Michel Picod i ich prezentacji o internalsach DPAPI, która została przedstawiona w ubiegłym roku na konferencji BlackHat DC 2010 , a do której materiały dostępne są na stronie www.dpapick.com.
Znajdziemy tam między innymi względnie kompletny opis bloba, a co za tym idzie również naszego bloku A.
I tak, nasz blok A po podzieleniu na części to:
01000000 - liczba użytych dostawców usług kryptograficznych (crypto provider), w naszym i praktycznie każdym przypadku - 1;
d08c9ddf0115d1118c7a00c04fc297eb - GUIDy użytych crypto providers, w naszym przypadku jest to df9d8cd0-1501-11d1-8c7a-00c04fc297eb i odpowiada bibliotece psbase.dll (zgodnie z opisem, znajdziemy tę informację w kluczu HKLM\Software\Microsoft\Cryptography\Protect\Providers\df9d8cd0-1501-11d1-8c7a-00c04fc297eb);
01000000 - liczba kluczy MasterKeys użytych do szyfrowania.
Cała struktura bloba przedstawia się następująco [ze wspomnianej wyżej pracy E.B. i J-M.P]:
struct dpapi_blob_t {
DWORD cbProviders; // Num of crypto providers
GUID *arrProviders; // Crypto Providers GUIDs
DWORD cbKeys;
GUID *arrKeys; // Keys GUIDs (tu zaczyna się nasz blok B)
DWORD ppszDataDescrSize; // WARNING: in bytes
WCHAR *ppszDataDescr;
DWORD idCipherAlgo;
DWORD idHashAlgo;
BYTE *pbSalt; //Salt
BYTE *pbCipher; //Encrypted data
BYTE *pbHMAC; //HMAC
};
Ale co jest takiego wyjątkowego w ciągu z bloku A? Mogliśmy już to przyuważyć porównując dane z przykładu MSDN oraz mojego, choć jeszcze nie mieliśmy tej pewności. Odpowiedź jest prosta i po powyższych wywodach powinniśmy już ją znać: otóż jest to 'odcisk buciora' DPAPI, znak rozpoznawalny praktycznie zawsze i wszędzie. Jeśli kiedykolwiek będziecie czesali dysk, rejestr, pamięć, cokolwiek, w poszukiwaniu danych zaszyfrowanych z użyciem DPAPI, to wystarczy, że znajdziecie ten ciąg, a reszta będzie już tylko formalnością. :)
"Ale jaka ‘reszta’?" - ktoś spyta.
"Offline’owe odszyfrowanie tych danych" - odpowiedzą zgodnie panowie Bursztein i Picod.
"Zaraz, zaraz - przecież pisałeś, że dane dostępne są wyłącznie dla zalogowanego użytkownika, do tego LSA i… w ogóle to niemożliwe!" - słyszę kolejny oburzony głos.
Wszystko prawda. A przynajmniej w dokumentacji na stronach MSDN. Ale nie dla panów Picod i Bursztein :)
Na stronie dpapick.com możemy znaleźć przykładową aplikację wykorzystującą przygotowaną przez autorów bibliotekę libDPAPIck.dll pozwalającą na offline’owy dostęp do danych zaszyfrowanych z użyciem DPAPI. Wszystko, czego potrzebujemy, to:
- zestaw plików MasterKeys z katalogu kluczy użytkownika, w imieniu którego dane zostały zaszyfrowane;
- zaszyfrowany ciąg danych;
- tekstowe hasło użytkownika.
"Eee" - ktoś się skrzywi - "Hasło?"
Tak, cudów nie ma. Do odszyfrowania klucza MasterKey musi zostać wygenerowany Pre-Key, który wyprowadzany jest właśnie z hasła użytkownika, obejść się tego nie da. Oczywiście możemy zajrzeć do bazy sam i spróbować znaleźć hasło, dla którego mamy skrót, ale jest to pracochłonne i niekoniecznie najprostsze.
Są jednak sytuacje, kiedy znamy hasło i chcielibyśmy dostać się bez podnoszenia systemu do zaszyfrowanych danych i ta biblioteka nam to umożliwia. Autorzy podają jako przykład dostęp do plików szyfrowanych z użyciem EFS, haseł Internet Explorera i innych aplikacji z poziomu Wine’a, czyli ogólnie wsparcie interoperacyjności, lecz nie tylko. Tak na marginesie dodam, że przechowywaniem haseł z użyciem Credential Managera (opartym rzecz jasna na DPAPI) zajmiemy się w jednym z kolejnych wpisów.
Ja oczywiście podam jako przykład aplikację, która odczytuje offline’owo hasła zapisane do pliku w PowerShellu, naturalnie z wykorzystaniem biblioteki libDPAPIck.dll. Nie będę przedstawiał całego kodu źródłowego, jednak w oryginalnym przykładzie zabrakło paru drobiazgów, które mogą uniemożliwić nam odszyfrowanie danych, i nimi się teraz zajmiemy.
Do deszyfrowania danych skorzystamy z wyeksportowanej funkcji:
[DllImport("libDPAPIck.dll", CharSet = CharSet.Auto)]
publicstaticexternbool DPAPIBlobDecrypt(StringBuilder blobPath, ref DATA_BLOB blobEntropy, ref DATA_BLOB blobStrongPassword, ref DATA_BLOB blobMasterkey, ref DATA_BLOB blobResult);
Po pierwsze - do oryginału (tu uwaga: kodu źródłowego nie ma, ale wystarczy użyć reflectora, lub innego dezasemblera .net i możemy to i owo dodać) należy dodać funkcję odczytującą GUID MasterKey użytego do zaszyfrowania danych, co na podstawie powyższego opisu sprowadza się mniej-więcej do czegoś takiego:
privatestring retrieveMasterGuidFromBlob(byte[] blob)
{
int startGuidPos = 24; // początek bloku B
int guidLen = 16; // 16 bajtów na guid
byte[] byteGuid =newbyte[guidLen];
Array.Copy(blob, startGuidPos, byteGuid, 0, guidLen);
returnnew Guid(byteGuid).ToString();
}
Tak odczytany Master-Guid należy wykorzystać w funkcji deszyfrującej MasterKey, gdzie w oryginale wykorzystywany jest pierwszy Master z listy, zamiast tego użytego do zaszyfrowania danych. Pozostawiam to jako ćwiczenie.
Nasze dane zapisane są w postaci tekstowej, należy więc dokonać konwersji ich na ciąg bajtów, co pozostawiam jako proste ćwiczenie. Oczywiście w przypadku danych z innych aplikacji taka konwersja nie musi być konieczna, więc myśląc ‘przyszłościowo’ należałoby to uwzględnić.
W samej funkcji deszyfrującej blob należy jeszcze pamiętać o tym, że zaszyfrowany przez PowerShell ciąg znaków był unikodowy, a nie ASCII i to już właściwie daje nam możliwość uruchomienia aplikacji na dowolnym komputerze, na który skopiowaliśmy katalog z kluczami oraz zaszyfrowany plik:
The original data is:
alamakota256
Pierwszym parametrem jest nazwa katalogu zawierającego MasterKeys, drugim - hasło użytkownika, a jako trzeci podaję nazwę pliku txt zawierającego zaszyfrowany ciąg. I gotowe! :)
Jakiś czas temu zgłosiłem Eliemu i Jean-Michelowi drobne uwagi (w tym powyższe) i następna wersja, planowana na kolejną edycję konferencji BlackHat miała je zawierać. Dodatkowo (co wiem nieoficjalnie z wymiany mailowej z Elie i Jean-Michelem) DPAPIck prawdopodobnie będzie wspierać Windows 7 i środowisko domenowe, a do pobrania będzie kilka przykładowych aplikacji do odszyfrowywania danych konkretnych aplikacji.
Całość badań nad DPAPI wygląda bardzo obiecująco, więc warto co jakiś czas sprawdzać postęp prac nad biblioteką.
Przyznaję, że początkowo tekst miał wyglądać nieco inaczej, jednak ostatecznie stwierdziłem, że pewne rzeczy - jak np. odniesienia do EFSa, czy Credential Managera zostawię na inne wpisy. Zrezygnowałem również z publikacji większych kawałków kodu - pomimo tego, że dyskutowałem z Elie i Jean-Michelem o kodzie, to jednak nigdy nie spytałem wprost, czy mogę fragmenty swojej przeróbki opublikować. Tak więc - należy uzbroić się w cierpliwość - już za kilka tygodni kolejna edycja BlackHata, a tym samym publikacja poprawionej wersji DPAPIck :)