Rok akademicki 200 /200 - Repozytorium PW
Transkrypt
Rok akademicki 200 /200 - Repozytorium PW
Rok akademicki 2013/2014 Politechnika Warszawska Wydział Elektroniki i Technik Informacyjnych Instytut Informatyki PRACA DYPLOMOWA INŻYNIERSKA Barbara Rychalska Memory Inspector – bezpieczna dealokacja pamięci w systemie Android Opiekun pracy dr inż. Piotr Gawkowski Ocena: ..................................................... Podpis Przewodniczącego Komisji Egzaminu Dyplomowego Kierunek: Informatyka Specjalność: Inżynieria Systemów Informatycznych Data urodzenia: 1989.05.12 Data rozpoczęcia studiów: 2011.02.21 Życiorys Urodziłam się 12 maja 1989 roku w Warszawie. Po ukończeniu szkoły gimnazjalnej podjęłam naukę w XIV Liceum Ogólnokształcącym im. Stanisława Staszica w Warszawie, które ukończyłam ze świadectwem dojrzałości w 2008 r. Tego samego roku podjęłam studia dzienne licencjackie na Wydziale Lingwistyki Stosowanej Uniwersytetu Warszawskiego na kierunku Lingwistyka Stosowana (języki angielski i niemiecki). W 2011 r. rozpoczęłam także studia na Wydziale Elektroniki i Technik Informacyjnych na kierunku Informatyka. Do moich technicznych zainteresowań należą między innymi zagadnienia bezpieczeństwa systemów komputerowych oraz uczenie maszynowe. ....................................................... Podpis studenta EGZAMIN DYPLOMOWY Złożył egzamin dyplomowy w dniu ..................................................................................20__ r z wynikiem ................................................................................................................................... Ogólny wynik studiów: ................................................................................................................ Dodatkowe wnioski i uwagi Komisji: .......................................................................................... ....................................................................................................................................................... ....................................................................................................................................................... STRESZCZENIE Praca porusza zagadnienia związane z bezpieczeństwem wrażliwych danych w pamięci operacyjnej systemu Android. Przedstawiono w niej ogólny opis zagadnień związanych z zarządzaniem pamięcią w systemie Android oraz wyniki eksperymentów demonstrujących problemy przechowywania wrażliwych danych w pamięci. Zaprezentowano propozycję wprowadzenia bezpiecznej dealokacji, czyli zbioru rozwiązań pozwalających na możliwie szybkie usuwanie wrażliwych danych z pamięci. Przeprowadzono badania poprawienia bezpieczeństwa wrażliwych danych w systemie Android 4.3 JellyBean. Zaprezentowano implementację zbioru narzędzi realizujących bezpieczną dealokację. Przedstawiono i omówiono przykładowe wyniki badań dokumentujące poprawę bezpieczeństwa danych przy użyciu opracowanego systemu. Słowa kluczowe: bezpieczeństwo danych, bezpieczna dealokacja, jądro Linux, Android, maszyna wirtualna Dalvik MEMORY INSPECTOR – SECURE MEMORY DEALLOCATION IN ANDROID This thesis describes some security issues related to the storage of sensitive data in randomaccess memory in the Android operating system. A description of the general ideas and problems related to memory management in Android has been presented, along with the results of research demonstrating the issues of storing sensitive data in random-access memory. The introduction of the secure deallocation mechanism was proposed, which is a set of solutions for removing sensitive data from memory as soon as possible after deallocation. Research has been conducted in order to identify possible ways of enhancing the security of sensitive data in Android 4.3 JellyBean operating system. An implementation of a set of tools performing secure deallocation has been introduced. Exemplary results of experiments conducted using the developed system were presented and discussed in respect to the enhancement of sensitive data security. Keywords: data security, secure deallocation, Linux kernel, Android, Dalvik virtual machine SPIS TREŚCI 1. Wstęp......................................................................................................................................5 2. Analiza problemu....................................................................................................................7 2.1 Przegląd literatury............................................................................................................7 2.2 Eksperymenty własne.......................................................................................................9 2.3 Zagrożenia......................................................................................................................14 2.4 Podsumowanie................................................................................................................15 3. System Android.....................................................................................................................16 3.1 Architektura systemu......................................................................................................16 3.2 Maszyna wirtualna Dalvik..............................................................................................18 3.2.1 Alokacja pamięci....................................................................................................18 3.2.2 Dealokacja pamięci.................................................................................................19 3.3 Dystrybucja aplikacji......................................................................................................20 4. Problemy realizacji bezpiecznej dealokacji..........................................................................22 5. System Memory Inspector....................................................................................................26 5.2 Szczegóły implementacji systemu..................................................................................30 5.2.1 Moduł MI Core.......................................................................................................30 5.2.2 Moduł MI Dalvik Patch..........................................................................................41 6 Testy i obserwacje..................................................................................................................46 6.1 Aplikacja testowa...........................................................................................................46 6.2 Przeglądarka internetowa (Android Browser)................................................................47 6.3 Klient poczty elektronicznej...........................................................................................50 6.4 Wnioski..........................................................................................................................51 7. Podsumowanie......................................................................................................................53 Bibliografia...............................................................................................................................54 Dodatki......................................................................................................................................56 Dodatek A - Kompilacja systemu........................................................................................56 Dodatek B – Narzędzia do analizy pamięci w systemie Android........................................58 Volatility i moduł LiME..................................................................................................58 DDMS – Dalvik Debug Monitor Server..........................................................................59 Dodatek C – Zawartość załączonej płyty CD......................................................................59 1. WSTĘP Android to system operacyjny przeznaczony dla urządzeń mobilnych rozwijany przez firmę Google. Został zaprojektowany przede wszystkim dla urządzeń posiadających ekrany dotykowe, takich jak smartfony, tablety czy telewizory wyposażone w specjalne interfejsy. Jest to obecnie najpopularniejszy system mobilny. W 2013 roku urządzeń z tym systemem zostało sprzedanych więcej niż urządzeń wyposażonych w Windows Phone, iOS i MacOS razem wziętych, a przewidywane wyniki na kolejne lata zapowiadają coraz większą przewagę systemu firmy Google [6]. W czerwcu 2014 roku liczba użytkowników Androida sięgnęła miliarda osób [7]. Ogromnej popularności systemu dowodzą ankiety przeprowadzane wśród programistów aplikacji mobilnych. W pierwszej połowie 2014 roku aż 71% z nich deklarowało, że tworzy swoje programy właśnie na platformę Android [8]. W związku z popularnością systemu powstają ogromne ilości dedykowanego dla niego oprogramowania. Aplikacje z różnych dziedzin, takie jak gry, programy do dokonywania płatności, przeglądarki internetowe czy systemy nawigacji wykorzystujące coraz większe możliwości sprzętowe współczesnych urządzeń mobilnych towarzyszą użytkownikom na co dzień. Niestety, wraz z rosnącą popularnością wiąże się także wzrost zagrożeń związanych z użytkowaniem systemu. Zgodnie z doniesieniami firm zajmujących się zagadnieniami bezpieczeństwa, nawet 72% wszystkich aplikacji dostępnych w sklepie Google Play żąda zbyt szerokich uprawnień, co może prowadzić do naruszeń bezpieczeństwa [9]. Zagrożenie stanowią nie tylko złośliwe aplikacje, ale także programy zaprojektowane w niedbały sposób, nie zapewniające bezpieczeństwa wprowadzanych do nich danych. W ramach niniejszej pracy przeanalizowano dostępne na platformie Android 4.3 JellyBean aplikacje przetwarzające najbardziej poufne dane użytkowników, takie jak loginy i hasła do serwisów. Jednym z kluczowych zadań takich aplikacji powinno być zapewnienie bezpieczeństwa przechowywanych zasobów. Niestety, uzyskane wyniki badań pokazują, że aplikacje te przechowują wrażliwe dane w pamięci w postaci niezaszyfrowanej. Co gorsza, takie dane mogą być odczytane nawet po zakończeniu działania aplikacji. System Android nie oferuje bowiem wbudowanych mechanizmów czyszczenia pamięci bezpośrednio po zwolnieniu danych lub po zamknięciu aplikacji. W rezultacie niemożliwe jest dokładne określenie czasu życia danych w pamięci. Sytuacja taka ma miejsce również w starszych wersjach systemu (patrz Rozdział 2.1) i prawdopodobnie będzie miała miejsce w wersjach kolejnych, chyba że mechanizmy zarządzana pamięcią w Androidzie zostaną zasadniczo zmienione. 6 Stwierdzono ponadto, że standardowe funkcje zarządzania pamięcią z bibliotek popularnych języków programowania również nie czyszczą zwolnionej pamięci. Zwolnienie obszaru nie oznacza wyczyszczenia danych obecnych w tym obszarze, a jedynie udostępnienie tego obszaru dla przyszłych alokacji. By przyspieszyć czyszczenie niepotrzebnej już zawartości pamięci można zastosować szereg rozwiązań bazujących na heurystykach pozwalających przewidzieć, kiedy dane nie będą już używane, a więc kiedy można je bezpiecznie usunąć z pamięci. Mechanizmy te można określić zbiorczą nazwą bezpieczna dealokacja. Ich zastosowanie zapewnia możliwie wczesne usunięcie wrażliwych danych z pamięci, co potencjalnie poprawia bezpieczeństwo całego systemu. Sposób wyznaczania momentu, w którym dane przestają być używane przez aplikację, a więc są gotowe do usunięcia, jest zależny od języka programowania i środowiska uruchomienia aplikacji. Niniejsza praca przedstawia prototyp realizacji bezpiecznej dealokacji w systemie Android, która może być kontrolowana przez użytkownika. W Rozdziale 2 omówiono przebieg i wyniki badań pamięci aplikacji Androida (zarówno własnych, jak i zaczerpniętych z literatury) oraz wymieniono przykłady zagrożeń związanych z niedbałym przechowywaniem wrażliwych danych w pamięci. Rozdział 3 zawiera opis architektury systemu Android z naciskiem na zagadnienia związane z zarządzaniem pamięcią, oraz zagadnienia dotyczące jakości i sposobu dystrybucji aplikacji. W Rozdziale 4 przedstawiono możliwości i ograniczenia realizacji bezpiecznej dealokacji w systemie Android. Architekturę stworzonego przez autorkę systemu do analizy pamięci i bezpiecznej dealokacji oraz sposób jego wykorzystania opisano w Rozdziale 5. Istotnym elementem pracy były badania efektywności stworzonego systemu. Metodologię oraz rezultaty testowania przedstawiono w Rozdziale 6. W Dodatku A opisano procedurę kompilacji i instalacji systemu. Dodatek B zawiera informacje na temat istniejących narzędzi służących do analizy pamięci w systemie Android. 7 2. ANALIZA PROBLEMU Zagadnienie bezpieczeństwa w systemach Android jest przedmiotem wielu publikacji zarówno naukowych jak i popularnonaukowych w rozmaitych portalach internetowych i czasopismach. W rozdziale tym przedstawiono syntetyczny opis wyników prezentowanych w publikacjach, których zakres i/lub metodologia wiążą się bezpośrednio z tematyką pracy. Ponadto przedstawiono opis badań własnych potwierdzających wyniki zamieszczone w publikacjach oraz zagrożenia, na jakie narażone są wrażliwe dane przechowywane w pamięci. 2.1 Przegląd literatury Praca [1] dokumentuje wyniki badań pamięci aplikacji przetwarzających wrażliwe dane w systemie Android. Eksperymenty prowadzono na urządzeniu mobilnym (Samsung Galaxy S Plus), za pomocą narzędzia LiME (opis narzędzia przedstawiono w Dodatku B). W pamięci różnych aplikacji poszukiwano danych logowania na konta. Przetestowano 13 aplikacji z 4 kategorii: aplikacje bankowe, finansowe/do robienia zakupów, menedżery haseł oraz aplikacje do szyfrowania. Autorzy poszukiwali w pamięci urządzenia znanych wzorców (takich jak charakterystyczne nazwy kontrolek i zmiennych, np. password). Przygotowanych zostało 30 scenariuszy testowych, z których na każdy przypadło 13 przypadków testowych, różniących się odstępem czasowym od wpisania hasła w kontrolkę do przeprowadzenia badania (0 – 60 min) oraz stopniem obciążenia urządzenia (bez użytkowania, użytkowanie jako telefon – mniejsze obciążenie, użytkowanie jako smartfon – większe obciążenie). Uzyskano następujące obserwacje: • Brak użytkowania telefonu zwiększa prawdopodobieństwo pozostania w pamięci wrażliwych danych związanych z procesem logowania. • Duże wykorzystanie pamięci (np. uruchamianie gier) pomaga szybciej usunąć wrażliwe dane. • Zamykanie aplikacji za pomocą menedżera aplikacji mogącego zakończyć proces (task killer), przenoszenie aplikacji w tło ani włączenie trybu samolotowego nie powoduje automatycznego usunięcia wrażliwych danych z pamięci (a przenoszenie aplikacji w tło bez wylogowania się jest bardzo częstą praktyką u użytkowników). • W nazwach zmiennych powszechnie używane są wyrażenia ułatwiające wyszukiwanie haseł w pamięci, takie jak password lub username. 8 • Gwarancję usunięcia całości danych daje dopiero wyłączenie i włączenie urządzenia lub wyjęcie baterii z urządzenia. W większości analizowanych aplikacji stwierdzono podatność na odzyskanie poufnych danych z pamięci, jednak wykazano też, że część aplikacji przechowuje wrażliwe dane w bezpieczny sposób. Obserwacja pamięci urządzenia mobilnego pod kątem wykrywania czasów przechowywania w niej informacji wprowadzanej przez użytkownika była przedmiotem pracy [2]. Zaprezentowany w pracy system symulował rozmowę przez komunikator, co jakiś czas dokonując zrzutów pamięć i przetwarzając je. Celem pracy była obserwacja pamięci urządzenia mobilnego w czasie wymiany wiadomości pomiędzy użytkownikiem telefonu a partnerem korzystającym z komputera. W pamięci telefonu poszukiwano pozostałości prowadzonej konwersacji. W testach symulowano różne zachowania użytkownika prowadzącego rozmowę: 1. Symulowany użytkownik wysyłał wiadomość i nie czekając na odpowiedź pisał kolejne wiadomości. 2. Symulowany użytkownik wysyłał wiadomość, czekał na odpowiedź partnera i po jej otrzymaniu zaczynał pisać kolejną wiadomość. W przypadku 1. wszystkie wysyłane wiadomości było dostępne w pamięci przez co najmniej 37 sekund po wysłaniu. Najdłuższy zanotowany czas życia wysłanej wiadomości w pamięci to 4,9 min. Natomiast wśród wiadomości odbieranych najdłuższy czas życia określono na 3,7 min. Udało się odnaleźć odpowiednio 100%, 86,7%, 75,6% i 84,4% wiadomości wychodzących w badaniach prowadzonych w odstępie 5, 10, 20 i 30 sekund od wysłania. W przypadku 2. po 60 sekundach od wysłania w pamięci można było odnaleźć 100% wiadomości wychodzących i 95,6% wiadomości przychodzących. W zrzutach odnaleziono wiele kopii każdej wysyłanej wiadomości. Kolejne prace pokazują, że problem zarządzania wrażliwymi (i nie tylko) danymi w pamięci aplikacji i urządzenia nie ogranicza się tylko do systemu Android ale dotyczy również systemów klasy PC. Podobne zagadnienie do już opisanych lecz dotyczące systemów Linux Server, Linux Desktop oraz Windows prezentuje [3]. Problem ilustrują wyniki dwóch badań pamięci. W pierwszym z eksperymentów do pamięci komputerów wstrzyknięto 64 MB danych testowych, po czym monitorowano czas ich życia podczas normalnego użytkowania komputera. Pozostałości wstrzykniętych danych można było odnaleźć w pamięci po 2 tygodniach od rozpoczęcia eksperymentu. Za względnie najbezpieczniejszy został uznany system Linux Server 9 – po 14 dniach ilość danych w pamięci spadła do 23 KB. W systemie Linux Desktop odnaleźć można było mniej niż 1 MB danych, natomiast najgorzej sprawdził się system Windows, w którym po 14 dniach wciąż dostępne było ok. 3 MB danych testowych. Podejrzewano, że te pozostałości szybko znikną z obserwowanych systemów, jednak tak się nie stało – po 28 dniach od rozpoczęcia eksperymentu wciąż można było odnaleźć 7 KB danych w systemie Linux Server. Na podstawie tych wyników stwierdzono, że pewne określone miejsca w systemie są niezwykle rzadko nadpisywane. Przypadkowe umieszczenie danych w takich miejscach gwarantuje im niezwykle długie życie. Kolejne badanie sprawdzało czas oczyszczania pamięci po odłączeniu zasilania, również z wykorzystaniem wstrzykniętych do pamięci danych testowych [3]. Wykryto, że po tzw. miękkim restarcie komputera (czyli ponownym uruchomieniu systemu bez odłączania zasilania) część danych testowych wciąż pozostała w pamięci operacyjnej. W przypadku całkowitego odłączenia zasilania, na niektórych urządzeniach trzeba było poczekać nawet do 30 sekund by pamięć operacyjna została oczyszczona. Wyniki testów badających możliwości odzyskania wrażliwych danych po logowaniu w serwisach Facebook, Gmail i MSN w przeglądarce Firefox i w aplikacji Skype na komputerze stacjonarnym przedstawiono w [4]. Wykonano zrzuty pamięci w odstępach 5, 15 i 60 minut od zamknięcia aplikacji. Zaobserwowano pozostające w pamięci dane aplikacji: w przypadku logowania do Facebooka po 5 minutach zaobserwowano ponad 6000 śladów danych, z czego po 60 minutach wciąż można było odnaleźć ok. 3500 z nich. Najlepiej w teście wypadł Gmail – po 60 minutach pozostało 314 z początkowych 1692 śladów danych. Stwierdzono, że przeglądarka Firefox nie szyfruje danych logowania ani haseł. Zaobserwowano przypadki długotrwałego utrzymywania się w pamięci pakietów danych o stałych rozmiarach. W przypadku logowania do Facebooka po 60 minutach od wylogowania i zamknięcia przeglądarki liczba haseł w pamięci spadła, jednak po 15 minutach od wylogowania obserwowano zagadkowy wzrost liczby kopii haseł. Na tej podstawie stwierdzono, że stan pamięci aplikacji pod kątem obecności haseł jest nieprzewidywalny. 2.2 Eksperymenty własne Wyniki testów własnych przeprowadzonych na potrzeby niniejszej pracy w systemie Android 4.3 JellyBean są zgodne z przytaczanymi wynikami z publikacji. Poniżej zaprezentowano wyniki badania sprawdzającego przechowywanie danych w kontrolkach 10 aplikacji Android Browser. Wyniki badania dowodzą, że wrażliwe dane, takie jak login i hasło użytkownika, nie są szyfrowane i po wpisaniu w kontrolkę są dostępne w pamięci przez zbyt długi czas (dłużej niż mogą być faktycznie potrzebne). Środowisko testowe system operacyjny: Linux Ubuntu 12.04 narzędzie do obsługi maszyn wirtualnych: VirtualBox 4.1.12 testowany obraz systemu: Android x86 4.3 JellyBean testowana aplikacja: Android Browser (domyślna przeglądarka internetowa Androida) Przebieg eksperymentu Celem badania było sprawdzenie możliwości odzyskania wrażliwych danych z pamięci procesu. Sprawdzono zawartość pamięci aplikacji podczas logowania na konto Google. Dane (login i hasło) zostały wpisane do formularza, następnie przeprowadzono logowanie bez zapamiętywania hasła. System poddawany badaniu został uruchomiony w maszynie wirtualnej, a analizowane były zrzuty pamięci z poszczególnych zapamiętanych stanów maszyny (tzw. kopie migawkowe – ang. snapshots). Do badania użyto obrazu systemu Android w wersji 4.3, który został pobrany z oficjalnej strony firmy Google [16]. Zastosowane dane logowania to: login: TestoweKonto1 hasło: TajneHaslo1 Rezultaty Po ukończonym logowaniu na konto Google wpisane dane nie były już potrzebne i powinny były zostać usunięte, jednak odnalezienie ich w postaci tekstu jawnego w zrzucie pamięci procesu nie przedstawiało trudności. Eksperymentalne dane można było łatwo odnaleźć w pamięci podczas działania aplikacji (tuż po zalogowaniu), po kolejnych logowaniach z użyciem innych danych oraz po zakończeniu działania aplikacji. Wyniki były podobne po pozostawieniu systemu w bezczynności przez 10 do 30 minut po wpisaniu danych. Przedstawione przykłady pokazują wybrane fragmenty pamięci w różnych stanach procesu: 1. Po zalogowaniu na konto Google – Wydruk 2.1 2. Po ponownym zalogowaniu na konto Google z pomocą innych danych –Wydruk 2.2 3. Po wstrzymaniu aplikacji (przycisk Back) – Wydruk 2.3 11 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 01c8a6c0 01c8a6d0 01c8a6e0 01c8a6f0 01c8a700 -033edf40 033edf50 033edf60 033edf70 033edf80 033edf90 -0a97c510 0a97c520 0a97c530 0a97c540 |..x'.`.........D| |l..El.b....b.@..| |Tesytow. . ..eKo| |nto1TajneHaslo1x| |.<.@ ... ....<.X| 0b700100 0b700110 0b700120 0b700130 -0bf87b30 0bf87b40 0bf87b50 0bf87b60 0bf87b70 |Dnpb-hITb_Y&Emai| |l=Testowe.K"..o1| |&Passwd=TajneHas| |l ..signIn=S ..+| |Dnpb-hITb_Y&Emai| |l=Testowe.K"..o1| |&Passwd=TajneHas| |l ..s"q.In=Bx.+i| |n&Persist"..Cook| |ie=yes.!.:......| |.............Tes| |ytow. . ..eKonto| |1TajneHaslo1....| |................| |ail=Testowe.K"..| |o1&Passwd=TajneH| |asl ..signIn=S .| |.+in&Persist"..C| |ookie=yes.B...#.| Wydruk 2.1. Hasła w pamięci procesu po zalogowaniu 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 01c8a6c0 01c8a6d0 01c8a6e0 01c8a6f0 01c8a700 01c8a710 01c8a720 ... 033edf30 033edf40 033edf50 033edf60 033edf70 033edf80 033edf90 ... 0a97c500 0a97c510 0a97c520 0a97c530 0a97c540 0a97c550 0a97c560 ... |..x'.`.........D| |l..El.b....b.@..| |Tesytow. . ..eKo| |nto1TajneHaslo1x| |.<.@ ... ....<.X| | .. ... ..p.<.8| | ... ... ..0.<..| |GZIeM2BJ.aZEpHH_| |Dnpb-hITb_Y&Emai| |l=Testowe.K"..o1| |&Passwd=TajneHas| |l ..s"q.In=Bx.+i| |n&Persist"..Cook| |ie=yes.!.:......| |...............9| |.............Tes| |ytow. . ..eKonto| |1TajneHaslo1....| |................| |................| |...........\....| 12 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 0b7000f0 0b700100 0b700110 0b700120 0b700130 0b700140 0b700150 ... 0bf87b10 0bf87b20 0bf87b30 0bf87b40 0bf87b50 0bf87b60 0bf87b70 |GZIeM2BJ.aZEpHH_| |Dnpb-hITb_Y&Emai| |l=Testowe.K"..o1| |&Passwd=TajneHas| |l ..signIn=S ..+| |in&Persist"..Coo| |kie=yes?....C ..| |yoGZIeM2BJ.aZEpH| |H_Dnpb-hITb_Y&Em| |ail=Testowe.K"..| |o1&Passwd=TajneH| |asl ..signIn=S .| |.+in&Persist"..C| |ookie=yes.B...#.| Wydruk 2.2. Hasła w pamięci procesu po ponownym zalogowaniu 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 01dbbc30 01dbbc40 01dbbc50 01dbbc60 ... 0316b4b0 0316b4c0 0316b4d0 0316b4e0 0316b4f0 ... 062366d0 062366e0 062366f0 06236700 06236710 06236720 06236730 ... 0637a940 0637a950 0637a960 0637a970 0637a980 0637a990 0637a9a0 ... 0637bd10 0637bd20 0637bd30 0637bd40 0637bd50 0637bd60 0637bd70 ... 0a8ba380 0a8ba390 0a8ba3a0 0a8ba3b0 0a8ba3c0 |.El..@.....@.@..| |Tesytow. . ..eKo| |nto1TajneHaslo1.| |2Cos .Nowego2.<.| |..T.........o1&P| |asswd=TajneHaslo| |1&signIn=S ..+in| |&PersistentCooki| |e=yes.!.:....@..| |ZIeM2BJ.aZEpHH_D| |npb-hITb_Y&Email| |=Testowe.K"..o1&| |Passwd=TajneHasl| | ..s"q.In=Bx.+in| |&Persist"..Cooki| |e=yes.!.:....@..| |oGZIeM2BJ.aZEpHH| |_Dnpb-hITb_Y&Ema| |il=Testowe.K"..o| |1&Passwd=TajneHa| |sl ..s"q.In=Bx.+| |in&Persist"..Coo| |kie=yes.!.:....@| |xRnvCdQflJyoGZIe| |M2BJ.aZEpHH_Dnpb| |-hITb_Y&cA.=Tes.| |.5.&Passwd=Tajne| |Haslo1&s#..In=C.| |.+in&Pe+..st,..C| |ookie=yes.!.:..E| |.....9..........| |...Tesytow. . ..| |eKonto1TajneHasl| |o1.2Cos .Nowego2| |................| 13 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 ... 0b570160 0b570170 0b570180 0b570190 0b5701a0 0b5701b0 0b5701c0 ... 0c123a60 0c123a70 0c123a80 0c123a90 0c123aa0 0c123ab0 0c123ac0 |2BJ.aZEpHH_Dnpb-| |hITb_Y&Email=Tes| |towe.Konto1&Pass| |wd=TajneHasl ..s| |ignIn=S ..+in&Pe| |rsist"..Cookie=y| |es?. ..`...B ...| |JyoGZIeM2BJ.aZEp| |HH_Dnpb-hITb_Y&E| |mail=Testowe.K".| |.o1&Passwd=Tajne| |Hasl ..s"q.In=Bx| |.+in&Persist"..C| |ookie=yes.!.:..D| Wydruk 2.3. Hasła w pamięci procesu po zatrzymaniu aplikacji Wnioski • Badana przeglądarka nie szyfruje wprowadzanych danych (wrażliwe dane nie są traktowane w sposób wyjątkowy). • Ponowne zalogowanie na konto z pomocą nowych danych nie nadpisuje poprzednio użytych loginów i haseł – dostępnej pamięci jest na tyle dużo, że aplikacja nie musi przy okazji każdego logowania uruchamiać odśmiecania i wykorzystywać tych samych obszarów. • Odnalezienie haseł w pamięci ułatwiają nazwy zmiennych ("passwd") oraz bliskość charakterystycznych wzorców ("signIn=") związanych z procesem logowania;. • Na zawartość pamięci nie ma wpływu stan aplikacji (aktywna/zawieszona). Ryzyko zwiększa fakt, że aplikacje w systemie Android są rzadko zamykane, ponieważ przycisk Back na obudowie jedynie wstrzymuje aplikację. Wciśnięcie przycisku Home na obudowie powoduje przeniesienie aplikacji w tło. • Dane logowania są wciąż dostępne w pamięci pomimo tego, że od dawna są niepotrzebne. 14 2.3 Zagrożenia Współczesne systemy operacyjne zapewniają wiele mechanizmów chroniących pamięć (a więc także przechowywane w niej wrażliwe dane) przed niepowołanym dostępem. Są to m. in.: • ochrona pamięci procesu przed dostępem innych procesów użytkownika – zapobiega to odczytywaniu pamięci procesu przez inny, potencjalnie szkodliwy proces. Systemy mogą też udostępniać API pozwalające na odczytywanie pamięci procesów, jednak jest to mechanizm przeznaczony dla aplikacji specjalnego przeznaczenia (np. debuggerów); • czyszczenie pamięci przed przydzieleniem jej do nowego procesu – nowy proces nie uzyska dostępu do danych poprzednio przechowywanych w danej przestrzeni adresowej; • możliwość zablokowania wymiatania pamięci na dysk (ang. swapping) – uniemożliwia przenoszenie zawartości pamięci operacyjnej na dysk twardy, gdzie żywotność danych byłaby większa i istniałoby zagrożenie przekazania wrażliwych danych np. podczas sprzedaży urządzenia; • blokowanie wybranych mechanizmów – założenia projektowe niektórych systemów nie pozwalają na używanie określonych funkcji (np. w systemie Android oficjalnie nie jest możliwe uzyskanie uprawnień administratora). Niestety, tego typu mechanizmy nie chronią przed nieuprawnionym dostępem procesu do swojej własnej pamięci. Współczesne aplikacje często wykorzystują języki skryptowe, takie jak np. JavaScript w przypadku przeglądarek. Języki skryptowe działają często w ramach tego samego procesu. W wyniku błędu programisty pamięć alokowana przez taki język może nie zostać wyczyszczona i może zawierać dane, które poprzednio się w niej znajdowały, a do których dany skrypt nie powinien mieć dostępu. Nieuprawniony dostęp do pamięci w obrębie własnego procesu wykorzystywała ostatnio wykryta luka Heartbleed [13]. Zaplanowane przez twórców systemu zabezpieczenia można też omijać z poziomu innych procesów. Na przykład, w starszych wersjach systemu Android wysłanie sygnału SIGUSR1 do procesu powodowało dokonanie pełnego zrzutu sterty aplikacji, co stanowiło poważne naruszenie bezpieczeństwa (oraz było niezgodne z filozofią systemu, która zabrania dokonywania przez administracyjnych). użytkownika większości potencjalnie niebezpiecznych czynności 15 Innym przykładem jest tzw. cold boot attack – atak pozwalający osobie posiadającej fizyczny dostęp do urządzenia na uzyskanie dostępu do wrażliwych danych przechowywanych w pamięci operacyjnej po zrestartowaniu maszyny. Atak opiera się o własność pamięci DRAM i SRAM, które przechowują dane od kilku sekund do nawet kilku minut po odłączeniu zasilania. Należy zakładać, że wraz z powstawaniem kolejnych rozwiązań technicznych będą wciąż pojawiać się doniesienia o nowo odkrytych, niebezpiecznych błędach, tak jak to było dotychczas. Powinno się więc z wyprzedzeniem minimalizować potencjalne zagrożenia. Jedną z metod ochrony przed kradzieżą poufnych danych jest bezpieczna dealokacja – ograniczanie czasu życia danych w pamięci operacyjnej do niezbędnego minimum. 2.4 Podsumowanie Można z całą pewnością stwierdzić, że dane użytkownika, szczególnie te wrażliwe, w pamięci aplikacji programowych mogą pozostawać znacznie dłużej i w większej liczbie kopii niż jest to wymagane. Nie są one przy tym w żaden sposób chronione (poza mechanizmami ochrony systemowej) i pozostają w postaci jawnej. Przez to są narażone na możliwość przechwycenia np. przez złośliwe oprogramowanie wykorzystujące błędy programisty. Jak pokazano w [1] możliwe jest też przechwycenie obrazu pamięci całego systemu wykonywanego jako maszyna wirtualna. Potwierdzają to też badania autorki przedstawione w Rozdziale. 2.2. Istnieje zatem potrzeba dalszych badań nad mechanizmami wykrywania sytuacji, w których wrażliwe dane pozostają niechronione oraz rozwiązaniami pozwalającymi na możliwie szybkie usuwanie tych danych z pamięci. 16 3. SYSTEM ANDROID Rozważania nad poprawieniem bezpieczeństwa systemu Android należy rozpocząć od prezentacji struktury samego systemu: ogólnych założeń architektury oraz rozwiązań dotyczących w szczególności zarządzania pamięcią. Cechy Androida szczególnie istotne dla tematu pracy to wykorzystanie jądra Linuksa u podstawy systemu (m. in. do zarządzania pamięcią) oraz zastosowanie specjalnego środowiska uruchamiania aplikacji – wirtualnej maszyny Dalvik. Przedstawiono też sposób dystrybucji aplikacji, ponieważ determinuje on ich jakość, co z kolei ma niebagatelny wpływ na bezpieczeństwo systemu. 3.1 Architektura systemu Architekturę systemu Android można przedstawić jako hierarchiczny model złożony z warstw reprezentujących wzajemnie zależne od siebie grupy komponentów programowych, od warstwy sterowników sprzętowych po aplikacje - komponenty oferujące bezpośrednią interakcję z użytkownikiem. Przedstawia to Rys. 3.1. Rysunek 3.1. Architektura systemu Android [14] 17 U podstawy systemu znajduje się jądro Linuksa o nieco zmodyfikowanej architekturze, dostosowanej do potrzeb urządzeń mobilnych. Najnowsze wersje Androida oparte są o jądro Linuksa w wersji 3.0 (Android 4.1 JellyBean) lub nowsze. Jądro odpowiada za zasadnicze funkcje systemu, takie jak zarządzanie pamięcią, zarządzanie procesami i zapewnianie sterowników dla komponentów sprzętowych. Jedna z istotnych modyfikacji wprowadzonych przez firmę Google w stosunku do oryginalnego jądra Linuksa polega na zablokowaniu dostępu administratora (root) dla użytkownika telefonu. Zwiększa to bezpieczeństwo systemu poprzez uniemożliwienie wykonania wielu potencjalnie niebezpiecznych operacji, jest jednak także przedmiotem krytyki, ponieważ ogranicza możliwości konfiguracji systemu. Ograniczenie to można ominąć poprzez wykorzystanie luk w systemie. Dla wielu popularnych urządzeń istnieją przewodniki uzyskania dostępu administratora. Kolejną warstwę abstrakcji stanowią biblioteki dostarczane wraz z systemem. Stanowią one warstwę pośrednią pomiędzy urządzeniem a różnymi typami danych wejściowych i wyjściowych; odpowiadają za np. nagrywanie i odtwarzanie dźwięku (biblioteka Media Framework), przetwarzanie informacji o dotknięciu ekranu (biblioteka Surface Manager), rysowanie grafik 2D i 3D na ekranie (biblioteka OpenGL). Następne miejsce w hierarchii zajmuje środowisko uruchomieniowe (Android Runtime), złożone z wirtualnej maszyny Dalvik (Dalvik Virtual Machine) oraz bibliotek języka Java (Core Libraries). Dalvik zapewnia środowisko uruchomienia androidowych aplikacji, przystosowane do działania z użyciem ograniczonych zasobów sprzętowych dostępnych na urządzeniach mobilnych. Dla każdej uruchamianej w Androidzie aplikacji użytkownika tworzona jest osobna instancja maszyny wirtualnej. W kolejnej warstwie hierarchii znajduje się warstwa szkieletu oprogramowania zapewniającego podstawowe funkcjonalności telefonu, takie jak zarządzanie połączeniami głosowymi czy modułami GPS, oraz kontrolującego aktywność aplikacji użytkownika. Bezpośrednio z tą warstwą komunikują się aplikacje użytkownika. Aplikacje w Androidzie mogą korzystać z wielu modułów sprzętowych, takich jak GPS czy Bluetooth oraz obsługiwać podstawowe funkcje telefonu, takie jak połączenia telefoniczne, wiadomości SMS, konfiguracje sieci czy informacje o stanie baterii. Listy funkcjonalności, do których dostępu żąda aplikacja są znane jako przywileje (ang. permissions) i mogą być kontrolowane przez użytkownika systemu w którym program jest zainstalowany. 18 3.2 Maszyna wirtualna Dalvik Maszyna wirtualna Dalvik to warstwa oprogramowania, która tworzy środowisko dla uruchamiania aplikacji w systemie Android. Pozwala na uruchamianie programów napisanych w języku Java i skompilowanych do kodu bajtowego (ang. bytecode) Javy, które są następnie tłumaczone do specjalnego kodu bajtowego Dalvika i przechowywane w plikach o formacie .dex (Dalvik Executable) oraz .odex (Optimized Dalvik Executable). Format kodu bajtowego Dalvika jest specjalnie dostosowany do uruchamiania w warunkach ograniczonych zasobów sprzętowych (mała szybkość procesora, niewielka ilość pamięci). Każda aplikacja uruchomiona w systemie Android ma własną instancję maszyny wirtualnej Dalvik, która zarządza pamięcią aplikacji. 3.2.1 Alokacja pamięci Maszyna wirtualna Dalvik do przeprowadzania alokacji na stercie wykorzystuje bibliotekę dlmalloc [12]. Biblioteka ta stosowana jest do zarządzania pamięcią na najniższym poziomie – używa pojęcia bloku pamięci, podczas gdy wyższe warstwy biblioteki Dalvik operują na obiektach. Algorytm alokacji biblioteki dlmalloc opiera się na dwóch najważniejszych koncepcjach: 1. Tagi graniczne (boundary tags) Pamięć podzielona jest na fragmenty, a każdy fragment zarówno na początku, jak i na końcu obszaru zawiera specjalne pola z informacją o swoim rozmiarze. Rozwiązanie takie przyjęto, by umożliwić scalanie sąsiadujących fragmentów, jeżeli oba nie są już używane. 2. Koszyki (binning) Koszyki grupują wolne fragmenty o tym samym rozmiarze. Przyspiesza to wyszukiwanie wolnych fragmentów do kolejnych alokacji. Wybór miejsca alokacji odbywa się zgodnie ze strategią najlepszego dopasowania (ang. best-fit). Celem stosowania tej strategii jest maksymalne ograniczenie fragmentacji. 19 3.2.2 Dealokacja pamięci W związku z użyciem języka Java maszyna wirtualna posiada własny odśmiecacz (ang. garbage collector) – automatyczny mechanizm służący do odzyskiwania nieużywanej już pamięci. Odśmiecacz uruchamiany jest w określonych momentach przez maszynę wirtualną i nie jest kontrolowany przez programistę. Odśmiecacz Androida działa zgodnie ze strategią Mark-and-Sweep [11]. Według tej strategii nieużywane obiekty nie są usuwane natychmiast, czyli w momencie utraty ostatniej prowadzącej do nich referencji. Obszary zajęte przez nieużywane już dane zwiększają się do czasu gdy maszyna wirtualna automatyczne uruchomi odśmiecanie (z powodu np. braku dostępnej pamięci dla kolejnych alokacji). Zależnie od przyjętej strategii zwalniane mogą być wszystkie lub tylko część niepotrzebnych danych. Na czas odśmiecania działanie aplikacji może być zawieszane lub czasowo zatrzymywane mogą być jedynie niektóre wątki, by uniknąć przerw w działaniu programu. Wykrywanie obiektów, które można zwolnić odbywa się poprzez śledzenie drzew referencji. Obiektami aktywnymi (ang. live objects), czyli obiektami, których nie można zwolnić, określane są obszary, do których istnieją (bezpośrednio lub pośrednio) referencje prowadzące z obiektów głównych (tzw. root; są to m. in. obiekty na stosie maszyny wirtualnej). Jeżeli obiekt nie jest aktywny, oznacza to, że można go zwolnić. Algorytm Mark-and-Sweep składa się z 2 faz: 1. Oznaczanie wszystkich aktywnych obiektów. 2. Zwalnianie wszystkich obiektów, które nie zostały oznaczone. Pamięć należąca do zwalnianych obiektów trafia z powrotem do puli pamięci dostępnej dla kolejnych alokacji. Dane umieszczone w tych obszarach nie są nadpisywane do chwili przydzielenia obszaru dla kolejnej alokacji. Podsumowując, gdy tracona jest ostatnia referencja do obiektu, nie jest wprost wywoływana żadna specjalna metoda. Dopiero po uruchomieniu odśmiecacza można odkryć, że obiekt nie jest już używany (obiekty oznaczone kolorem czerwonym na Rys. 3.2). 20 Rysunek 3.2. Odśmiecanie w języku Java [18] Do tego czasu będzie on przebywał w pamięci jako obiekt aktywny. Jednak nawet po odkryciu, że obszar nie jest już używany odśmiecacz jedynie oznaczy, że dany blok pamięci jest już wolny, ale nie nadpisze jego zawartości. Nadpisanie zawartości nastąpi dopiero po zaalokowaniu w tym obszarze nowych danych. 3.3 Dystrybucja aplikacji Głównym źródłem aplikacji instalowanych na urządzeniach z Androidem jest oficjalny sklep Google Play. W lipcu 2014 roku w sklepie dostępnych było 1,3 mln aplikacji [10]. Własne 21 programy do sklepu może wstawić każdy zarejestrowany użytkownik. Kontrola bezpieczeństwa aplikacji przeprowadzana przez automatyczny system antywirusowy Google Bouncer. Niestety, przed udostępnieniem aplikacji mającej przetwarzać wrażliwe dane nie jest przeprowadzany audyt sprawdzający czy zachowane są standardy bezpieczeństwa takie jak szyfrowanie danych. Istnieje także wiele innych źródeł aplikacji, takie jak oficjalne sklepy producentów sprzętu (np. Samsung Apps). Standardy bezpieczeństwa nie są jednolite, różnią się w zależności od sklepu. Zgodnie z filozofią Androida, część odpowiedzialności za bezpieczeństwo przenoszona jest na użytkownika telefonu poprzez wspomniany już mechanizm przywilejów. Przed instalacją aplikacji użytkownik proszony jest o potwierdzenie listy uprawnień aplikacji, na której może znajdować się np. prawo do wykonywania połączeń telefonicznych czy odczytu karty SD. Niestety, uprawnienia te podawane są jedynie na zasadzie listy ogólnych haseł i nie wiadomo, w jakim celu dana aplikacja ich potrzebuje. 22 4. PROBLEMY REALIZACJI BEZPIECZNEJ DEALOKACJI W celu zwalczenia słabości aplikacji należy zapewnić mechanizm pozwalający na zamazanie wrażliwych danych w pamięci w chwili ich dealokacji lub niedługim czasie po niej. Taki system mechanizmów został określony nazwą bezpieczna dealokacja [3]. Jak już wspomniano, bezpieczna dealokacja nie jest domyślnie implementowana we współczesnych systemach operacyjnych. Implementacja tego mechanizmu musi być ściśle związana z wykorzystywaną przez system technologią zarządzania pamięcią, co z kolei powiązane jest z dominującymi w danym systemie językami programowania. Poniżej omówione zostaną problemy bezpiecznej dealokacji w systemie Android, a przedtem wspomniany zostanie system Linux, jako system leżący u podstaw Androida. Przedstawione zostaną różnice pomiędzy systemami które sprawiają, że bezpieczna dealokacja w Androidzie musi być realizowana inaczej niż w Linuksie. Większość aplikacji dla systemu Linux pisanych jest w językach C i C++. W przypadku języka C++ za każdym razem gdy zmienna opuszcza zakres, wykonywany jest destruktor odpowiadającego jej obiektu, który zazwyczaj zwalnia pamięć zaalokowaną przez obiekt na stercie za pomocą wywołania funkcji delete z biblioteki standardowej. Należy pamiętać, że w kontekście biblioteki standardowej języka C++ dealokacja oznacza jedynie symboliczne oznaczenie bloku pamięci jako wolny. Nie ma to wpływu na dane przechowywane w obszarze pamięci, które niezależnie od stanu alokacji są dostępne w pamięci i nie są bezpośrednio czyszczone (mogą jedynie zostać nadpisane przez zaalokowanie innych zmiennych na nieużywanym już obszarze). By czyścić dane w pamięci, można uruchamiać aplikacje ze zmodyfikowaną wersją biblioteki malloc, która zapewni dealokację z uprzednim czyszczeniem zawartości zmiennej. W systemie Linux użycie własnej biblioteki dealokującej jest możliwe z wykorzystaniem mechanizmu wykorzystującego zmienną środowiskową LD_PRELOAD. Ta zmienna modyfikuje zachowanie dynamicznego linkera. Zmienna LD_PRELOAD zawiera listę bibliotek współdzielonych, które linker powinien załadować przed wszystkimi innymi bibliotekami. Pozwala to na zastąpienie wybranych funkcji z bibliotek współdzielonych, z którymi łączy się dany program, własnymi funkcjami. System Android, choć oparty na Linuksie, wykorzystuje maszynę wirtualną, a aplikacje dla tego systemu pisane są głównie w języku Java (możliwe jest dołączanie fragmentów kodu w C++ za pomocą interfejsu JNI). Podmiana kodu odśmiecacza nie jest w Androidzie możliwa za pomocą mechanizmu analogicznego do opisanego wyżej LD_PRELOAD. 23 Kolejny problem związany jest z częstością uruchamiana samego odśmiecacza. Dokładny czas jego uruchomienia przez system operacyjny jest trudny do przewidzenia. Może okazać się, że dzieje się to zbyt rzadko, by można było efektywnie chronić dane. Każde uruchomienie odśmiecacza jest sygnalizowane w systemie logowania Androida za pomocą identyfikatorów trybu odśmiecania, które sygnalizują powód uruchomienia odśmiecacza (komunikaty systemu można przeglądać np. z pomocą narzędzia logcat [19]). Poniżej przedstawiono charakterystyki poszczególnych trybów odśmiecania. GC_CONCURRENT – uruchomienie odśmiecacza z powodu pojawienia się konieczności powiększenia obszaru sterty. Zwolnienie miejsca może pozwolić uniknąć zwiększania sterty. GC_FOR_MALLOC – uruchomianie odśmiecacza ponieważ na stercie nie ma wystarczająco dużo miejsca by wykonać alokację, a sterta osiągnęła już maksymalną wielkość. GC_BEFORE_OOM – uruchomienie odśmiecacza ponieważ na stercie nie ma już miejsca, sterty nie można rozszerzyć, a inne formy zwalniania nie przyniosły rezultatu. To najbardziej radykalny ze wszystkich trybów, jego celem jest zwolnienie wszystkich referencji, które tylko można zwolnić (nawet części referencji specjalnie przeznaczonych do przechowywania danych tymczasowych). Z reguły jest to ostatnia próba zwolnienia pamięci widniejąca w logach przed zwróceniem z aplikacji błędu OutOfMemoryError (zabicie aplikacji z powodu braku pamięci). GC_EXPLICIT – w przeciwieństwie do pozostałych trybów, ten tryb odśmiecania jest wykonywany na żądanie. Można go uruchomić z poziomu aplikacji użytkownika za pomocą funkcji System.gc() języka Java, jednak zgodnie z oficjalną dokumentacją [20] wywołanie tej funkcji nie daje gwarancji uruchomienia odśmiecania. Jest to jedynie wskazówka dla maszyny wirtualnej, którą Dalvik może zignorować. Metody tej aplikacja może używać jedynie względem własnej maszyny wirtualnej, nie istnieje analogiczne rozwiązanie do wywoływania odśmiecania w aplikacjach innych niż bieżąca (identyfikowanych np. za pomocą numeru PID). Drugim z przypadków uruchomienia GC_EXPLICIT jest odebranie przez proces sygnału SIGUSR1 (w tym przypadku uruchomienie odśmiecania również nie jest gwarantowane). Niestety, wysyłanie sygnału z aplikacji użytkownika do innego procesu nie przynosi pożądanego efektu ze względu na poziom uprawnień (choć w Javie jest dostępna odpowiednia metoda pozwalająca na wysłanie sygnału Runtime.getRuntime().exec("kill -SIGUSR1 <PID do procesu: procesu>"; z powodu 24 uprawnień nie uda się również wysłanie sygnału z kodu natywnego). By wywołać w innym procesie GC_EXPLICIT, aplikacja wysyłająca sygnał musiałaby pracować z uprawnieniami administratora, co jest zablokowane w Androidzie, lub być uruchamiana przez tego samego użytkownika, co jest sprzeczne z architekturą systemu Android, w którym każda aplikacja ma przypisanego unikalnego użytkownika i poza szczególnymi wyjątkami dwie aplikacje nie mogą być uruchomione przez tych samych użytkowników. Kolejne ograniczenie systemu Android (tym razem z perspektywy twórcy aplikacji przetwarzającej wrażliwe dane tekstowe) związane jest ze specyfiką obiektów w języku Java. Aplikacje pisane w tym języku nie mogą nadpisać własnych wrażliwych danych jeśli dane te przechowywane są standardowo w obiektach typu String, nawet gdyby ich autorzy przewidzieli konieczność czyszczenia i próbowali to robić na poziomie kodu źródłowego aplikacji. Wyczyszczenie zawartości obiektu łańcucha znakowego w kodzie programu napisanego w języku Java nie jest możliwe. Obiekty typu String są niezmienialne (ang. immutable), co oznacza, że przy próbie przypisania pustego łańcucha do zmiennej, która przechowuje hasło, powstanie nowy obiekt typu String, a poprzedni nie zostanie nadpisany – utracona zostanie jedynie prowadząca do niego referencja, zaś sam łańcuch pozostanie w pamięci (Rys. 4.1). Stąd wielokrotne pojawianie się wrażliwych danych w pamięci operacyjnej raportowane w Rozdz. 2. Rysunek 4.1. Zmiana referencji do obiektów typu String w języku Java [15] 25 Brak referencji do obiektu zawierającego hasło zostanie odkryty dopiero przez odśmiecacz, który odznaczy ten obiekt jako nie używany, lecz wewnętrzne dane obiektu pozostaną bez zmian, dopóki nie zostaną przypadkiem nadpisane przez inne zmienne programu. Podobny problem dotyczy szyfrowania. Szyfrowanie łańcucha po wczytaniu danych wpisanych do kontrolki pozostawi w pamięci wiele różnych obiektów zawierających tymczasowe stany pośrednie łańcucha powstałe w trakcie wpisywania (czyli np. obiekt typu String zawierający pierwszą literę hasła, kolejny obiekt zawierający dwie pierwsze litery, itd). By zapewnić możliwość czyszczenia zmiennych trzeba zrezygnować z przechowywania łańcuchów w obiektach typu String i używać np. typu ByteArray. Podsumowując, podczas realizacji systemu bezpiecznej dealokacji w Androidzie należy wziąć pod uwagę liczne ograniczenia: • brak gwarancji wywołania odśmiecania w bieżącej aplikacji, • brak metod do wywołania odśmiecania w innych aplikacjach, • problemy z uprawnieniami aplikacji uniemożliwiające wysyłanie sygnałów, • brak możliwości celowego nadpisania danych przechowywanych w obiektach typu String z poziomu bieżącej aplikacji. Pomimo to bezpieczna dealokacja w Androidzie jest możliwa, co zaprezentowano w kolejnym rozdziale na przykładzie stworzonego na potrzeby niniejszej pracy systemu. 26 5. SYSTEM MEMORY INSPECTOR Na podstawie analizy możliwości realizacji bezpiecznej dealokacji w systemie Android został stworzony system Memory Inspector (MI). System ten działa bezpośrednio na urządzeniu z systemem Android i pozwala na podgląd obszarów pamięci operacyjnej wybranych procesów, wyszukiwanie w nich zdefiniowanych przez użytkownika wzorców oraz usuwanie z pamięci wrażliwych danych. W ramach systemu zrealizowane zostały następujące moduły: MI Manager, MI Core oraz MI Dalvik Patch. 1. MI Manager – aplikacja z graficznym interfejsem użytkownika pozwalająca na zarządzanie systemem MI z poziomu użytkownika telefonu. Ekran główny aplikacji przedstawiono na Rys. 5.1. Rysunek 5.1. Okno główne aplikacji MI Manager 27 2. MI Core – moduł odpowiadający za dostęp do pamięci procesów. Został zrealizowany jako moduł jądra Linuksa (system Linux odpowiada w Androidzie m.in. za niskopoziomowe zarządzanie procesami i pamięcią). Było to konieczne ponieważ do realizacji zadań przeglądania i modyfikacji pamięci procesów potrzebny jest bezpośredni dostęp do struktur jądra oraz posiadanie najwyższych uprawnień (m. in. do modyfikacji zawartości pamięci dowolnego procesu). Z poziomu modułu jądra systemu możliwe jest uzyskanie dostępu do przestrzeni adresowych wszystkich aplikacji. Jednocześnie istnieją specjalne mechanizmy umożliwiające sterowanie pracą modułu z poziomu użytkownika, MI Core może więc pracować pod kontrolą aplikacji MI Manager. Jądro Linuksa używane w Androidzie udostępnia mechanizm ładowania modułów, tak samo jak w standardowej wersji systemu Linux. By odblokować ten mechanizm, należy przekompilować jądro z flagami przedstawionymi na Wydruku 5.1. 1 CONFIG_MODULES=y 2 CONFIG_MODULES_UNLOAD=y 3 CONFIG_MODULES_FORCE_UNLOAD=y Wydruk 5.1. Flagi umożliwiające ładowanie modułów do jądra Linuksa 3. MI Dalvik Patch – łata nakładana na kod systemu Android. Modyfikuje ona działanie odśmiecacza tak, by możliwe było uruchomienie jego specjalnej wersji, która wypełni zwalniane obszary umowną sekwencją znaków. Funkcjonalności systemu: • Przeglądanie aktualnie uruchomionych procesów. • Przeglądanie łańcuchów znakowych przechowywanych w obiektach typu String wybranego procesu. • Dokonanie zrzutu pamięci wybranego procesu. • Poszukiwanie podanych przez użytkownika ciągów znaków w pamięci wybranego procesu. 28 • Poszukiwanie podanych przez użytkownika ciągów znaków w pamięci wszystkich procesów. • Wywołanie zmodyfikowanego, czyszczącego odśmiecacza dla wybranego procesu na żądanie użytkownika. • Bezpośrednie usuwanie z pamięci wybranych danych na żądanie użytkownika (nadpisywanie wybranych adresów umownymi sekwencjami znaków). System może być używany do obsługi kodu Javy dowolnej aplikacji uruchamianej w systemie Android. Nie obejmuje jednak czyszczenia danych natywnych (alokowanych z poziomu kodu języka C++ dołączanego przez interfejs JNI). Moduł MI Dalvik Patch modyfikuje działanie odśmiecacza maszyny wirtualnej Dalvik, w celu jego realizacji konieczne są więc ingerencje w kod źródłowy Androida. Rozważano 2 możliwości realizacji tego modułu: 1. Podmiana biblioteki libdvm 2. Modyfikacja kodu źródłowego Androida i instalacja zmodyfikowanego systemu Rozwiązanie 1. okazało się niemożliwe do zrealizowania ze względu na stosowany na urządzeniach mobilnych system plików. Podmiana biblioteki dzielonej to rozwiązanie teoretycznie nie wymagające budowania i reinstalacji całości systemu. Wymaga jednak modyfikacji kodu biblioteki Dalvik, przebudowania jej i przekopiowania na miejsce poprzedniej wersji tej biblioteki w systemie, tak by nowo uruchamiane aplikacje korzystały już z wersji zmodyfikowanej. Rozwiązanie takie ma szansę powodzenia w systemie Linux. Aplikacje korzystające ze starej wersji biblioteki (uruchomione przed jej nadpisaniem nową wersją) będą wciąż korzystały z poprzedniej wersji, mimo że została już ona pozornie usunięta. Dzieje się tak, ponieważ w systemie Linux odwołania do plików realizowane poprzez system i-węzłów (inode). Usunięta biblioteka jest reprezentowana przez odłączony od systemu plików i-węzeł połączony z wciąż dostępnymi, załadowanymi do pamięci danymi. W urządzeniach mobilnych rozwiązania tego nie można zastosować z powodu trudności z zapisaniem nowej wersji biblioteki na dysk. Pliki systemowe przechowywane są na partycjach MTD (Memory Technology Device) z systemem YAFFS2. By nadpisać plik w tym systemie należy zmodyfikować cały blok pamięci. Strukturę partycji urządzenia mobilnego można 29 obejrzeć w pliku /proc/partitions. Wydruk 5.2 przedstawia przykład struktury partycji dla konkretnego urządzenia. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 root@generic:/ # cat /proc/partitions major minor #blocks name 31 31 31 0 1 2 216576 mtdblock0 199680 mtdblock1 65536 mtdblock2 root@generic:/ # cat /proc/mtd dev: size erasesize name mtd0: 0d380000 00020000 "system" mtd1: 0c300000 00020000 "userdata" mtd2: 04000000 00020000 "cache" /dev/block/mtdblock0 /system yaffs2 ro,seclabel,relatime 0 0 /dev/block/mtdblock1 /data yaffs2 rw,seclabel,nosuid,nodev,relatime 0 0 /dev/block/mtdblock2 /cache yaffs2 rw,seclabel,nosuid,nodev,relatime 0 0 Wydruk 5.2. Przeglądanie struktury partycji Ilość wolnego miejsca na partycji systemowej wynosi 0, a kolumna erasesize pokazuje, ile miejsca należy nadpisać żeby móc cokolwiek zmodyfikować (jest to rozmiar całego bloku). W systemie plików YAFFS2 nadpisanie pliku wymaga dostępnego wolnego miejsca. Na partycji systemowej nie ma wolnego miejsca, więc nie ma też możliwości nadpisania pliku biblioteki libdvm z poziomu systemu plików. Teoretycznie można by taką operację przeprowadzić na niższym poziomie, przez bezpośrednią modyfikację bloków MTD, jednak byłoby to skomplikowane, a rozmiar docelowej biblioteki byłby ograniczony rozmiarem tej nadpisywanej. W świetle powyższego, w systemie Memory Inspector zostało wybrane rozwiązanie 2. wymagające nałożenia łaty na kod źródłowy systemu Android, przebudowania i reinstalacji systemu. Pozwala to jednak na uniknięcie w. w. ograniczeń związanych z systemem plików. Nałożenie łaty za pomocą polecenia patch nie nastręcza szczególnych trudności. System budowany jest z pomocą narzędzia make a instalacja odbywa się zgodnie z instrukcjami dla konkretnego urządzenia. Szczegółowy opis wykonania poszczególnych kroków zawarto w Dodatku A. 30 5.2 Szczegóły implementacji systemu 5.2.1 Moduł MI Core Moduł MI Core został napisany jako moduł jądra Linuxa i może być dynamicznie ładowany do systemu oraz z niego usuwany. Może być stosowany m. in. do sterowania zmodyfikowanym odśmiecaczem maszyny wirtualnej Dalvik. W celu osiągnięcia planowanej funkcjonalności modułu MI Core należało stworzyć funkcje odpowiadające za następujące zadania: • Uzyskanie list aktualnie uruchomionych w systemie procesów (funkcja get_processes). • Uzyskanie list obszarów pamięci (ang. virtual memory area – w skrócie VMA) wybranego procesu, tak by możliwe było uzyskanie dostępu do obszaru sterty Dalvika, w której przechowywane są wrażliwe dane (funkcja get_process_memareas). • Uzyskanie dostępu do adresów przestrzeni użytkownika (user space) wybranego procesu, tak by możliwe było odczytywanie zawartości adresów w stertach programów oraz pisanie do ich pamięci (funkcja access_process_vm2). • Komunikacja z modułem z poziomu przestrzeni użytkownika, tak by możliwe było sterowanie pracą modułu z aplikacji MI Manager (funkcje store i show). • Prześledzenie struktur w obszarze sterty wirtualnej maszyny Dalvik celem uzyskania wzorców klas stosowanych w programie (funkcja get_class_prototypes). • Odnalezienie w stercie Dalvika konkretnych obiektów klas typu String (za pomocą adresu prototypu i danych o strukturze klas), by odczytać przechowywane łańcuchy znakowe (funkcja get_class_objects). • Przesłanie do maszyny wirtualnej Dalvik żądania uruchomienia odśmiecacza w formie sygnału SIGUSR1 (funkcja launch_gc). 31 Najważniejsze funkcje stworzone w ramach modułu MI Core można podzielić na dwie kategorie (patrz Rys. 5.2): 1. funkcje korzystające wyłącznie z mechanizmów jądra systemu, 2. funkcje dodatkowo wykorzystujące mechanizmy maszyny wirtualnej Dalvik. Rysunek 5.2. Funkcje modułu MI Core Wykorzystywane mechanizmy jądra W ramach przeglądania i modyfikowania zawartości pamięci procesów zostały wykorzystane niskopoziomowe mechanizmy jądra systemu. Zostały one opisane w ramach poszczególnych funkcji. Funkcja get_processes Funkcja ta wyszukuje w liście aktualnie uruchomionych w systemie procesów proces o żądanym identyfikatorze PID. Szczegółowe dane każdego procesu przechowywane są przez jądro Androida w strukturze typu task_structure (zdefiniowanej w pliku shed.h). Obiekty tej struktury reprezentujące poszczególne procesy połączone są w listę dwukierunkową. 32 Dostęp do elementów listy realizowany jest przy pomocy systemowej funkcji for_each_process. Funkcja get_process_memareas Funkcja ta zwraca listę obszarów w pamięci danego procesu. Obszary pamięci (zwane także VMA – Virtual Memory Area) reprezentowane są poprzez strukturę vm_area_struct, zdefiniowaną w pliku linux/mm.h. Dostęp do list obszarów pamięci można uzyskać poprzez strukturę task_structure procesu. Struktury obszarów przechowują m. in. informacje na temat nazwy pliku z jakiego jest mapowany dany obszar. Dzięki temu możliwe jest odnalezienie obszaru zajmowanego przez instancję maszyny wirtualnej Dalvik. Funkcja access_process_vm2 By zyskać dostęp do pamięci procesu użytkownika z poziomu jądra konieczne jest mapowanie wirtualnych adresów przestrzeni użytkownika na adresy przestrzeni jądra. W tym celu stworzona została funkcja oparta na zaimplementowanej przez twórców Linuksa procedurze mapowania adresów, która jednak nie jest już dostępna w nowszych wersjach systemu Linux. Dostęp do pamięci procesu realizowany jest poprzez kopiowanie jej fragmentów do tymczasowego bufora z użyciem funkcji systemowych: get_user_pages – zwraca listę wskaźników do struktur struct page opisujących strony pamięci dla danego bufora w przestrzeni użytkownika oraz listę wskaźników do struktur struct vm_area_struct opisujących obszary pamięci dla tego bufora; by uzyskać dostęp do pamięci przestrzeni użytkownika trzeba mapować zwrócone strony pamięci w przestrzeń pamięci wirtualnej jądra za pomocą wywołania funkcji systemowej kmap. find_vma – sprawdza czy w danej przestrzeni adresowej jest kolejny dostępny obszar, jeśli obszar nie zostanie znaleziony, oznacza to, że proces kopiowania jest zakończony; copy_from_user_page – kopiuje zawartość bufora pamięci z przestrzeni użytkownika do bufora jądra. 33 Przed dokonaniem odczytu dostęp do pamięci blokowany jest poprzez zamknięcie semafora odczytu (down_read), zaś po odczycie semafor jest otwierany(up_read). W przyjętym rozwiązaniu nie zostało zrealizowane przeglądanie pamięci procesu na bieżąco (bez pośredniego kroku kopiowania do tymczasowych buforów), ponieważ wymagałoby to wstrzymania wykonywania procesu nie tylko na czas odczytu, ale także na czas przeprowadzenia kolejnych analiz pamięci. Stwarza to zagrożenie w przypadku wstrzymania pracy modułu lub ewentualnych błędów programistycznych, które mogłyby spowodować zatrzymanie procesu na potencjalnie długi czas. Rozwiązanie zakładające możliwie sprawne przekopiowanie zawartości pamięci do tymczasowego bufora, na którym przeprowadzane będą późniejsze, potencjalnie czasochłonne analizy wydaje się być najkorzystniejsze z punktu widzenia bezpieczeństwa i szybkości działania całości systemu. Funkcje store i show Funkcje te odpowiadają za komunikację modułu z programami przestrzeni użytkownika. Wykorzystują specjalne obiekty typu struct kobject. Stworzenie obiektu kobject podczas inicjalizacji modułu MI Core powoduje utworzenie w katalogu/sys/kernel pliku, który posłuży do sterowania pracą modułu. Zapisanie do takiego pliku jakiejś wartości powoduje uruchomienie w module MI Core wcześniej zdefiniowanej funkcji, która pobiera wpisaną wartość i przetwarza ją. Funkcje modułu, które mają być uruchamiane gdy do pliku dane będą zapisywane (wskaźnik store na Wydruku 5.3) lub odczytywane (wskaźnik show na Wydruku 5.3) rejestruje się w module tworząc obiekt typu struct kobj_attribute (Wydruk 5.3). Atrybuty są dołączane do obiektów kobject podczas ich tworzenia za pomocą funkcji sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp). 1 struct kobj_attribute { 2 struct attribute attr; 3 ssize_t* (*show) (struct kobject *kobj, struct kobj_attribute 4 *attr, char *buf); 5 ssize_t* (*store) (struct kobject *kobj, struct kobj_attribute 6 *attr, const char *buf, size_t count); Wydruk 5.3 Struktura kobj_attribute 34 Mechanizm obiektów kobject pozwala na dwustronną komunikację z modułem – zarówno na przekazywanie mu parametrów oraz automatyczne wywoływanie żądanej funkcji, jak i odczytanie wyników wywołania funkcji. Zestaw konfiguracyjnych plików jądra jest wykorzystywany przez moduł MI Manager, który poprzez zapisywanie do nich danych wejściowych (takich jak np. identyfikator PID procesu, którego pamięć chcemy przebadać) przekazuje instrukcje do modułu oraz odbiera wyniki funkcji poprzez otwarcie pliku konfiguracyjnego. Wykorzystywane mechanizmy maszyny wirtualnej Funkcja get_class_prototypes W celu pozyskania informacji o wewnętrznej strukturze programu, moduł MI Core korzysta z informacji zawartych w strukturach maszyny wirtualnej Dalvik. Każdy uruchamiany w systemie Android program korzysta z własnej instancji maszyny wirtualnej w postaci biblioteki dzielonej libdvm.so. Obszar pamięci tej biblioteki znajduje się w obrębie listy obszarów każdego procesu w systemie Android. By otrzymać istotne dla modułu MI Core informacje, zawartość biblioteki libdvm.so została przeanalizowana z pomocą programu readelf, który służy do analizy struktur plików w formacie ELF. Analiza binarnego pliku biblioteki pozwala otrzymać stałe lokalizacje obiektów wewnątrz biblioteki. Dane te będą takie same dla wszystkich programów używających jednej wersji maszyny wirtualnej Dalvik. Obszar pamięci wybranego procesu jest znajdowany poprzez przeglądanie mapy pamięci uruchomionego programu, natomiast obszar libdvm.so odnajduje się przeglądając listę struktur vm_area_struct danego procesu. Przesunięcie od początku obszaru zajmowanego przez libdvm.so o wskazany przez readelf offset prowadzi do miejsca w pamięci zajmowanego przez poszukiwaną strukturę. 35 Struktura DvmGlobals 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 struct DvmGlobals { /* * Some options from the command line or environment. */ char* bootClassPathStr; char* classPathStr; ... ... } size_t size_t size_t double size_t size_t size_t size_t heapStartingSize; heapMaximumSize; heapGrowthLimit; heapTargetUtilization; heapMinFree; heapMaxFree; stackSize; mainThreadStackSize; /* * Loaded classes, hashed by class name. * allocated in GC space. */ HashTable* loadedClasses; Each entry is a ClassObject*, Wydruk 5.4. Struktura DvmGlobals DvmGlobals (Wydruk 5.4) to struktura przechowująca informacje na temat m. in. definicji klas używanych w programie [5]. Informację o lokalizacji struktury (jej instancja nazywa się gDvm) w obrębie załadowanej biblioteki otrzymujemy za pomocą polecenia readelf (Wydruk 5.5). W Androidzie polecenie readelf nie jest dostępne, trzeba więc przekopiować bibliotekę libdvm na komputer stacjonarny i tam przeprowadzić badanie. Odczytanie wyniku polecenia readelf pozwala odczytać lokalizację struktury względem początku obszaru zajmowanego przez libdvm. Zgodne z wynikiem badania zaprezentowanym w Wydruku 5.5, offset równy 000ae1a0 prowadzi do miejsca w pamięci zajmowanego przez poszukiwaną strukturę. Dostęp do poszczególnych pól struktury realizuje się następnie przesuwając się względem początku obszaru struktury o przesunięcia wyznaczane przez rozmiary typów kolejnych pól (opisane dalej). 36 1 b@echidna:~/Desktop$ readelf -a libdvm.so | grep gDvm 2 54: 000a93f0 464 OBJECT GLOBAL DEFAULT 14 gDvmInlineOpsTable 3 169: 000b0078 12 OBJECT GLOBAL DEFAULT 18 gDvmJni 4 206: 000ae1a0 1232 OBJECT GLOBAL DEFAULT 18 gDvm 5 331: 000ae670 6664 OBJECT GLOBAL DEFAULT 18 gDvmJit 6 1229: 000a4db6 625 OBJECT GLOBAL DEFAULT 11 gDvmMergeTab Wydruk 5.5. Odczyt formatu ELF za pomocą polecenia readelf Struktura DvmGlobals zawiera wiele danych na temat bieżącej instancji maszyny wirtualnej Dalvik, takich jak rozmiary stosu, listy wątków, parametry odśmiecacza, wskaźniki do wewnętrznych klas (np. klas wyjątków). Z perspektywy analizy pamięci najważniejsze z pól struktury to pole loadedClasses, zawierające wskaźnik do listy prototypów klas języka Java. Za pomocą tej listy można uzyskać dostęp do struktur zawierających pełną informację o klasach (takich jak m. in. nazwy, modyfikatory dostępu i listy pól). Wskaźnik do załadowanych klas wskazuje na adres obiektu typu HashTable (Wydruk 5.6). Jest to kontener zawierający wiele obiektów typu HashEntry (Wydruk 5.7). 1 struct HashTable { 2 int tableSize; 3 int numEntries; 4 int numDeadEntries; 5 */ 6 HashEntry* pEntries; 7 HashFreeFunc freeFunc; 8 pthread_mutex_t lock; 9 }; /* must be power of 2 */ /* current #of "live" entries */ /* current #of tombstone entries /* array on heap */ Wydruk 5.6. Struktura HashTable 1 struct HashEntry { 2 u4 hashValue; 3 void* data; 4 }; Wydruk 5.7. Struktura HashEntry Pod wskaźnikiem void* data struktury HashEntry znajduje się obiekt klasy ClassObject (Wydruk 5.9). 37 1 struct Object { 2 ClassObject* 3 u4 4 }; clazz; lock; Wydruk 5.8. Struktura Object 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct ClassObject : Object { u4 instanceData[CLASS_FIELD_SLOTS]; const char* descriptor; char* descriptorAlloc; ... /* instance fields * * These describe the layout of the contents of a DataObjectcompatible * Object. * All instance fields that refer to objects are guaranteed to be * at the beginning of the field list. ifieldRefCount specifies * the number of reference fields. */ int ifieldCount; int ifieldRefCount; // number of fields that are object refs InstField* ifields; ... } Wydruk 5.9. Struktura ClassObject 1 struct Field { 2 ClassObject* 3 const char* 4 const char* 5 u4 6 }; clazz; name; signature; accessFlags; Wydruk 5.10. Struktura Field 1 struct InstField : Field { 2 /* 3 * This field indicates the byte offset from the beginning of the 4 * (Object *) to the actual instance data; e.g., byteOffset==0 is 5 * the same as the object pointer (bug!), and byteOffset==4 is 4 6 * bytes farther. 7 */ 8 int byteOffset; 9 }; Wydruk 5.11. Struktura InstField 38 Struktura ClassObject definiuje prototyp klasy. To tu zawarta jest pełna informacja o strukturze klasy, między innymi wskaźnik do listy pól klasy – InstField* ifields. Struktura Field (Wydruk 5.10), reprezentująca pole klasy, zawiera m. in. wskaźnik do swojej klasy bazowej (pole clazz) i nazwę pola (pole name). InstField (Wydruk 5.11) dziedziczy po strukturze Field i zawiera dodatkowe pole offset. To pole pozwala na odnalezienie zawartości pola w instancji obiektu. Wartość liczbowa pola offset wyraża oddalenie położenia pola analizowanej klasy od początku obszaru klasy. Innymi słowy, w przypadku zlokalizowania instancji obiektu (w odróżnieniu od prototypu) danej klasy w pamięci, dodanie wartości offset do wskaźnika wskazujacego na początek obszaru obiektu pozwoli na uzyskanie dostępu do wartości pola. Poza strukturą ClassObject, mogącą opisywać typy własne definiowane przez programistę, istnieją jeszcze inne struktury dziedziczące po strukturze Object, opisujące wbudowane typy danych. Spośród nich najbardziej istotna dla analizy łańcuchów znakowych jest struktura StringObject (Wydruk 5.12). Obiekty tego typu przechowują łańcuchy znakowe. 1 2 3 4 5 6 7 struct StringObject : Object { U4 int Int ArrayObject* array() const u2* chars() }; instanceData[1]; length() const; utfLength() const; const; const; Wydruk 5.12. Struktura StringObject Wymienione powyżej struktury definiują prototypy klas aplikacji. Kolejnym krokiem analizy pamięci jest poszukiwanie instancji klas. Funkcja get_class_objects Każdy stworzony w trakcie działania programu obiekt klasy jest typu Object. W związku z tym, każdy obiekt przechowuje informację o swoim prototypie (prototypy to struktury typu ClassObject) w postaci wskaźnika ClassObject* clazz. Chcąc odnaleźć 39 wszystkie obiekty danej klasy, należy przejrzeć pamięć w poszukiwaniu wskaźników zawierających adres wybranego obiektu ClassObject wybranej klasy (Rysunek 5.3). Rysunek 5.3. Obiekty klas w pamięci Znaleziony w ten sposób wskaźnik z dużym prawdopodobieństwem wyznacza początek obszaru zajmowanego przez obiekt klasy. Istnieje ryzyko, ze dany obszar jest już zwolniony i niekoniecznie w całości zawiera wiarygodne dane, część z nich mogła już zostać nadpisana. By wyeliminować takie przypadki, weryfikowane są wartości obszarów, w których powinny znajdować się wskaźniki. Weryfikacja taka polega na sprawdzeniu, czy wartość zawiera się w przedziale adresów dostępnym dla wskaźników do obiektów na danym obszarze sterty. Po odnalezieniu początku obszaru obiektu klasy można przystąpić do poszukiwania wartości pól. W tym celu należy wykorzystać wartość offset pobraną wcześniej ze struktury InstField. Wartość ta wyznacza przesunięcie od początku obszaru klasy, pod którym znajduje się wskaźnik do obszaru pamięci zajmowanego przez pole klasy. Kolejnym krokiem może być sprawdzenie, czy pole jest obiektem konkretnego typu (w przypadku modułu MI Core, typu definiującego łańcuch znakowy - String). W tym celu należy sprawdzić, czy na samym początku obszaru jest wskaźnik do obiektu struktury ClassObject 40 opisującego typ String (np. sprawdzając, czy nazwa tego obiektu to Ljava/lang/String). Jeśli tak, analizowany obiekt jest typu StringObject i pod stałym przesunięciem równym 8 bajtów od początku obiektu znajduje się pierwsze pole klasy - pole value (przesunięcie jest równe 8 ponieważ pole value jest pierwszym polem klasy, a przed nim musi znaleźć się obiekt typu Object o wielkości równej 8 bajtów – Rys. 5.4). Pole to zawiera wskaźnik do tablicy znaków, która reprezentuje zawartość obiektu String. Wartość przesunięcia można ustalić na podstawie wartości pola byteOffset obiektu typu InstField, który reprezentuje pole value lub analizy kodu metody StringObject const u2* chars() const, która zwraca właśnie łańcuch znaków przechowywany w obiekcie typu String. Automatyczna analiza kodu źródłowego systemu pozwoli na ustalenie tych przesunięć dla każdej wersji systemu Android. Rysunek 5.4. Obiekt klasy StringObject w pamięci Za pomocą przedstawionego łańcucha powiązań funkcja get_class_objects poszukuje obiektów w stercie aplikacji. Lokalizowane są obiekty typu String, adres prototypu tej klasy jest pobierany z wyników uruchomienia funkcji get_class_prototypes. Z pomocą informacji o polach obiektów odczytywane są konkretne łańcuchy znakowe przechowywane w obiektach typu String. 41 Funkcja launch_gc Funkcja ta odpowiada za przesłanie do wskazanego procesu sygnału SIGUSR1, który domyślnie sugeruje rozpoczęcie odśmiecania (w ten sposób wywoływany jest tryb odśmiecania GC_EXPLICIT). Do przesyłania sygnału wykorzystywana jest funkcja jądra kill_pid(struct pid *pid, int sig, int priv). Przesłanie sygnału musi się odbywać z wartością pola priv ustawioną na 1, inaczej próba wysłania sygnału nie powiedzie się z powodu niewystarczających uprawnień. Kod wysyłający sygnał jest inicjowany przez zapisanie danych przez MI Manager do pliku konfiguracyjnego w katalogu /sys/kernel, i domyślnie posiada uprawnienia tego programu, które są niewystarczające do wysłania sygnału do procesu innego użytkownika, a takim w Androidzie jest proces każdej innej aplikacji. Ustawienie wartości pola priv na 1 wymusza wykonanie tego kodu z najwyższymi uprawnieniami. 5.2.2 Moduł MI Dalvik Patch Łata MI Dalvik Patch nakładana na kod źródłowy Androida zapewnia czyszczenie zwalnianych danych przy każdym uruchomieniu zmodyfikowanej wersji odśmiecacza za pomocą sygnału SIGUSR1, a więc modyfikuje ona działanie trybu odśmiecania GC_EXPLICIT, jednocześnie usuwając mechanizm pozwalający na blokowanie tego trybu (usuwa sprawdzenie flagi gDvm.disableExplicitGc przed wywołaniem odśmiecania). Za komunikację pomiędzy aplikacją użytkową a systemem odpowiada moduł MI Core, przesyłając na żądanie użytkownika sygnał SIGUSR1 do badanej aplikacji, jak to przedstawiono w opisie funkcji launch_gc. Odebranie tego sygnału rozpoczyna procedurę odśmiecania. Sygnał SIGUSR1 w nie zmodyfikowanej wersji Androida służy do uruchamiania odśmiecania na żądanie (GC_EXPLICIT). Łata MI Dalvik Patch modyfikuje działanie tego trybu odśmiecania, tak by wraz z odznaczaniem obszarów jako nie zajęte były one dodatkowo nadpisywane umowną sekwencją znaków (w Systemie Memory Inspector przyjęto serię znaków 'x'). W kolejnych wersjach systemu można by pokusić się o zmodyfikowanie wszystkich trybów odśmiecania tak, 42 by nadpisywały zwalniane dane, jednak należy uprzednio zrobić dokładne badania sprawdzające, czy takie rozwiązanie nie spowalnia nadmiernie systemu. Zmiany objęły struktury i funkcje Dalvika oraz niskopoziomową bibliotekę do zarządzania pamięcią - dlmalloc. Zmodyfikowane pliki (m. in.): Heap.cpp struktura kGcExplicitSpec funkcja dvmCollectGarbageInternal MarkSweep.cpp funkcja dvmHeapSweepUnmarkedObjects malloc.c funkcja internal_bulk_free() Czynnikiem utrudniającym pracę nad modułem MI Dalvik Patch był brak oficjalnej dokumentacji na temat odśmiecania w Androidzie (wykorzystano jedynie szczątkową dokumentację biblioteki dlmalloc, nie znaleziono natomiast oficjalnej dokumentacji na temat szczegółów strategii działania licznych procedur wyższego poziomu). Poniższe wnioski są więc w większości efektem analizy kodu źródłowego. Parametry odśmiecania Tryb odśmiecania definiowany jest w Androidzie za pomocą 3 parametrów: isPartial, isConcurrent oraz doPreserve. Poniżej przedstawiono krótkie charakterystyki każdego z nich. • isPartial Każdy proces może mieć wiele obszarów sterty. Jeden z tych obszarów jest obszarem własnym procesu (aktywna sterta, heap[0]), a pozostałe są dziedziczone po procesach-rodzicach. Jeżeli proces potomny chce zmienić dane w obszarze odziedziczonym po stercie rodzica, wykonywana jest kopia stron pamięci sterty rodzica, których dotyczą zmiany, a oryginalna sterta rodzica pozostaje bez zmian (mechanizm copy-on-write w systemach linuksowych). 43 Jeśli flaga jest ustawiona jest na true, proces odśmiecania obejmie tylko dane znajdujące się na aktywnej stercie. Natomiast jeżeli flaga zostanie ustawiona na wartość false, odśmiecacz przejrzy dane na wszystkich stertach. Dla aktywnej sterty odśmiecacz odznaczy zwolnione obiekty zarówno w swoich własnych wewnętrznych strukturach, jak i na stercie. Dla pozostałych obszarów stery (jeżeli są brane pod uwagę) odśmiecacz odznaczy zwolnione obiekty tylko w swoich wewnętrznych strukturach. Modyfikacja danych w obszarach sterty odziedziczonych po procesie rodzica spowodowałaby powstanie kopii stron pamięci, których dotyczą zmiany, a to mogłoby spowodować wręcz utratę pamięci w wyniku odśmiecania. Strategia ta nie została zmodyfikowana w module MI Dalvik Patch. Wydaje się, że byłaby to zbyt duża ingerencja, która mogłaby znacznie zaburzyć działanie systemu. • isConcurrent – jeśli true, podczas etapu oznaczania (mark) odśmiecacz czasowo blokuje i odblokowuje wątki, tak by nie powodować przerw w działaniu całej aplikacji. Aplikacja przez cały czas reaguje na działania użytkownika, przez co odśmiecanie nie staje się uciążliwym dla niego procesem. • doPreserve – jeśli true, odśmiecacz nie zwalnia miękkich referencji (soft reference), dopóki nie jest to konieczne z powodu braku pamięci. Taka strategia pozwala programistom na używanie miękkich referencji jako pamięci tymczasowej służącej np. do przechowywania załadowanych obrazków. Wszystkie te parametry ustawione są w trybie MI Dalvik Patch na false, by zapewnić pełne czyszczenie bez pominięcia żadnego z obszarów. Flaga isConcurrent o wartości false nie powoduje problemów z użytkowaniem aplikacji, ponieważ wywołanie czyszczącego odśmiecacza musi nastąpić z poziomu innej aplikacji (MI Manager), a aplikacja podlegająca czyszczeniu znajduje się wtedy w stanie zawieszenia. Wydruk 5.13, Wydruk.5.14, Wydruk 5.15 oraz Wydruk 5.16 prezentują oryginalne parametry różnych trybów odśmiecania. 44 1 2 3 4 5 6 static const GcSpec kGcForMallocSpec = { true, /* isPartial */ false, /* isConcurrent */ true, /* doPreserve */ "GC_FOR_ALLOC" }; Wydruk 5.13. Obiekt struktury GcSpec dla trybu GC_FOR_MALLOC 1 2 3 4 5 6 static const GcSpec kGcConcurrentSpec true, /* isPartial */ true, /* isConcurrent */ true, /* doPreserve */ "GC_CONCURRENT" }; = { Wydruk 5.14. Obiekt struktury GcSpec dla trybu GC_CONCURRENT 1 2 3 4 5 6 static const GcSpec kGcExplicitSpec = { false, /* isPartial */ true, /* isConcurrent */ true, /* doPreserve */ "GC_EXPLICIT" }; Wydruk 5.15. Obiekt struktury GcSpec dla trybu GC_EXPLICIT 1 2 3 4 5 6 static const GcSpec kGcBeforeOomSpec = { false, /* isPartial */ false, /* isConcurrent */ false, /* doPreserve */ "GC_BEFORE_OOM" }; Wydruk 5.16. Obiekt struktury GcSpec dla trybu GC_BEFORE_OOM Nadpisywanie zwalnianych zmiennych Nadpisywanie zmiennych realizowane jest w obrębie biblioteki dlmalloc. Zwalnianie pamięci w tej bibliotece odbywa się etapami 1. Nieużywane fragmenty pamięci (chunk) są łączone z sąsiadującymi nieużywanymi fragmentami. 2. Połączone, duże fragmenty są odznaczane jako nieużywane. By usunąć dane z pamięci, w kroku 2. złączony obszar pamięci jest nadpisywany znakami 'x', z pominięciem końcowego fragmentu o wielkości TWO_SIZE_T_SIZES. W tym fragmencie 45 przechowywane są dane na temat całego obszaru, nadpisanie go powoduje błędne działanie biblioteki i brak możliwości uruchomienia systemu. Modyfikacja objęła jedynie funkcję internal_bulk_free(), ponieważ z analizy kodu całości odśmiecacza wynika, że pozostałe funkcje zwalniające nie są w ogóle wywoływane w kodzie Dalvika. Celem powyższych zmian była modyfikacja minimalnej ilości kodu, ponieważ każda dodatkowa operacja dołączona do tak krytycznych procedur jak zarządzanie pamięcią może wpłynąć na szybkość i stabilność działania systemu. 46 6 TESTY I OBSERWACJE Działanie łaty MI Dalvik Patch oraz systemu MI Core zostało sprawdzone z użyciem specjalnie przygotowanej aplikacji testowej (pozwalającej obserwować łańcuchy znakowe w kontrolowanym środowisku) oraz powszechnie używanych aplikacjach: domyślnej przeglądarki Androida (Android Browser) i domyślnego klienta poczty elektronicznej Androida (Email). Sprawdzono zawartość pamięci uruchomionych aplikacji w systemie w wersji niezmodyfikowanej oraz w wersji z czyszczącym odśmiecaczem. Testy wykonano 30 razy dla każdej aplikacji. Sprawdzono 2 scenariusze testowe, z których jeden dotyczył działania czyszczącego odśmiecacza, a drugi czyszczenia na żądanie. 6.1 Aplikacja testowa Aplikacja testowa badała wpływ czyszczenia na obecność w pamięci haseł wpisywanych do najprostszej domyślnej kontrolki Androida (EditText). Przebieg eksperymentu 1: 1. Wprowadzenie hasła do kontrolki. 2. Wciśnięcie przycisku powodującego usunięcie widocznego tekstu. Tekst z pola tekstowego nie był dodatkowo zapamiętywany w żadnej zmiennej. 3. Przeniesienie aplikacji testowej w tło i uruchomienie aplikacji MI Manager. 4. Wyszukanie instancji tekstu wpisanego do kontrolki w pamięci aplikacji testowej. 5. Uruchomienie czyszczącego odśmiecacza w aplikacji testowej. Obserwacje: Wpisanie ciągu znaków do kontrolki powodowało powstanie na stercie wielu instancji wpisanego łańcucha (Wydruk 6.1). Uruchomienie czyszczącego odśmiecacza powodowało całkowite zniknięcie hasła z pamięci, co oznacza, że po usunięciu z widoku dane nie były już przechowywane w żadnej aktywnej zmiennej kontrolki. 1 2 3 4 5 6 7 8 0043b3d0 0043b3e0 ... 00442550 00442560 ... 00456a40 00456a50 |H.a.s.l.o.T.a.j.| |n........8......| |........H.a.s.l.| |o.T.a.......#...| |........H.a.s.l.| |o.T.a.j.n.e.....| Wydruk 6.1. Ślady haseł w systemie 47 Przebieg eksperymentu 2: 1. Wprowadzenie hasła do kontrolki. 2. Przeniesienie aplikacji testowej w tło i uruchomienie aplikacji MI Manager. 3. Wyszukanie instancji wpisanego do kontrolki hasła w pamięci aplikacji testowej. 4. Czyszczenie na żądanie wszystkich znalezionych instancji hasła. 5. Przywrócenie aplikacji testowej i obserwacja wpływu czyszczenia na widoczny w kontrolce tekst. Obserwacje: Dane widoczne w kontrolce zmieniły się, co potwierdza skuteczność nadpisywania danych (Rys. 6.1 i Rys 6.2). Przy przenoszeniu aplikacji w tło kontrolka nie zapamiętuje swojej zawartości w plikach zewnętrznych i nie odczytuje ich przy przywracaniu, dzięki czemu możliwe jest trwałe usunięcie danych. Rysunek 6.1. Hasło w kontrolce przed czyszczeniem Rysunek 6.2 Hasło w kontrolce po czyszczeniu 6.2 Przeglądarka internetowa (Android Browser) Przebieg eksperymentu 1: 1. Wprowadzenie hasła i loginu do kontrolki logowania na konto Google. 2. Dokonanie nieudanej próby logowania, w wyniku czego dane znikały z kontrolki (nie była wybrana opcja zapamiętania hasła). 48 3. Przeniesienie aplikacji Browser w tło i uruchomienie aplikacji MI Manager. 4. Wyszukanie instancji wpisanego do kontrolki hasła w pamięci aplikacji Browser. 5. Uruchomienie czyszczącego odśmiecacza w aplikacji Browser. 6. Uruchomienie czyszczenia na żądanie wszystkich instancji haseł w aplikacji Browser. Obserwacje: Po wpisaniu hasła i próbie logowania w pamięci występowały liczne instancje hasła (Wydruk 6.2). Obserwowano od 3 do 8 wystąpień. Po ukończonym odśmiecaniu można było odnaleźć od 1 do 2 instancji hasła (Wydruk 6.3). Prawdopodobnie były to aktywne zmienne historii haseł, ponieważ odśmiecanie nie usuwało ich nawet w pewnym odstępie czasowym (ok. 30 minut) od wpisania hasła. Usunięcie haseł za pomocą czyszczenia na żądanie powodowało całkowite zniknięcie wybranych podciągów z pamięci bez zaburzenia działania aplikacji. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0052e810 0052e820 0052e830 0052e840 0052e850 0052e860 0052e870 ... 00547dd0 00547de0 00547df0 00547e40 ... 00547e50 00547e60 00547e70 ... 005772f0 00577300 00577310 00577320 ... 00580950 00580960 00580970 |Q.I.g.&.E.m.a.i.| |l.=.a.s.h.i.r.e.| |3.4.3.0.%.4.0.g.| |m.a.i.l...c.o.m.| |&.P.a.s.s.w.d.=.| |H.a.s.l.o.T.a.j.| |n.e.....j...c.z.| |....;...PI......| |........H.a.s.l.| |o.T.a.j.n.e.....| |.@..+...PI......| |........H.a.s.l.| |o.T.a.j.n.e.....| |.>..............| |PI..............| |H.a.s.l.o.T.a.j.| |n.e.............| |....B...x]......| |........H.a.s.l.| |o.T.a.j.n.e.....| |........k.......| Wydruk 6.2. Ślady haseł w systemie niezmodyfikowanym 49 1 2 3 4 5 6 7 8 9 004a5920 004a5930 004a5940 004a5950 ... 005764f0 00576500 00576510 00576520 |P...............| |H.a.s.l.o.T.a.j.| |n.e.............| |................| |xxxx+...P.......| |........H.a.s.l.| |o.T.a.j.n.e.I...| |.....v..xxxxxxxx| Wydruk 6.3. Ślady haseł w systemie zmodyfikowanym Przebieg eksperymentu 2: 1. Wprowadzenie loginu do kontrolki logowania na konto Google. 2. Przeniesienie aplikacji w tło i uruchomienie aplikacji MI Manager. 3. Wyszukanie wszystkich instancji wpisanego do kontrolki hasła w pamięci aplikacji. 4. Czyszczenie na żądanie wszystkich znalezionych instancji hasła. 5. Przywrócenie aplikacji i obserwacja wpływu czyszczenia na widoczny w kontrolce tekst. Obserwacje: Dane widoczne w kontrolce nie zmieniły się (Rys. 6.3). Wynik ten sugeruje, że przy przenoszeniu aplikacji w tło zawartość kontrolki została zapamiętana w zewnętrznych zasobach i została ponownie wczytana przy przywracaniu kontrolki lub jest przechowywana w pamięci alokowanej przez kod natywny. Rysunek 6.3. Widok aplikacji Android Browser z nie zmienionym tekstem w kontrolce 50 6.3 Klient poczty elektronicznej Przebieg eksperymentu 1: Jak w punkcie 6.2 (zamiast na konto Google logowano się na konto e-mailowe) Obserwacje: Podobnie jak w przypadku przeglądarki Android Browser, w systemie niezmodyfikowanym występowały liczne ślady hasła (Wydruk 6.4), zaś w systemie zmodyfikowanym po ukończonym odśmiecaniu można było odnaleźć jedną instancję hasła (najwyraźniej przechowywaną w aktywnej zmiennej). Usunięcie haseł za pomocą czyszczenia na żądanie powodowało całkowite zniknięcie wybranych podciągów z pamięci bez zaburzenia działania aplikacji. 1 2 3 4 5 6 7 8 9 10 11 12 004c11c0 004c11d0 004c11e0 0044deb0 ... 0044dec0 0044ded0 0044dee0 ... 004dea40 004dea50 004dea60 |P...............| |H.a.s.l.o.T.a.j.| |n.e.1.2.3.......| |.#...D..........| |H.a.s.l.o.T.a.j.| |n.e.1.2.3.......| |................| |P...............| |H.a.s.l.o.T.a.j.| |................| Wydruk 6.4. Ślady haseł w systemie niezmodyfikowanym 1 2 3 00487360 00487370 00487380 |P...............| |H.a.s.l.o.T.a.j.| |n.e.1.2.3.xxxxx.| Wydruk 6.5. Ślady haseł w systemie zmodyfikowanym Przebieg eksperymentu 2: Jak w punkcie 6.2 (zamiast loginu konta Google wpisywano adres e-mail) Obserwacje: Dane widoczne w kontrolce zostały nadpisane (Rys. 6.4 i Rys 6.5). 51 Rysunek 6.4. Login w kontrolce aplikacji Email przed czyszczeniem Rysunek 6.5. Login w kontrolce aplikacji Email po czyszczeniu 6.4 Wnioski 1. W odstępie do 30 minut od usunięcia widocznego łańcucha z kontrolki w pamięci testowanych aplikacji wykryto jeszcze trzy do ośmiu instancji łańcucha znakowego. Przeniesiona w tło aplikacja nie przetwarza intensywnie danych, więc hasła nie są nadpisywane. Liczba wykrytych haseł może się różnić w zależności od środowiska. Podczas intensywnej pracy aplikacji na słabszym sprzęcie dane będą częściej nadpisywane [1]. 2. Aplikacje Browser i Email przechowują jedną lub więcej instancji wprowadzonego do kontrolki łańcucha znakowego wśród swoich aktywnych referencji (nawet jeśli 52 reprezentuje on hasło już niepotrzebne, które oficjalnie nie zostało zapisane), co uniemożliwia całkowite usunięcie hasła za pomocą czyszczącego odśmiecacza. W związku z tym czyszczący odśmiecacz usuwa około 80% wrażliwych danych. 3. W przypadku przeglądarki internetowej za pomocą odśmiecania udało się usunąć najłatwiejsze do znalezienia w pamięci hasło (zlokalizowane w sąsiedztwie adresu email i nazw kontrolek: Email=ashire3430%40gmail.com&Passwd= HasloTajne). 4. Oficjalne aplikacje Androida używają różnych kontrolek. Kontrolki te obsługują wrażliwe dane w zupełnie odmienny sposób. Ich zawartość może być przechowywana nie tylko w pamięci, ale także w zewnętrznych zasobach. Wskazuje to na brak jednolitej strategii ochrony wrażliwych danych. 5. Nadpisywanie wrażliwych danych kontrolek po zakończeniu procesu logowania nie wpływa na stabilność aplikacji. Podobnie nadpisywanie tekstu przechowywanego w kontrolce nie powoduje problemów z aplikacją. 6. Badania testujące działanie czyszczącego odśmiecacza były przeprowadzane jedynie w przypadkach gdy po umieszczeniu tekstu w kontrolce nie uruchomił się odśmiecacz typu innego niż GC_EXPLICIT, którego działanie zostało zmodyfikowane. Pozostałe tryby są uruchamiane przez system w wybranych przez niego momentach i nie można ich kontrolować z zewnątrz. Jak już wspomniano, tryby te nie zostały zmodyfikowane tak, by odśmiecanie automatycznie nadpisywało dane. Uruchomienie systemowego odśmiecacza powoduje odznaczenie nieaktywnych obiektów w wewnętrznych strukturach maszyny wirtualnej, w związku z czym uruchomiony później czyszczący odśmiecacz nie wykryje tych danych ponownie i nie nadpisze ich. W testowanym systemie nie wystąpiła taka sytuacja, ale na systemach bardzo obciążonych lub o mniejszej ilości pamięci pamięci RAM (a co za tym idzie, mniejszej ramce pamięci dostępnej dla pojedynczej aplikacji) systemowe odśmiecanie będzie prawdopodobnie uruchamiane częściej. Wtedy należy się zdecydować na czyszczenie na żądanie, jeśli istnieje uzasadnione przypuszczenie, że dane nie będą już używane przez aplikację. Po przeprowadzeniu dokładnych badań wydajności można by także zdecydować o zmianie wszystkich rodzajów odśmiecania na odśmiecanie czyszczące w module MI Dalvik Patch. 53 7. PODSUMOWANIE Głównym celem niniejszej pracy było stworzenie prototypu systemu realizującego mechanizm bezpiecznej dealokacji w systemie Android. Powstałe rozwiązanie realizuje założenia koncepcyjne i cele sformułowane na początku pracy nad projektem. Przedstawione wyniki testów pokazują jego przydatność i możliwości. Podczas pracy nad systemem dostrzeżono możliwości dalszych badań oraz rozszerzenia jego funkcjonalności. Modyfikacją wartą rozważenia jest wprowadzenie mechanizmu czyszczenia do wszystkich trybów odśmiecania w Androidzie. Wymaga to oczywiście przeprowadzenia dodatkowych badań wydajności zmodyfikowanego systemu, jednak wydaje się, że narzuty nie byłyby znaczne. Czyszczenie pamięci przy każdym odśmiecaniu zwiększyłoby bezpieczeństwo systemu. Moduł MI Core mógłby zostać rozszerzony o możliwość wyszukiwania wyrażeń regularnych w pamięci oraz konfigurację zapisywania do pliku obiektów typu String o wybranych cechach. Dobrym pomysłem byłoby też zapewnienie integracji z silnikiem Volatility, tak by system MI mógł generować pliki gotowe do analizy pamięci na komputerze stacjonarnym. Ważną część pracy stanowiły wykonane testy oficjalnych aplikacji Androida. Zademonstrowano możliwe zagrożenia wrażliwych danych. Niestety, eksperymenty własne potwierdziły wyniki badań prezentowane w literaturze: aplikacje mobilne nie szyfrują danych oraz nie stosują bezpiecznych kontrolek. Wykryto różne sposoby przetwarzania wrażliwych danych przez kontrolki używane w oficjalnych aplikacjach Androida, co wydaje się świadczyć o braku jednolitych standardów bezpieczeństwa. Udokumentowano zwiększenie bezpieczeństwa, które udało się uzyskać za pomocą systemu MI. Zaobserwowano, że większość niepotrzebnych wrażliwych danych pozostających w pamięci aplikacji można zwolnić za pomocą czyszczącego odśmiecacza. Dane w aktywnych zmiennych można wyczyścić za pomocą mechanizmu nadpisywania na żądanie, co w badanych przypadkach nie spowodowało utraty stabilności aplikacji. Wnioskując, bezpieczna dealokacja w systemie Android jest możliwa i warto rozwijać systemy, które mogą w ten sposób zwiększyć bezpieczeństwo użytkowania urządzeń mobilnych. 54 BIBLIOGRAFIA [1] Xenakis Ch., Ntantogian Ch. Acquisition and Analysis of Android Memory. Department of Digital Systems, University of Piraeus [2] Thing, Vrizlynn L. L., Kian Yong Ng, Ee-Chien Chang Live memory forensics of mobile phones. Digital Forensic Research Workshop 2010. [3] Chow J., Pfaff B., Garfinkel T., Rosenblum M. Shredding Your Garbage: Reducing Data Lifetime Through Secure Deallocation. Stanford University, Department of Computer Science [4] Karayianni, S., Katos V. and Georgiadis, C. K., A framework for password harvesting from volatile memory, International Journal of Electronic Security and Digital Forensics [5] Macht H. Live Memory Forensics on Android with Volatility. Department of Computer Science, Friedrich-Alexander University Erlangen-Nuremberg [6] Yarrow J. This Chart Shows Google's Incredible Domination Of The World's Computing Platforms – http://www.businessinsider.com/androids-share-of-the-computing-market-2014-3 [7] Warren T. Google touts 1 billion active Android users per month – http://www.theverge.com/2014/6/25/5841924/google-android-users-1-billion-stats [8] Raporty dot. najnowszych trendów w rozwoju aplikacji – http://www.developereconomics.com/reports/q1-2014/ [9] Sverdlove H., Cilley J. Pausing Google Play: More Than 100,000 Android Apps May Pose Security Risks – https://www.bit9.com/files/1/Pausing-Google-Play-October2012.pdf [10] Oficjalne statystyki na temat sklepu Google Play – http://www.appbrain.com/stats/number-of-android-apps [11] Preiss B. Mark-and-Sweep Garbage Collection – http://www.brpreiss.com/ books/opus5/html/page424.html [12] Lea D. A Memory Allocator – http://g.oswego.edu/dl/html/malloc.html 55 [13] Cassidy S. Diagnosis of the OpenSSL Heartbleed Bug – http://blog.existentialize.com/diagnosis-of-the-openssl-heartbleed-bug.html [14] http://commons.wikimedia.org/wiki/File:Android-System-Architecture.svg [15] http://www.programcreek.com/2009/02/diagram-to-show-java-strings-immutability/ [16] Żródło obrazu systemu Android 4.3 – https://code.google.com/p/android-x86/downloads/detail?name=android-x86-4.320130725.iso& [17] Instrukcja budowy jądra Linuksa do wykorzystania w Androidzie – https://code.google.com/p/volatility/wiki/AndroidMemoryForensics [18] http://insightfullogic.com/blog/2013/mar/6/garbage-collection-java-2/ [19] Opis narzędzia logcat – http://developer.android.com/tools/help/logcat.html [20] Dokumentacja klasy System – http://developer.android.com/reference/java/lang/System.html 56 DODATKI Dodatek A - Kompilacja systemu 1. Budowa jądra Przebudowa jądra jest konieczna by uruchomić podsystem MI Core. W celu użytkowania systemu na urządzeniu należy pobrać kod źródłowy jądra z serwisu producenta urządzenia i wykonać kroki 3-6 poniższej instrukcji. W celu użytkowania systemu na emulatorze należy pobrać i zbudować kod źródłowy emulatora Androida Goldfish zgodnie z instrukcjami [17]: 1. Klonowanie repozytorium: $git clone https://android.googlesource.com/kernel/goldfish.git ~/androidsource 2. Ewentualna zmiana gałęzi w repozytorium (celem wybrania innej niż domyślna wersji emulatora): $git checkout -t remotes/origin/android-goldfish-2.6.29 -b goldfish 3. Ustawienie zmiennych środowiskowych: $export ARCH=arm $export SUBARCH=arm $export CROSS_COMPILE=arm-eabi- 4. (Emulator) Stworzenie pliku .config w katalog głównym, w którym znajduje się Goldfish: $make goldfish_armv7_defconfig (Urządzenie fizyczne) Przekopiowanie z telefonu pliku konfiguracyjnego (jego lokalizacja na telefonie to /proc/config.gz), rozpakowanie pliku, zmiana nazwy na .config i przekopiowanie do katalogu głównego źródeł jądra 5. Ustawienie w pliku .config opcji pozwalających na dynamiczne ładowanie modułów: CONFIG_MODULES=y CONFIG_MODULES_UNLOAD=y CONFIG_MODULES_FORCE_UNLOAD=y 6. Budowanie jądra: $make 57 2. Budowa systemu Android z łatą MI Dalvik Patch By uruchomić system z czyszczącym odśmiecaczem należy pobrać źródła projektu Android Open Source Project, zastosować łatę MI Dalvik Patch, a następnie zbudować system. Poniżej opis poszczególnych kroków: 1. Pobranie źródeł systemu Android 4.3 ze strony Android Open Source Project zgodnie z instrukcjami zamieszczonymi na stronie https://source.android.com; 2. Nałożenie na odpowiednie pliki łaty MI Dalvik Patch. $cd sciezka/do/katalogu/zrodel/androida $patch < mi-dalvik-patch 3. Zbudowanie systemu (z katalogu głównego źródeł Androida): $source build/envsetup.sh $lunch full-eng $make -j4 3. Budowa i uruchamianie modułu MI Core 1. Ściągnięcie pakietu Android Development Tools (ADT) ze strony http://developer.android.com/tools/sdk/eclipse-adt.html oraz Android Native Development Kit (NDK) ze strony: https://developer.android.com/tools/sdk/ndk/index.html 2. Podstawienie własnej ścieżki do NDK w pliku Makefile w katalogu głównym MI Core: CROSS_COMPILE=/sciezka/do/NDK/android-ndk-r9c/toolchains/arm-linuxandroideabi-4.6/prebuilt/linux-x86_64/bin/arm-linux-androideabi- 3. Przejście w konsoli do katalogu głównego MI Core i budowa modułu: $make 4. Przeniesienie modułu do systemu Android (urządzenie musi być podłączone do komputera przez kabel USB): $<sciezka-do-ADT>/adt-bundle-linux-x86_64-20131030/sdk/platform-tools/adb push ./memory-print-full-scan.ko /sdcard/memory-print 5. Zalogowanie na konsolę Androida: $<sciezka-do-ADT>/adt-bundle-linux-x86_64-20131030/sdk/platform-tools/adb shell 6. W konsoli Androida zamontowanie modułu na urządzeniu (na urządzeniu musi być dostępny tryb administratora – tryb jest domyślnie aktywowany na emulatorze) $insmod /sdcard/memory-print 7. By usunąć moduł z systemu należy wydać polecenie: $rmmod memory-print-full-scan 58 4. Uruchamianie systemu 1. (Emulator) uruchomienie systemu z podaniem ścieżki do wcześniej zbudowanego jądra po opcji -kernel: $emulator -kernel ~/sciezka/do/zbudowanego/jadra/zImage -show-kernel (Urządzenie) uruchomienie systemu zgodnie z instrukcjami: https://source.android.com/source/building-devices.html 2. Instalacja aplikacji Memory Manager poprzez Androidowy instalator aplikacji .apk 3. Zamontowanie modułu MI Core (punkt 3.5 – 3.6) 4. System jest gotowy do pracy Dodatek B – Narzędzia do analizy pamięci w systemie Android W poszukiwaniu ogólnie dostępnych narzędzi związanych z zagadnieniem analizy pamięci w systemie Android autorka zlokalizowała dwa narzędzia niekomercyjne o różnych możliwościach i przeznaczeniu. Volatility i moduł LiME LiME jest modułem jądra Linuksa pozwalającym na dokonanie zrzutu całej pamięci fizycznej systemu, dostosowanym do użytkowania w Androidzie (wykorzystuje tylko funkcje eksportowane przez androidową wersję jądra Linuksa). Zrzut pamięci można zapisać na dysk lub przesłać poprzez sieć. Zrzuty pamięci wykonane za pomocą modułu LiME mogą być następnie analizowane przez Volatility, silnik do analizy pamięci różnych systemów (Android, Linux, MacOS). Do analizy zrzutów Volatility wykorzystuje struktury jądra Linuksa. Możliwe jest zatem np. wyseparowanie listy procesów czy przeglądanie otwartych połączeń. Celem, którym kierowali się twórcy przy pracy nad modułem zrzucającym pamięć było zminimalizowanie interakcji pomiędzy procesami przestrzeni użytkownika a procesami przestrzeni jądra, tak by produkowane zrzuty miały jak największą przydatność analityczną. Dokładny opis działania Volatility wraz z opisami struktur wirtualnej maszyny Dalvik zamieszczono w [5]. W celu wykorzystania LiME należy zbudować jądro systemu Linux dla Androida ze specjalną opcją pozwalającą na ładowanie modułów. By załadować moduł na urządzeniu, potrzebny jest dostęp administratora (root). 59 Ograniczeniem tego systemu jest konieczność wykonywania analizy na komputerze stacjonarnym. Zrzuty pamięci wykonywane przez LiME muszą być przeniesione na stację roboczą z zainstalowanym programem Volatility. Nie jest również możliwe ograniczenie zakresu zrzutu do pamięci interesujących nas procesów, przez co pozyskiwanie i przesyłanie zrzutów pamięci może trwać długo. Przeznaczeniem całego systemu jest analiza pamięci, nie udostępnia on narzędzi pozwalających na modyfikowanie zawartości wybranych obszarów pamięci na urządzeniu. DDMS – Dalvik Debug Monitor Server DDMS to jedno z oficjalnych narzędzi do analizy i debugowania aplikacji na Androida. Można go używać z poziomu środowiska Eclipse lub jako niezależnego narzędzia. DDMS pozwala na wykonywanie i szczegółową analizę zrzutów pamięci aplikacji w narzędziu MAT (Eclipse Memory Analyzer). Przeznaczeniem systemu jest wykrywanie problemów z zarządzaniem pamięcią w tworzonych aplikacjach. Można go używać jedynie do profilowania aplikacji, których kod źródłowy jest dostępny. DDMS działa jedynie z poziomu komputera stacjonarnego, do którego urządzenie jest podłączane kablem USB. Dodatek C – Zawartość załączonej płyty CD I. thesis/ Pliki związane z niniejszą pracą I.I. images/ Obrazki wykorzystane w pracy oraz źródła I.II. text/ Źródła pracy w formacie ODT II. src/ Pliki źródłowe II.I. mi-manager/ Źródła modułu MI Manager II.II. mi-core/ Źródła modułu MI Core II.III. mi-dalvik-patch/ III. bin/ III.I. Źródła modułu MI Dalvik Patch Pliki binarne mi-manager/ Skompilowana wersja aplikacji .apk III.II. mi-core/ Skompilowany moduł jądra Linuksa III.III. emulator/ Plik wykonywalny emulatora III.IV. kernel/ Skompilowane jądro Linuksa