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

Podobne dokumenty