Ochrona sieci, bezpieczeństwo systemów komputerowych - securitymag.pl
February 1, 2012, 2:00 pm

Badanie kryptograficzne komunikatora GG 8/10

1 Star2 Stars3 Stars4 Stars5 Stars (1 głosów, średnia: 5,00 / 5)
Loading ... Loading ...

Artykuł ukazuje metody bezpieczeństwa używane w komunikatorze Gadu-Gadu w wersjach 8/10.

Źródło: hakin9.org
Autor:
Arkadiusz Kiciak

Komunikatory internetowe powinny skupiać w sobie cechy prostoty, wygody oraz bezpieczeństwa. Wygodę, jak i wygląd może oceniać każdy użytkownik. W tym artykule zajmiemy się analizą cech bezpieczeństwa, które trudno dostrzec gołym okiem zwykłego użytkownika.
Artykuł jest poświęcony analizie kryptograficznej popularnego polskiego komunikatora internetowego Gadu-Gadu w wersji 10-tej. Warto sprawdzić, czy wraz z rozwojem informatyki, metody użyte przez programistów komunikatora są nadal skuteczne i pewne.

Analiza hasła profilowego

Na pierwszy rzut oka narzuca się implementacja haseł, które można nałożyć na profil użytkownika.
Nałożone hasła profilowe, zanim zostaną zapisane do plików konfiguracyjnych profilu użytkownika, zostają poddane obróbce przez metodę kryptograficzną, tworzącą 128-bitowy skrót binarny.
Do tworzenia skrótów binarnych został użyty popularny algorytm MD5.
Dane binarne utworzonego skrótu 128-bitowego zostają następnie przekazane do zapisu alfanumerycznego algorytmu BASE64 i dopiero teraz zapisane w postaci ciągów znakowych do pliku konfiguracyjnego (profilebasic.xml, znaczniki <profilehashpassword>).
W dzisiejszych czasach użycie surowego algorytmu MD5 do zapewnienia bezpieczeństwa jest lekkim nadużyciem. Każdy użytkownik posiadający nowoczesną jednostkę obliczeniową jest w stanie wygenerować kolizję tego algorytmu. A kiedy brak mu czasu, może znaleźć swój skrót w publicznych sieciowych serwisach hostujących bazy danych ze złamanymi skrótami MD5.
Warto dodać, że skróty takie zapisuje się w postaci alfanumerycznych znaków heksadecymalnych.
Listing 1 języka C przedstawia sposób przetwarzania ciągów base64 na zapis heksadecymalny, reprezentujący popularny skrót md5.
Na końcu tego rozdziału warto wspomnieć, że komunikator korzysta z bardziej zaawansowanych i nowszych metod kryptograficznych tworzących skróty binarne (SHA-512), lecz programiści zastosowali je wyłącznie do komunikacji sieciowej. Programiści dysponowali modułem biblioteki kryptograficznej OPENSSL, ale najwidoczniej byli bardziej zajęci programowaniem cech wyglądu niż bezpieczeństwa.

Dekodowanie plików konfiguracyjnych hasłem profilowym

Hasło profilowe jest nie tylko kluczem do uruchomienia komunikatora, lecz również kluczem zaszyfrowanych prywatnych profilowych plików konfiguracyjnych, w skład których wchodzi lista zdefiniowanych kontaktów czy też główny plik konfiguracyjny, w którym może być zapisane zaszyfrowane hasło do danego numeru sieci GG.
Pliki konfiguracyjne są zakodowane starą już modyfikacją algorytmu BLOWFISH-CBC, którego definicje można znaleźć np. w projekcie OPENSSL.
Algorytm ten można atakować metodą brutaforce, lecz po co się wysilać skoro programiści komunikatora zostawili nam do dyspozycji skrót MD5 opisywany w poprzednim rozdziale.
Algorytm BLOWFISH operuje na blokach 256 bajtowych.
Niektóre pliki konfiguracyjne mogą być znacznie większe niż blok algorytmu, więc mogą wymagać kliku powtórzeń procesu dekodowania.
Kluczem dekodowania jest ciąg reprezentujący hasło profilowe, zaś ciąg IVS został z góry zdefiniowany przez programistów (0123456).
Listing 2 ukazuje kod programu języka C, który dekoduje wskazany plik konfiguracyjny algorytmem BLOWFISH-CBC.
Zaimplementowanie procesu szyfrowania prywatnych plików profilowych na pewno jest plusem, lecz użyta metoda zostawia nam dużo do życzenia.
BLOWFISH jest już starym algorytmem, zaś nowszym jego odpowiednikiem jest algorytm AES256, który z pewnością dopracowałby proces szyfrowania.

