Hackowanie książki – PIW Cover CrackMe

Ciąg dalszy hackowania książek. Po udanym złamaniu (rozpakowaniu) okładkiZrozumieć programowanie” przyszła pora na drugą książkę Gynvaela Coldwinda (przy współpracy Mateusza Jurczyka1), czyli “Praktyczna inżynieria wsteczna”. Po plotkach i niusach krążących w Internecie spodziewam się jeszcze lepszej zabawy ;)

Tej książki też (jeszcze) nie posiadam, to znów musiałem zdobyć z sieci okładkę w przyzwoitej jakości2. Z okładki aż promieniuje kawałek jakiegoś kodu w asemblerze i bliżej nieokreślone dane, pewnie jakaś binarka zrzucona w 16-stkach.

Kod z okładki

Początkowo olałem widoczny kod asemblera uznając, że ta “pętelka” to znów jest jakiś losowy ozdobnik okładki, a prawdziwy kod kryje się pod postacią tego sformatowanego hexadecymalnie kawałka danych widocznego w tle.

ad524552450fc6bf40638c638503cfc648cb455245d697264da04a6ab59004b9a80b7cd6c86f455245274913c5ab52279fea023d2a3689ea0b1d1517899cd11f13d73c1a13b4233b886a10493ce5fa92f8c1b099cbf0810083a5aef1d1ccf121634c36a5cc16ae9377d1b5d43d16feb017f8baaf820845ab2cfe0615a576d07001336c51ad4c4a91c9f19b1e4dff941aae12d2d2d508687ba1063026243812222c213f0624382b370d33363e2a7364734552000

Przy digitalizacji tych hexów (data.txt) i konwersji do pliku binarnego (data.bin) mnie olśniło.

Te dwa lebele widoczne w kodzie asemblera (decode.asm) to nie może być przypadek! Etykieta bk_cvr zdecydowanie odwołuje się do danych z tła okładki, a rwx_mem musi być wskaźnikiem do kawałka pamięci z atrybutami rwx.

lea esi, [bk_cvr]		; esi = ptr to book cover data
lea edi, [rwx_mem]		; edi = ptr to rwx memory page
mov ecx, 89				; ecx = number of words

dcd:
	lodsw				; ax = *bk_cvr, bk_cvr += 2
	xor ax, 0x5245		; ax ^ 0x5245
	stosw				; *rwx_mem = ax, rwx_mem += 2
	loop dcd			; jmp to dcd, ecx -= 1

jmp rwx_mem				; call decoded code

Ba! Ten kod wygląda na unpacker dekodujący i wykonywujący kod binarny z okładki!

Dekodowanie

Działanie dekodera jest bardzo proste - patrz moje dodane komentarze w kodzie asm. Jest to pętla iterująca po 89 word-ach (188 bajtach) kodu z okładki z operacją XOR-owania każdego z nich wartością 0x5245. Na koniec następuje skok do bloku pamięci ze zdekodowanym kodem i jego wykonanie.

Nie chciałem tego asemblować ani uruchamiać, tylko szybko dostać zdekodowany kod, najlepiej w pliku (code.bin) do dalszej analizy. W tym celu napisałem sobie kilka linijek kodu w C do konwersji jednego pliku w drugi.

uint16_t word;
while (fread(&word, 1, sizeof(word), fs) == sizeof(word)) {
	word ^= 0x5245;
	fwrite(&word, 1, sizeof(word), fd);
}

Jego zasadnicza rola to właśnie read-xor-write pomiędzy dwoma plikami, szczegóły do podejrzenia w pliku decode.c.

Po odpaleniu decode.exe “pojawił” się plik code.bin z przekonwertowaną zawartością jak poniżej:

00000000  E8 00 00 00 00 5D 83 ED 05 31 C9 31 C0 51 8A 94  č....].í.1É1ŔQŠ”
00000010  0D 99 00 00 00 84 D2 74 08 F2 0F 38 F0 C2 41 EB  .™...„Ňt.ň.8đÂAë
00000020  ED 59 39 84 8D 3D 00 00 00 75 0C 41 80 F9 17 75  íY9„Ť=...u.A€ů.u
00000030  DA B8 47 6F 6F 64 CC B8 4E 4F 50 45 CC CE 94 4D  Ú¸Good̸NOPEĚΔM
00000040  56 85 79 48 56 E6 66 69 CD 38 55 1B 79 B7 BF C0  V…yHVćfiÍ8U.y·żŔ
00000050  BD 93 F5 CB 8E A2 C4 52 C6 F7 EB A3 94 9E B4 73  ˝“őËŽ˘ÄRĆ÷룔ž´s
00000060  26 1E 73 F7 89 44 EB C1 32 83 F0 86 78 44 BB E2  &.s÷‰DëÁ2.đ†xD»â
00000070  52 AA FF FD C7 5A 00 F9 69 AC 43 47 E0 24 95 22  RŞ˙ýÇZ.ůi¬CGŕ$•"
00000080  44 61 29 03 E8 1E 0F C3 8C A3 DE 4C 08 AD D1 48  Da).č..ĂŚŁŢL..ŃH
00000090  EB 40 97 80 90 5A 2D 29 E4 54 75 74 61 6A 57 70  ë@—€.Z-)äTutajWp
000000A0  69 73 7A 54 61 6A 6E 65 48 61 73 6C 6F 21 21 21  iszTajneHaslo!!!
000000B0  00 00                                            ..

