Autoryzacja pluginów w komunikatorze Tlen.pl

tech • 957 słów • 5 minut czytania

Ta notatka jest częścią serii Tlen.pl bez tajemnic. Zapoznaj się z pozostałymi wpisami.

Kolejna publikacja z serii “Tlen.pl bez tajemnic” prezentująca tajemnice systemu autoryzacji pluginów. Oczywiście, tak jak w przypadku połączeń szyfrowanych, i tym razem udało się w to wmieszać andka, który nieco prędzej opublikował swoje wnioski na temat autoryzacji pluginów.

System pluginów w komunikatorze tlen.pl opiera się na łączonych dynamicznie bibliotekach - DLL (Dynamic Link Library) ze zmienionym rozszerzeniem na .tpl, co ułatwia identyfikacje i odróżnienie pluginu od innych bibliotek.

Oficjalne pluginy wydane przez twórców tlen.pl są również typowymi bibliotekami DLL, ale dodatkowo zostały troszkę wzbogacone o pewne informacje. Dzięki którym możliwa jest autoryzacja pluginów, na podstawie której, klient potrafi odróżnić oficjalne pluginy od nieoficjalnych i ewentualnie ostrzec przed próbą uruchomienia nie autoryzowanego (pochodzącego z nieznanych źródeł) pluginu.

W niniejszej publikacji zajmiemy się systemem autoryzacji pluginów w komunikatorze tlen.pl. Opiszemy format dodatkowych danych dołączanych do plików, dzięki którym możliwa jest autoryzacja pluginu oraz wzbogacenie wtyczki o kilka dodatkowych, niestandardowych właściwości, niedostępnych innym autorom wtyczek.

Autoryzacja pluginów opiera się na porównaniu wartości funkcji skrótu SHA-1 obliczonej dla pliku pluginu z zaszyfrowaną wartością znajdującą się w danych dopisanych do końca pliku. Dane autoryzacyjne nie są częścią pliku, dla której obliczany jest hash SHA-1.

W celu łatwiejszego zrozumienia i zobrazowania dalszego opisu, poniżej zaprezentowano końcowy fragment pluginu video.tpl (Videokonferencje).

Końcowy fragment pluginu video.tpl

Obecność sygnaturki i danych autoryzacyjnych identyfikowane jest na podstawie wartości dwóch 4-bajtowych liczb typu int znajdujących się na końcu pliku.

Ostatnia liczba (pierwsza patrząc od końca pliku, kolor zielony), posiada stałą wartość równą 1A2B3C4Dh.

Przedostatnia (kolor czerwony) określa offset do początku danych autoryzacyjnych, licząc od swojej pozycji, czyli określa ich rozmiar, a całkowita długość dodatkowych danych dołączonych do pliku jest równa jej wartości powiększonej o 8 (dwie ostatnie liczby 4 bajtowe).

Patrząc na zamieszczony fragment kodu pluginu, widzimy, że dane zawierające informacje o autoryzacji zaczynają się od pozycji 2F050h, co można łatwo obliczyć, według podanych wyżej informacji:

0002F050h = 0002F210h - 000001C0h

Dla oficjalnych pluginów firmowanych przez o2.pl wartość tej przedostatniej liczby wynosi 000001C0h, co odpowiada długości danych równej 448 bajtów.

Z 448 bajtów danych najistotniejsze są początkowe 64 bajty (zaznaczone kolorem czerwonym), które zawierają zaszyfrowaną wartość skrótu SHA-1 zawartości pluginu, pozostałe to losowe dane.

Wartość hasza SHA-1 znajduje się w zaszyfrowanym 512-bitowym algorytmem RSA bloku danych, który można odszyfrować za pomocą stałego klucza publicznego:

 e = 10001
pq = 807cef99098604b50cdf71e7e4ad7be882c04cec2511423a78da9abdea91
     cf861b0695955d980ba5cbf77759d1780eb0a8912bce30d69215234782d2
     5a8f8199

Pożądana wartość SHA-1 zawiera się w ostatnich 20 bajtach odszyfrowanego bloku. Jeśli obliczona wartość jest równa odszyfrowanej, plugin uważany jest za autoryzowany.

Po poprawnym stwierdzeniu autoryzacji pluginu, można “dobrać się” do właściwej sygnaturki zawierającej informacje m.in. o dodatkowych, niestandardowych właściwościach wtyczki.

Tuż nad danymi autoryzacyjnymi znajduje się 4-bajtowa liczba typu int (kolor niebieski) określająca offset do danych sygnaturki (znaczonych kolorem czerwonym) zapisanych bezpośrednio przed nią. Wartość tej liczby można również interpretować jako długość sygnaturki, w przypadku oficjalnych pluginów firmowanych przez o2.pl, rozmiar ten wynosi 0000004Ch (76 bajtów).

Sygnaturka zapisana jest w postaci poniższej struktury:

struct PluginSignatureStruct {
	int structSize;		// rozmiar struktury
	int Kind;			// rodzaj
	int Flags;			// flagi
	char Name[64];		// nazwa
};