Dekodowanie archiwum na podstawie hasła profilowego

Doszliśmy do momentu kulminacyjnego analizy komunikatora, mianowicie doszliśmy do cechy bezpieczeństwa, o której programiści się chwalili publicznie. Mowa o szyfrowaniu archiwum rozmów komunikatora w wersjach 8 (nowe GG) i 10.
Owszem jest to chyba najbardziej dopracowana metoda użyta przez programistów w dodatku w całości zakupiona za sporą gotówkę (bagatela 2000 $).
Programiści zakupili bibliotekę kodu operującego na kodowanych plikach bazodanowych SQLITE, która nie tylko zapewniła bezpieczeństwo, ale również ułatwiła pracę programistom przy budowaniu mechanizmu archiwizacji przeprowadzonych rozmów.
Z racji, że biblioteka jest komercyjna, aby zanalizować proces jej pracy należy wykonać jej disasemblację, po czym po kolei analizować jej kod.
Do kodowania plików bazodanowych archiwum jest wykorzystywana niepubliczna modyfikacja algorytmu AES256. Algorytm ten operuje na blokach danych o wielkości 128 bitów (16 bajtów).
Generator kluczy dekodowania spodziewa się bloku hasła dekodowania o długości 16 bajtów.
W tym bloku danych znajduje się hasło profilowe. Jeśli hasło profilowe jest krótsze niż 16 znaków, zostaje powielone, aby wypełnić cały 16-bajtowy blok. Na podstawie tego bloku, generator kluczy generuje binarny blok klucza o długości 176 bajtów, który jest później wykorzystywany podczas procesu dekodowania.
Co prawda na podstawie pierwotnego bloku klucza 16-bajtowego algorytm ten powinien nosić nazwę AES128, przyjmijmy jednak, że nazywa się AES256CUSTOM nie poniżając przy tym programistów samej biblioteki.
Wspomniana biblioteka operuje na 64 blokach algorytmu AES256CUSTOM, czyli dokładnie na 1024 bajtach danych – krótko mówiąc na bloku jednokilobajtowym. Każdy wyżej wspomniany blok jest jednostkową częścią pliku bazodanowego SQLITE przetwarzanego przez bibliotekę. Każdy blok dostaje swój unikalny numer, zaczynając od początku pliku bazodanowego, czyli pierwszy blok dostaje numer 1, zaś kolejne o numer wyższe.

Na podstawie numeru przetwarzanego bloku zostaje wygenerowany odzwierciedlany blok, wygenerowany przez algorytm AES256CUSTOM z założeniem, że:

  • numer bloku zostaje rozszerzony do 16 bajtów, które stanowią pierwszy blok do przetworzenia przez algorytm AES256CUSTOM (16 bajtów źródłowych dla algorytmu),
  • każdy kolejny blok 16 bajtów zostaje wygenerowany na podstawie poprzedniego wygenerowanego bloku aż do ostatniego 64 bloku, który stanowi zakończenie bloku głównego, klucz dekodowania jest stały i ma długość 176 bajtów.

Po przetworzeniu całego bloku jednostkowego otrzymujemy 1024 bajtów, które należy następnie wymieszać metodą XOR-owania z zawartością zaszyfrowanego bloku danych z pliku bazodanowego archiwum (archives.db). Proces XOR-owania następuje oczywiście bajt po bajcie aż do zakończenia bloku 1024 bajtów. Listing 3 przedstawia proces dekodowania archiwum za pomocą języka C.
Pewnym niedopatrzeniem programistów biblioteki jest to, że nagłówek pliku bazodanowego SQLITE umożliwia wykonanie ataku na hasło szyfrowania całego pliku.
Wartość pierwszych 16 bajtów nagłówka pliku SQLITE po zdekodowaniu jest zawsze STAŁA. W dodatku 16 bajtów to dokładnie pierwszy przebieg algorytmu, co pozwala na podstawianie dowolnych haseł i sprawdzanie czy wygenerowane dane pokrywają się z wartościami bajtów stałych. Zainteresowanych odsyłam do dokumentacji plików SQLITE.
Jest to zawsze metoda alternatywna na łamanie użytego hasła lecz zawsze szybciej jest zanalizować skrót MD5 opisywany w pierwszym rozdziale.
W podsumowaniu warto powtórzyć, że jest to najlepsza metoda kryptograficzna użyta w komunikatorze od dłuższego czasu, co z pewnością jest rewolucyjnym rozwiązaniem.