I już coś ciekawego tam widać - kilka stringów, z czego intrygujący jest ciąg tekstowy “TutajWpiszTajneHaslo!!!”. Chyba zapowiada się jakiś CrackMe. Jako że kod ten docelowo jest odpalany zaraz po zdekodowaniu przez unpackera, to na pewno jest to kod x86, więc teraz nadeszła pora na jego analizę…

Analiza

Nie chciałem uruchamiać fizycznie tego kodu, więc pozostała mi jego statyczna analiza. Może to być trochę trudniejsze w porównaniu do podglądania wykonywującego się kodu pod debugerem, kiedy to często można zauważyć i wydedukować pewne niuanse algorytmu. No, ale kod ten chyba nie będzie jakoś bardzo skomplikowany…

W miarę szybko udało mi się kod dezasemblować i przetworzyć do bardziej źródłowej postaci nadającej się do czytania przez człowieka. Dodałem także trochę komentarzy dla ułatwienia zrozumienia jego działania. Pełny kod w czytelnej “zrewersowanej” formie dostępny jest w pliku code.asm, a poniżej załączony jest najistotniejszy fragment.

loop:
	xor eax, eax					; clear eax / crc_val
	push ecx						; store the start pos

crc_calc:
	mov dl, [ebp+ecx+passwd]		; dl = passwd[ecx]
	test dl, dl						; if got '\0'
	jz short crc_cmp				; check crc sum

	crc32 eax, dl					; otherwise calc crc for dl
	inc ecx							; and goto to next
	jmp short crc_calc				; char processing

crc_cmp:
	pop ecx							; get the start pos
	cmp [ebp+ecx*4+crcsum], eax		; crcsum[ecx] == eax
	jnz short bad					; if not jmp to error

	inc ecx							; otherwise continue
	cmp cl, 17h						; checking next substring
	jnz short loop					; until all crcsum pass

	mov eax, 646F6F47h				; eax = "Good"
	int3							; dbg break

bad:
	mov eax, 45504F4Eh				; eax = "NOPE"
	int3							; dbg break

crcsum	dd 0x564D94CE, ..., 0xE4292D5A
passwd	db 'TutajWpiszTajneHaslo!!!',0

Szczegółową analizę kodu zostawiam czytającym, a ja przedstawię tylko ideę działanie algorytmu sprawdzania hasła.

