> INDICE DEL CORSO
Avvertenza: questa pagina non rientra nel modulo introduttivo del corso. E' rivolta a chi ha già un po' di esperienza con Codename One.
Argomenti:
- È possibile il reverse engineering di un'app fatta con Codename One per risalire al codice sorgente?
- Risalire al codice sorgente delle app fatte con Codename One per Android (transcompilazione da Java a Java, con API differenti)
- Risalire al codice sorgente delle app fatte con Codename One per iOS (transcompilazione da Java a Objective-C)
- Risalire al codice sorgente delle web-app fatte con Codename One (transcompilazione da Java a Javascript)
- Confronto tra il reverse engineering delle applicazioni fatte con Codename One e di quello di applicazioni fatte con strumenti multipiattaforma alternativi
- Conclusioni
È possibile il reverse engineering di un'app fatta con Codename One per risalire al codice sorgente?
Risposta breve: no, in generale la protezione del codice offerta da Codename One è robusta (nel proseguo di questo articolo analizzo le app Android, iOS e Javascript fatte con Codename One), ma ciò non preclude comunque ad un attaccante di studiarsi quel che riesce a decompilare e di provare a modificarlo. Crackers e hackers sono molto appassionati nella loro "professione" e sanno già molto più di quanto si possa insegnare loro: sono capaci di starsene per ore e giorni davanti a un computer finché non riescono a impadronirsi di un sistema. Non sarebbe la prima volta che un ragazzo o una ragazza adolescenti riescano a entrare in sistemi iper-protetti. Neanche i big dell'informatica sono riusciti a prevenire importanti fughe di dati riservati.
Risposta lunga: guardando più in dettaglio la questione, con specifico riferimento alle app fatte con Codename One, posso osservare che:
- la sicurezza informatica, in senso assoluto, non esiste (cfr. "L'era della persuasione tecnologica", par. "La sicurezza informatica non esiste" e seguenti, pag. 56, sez. 3.11.2), quindi in realtà non posso affermare che un certo tipo di azione non sia possibile (in questo caso risalire al codice sorgente di un'app fatta con Codename One), posso invece asserire a ragion veduta, per quelle che sono le mie conoscenze, se è difficile che accada oppure no;
- se l'app rispetta almeno le precauzioni di base, come l'esecuzione soltanto lato server (e mai lato client) di tutte le operazioni che hanno davvero implicazioni importanti di sicurezza, allora il fatto che qualcuno possa curiosare nel codice sorgente dell'app e metterci mano, in realtà, non dovrebbe preoccuparci più di tanto;
- teniamo presente che tutte le comunicazioni di rete fatte dall'app sono sniffabili e visibili in chiaro con semplici tool, anche usando il protocollo https; un attaccante può modificare le richieste inviate al server e i comportamenti dell'app: come regola generale, l'app non dovrebbe mai avere la possibilità di accedere direttamente al database lato server o di inviare richieste ad esso, tutte le comunicazioni dovrebbero essere filtrate (nel caso di Spring Boot, l'app dovrebbe poter comunicare soltanto con i controllers, i quali a loro volta comunicano con i services, i quali sono gli unici ad avere accesso alle entities, che infine possono accedere al database, e tutte queste comunicazioni tra livelli sono filtrate tramite dao e opportuni controlli di sicurezza);
- la sicurezza di un sistema è come una catena la cui robustezza dipende dall'anello più debole, ma è anche processo continuo e una questione di compromessi: con Codename One + Spring Boot possiamo creare vari livelli di sicurezza, anche un'autenticazione a due fattori con OTP se opportuno (cosa che anch'io ho fatto per account amministrativi); nel codice lato server possiamo adottare alcune misure di sicurezza suggerite da Shai nei suoi corsi nella Codename One Accademy; al tempo stesso, però, la sicurezza insita nel codice ha poca importanza se il server è facilmente bucabile, permettendo ad un attaccante di scaricarsi l'intero database o l'interno fat jar del server (che, peraltro, quello sì che permetterebbe facilmente di risalire al codice sorgente, visto che i classici strumenti di offuscamento, come Proguard, non sono facilmente compatibili con Spring Boot, anzi, creano più problemi che altro);
- progettiamo i nostri sistemi informatici assumendo che i nostri "avversari" li conoscano in dettaglio, sia nel caso di progetti open-source, sia closed-source (cfr. "security by oscurity"): la "sicurezza tramite segretezza" non funziona, è scoraggiata e mai raccomandabile.
Detto ciò, ricordiamo che il codice sorgente di qualsiasi app fatta con Codename One è transcompilato, quindi ammesso anche una decompilazione dell'app sia possibile, questa, al massimo, permetterà di risalire al codice transcompilato dal build server, e non al codice originale. Prendiamo in esame tre casi: le app per Android, le app per iOS, le web-app in javascript.
Risalire al codice sorgente delle app fatte con Codename One per Android (transcompilazione da Java a Java, con API differenti)
Questo è il caso più semplice in assoluto. Esistono ottimi strumenti per decompilare le app per Android, lo stesso Android Studio è capace di decompilare i file class senza problemi. Anche la decompilazione degli apk offuscati con Proguard è semplice, sebbene il codice risultante sia poco utilizzabile.
Codename One offre due modalità per la generazione degli apk: "release" e "debug". Nella prima il codice sorgente è offuscato e ottimizzato da Proguard, l'apk risultante è più leggero (di circa il 25% nei miei test) e la sua decompilazione produce un codice scarsamente leggibile. La modalità debug, invece, genera apk più pesanti, la cui decompilazione va risalire direttamente al codice sorgente.
Supponiamo di generare un apk con il codice sorgente del template "Hello World (Bare Bones)" e di porre la nostra attenzione su queste tre righe di codice:
Form hi = new Form("Decompile Me", BoxLayout.y()); hi.add(new Label("Is it easy to decompile?")); hi.show();
La decompilazione (tramite jadx) dell'apk prodotto in modalità debug genera il seguente codice, che è quasi identico all'originale:
Form hi = new Form("Decompile Me", BoxLayout.m33y()); hi.add((Component) new Label("Is it easy to decompile?")); hi.show();
La decompilazione (sempre tramite jadx) dell'apk prodotto in modalità release genera invece quest'altro codice, che decisamente è assai meno leggibile:
C1617w wVar = new C1617w("Decompile Me", C1444b.m7918a()); wVar.mo7381r((C1582m) new C1282ac("Is it easy to decompile?")); wVar.mo7431cg();
In entrambi i casi, i commenti, se presenti, vengono perduti, mentre le stringhe di testo rimangono come sono (e pertanto permettono ad un attaccante di cercare qualcosa di specifico). Comunque la lezione dovrebbe essere chiara: gli apk prodotti in modalità debug permettono abbastanza facilmente di risalire al codice sorgente, quelli in modalità release no. Se nel codice è stata inserita qualche chiave di accesso, questa resterà leggibile: un trucchetto può essere quello di farne un codifica Xor (nel modo suggerito qui), ma in generale tutte le chiavi di sicurezza dovrebbero trovarsi lato server, se possibile.
Da notare che il reverse engineering delle applicazioni Android fatte con Codename One è generalmente più difficile di quello delle applicazioni "native" (quelle fatte con Android Studio, per capirci) perché Codename One non usa il formato XML (fonte).
Risalire al codice sorgente delle app fatte con Codename One per iOS (transcompilazione da Java a Objective-C)
Mentre decompilare le app per Android è alla portata di chiunque e non richiede più di un minuto, decompilare le app per iOS può sembrare una mission impossible. Tra l'altro sono protette dal DRM di Apple (FairPlay), come spiegato qui. E' veramente molto difficile, è richiesta un'opportuna attrezzatura e molto lavoro manuale. Per avere un'idea del tipo di difficoltà, suggerisco di leggere queste risposte su Stack Overflow: Decompiling iPhone App e How to decompile iOS app. Ho armeggiato un po' con Hex-Rays Decompiler: capire il codice da esso generato mi pare gravoso.
Ad ogni modo, per completezza, riprendendo come esempio le tre linee di esempio precedentemente riportate, dal build server di Codename One sono transcompilate in un modo estremamente difficile da leggere. Consideriamo le scritte "Decompile Me" e "Is it to decompile?": queste si trovano immerse nel file cn1_class_method_index.m, della chilometrica lunghezza di ben 29958 righe, in questo modo:
"Decompile Me" /* 9379 */, "Is it easy to decompile?" /* 9380 */,
Mentre l'uso di tali stringhe avviene all'interno del file net_informaticalibera_decompileme_MyApplication.m, per la precisione in queste righe:
PUSH_POINTER(STRING_FROM_CONSTANT_POOL_OFFSET(9379));
e
/* CustomInvoke */com_codename1_ui_Label___INIT_____java_lang_String(threadStateData, SP[-1].data.o, STRING_FROM_CONSTANT_POOL_OFFSET(9380)); SP -= 1;
Del codice Java di partenza non c'è neanche lontanamente traccia. Se anche un attaccante riuscisse a decompilare un codice del genere, non se ne farebbe di nulla.
Risalire al codice sorgente delle web-app fatte con Codename One (transcompilazione da Java a Javascript)
Anche in questo caso risalire al codice sorgente di partenza può sembrare una mission impossible.
Le web-app Javascript prodotte da Codename One hanno tre livelli di offuscamento.
- Il primo livello è il "TeaVM's tree pruning" (potatura di TeaVM), che fa in modo che sia incluso solo il codice raggiunto dal metodo principale: tutto il resto viene eliminato.
- Il secondo livello è la trasformazione del codice operata da TeaVM per supportare i thread. Questa trasformazione mantiene intatte alcune cose come i nomi dei metodi e i nomi delle variabili, però non esiste un mapping uno a uno dalle linee di codice Java alle linee di codice Javascript (ad esempio, una linea di codice Java può essere tradotta in due o più linee Javascript in posizioni diverse, per supportare il threading). Questa trasformazione, comunque, di per sé, sarebbe già difficile da decompilare, perché:
- non è uno standard pubblicato e quindi noto (a differenza del bytecode della Java Virtual Machine);
- le regole di trasformazione cambiano ad ogni aggiornamento, man mano che TeaVM migliora;
- la trasformazione non ha necessariamente una trasformazione "inversa" unica.
- Il terzo livello di trasformazione è la minificazione, che scambia tutti i nomi di metodi e variabili con nomi brevi e privi di significato.
E' quindi evidente che risalire dal codice Javascript prodotto dal build server di Codename One a codice Java "utilizzabile" non è realistico. Comunque, per togliere ogni dubbio, propongo un esempio concreto.
Questa è una web-app fatta da me con Codename One: https://www.informatica-libera.net/giorno-per-giorno-ikeda/
Le classi da me scritte sono state così trasformate in javascript: https://www.informatica-libera.net/giorno-per-giorno-ikeda/teavm/classes.js
Sfido chiunque a capirci qualcosa...
Confronto tra il reverse engineering delle applicazioni fatte con Codename One e di quello di applicazioni fatte con strumenti multipiattaforma alternativi
Leggendo in Rete ho notato che alcuni credono che il reverse engineering delle applicazioni fatte con strumenti multipiattaforma sia semplice. Questo può esser vero per strumenti multipiattaforma basati su javascript, in cui il codice sorgente (html + css + javascript) può essere facilmente estratto dai package; ciò, però, non è neanche lontanamente vero per Codename One, che, come ormai dovrebbe essere chiaro, usa un approccio completamente diverso che rende difficile il reverse engineering (cfr. sez. "Packaging", nell'art. "Comparing PhoneGap/Cordova and Codename One").
Conclusioni
Spero di aver dato un contributo sufficiente per comprendere che la protezione del codice sorgente offerta da Codename One è ben robusta. Per concludere, i "fanatici" che vogliano proteggere il proprio codice dall'invio sui servizi cloud di Codename One (*) possono optare per l'uso degli "offline build server", che permettono di transcompilare il proprio codice nella propria macchina, senza inviarlo ai build server di Codename One. Ad ogni modo, ne sconsiglio l'uso: rendono le cose molto più complicate da gestire e fanno venire meno molti dei benefici offerti da Codename One stesso.
(*) tecnicamente, non viene inviato il codice sorgente, ma il Java bytecode, fatta eccezione per l'implementazione delle interfacce native.
Francesco Galgani,
29 dicembre 2019