Rodzaje pluginu:

// plugin wyprodukowany przez MediaOne (dawna nazwa o2.pl)
#define PLUGIN_SIGNATURE_KIND_MEDIAONE				32

Flagi, określające dodatkowe, niestandardowe, właściwości pluginu:

// ładowany automatycznie przy pierwszym "użyciu"
#define PLUGIN_SIGNATURE_FLAG_LOADWHENUSEDFIRSTTIME	0x00000001

// ładowany automatycznie bez możliwości wyłączenia
#define PLUGIN_SIGNATURE_FLAG_PERSISTENPLUGIN		0x00000002

Jak pisałem wyżej, sygnaturka jest pobierana tylko w przypadku poprawnej autoryzacji pluginu, a jej zawartość jest również częścią pliku, dla której obliczana jest funkcja skrótu SHA-1, przez co jakakolwiek modyfikacja sygnaturki bez utraty autoryzacji nie jest możliwa.

Podsumowując, aby komunikator Tlen.pl skorzystał z sygnaturki, musza zostać spełnione po sobie warunki:

  1. ostatnie 4 bajty muszą być równe liczbie typu `int` o wartości `1A2B3C4Dh`
  2. rozmiar pliku musi być większy od rozmiaru danych dołączonych do końca pliku
  3. wygenerowany hasz SHA-1 musi być równy odszyfrowanemu
  4. rozmiar offsetu do danych sygnaturki musi być równy rozmiarowi struktury sygnaturki

Mając listę wymaganych warunków możemy zaimplementować prostą funkcję CheckPluginSignature służącą do sprawdzania wtyczek. Jako argumenty podajemy adres do nazwy pliku wtyczki oraz wskaźnik na strukturę PluginSignatureStruct, gdzie zostaną zapisane dane sygnaturki.

bool CheckPluginSignature(char* fname, PluginSignatureStruct* dest) {
	bool ret = false;

	FILE* fp = fopen(fname, "rb");
	if (fp != NULL) {

		struct stat st;
		stat(fname, &st);	// rozmiar pliku znajduje sie w st.st_size

		unsigned char* buf = new unsigned char[st.st_size];
		fread(buf, 1, st.st_size, fp);

		fclose(fp);

		int y = *(int*)&buf[st.st_size - 8];		// tlenowe plugi = 1C0h = 448
		int z = *(int*)&buf[st.st_size - 4];		// ostatni int pliku = 1A2B3C4Dh

		if (z == 0x1A2B3C4D && st.st_size > y + 8) {

			z = st.st_size - y - 8;					// z okresla poczatek danych auth


			unsigned char sha1_tpl[20];
			unsigned char sha1_org[20];

			// generujemy hasz pluginu
			sha1_context ctx;
			sha1_starts(&ctx);
			sha1_update(&ctx, buf, z);
			sha1_finish(&ctx, sha1_tpl);
			memcpy(sha1_tpl, ctx.state, sizeof(sha1_tpl));


			CLINT cl1, cl2, clf, clm;

			char* k1 = "10001";
			char* k2 = "807cef99098604b50cdf71e7e4ad7be882c04cec2511423a78da9abdea91cf86"
					   "1b0695955d980ba5cbf77759d1780eb0a8912bce30d69215234782d25a8f8199";

			str2clint_l(cl1, k1, 16);
			str2clint_l(cl2, k2, 16);

			byte2clint_l(clf, buf + z, 64);
			mexp_l(clf, cl1, clm, cl2);

			int len = 64;
			unsigned char* out = clint2byte_l(clm, &len);
			memcpy(sha1_org, out + len - sizeof(sha1_org), sizeof(sha1_org));

			// porownujemy oba hasze, gdy sa takie same to plugin autoryzowany
			if (memcmp(sha1_org, sha1_tpl, sizeof(sha1_org)) == 0) {
				ret = true;

				// int (4 bajty) przed danymi autoryzacyjnymi okresla offset do struktury
				// znajdujacej sie 'wyzej' zawierajacej sygnaturke pluginu
				int offset = *(int*)&buf[z - 4];
				if (offset == sizeof(PluginSignatureStruct))
					memcpy(dest, buf + z - 4 - offset, sizeof(PluginSignatureStruct));

			}
		}

		delete buf;
	}

	return ret;
}

W zamieszczonej implementacji do wygenerowania funkcji skrótu SHA-1 została użyta implementacja Christophe Devine’a, a do obliczeń związanych z algorytmem RSA wykorzystano funkcje dostępne w bibliotece lint, która jest częścią książki “Cryptography in C and C++”. Bibliotekę ta również wykorzystuje oficjalny klient nie tylko w implementacjach RSA.

Tak jak poprzednio w sprawę został także wmieszany andk ;) Na swojej stronie udostępnił własną wersje opisu autoryzacji pluginów w komunikatorze tlen.pl.

[Za często użyto słowa plugin, który można zastąpić polskim słowem wtyczka.]

Komentarze (0)

Dodaj komentarz

/dozwolony markdown/

/nie zostanie opublikowany/