Dekodowanie zapamiętanego hasła numeru GG

Komunikator umożliwia zapisanie i zapamiętanie głównego hasła do używanego numeru GG.
Metoda archiwizacji tak ważnych danych dla bezpieczeństwa użytkownika jest archaiczna, przez co dane te można szybko i łatwo odczytać z plików konfiguracyjnych profilu komunikatora.
Jeśli hasło zostało zapamiętane przez komunikator jego zaszyfrowana postać pozostaje zapisana w pliku profile.xml.

Kolejną nowością wykorzystana przez programistów komunikatora jest zaszyfrowanie hasła poprzez unikalny klucz szyfrowania, w skład którego wchodzą takie dane jak:

  • unikalny numer konta GG,
  • unikalny serial partycji systemowej (partycji zamontowanej w systemie jako dysk c:) zapisany w postaci heksadecymalnej.

Taka metoda użycia klucza szyfrowania uniemożliwia jednoznaczną kradzież haseł. Skradzione dane wymagałyby poświęcenia dodatkowego czasu na złamanie unikatowych części składowych użytego klucza szyfrowania.
Użytym algorytmem kryptograficznym jest BLOWFISH-CBC, generujący blok danych (256 bajtów), który pozostaje zapisany poprzez algorytm BASE64 w pliku konfiguracyjnym profile.xml
Warto zaznaczyć, że jeśli zostało nałożone na profil hasło profilowe, plik profile.xml należy najpierw rozszyfrować metoda opisywaną w rozdziale: Dekodowanie archiwum na podstawie hasła profilowego.
Listing 4 przedstawia przykładowy kod języka C, który preparuje klucz (z informacji systemowych) oraz wykonuje rozszyfrowanie hasła (Listing 4 został zamieszczony na płycie CD).
Z pewnością metoda unikalnych kluczy szyfrowania haseł jest odpowiedzią programistów na programy kradnące hasła z poprzednich wersji komunikatora,możliwe że metoda ta pozwoli zwiększyć bezpieczeństwo tak zapisanych i zachowanych haseł niż jak to było w poprzednich wersjach komunikatora.

Podsumowanie

Zmiany bezpieczeństwa dokonane przez programistów są subtelne, a metody wdrożone do komunikatora nadal wyglądają na niedopracowane.
Ogólne podsumowanie metod kryptograficznych:

  • ++ użycie algorytmu
  • AES256CUSTOM,
  • ++ użycie unikalnego serialu partycji,
  • + szyfrowanie archiwum umieszczone
  • w bibliotece statycznej (.lib),
  • + możliwość nałożenia hasła
  • profilowego,
  • + szyfrowanie plików konfiguracyjnych,
  • - użycie algorytmu skrótu 128 bitowego MD5,
  • - użycie algorytmu BLOWFISH,
  • - bezpośrednie odwołania kodu do biblioteki (.dll) openssl.


Listing 1. Sposób przetwarzania ciągów base64 na zapis heksadecymalny,
reprezentujący popularny skrót md5.
//program konwertuje ciag znakow
//base64 do zapisu alfanumerycznego
//hex binarnego
#include
#include
#include "base64.c"
#defi ne STOP(x){\
puts("\n\npress enter key to exit ...\n");\
getch();\
return x;}
int main(int argc, char **argv){
if(argc<2) {
puts("\nbase64 decoder to hex binary string.");
puts("\nusage: dekoder ");
STOP(1);
}
unsigned char *out;
unsigned long out_len;
//dekodujemy ciag base64
out = base64_decoder(argv[1],strlen(argv[1]),&out_len);
out+=out_len;
printf("\n%ibits (%i bytes) converted data:\n\n",
out_len*8,out_len);
//wyswietlamy ciag 16 bajtow hex
while(out_len>0){
printf("%.2x",*(out-out_len));
out_len--;
}
STOP(0);
}