Algorytm jest dosyć prosty, acz ciekawy. Z kodu wynika, że liczona jest suma kontrolna CRC32 z dostępnego w “pamięci” ciągu tekstowego passwd reprezentującego hasło (domyślnie: “TutajWpiszTajneHaslo!!!"). Operacja ta powtarzana jest 23 razy, co odpowiada długości hasła. W każdej iteracji obliczanie CRC32 rozpoczyna się od kolejnej pozycji w stringu. W rezultacie checksuma wyznaczana jest dla wszystkich kolejnych podciągów składowych hasła:

01 CRC32 TutajWpiszTajneHaslo!!!
02 CRC32 utajWpiszTajneHaslo!!!
03 CRC32 tajWpiszTajneHaslo!!!
...
23 CRC32 !

Po każdym obliczeniu sumy kontrolnej sprawdzana jest jej poprawność poprzez porównanie z wartością oczekiwaną zapisaną w tablicy crcsum. Sprawdzanie hasła jest przerywane przy napotkaniu pierwszej niezgodności.

Działanie tego algorytmu zapisane w kodzie C mogłoby wygadać mniej więcej jakoś tak:

int CheckPasswd(const char passwd[Size]) {

	const uint32_t crcsum[Size] { ... };

	for (int i = 0; i < Size; i++) {

		uint32_t sum = 0;
		for (int x = i; x < Size; x++)
			sum = _mm_crc32_u8(sum, passwd[x]);

		if (sum != crcsum[i])
			return Nope;
	}

	return Good;
}

Jak można było się spodziewać, po treści domyślnego hasła, zapisana w kodzie tajna treść nie jest poprawna. Oczywistością staje się teraz cel tego zadania. Rozwiązanie polega na znalezieniu takiego hasła, z którego wygenerowane sumy kontrolne pokryją się z tymi zapisanymi w tablicy - przejdzie walidację opisanym tutaj algorytmem. No to crackowanie…

Przy okazji w kodzie zastosowano fajny “trick” (chyba dosyć znany) widoczny w kilku pierwszych instrukcjach:

	call $+5						; Position Independent Code
	pop ebp							; it's nice trick to determine
	sub ebp, 5						; the base address of this code

Kod ten jest ładowany do losowego kawałka pamięci, zatem musi być position independent. Do bezproblemowego dobrania się do zmiennych po offsetach potrzebny jest adres jego początku. Zadaniem tych kilku instrukcji jest określenie swojej lokalizacji (adresu) w pamięci. Fajne wyjaśnienie tego mechanizmu można znaleźć na stacku.

Rozwiązanie

Skoro już wiem jak dokładnie działa algorytm sprawdzania hasła to mogę napisać keygena do jego wygenerowania.

Znalezienie poprawnego hasła naiwną metodą brute-force może być kosztowne i czasochłonne, bo wymaga sprawdzenia aż 256^23 kombinacji. Znacznie lepszą opcją jest szukanie hasła w odwrotny do działania algorytmu sposób. Polega on na generowaniu i testowaniu poszczególnych znaków hasła poczynając od jego końca i przesuwaniu się kolejno ku początkowi. To w najgorszym przypadku będzie wymagało wyliczenia tylko 23*256 sum CRC.

Ten właśnie sposób wybrałem do swojej implementacji zakodowanej w C (keygen.c). Jego zasadnicza część wygląda tak:

for (int i = Size - 1; i >= 0; i--) {
	for (char c = 0; c < UCHAR_MAX; c++) {
		passwd[i] = c;

		uint32_t sum = 0;
		for (int x = i; x < Size; x++)
			sum = _mm_crc32_u8(sum, passwd[x]);

		if (sum == Checksum[i])
			break;
	}
}

Działanie tego kodu jest bardzo proste. Dla każdej pozycji w haśle, poczynając od końca, podstawiane są kolejno wszystkie możliwe wartości (0..255) i obliczana jest suma CRC z tak powstałego (od danej pozycji) ciągu tekstowego. Jeśli otrzymany wynik (suma CRC) zgadza się z tym oczekiwanym to program przechodzi do szukania następnego znaku (poprzedzającego). I tak, aż do znalezienia całego hasła.

Po skompilowaniu i uruchomieniu program niemal natychmiastowo “wypluł” znalezione hasło:

X:\hack-the-book\PIW-Cover-CrackMe>keygen.exe
passwd: coldwind.pl/piwflag1337

Poszukiwanym hasłem (flagą) jest adres do strony coldwind.pl/piwflag1337 z gratulacjami i “Wieczną Chwałą”!

Cześć i chwała poszukiwaczom zaginionego tajnego hasła!

Podsumowanie

Muszę pogratulować Gynvaelowi fajnego pomysłu z wrzuceniem CrackMe na okładkę książki!

Zadanie nie było trudne, ale jednak trzeba było wpaść na dobre tory przy przyglądaniu się okładce, bo potem już jechało się z górki. Przyznaję jednak, że trochę pracy i myślenia należało zaangażować w rozwiązanie. Szczególnie, że przy moim wyborze analizy statycznej i chęci szybkiego rozwiązania, kluczem było dokładne zrozumienie działania algorytmu.

Początkowo chciałem olać dokładną analizę i wykorzystać ten kawałek binarnego kodu z podmianą kilku opcode przystosowując go do działania w roli funkcji, aby po chamsku najniższym kosztem uruchomić pełny brute-force. Ocknąłem się w porę, bo pewnie trochę czasu na zakodowanie, a potem na szukanie hasła bym zmarnował.

Gratuluję też Radkowi, za znalezienie i rozwiązanie tego zadania jako pierwszego. Kiedyś dawno temu natrafieniu na info o jego odkryciu, dzięki czemu dotarła do mnie plotka o ukryciu czegoś ciekawego na okładkach książek Gynvaela!

To już pewnie koniec hackowania książek. Możliwe, że gdy uda mi się kiedyś zdobyć te książki fizycznie, albo trafić na jakieś ebooki, to poszukam pozostałych flag i easter eggs przemyconych w treściach książek. Bo jak sam autor wielokrotnie wspominał, kilka ich gdzieś tam czeka na odkrycie ;)

Wszystkie pliki i kody z hackowania książek dostępne są w moim gicie mal-code-repo w katalogu hack-the-book.


Przypisy

  1. W rzeczywistości książka ma wielu autorów - znanych osobistości z polskiej sceny RE i security, a Gynvael i Mateusz byli kierownikami zespołu redakcyjnego… czy jakoś tak ;) ↩︎

  2. Źródłowy plik graficzny z okładką pochodził z fragmentu książki dostępnej w sklepie Helion. ↩︎

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *