FEK ponownie
Poprzednią część zakończyliśmy zapisaniem parametrów klucza RSA w formacie XML.
<RSAKeyValue>
<Modulus>
n9WMraGeldyUSGqorfsmOiK4n3LgwxTWuUd0JXXJ5R+4fbZGtptbk4en1Z+pmS2w19to8u
6x9wZNFsf7ow+TMStKOTINDC37wGhLyroC2kOKs1nJQucHzXpT6iWl4WrcB2MDT+4tlmFX
sxvmstZdy2ewJZEwi7wqcZWH21fhRfSrs+oJ9iRcmHGB9ZickLbT+kt4BtefbC1sJsVmI5
Dpb4E64p4d8k/CmRYfhR5bw6oWHVgmu7kS8XhYNQTZUfVVALdgbkFx1WqkuEpUUj0SdtKW
UQOdTl8uYWqj/3jZcuKeaY1cNjYr0AyPLR0+FxS8exzyr749IBoVVlzGryYnDw==
</Modulus>
<Exponent>
AQAB
</Exponent>
<P>
2hY1mpkEhatMHAUdE9Mvb+B5VuPY46CeLvUJM1kesUoq7G5V5GXzZG8S2oWv4ZFA8xV6HW
MMZTk2AYgi2RpzNrxvsRCZwr0jDwMmc94BatKyMPeQQwnr9eO8L/64EZaw5AUvakD3a+qz
biZn4HqQIHK6qjpN23yDxtca4cb3Jck=
</P>
<Q>
u57aHizRDbDoimfZ1BjMIW+2u3G9IoZ4mW/iSdPQ/CWx6ODg5U9XlEgbBF9368EgzCKDDM
+VwyeOpKb8GvvJkvAaTjH0Qmw7KU+UAaDlB0Sq9G67kJ5C6lRgiNGaGZujO9fG2R4Jgv+v
ebMod2QP1t0pswOJWgmeKOtk7EdMshc=
</Q>
<DP>
N7V2qfAulIqmXX386ISI2JZJyKVZUQRFhA8583DvgzBD+LNSo45bdytccI+31vII2k+BKy
KTFwRfRFLnO/giuDe4fE2WSYWRABO47d4nFIeP1yxWIJnXOa+b8dKqaGvK8eRVMVo5GcsR
XLDY1iHY0UEdZ8CPBOMwskleZ5Awr+k=
</DP>
<DQ>
cXBtfvsn9zg0kaKS8QuBOOI7wP/XFG3rsxIT5wF8BUihViXJtLwRPuWN+EnkzxV5SBPB2t
gUO+fJ9kHEgmgki4RC026euh6IcsTbv1RRxeA94Qamv50A5n/X0kGxA7S3sHIZl6Lyq1L+
/P1XBUDp1sbn9IW54UeZajJRVg8SD0c=
</DQ>
<InverseQ>
Q8+QuCMDBKHs3ZtfPOhsLuo0W+TlkPIyhw9OiO3AAJhzr4e2+8/meMXEZ0s+2kAUaDCqk2
/AYbjFNsIPOgLm4MqZnuANJOHBg1t1VY3lqi5rwIryUOl0hIYMImyB7r59RCuaQqtCRRjI
67J5v4GTbBiexSIwtVJLWoYUv3P5d50=
</InverseQ>
<D>
CEPlw3DRH5TJgjkpwd1z65uyCmTJZK0mMWyVf/5oU1Xhl3aej4DXKnjgX/aisY4gT/lDox
Re1ZRY1i2/QH5ksS046F5DOHcERr3d9XzK+vh1KrMg6jnCOIjz3+7UdiStfiBLoo2Bg/5C
p7twoZzeOs+A12B7ry4qz7bY0Knpt1FLuOMXQbVBSMEo5RbfnYkleGB0UMdAosb57uNS3b
YWwTCCJC46t/EQc4MsHFmGoisIe1HKL1ilBpiUQyPRR9ZSNicmmDjuDLOTw2i+MBWRs34C
OdEJN3PWBuLFn6mTZzk3KBQEL9TL8mBki7iEizTWoVaKu9ojlX20i8HohYFOcQ==
</D>
</RSAKeyValue>
(Uwaga dla purystów base64: ciągi podzieliłem ręcznie ze względu na wyświetlanie na blogu, więc nie męczcie o znaki kontynuacji. W oryginale nie ma żadnego znaku podziału wiersza i linii).
Możemy zatem przystąpić do odszyfrowania FEKa, którego namierzyliśmy w pierwszej części:
>EFSAnalysis.exe encrypted_file.txt.efs
EFS Stream Header:
Len: 664, state: 0, ver: 2, crypto api ver: 0
Num of DDFs: 1, DRFs: 0
DDFs:
SID: S-1-5-21-580747136-2243477503-2994681153-1000:
Certificate:
Thumbprint: EFF5EDAB8123D06B6EFEA7D87716B03F9C8F48CD
Container name: 0d29406b-f9e6-4659-90e4-c407201049d2
Provider name: Microsoft Enhanced Cryptographic Provider v1.0
User name: Admin(Admin@VM7)
Encrypted FEK:
FE 1B 0D 07 2F 56 96 09 6C D8 DD 54 92 FF 23 2E ..../V?.lOYT?.#.
15 93 D2 4C 5F 4D 76 F6 35 DB 75 BF 5C 24 4C D5 .?OL_Mvö5Uu¿\$LO
46 12 B7 DD C2 09 F5 DB 0E 01 CC E0 51 B0 A6 87 F.·YA.oU..IàQ°▌?
15 AB A9 FC 56 BE 3C BD 0A BA C6 D8 DF 6A DE 4B .«cüV_<½.ºÆOßj_K
78 BB E0 CC 2F 88 86 BC 20 23 BB 86 EB 72 AC 4B x»àI/??¼.#»?ër¬K
4F AE 05 9F 1A 99 BC BC 80 52 FB D1 8B 7B 90 B1 Or.?.?¼¼?RûÑ?{?±
8B E9 B7 A4 85 D0 FF 18 3C C9 C5 8C 9A 7A 1C 04 ?é·☼?D..<ÉÅ??z..
D6 C7 A6 0B A8 76 4E 9C 87 3E B8 DC 62 C3 ED 57 ÖÇ▌."vN??>,ÜbAíW
CB FE 01 B8 20 91 87 EB 96 18 14 0B DF 2E E4 F8 E..,.??ë?...ß.äo
7A 5D C8 1D 77 0F E6 9A 7F 89 B9 C0 E5 8E DC 4D z]E.w.æ?.?1Aå?ÜM
5A C5 9C 3E 05 24 89 21 C9 79 73 9F 42 FE 64 AC ZÅ?>.$?!Éys?B.d¬
4E 28 B6 25 1A 58 0E A0 D0 68 4B 38 6B 78 6D F6 N(¶%.X. DhK8kxmö
41 6A 8A 70 24 CB 45 48 11 81 C9 06 9B 75 18 A1 Aj?p$EEH.?É.?u.¡
10 33 46 9B 63 D9 4E CF 44 C5 4E 4A 58 F6 F8 15 .3F?cUNIDÅNJXöo.
BF FD 43 98 74 2B E5 55 9A 9E FF 77 55 5B CD F9 ¿yC?t+åU??.wU[Iù
CC 48 31 BC 51 17 67 F5 61 BB A2 C4 88 2A 42 83 IH1¼Q.goa»¢Ä?*B?
Posługujemy się zatem algorytmem RSA z wyliczonym w drugiej części 2048-bitowym kluczem i w pierwszym podejściu otrzymujemy - niezwykle wredny skądinąd - komunikat ‘Złe dane’. Wrr!!! Odwracamy zaszyfrowany FEK, zapuszczamy ponownie algorytm i w wyniku otrzymujemy 48 bajtów (Rys 1).
Oczywiście najważniejszą składową jest 256-bitowy klucz deszyfrujący (zaznaczony na Rys 1 na czerwono). Druga istotna informacja oznaczona jest na niebiesko - jest to ALG_ID algorytmu symetrycznego (AES-256), który mamy zastosować podczas odszyfrowywania zawartości pliku. Mamy wszystko!
AES-256
Na chwilkę oddalamy się od naszego przypadku, bo potrzebujemy nieco innego spojrzenia na naszą zabawę. Swoje pierwsze próby podejmowałem dla pliku o rozmiarze 86 bajtów. Znałem oryginał, więc mogłem porównać go z odszyfrowanym plikiem, wyniki zabaw poniżej.
Strzał 1.
Już pierwszy rzut oka zdradza, że coś jest jednak nie tak. Korzystam z implementacji .netowej AES z kluczem 256-bitowym, w trybie CBC, a jako wektor inicjalizacyjny ustawiłem ciąg 16 zer.
Strzał 2.
Jak widać na Rys 3. to nie jest takie proste - zaglądam zatem do linuxowego sterownika ntfs-3g i dosyć szybko znajduję zahardkodowany wektor iv, który generuję następująco:
private byte[] generatateEFSIV(ulong offset)
{
ulong a = 0x5816657be9161312 + offset;
ulong b = 0x1989adbe44918961 + offset;
byte[] iv = new byte[16];
Buffer.BlockCopy(BitConverter.GetBytes(a), 0, iv, 0, 8);
Buffer.BlockCopy(BitConverter.GetBytes(b), 0, iv, 8, 8);
return iv;
}
Puszczam test i w wyniku otrzymuję plik (Rys 4)
A zatem początek pliku jest już ok. Dalej niepokoi mnie jednak końcowe 6 bajtów - ewidentnie coś jest z nimi nie tak.
Czas wrócić do korzeni. Badany przypadek to AES z kluczem 32-bajtowym, 16 bajtowym wektorem inicjalizacyjnym, a dane podzielone są na bloki 16-bajtowe, przy czym trybem pracy algorytmu jest CBC. Momencik! Bloki są 16-bajtowe… więc co się dzieje z danymi z ostatniego bloku? Tam jest tylko 6 bajtów danych, a co z resztą?!
Pierwszy pomysł, jaki przyszedł mi do głowy - trzeba to czymś uzupełnić. Tylko czym?
W tym momencie wracamy do naszego pliku 15-bajtowego.
Tu sprawa jest szybka - znajdźmy ostatni bajt, dla którego plik jest poprawnie odszyfrowany i po prostu trzymajmy się tego, może to jakiś tajemniczy decrypt-padding, o którym należy pamiętać i tyle? Po kilkudziesięciu próbach znalazłem tajemniczy bajt (0xAF) i postanowiłem sprawdzić go na drugim testowym pliku, również 16-bajtowym o identycznej zawartości. Wynik? Oczywisty: nic z tego! Padding - owszem, jest, ale tylko przy szyfrowaniu. Podczas odszyfrowywania musisz mieć kompletne dane, inaczej nic z tego! Żadnych tajemniczych łańcuchów, po prostu wynik szyfrowania, tylko tyle i aż tyle.
Wracam zatem do strumienia $EFS i zaczynam szukać jakiejkolwiek wskazówki, może brakujące bajty zapisane są w którymś z ‘unknown’ pól? ctrl+f, ‘af’ -> “No occurrences of ‘af’ found”.
W tym momencie naszło mnie olśnienie. Wektor iv zmieniany jest co 512 bajtów, czyli wielkość sektora. A może w takim razie… nie, niemożliwe! (Rys 6)
O, jest nasz 0xAF! Znalazł się :)
Sprawdźmy zatem, co otrzymamy po odszyfrowaniu danych z pierwszych 512 bajtów z klastra zawierającego strumień $DATA (Rys 7).
Tym samym wszystkie zagadki zostały rozwiązane. EFS używa danych ze slack space, przy czym uzupełnia oryginalne dane zerami i to szyfruje. Zaskoczeni? :) Ja przyznaję - byłem mocno zaskoczony! :)
Podsumowanie
Uff, dotarliśmy do końca wędrówki! Każdy z omawianych elementów to godziny testów i prób. Poza hasłem użytkownika znajdziecie tu wszystko, a i samo hasło nie jest wielką tajemnicą - inaczej nie podawałbym na tacy skrótów SHA-1 ;) Czas na podziękowania i wskazanie głównych źródeł informacji.
Wszystkie testy przeprowadzałem na Windows 7 SP1 x64, która nie jest podłączona do domeny (co było widać w polu domainKeyLen = 0 - w której strukturze? ;)).
- Pierwsza poważniejsza próba zmierzenia się z EFS: [KLIK]. Źródło inspiracji odnośnie pokolorowania danych, a także wskazanie najważniejszych elementów systemu.
Czy zdajecie sobie sprawę z tego, że Peter (autor tekstu) binaria opracowywał w edytorze WinHex, a wszystko kolorował z użyciem Paint Shop Pro? Kupił nawet specjalny font na potrzeby grafik, ponieważ domyślny w rozdzielczości 1024x768 był nieczytelny. No cóż, na szczęście jest już 010 Editor, z którego ja miałem szczęście korzystać. - Najważniejsze opracowanie DPAPI to DPAPIck. Znajdziecie tam artykuł + pełen kod napisany w Pythonie [KLIK], który starałem się przepisać na C#, oczywiście stosując dostępne w .NET Framework biblioteki kryptograficzne, a nie np. M2Crypto (na czym opiera się DPAPIck). Zmiany są bardzo poważne, w zasadzie to zupełnie nowa implementacja rozwiązania - bardziej zależało mi na pełnym zrozumieniu wszystkich mechanizmów, niż po prostu przetłumaczeniu kodu. Cel udało mi się osiągnąć, czego efekt widzieliście w części drugiej :) Przy okazji wyszło trochę braków, które zapewne pojawią się w wydaniu 0.4 DPAPIck.
- Źródła linuxowego sterownika ntfs-3g, w tym narzędzie ntfsdecrypt. Rozwiązanie linuxowe opiera się na zastosowaniu pliku certyfikatu .pfx, zawierającego pełen zestaw kluczy, o DPAPI należy zapomnieć.
- EFS to doskonały materiał na laboratorium. W zasadzie modelowo pokazuje jak można przechowywać wrażliwe dane, z jakich algorytmów należy korzystać, jakich parametrów użyć. Dla mnie to też była doskonała okazja, żeby przypomnieć sobie szczegóły RSA, AES, SHA, HMAC, PBKDF2 oraz wgryźć się w rozwiązania typu TSK. Nie może zatem zabraknąć literatury:
- "Kryptografia i bezpieczeństwo sieci komputerowych. Matematyka szyfrów i techniki kryptologii", William Stallings [KLIK]. Podręcznik akademicki.
- "Security Driven .NET", Stan Drapkin [KLIK]. Wydawać się może, że na 70 stronach nie da się za wiele napisać. Nic bardziej mylnego, ta książka to po prostu esencja esencji, wymaga jednak dosyć dobrej znajomości tematu.
- “Cryptography in .NET Succinctly”, Stephen Haunts [KLIK]. Założenie darmowej serii Succinctly to książka 50-100 stron, w której znajdziesz najważniejsze informacje z interesującego Cię tematu. Jako wprowadzenie, ale bez zbytnich uproszczeń i na odpowiednim poziomie, żeby móc coś zrobić - doskonała. Warto zajrzeć do niej przed Drapkinem, tym bardziej, że - powtarzam - jest całkowicie za darmo!
- “Windows Internals, part 2”, 6th edition. Mark Russinovich, David A. Salomon, Alex Ionescu. Co tu pisać - po prostu mus.
Myślałem, żeby przygotować z tego jakąś prezentację na którejś grupie (Windowsowej? .NET?), ale jakoś nie bardzo wiem kogo ten temat mógłby zainteresować. Jeśli uważasz, że temat wart jest szerszego pokazania, albo masz jakieś uwagi - zostaw komentarz! Podobało się, albo wręcz przeciwnie - podziel się!