Listing 2. Kod programu języka C, który dekoduje wskazany plik konfiguracyjny algorytmem BLOWFISH-CBC.
#include
#include
#include
#include
#include "buffer.c"
#include "fi le.c"
//includowanie blowfi sh
#include "blowfi sh/bf_skey.c"
#include "blowfi sh/bf_enc.c"
//makro zatrzymuajace program
#defi ne STOP(x,y){ if(y) puts(y);\
puts("\nWcisnij enter ...\n");\
getchar();return(x);}
#defi ne BANNER puts(\
"\n==================================\n"\
" Dekoder blowfi sh cbc dla plikow \n"\
" xml komunikatora gg8/10 \n"\
"----------------------------------\n"\
"cyber aka pl, unknownm3n@gmail.com\n"\
"----------------------------------\n"\
" 2 stycznia 2010 R \n"\
"==================================\n");
//16 bajtow ivs zdefi nowany przez
//develoeprow komunikatora
unsigned char ivss[16]={
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
0x00,0xF0,0xAD,0xBA,0x0D,0xF0,0xAD,0xBA};
int main(int argc, char **argv){
//sprawdzanie poprawnosci argumentow
if(argc<3)
STOP(1,"\ndekoder.exe \n")
else if(!plik_istnieje(argv[1]))
STOP(1,"\nNie mozna otworzyc danego pliku!\n")
else if(!strstr(argv[1],".xml"))
STOP(1,"\nZle roszerzenie pliku!\n")
else if(strlen(argv[2])<2||strlen(argv[2])>16)
STOP(1,"\nZla dlugosc hasla (2-16)\n");
BANNER;
//alokowanie stert pamieci
BUFFER *fi le = alloc_buffer(wielkosc_pliku(argv[1]));
BUFFER *fname= alloc_buffer(1024);
BUFFER *out = alloc_buffer(fi le->size);
BF_KEY bf_key;
strncpy(fname->buffer,argv[1],fname->size);
printf(
"\nDekodowanie pliku: %s"
"\nUzywamy klucza: %s"
"\nDlugosc klucza: 0x%02X"
"\nWielkosc pliku: 0x%04X\n",
argv[1],argv[2],strlen(argv[2]),fi le->size);
//pobieramy binarna zawartosc pliku
odczytaj_plik(fname->buffer,fi le->buffer,fi le->size);
//inicjacja klucza dekodowania
BF_set_key(&bf_key,strlen(argv[2]),argv[2]);
//dekodowanie zawartosci calego pliku
BF_cbc_encrypt(fi le->buffer,out->buffer,
(long)fi le->size,&bf_key,ivss,BF_DECRYPT);
strncat(fname->buffer,".decode.xml",
fname->size-strlen(fname->buffer));
//zapisanie danych do pliku wynikowego
zapisz_plik(fname->buffer,out->buffer,out->size);
printf("\nZdekodowany plik zostal zapisany w: \n\n%s\n",
fname->buffer);
//zwalnianie uzytej pamieci
free_buffer(fname);
free_buffer(fi le);
free_buffer(out);
STOP(0,0);
}


Listing 3a. Proces dekodowania archiwum za pomocą języka C.
#include
#include
#include
#include
#include "buffer.c"
#include "fi le.c"
#include "hexdump.c"
#include "aes256custom.c"
#include "aes256key.c"
#defi ne STOP(x){\
puts("\n\nEnter aby wyjsc ...");\
getch(); return x;}
#defi ne BREAK(x){ puts(x); return 1; }
void banner(char arg){
puts("\n==================================================
==");
if(arg)
puts("> Dekoder.exe
");
puts("> Dekoder archiwum (archive.db) gg 8 / 10
");
puts("----------------------------------------------------
");
puts("> cyber aka pl http://lowbyte.da.ru ");
puts("----------------------------------------------------
");
puts("> 27 grudnia 2009
");
puts("====================================================
");
}
int main(int argc, char **argv){
//defi nicje wskaznikow
BUFFER *fi le;
BUFFER *fi lename;
BUFFER *hexd;
BUFFER *aesblock;
AESKEY *key;
BYTE *aesdata;
//zmienne operuajce w petlach
LNG seek = 0;
LNG loop = 1;
//jesli zle wywolanie
if(argc<3){
banner(1);
STOP(1);
}
//wyswietlamy banner programu
banner(0);
//sprawdzania paramterow wywolania
if(!plik_istnieje(argv[1]))
BREAK("Wskazany plik nie istnieje!")
else if(strlen(argv[2])<2)
BREAK("Haslo za krotkie!")
else if(strlen(argv[2])>31)
BREAK("Haslo zadlugie, maxymalnie 31 znakow!");
fi lename = alloc_buffer(BLOCK);
strncpy(fi lename->buffer,argv[1],BLOCK);
//przygotowujemy miejsce i odczytujemy
//zawartosc pliku
fi le = alloc_buffer(wielkosc_pliku(fi lename->buffer));
odczytaj_plik(fi lename->buffer,fi le->buffer,fi le->size);
//bufor na hexdump klucza
hexd = alloc_buffer(10*KEYLEN);
clean_buffer(hexd);
//tworzomy 176 bajtowy klucz dekdowania
key = aeskey_alloc(makekey(argv[2]));
aesblock = alloc_buffer(BLOCK);
//tworzymy hexdump klucza dekodowania
hexdump(key,KEYLEN,hexd->buffer,1);
printf("\nUzywamy hasla: %s\n",argv[2]);
printf("\nHexdump klucza dekodowania:\n%s\n",
hexd->buffer);
fi le->seek = (LNG)fi le->buffer;
//w petli ignorujemy niepelne bloki
while(loop*BLOCK < fi le->size){
//tworzenie klucza do generowania aes
//przykladowy klucz dla pierwszego bloku:
//0100000000000000000000000000000000h
//przykladowy klucz dla drugiego bloku:
//0200000000000000000000000000000000h
memset(aesblock->buffer,0x00,AESBLOCK);
*(LNG *)(aesblock->buffer) = loop;
//tworzymy pierwszy blok danych
//od niego zacznie sie dalsza petla
//dekodowania
aesdata = (BYTE *)aes256custom(
aesblock->buffer,key);
memcpy(aesblock->buffer,aesdata,AESBLOCK);
free(aesdata);
seek = 0;
//petla tworzaca bloku aes 128 bitowe
//lacznie tworzy 1024 bajty bloku danych
while(seek<(BLOCK-AESBLOCK)) {
aesdata = (BYTE *)aes256custom(
aesblock->buffer+seek,key);
memcpy(aesblock->buffer+AESBLOCK+
seek,aesdata,AESBLOCK);
free(aesdata);
seek += AESBLOCK;
}
seek = 0;
//petla xorowania zaszyfrowanych danych
//z tymi wygenerowanymi
while(seek //zorowanie wartosci
*(fi le->buffer+seek) =
*(aesblock->buffer+seek)^
*(fi le->buffer+seek);
//odnawianie blokow naglowka
//pliku sqlite - tylko raz
if(loop==1&&seek>0x0F&&seek<0x18)
*(fi le->buffer+seek) =
*(aesblock->buffer+seek)^
*(fi le->buffer+seek);
seek++;
}
fi le->buffer += BLOCK;
loop++;
}
strncat(fi lename->buffer,".decode.db",
BLOCK-strlen(fi lename->buffer));
//zapisujemy zdekodwane dane archiwum
//do nowego pliku
zapisz_plik(fi lename->buffer,(BYTE *)
fi le->seek,fi le->size);
printf("Zdekodowano %lu bajtow archowum! "
"Utworzono plik w:\n%s",loop*BLOCK,
fi lename->buffer);
//zwalniamy uzyte bufor pamieci
free_buffer(aesblock);
free_buffer(hexd);
free_buffer(fi lename);
free_buffer(fi le);
STOP(0);
}

Komentarze zablokowane.



Software Press Sp. z o.o. Sp. Komandytowa 02-682 Warszawa, ul. Bokserska 1, NIP 9512279582, REGON 141804060, KRS: 0000327578