2013-02-17

Lire en loucedé les SMS envoyés par les applications

Eyes on you, probee ! - © NCIS
Et si il était possible de lire les SMS envoyés par les applications sans rooter son appareil ?
Sans décompiler l'application (ça peut mal tourner) !
Sans installer de coûteux logiciels dédiés aux forensics!
Sans jouer du fer à souder!

Et bien, tout cela est possible et c'est même tout à fait simple à faire.

Cet article, limité à l'envoi d'un SMS texte classique, est en fait de portée générale. En d'autres termes cette note de blog s'articule sur sendTextMessage().

Pour toi, le pressé :

$ adb shell setprop log.tag.SMS VERBOSE
$ adb logcat -c -b radio
# Envoi d'un SMS (avec l'application stock par exemple)
$ adb logcat -b radio GSM:V *:S | grep "sendText: destAddr="
D/GSM     (  902): [SimSmsInterfaceManager] sendText: destAddr=+33xxxxxxxxx scAddr=null text='shdhzh' sentIntent=PendingIntent{40xxxxx8: android.os.BinderProxy@40xxxxx8} deliveryIntent=null

Comprendre les SMS dans Android

Je vais laisser mes réflexes de reverser de côté et rester le plus possible le doigt sur la couture de la documentation. Comme il s'agit d'une analyse superficielle du fonctionnement d'Android, j'ai les sources à portée de main. J'utilise personnellement CyanogenMod mais cela fonctionne aussi avec les sources AOSP.

L'envoi d'un sms passe par sendTextMessage() (souvenez-vous, le périmètre de cet article est restreint), je commence par chercher où se trouve sendTextMessage() dans les sources:
~/android/system.10.1/frameworks $ find . -name "*.java" -exec grep -l "sendTextMessage" {} \+
./base/tests/permission/src/com/android/framework/permission/tests/SmsManagerPermissionTest.java
./opt/telephony/src/java/android/telephony/gsm/SmsManager.java
./opt/telephony/src/java/android/telephony/SmsManager.java
La classe "telephony/gsm/SmsManager" est dépréciée, je vais me focaliser sur "telephony/SmsManager.java". La méthode sendTextMessage() est relativement courte.
    public void sendTextMessage(
            String destinationAddress, String scAddress, String text,
            PendingIntent sentIntent, PendingIntent deliveryIntent) {
        if (TextUtils.isEmpty(destinationAddress)) {
            throw new IllegalArgumentException("Invalid destinationAddress");
        }

        if (TextUtils.isEmpty(text)) {
            throw new IllegalArgumentException("Invalid message body");
        }

        try {
            ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
            if (iccISms != null) {
                iccISms.sendText(destinationAddress, scAddress, text, sentIntent, deliveryIntent);
            }
        } catch (RemoteException ex) {
            // ignore it
        }
    }
Ce n'est pas ici que vais trouver des éléments utiles, je m'oriente vers sendText() de "isms" (IccSmsInterfaceManager.java).
Là encore la routine est courte :
    public void sendText(String destAddr, String scAddr,
            String text, PendingIntent sentIntent, PendingIntent deliveryIntent) {
        mPhone.getContext().enforceCallingPermission(
                "android.permission.SEND_SMS",
                "Sending SMS message");
        if (Log.isLoggable("SMS", Log.VERBOSE)) {
            log("sendText: destAddr=" + destAddr + " scAddr=" + scAddr +
                " text='"+ text + "' sentIntent=" +
                sentIntent + " deliveryIntent=" + deliveryIntent);
        }
        mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent);
    }
Avant tout envoi de SMS, Android pourrait inscrire dans ses logs une chaine de caractères reprenant l'adresse de destination, l'adresse source et le contenu du message. Tout ceci est au conditionnel, mais BINGO !

Comprendre logcat : la base radio

Le logcat ne donnera pas aussi gentiment ce que je cherche. Comme précisé dans la documentation en ligne, Android ne log pas les trop nombreux événements du modem, la "radio". L'information importante est qu'il faut ajouter "-b radio" à la commande adb pour observer le modem.

Par curiosité j'ai essayé de mesurer la loquacité du modem :
$ adb logcat -c -b radio;(((adb logcat -b radio) & sleep 30);kill $!) | wc -l
(...)
982
Bigre ... 982 lignes en 30 secondes.

Comprendre logcat : isLoggable()

Le code source de sendText() vu précédemment utilise dans le log la chaîne "sendText: destAddr=". Si isLoggable() retourne false, cette chaîne ne peut pas apparaitre dans la sortie de logcat.

Or la fonction isLoggable() retourne false par défaut, dit autrement l'envoi d'un sms ne produit AUCUN log.

Pour que isLoggable() retourne true, il faut élever "SMS" au niveau "Log.VERBOSE" :
$ adb shell setprop log.tag.SMS VERBOSE
Voici, en résumé, comment lire via adb un sms envoyé par l'application SMS d'Android :
$ adb shell setprop log.tag.SMS VERBOSE
$ adb logcat -c -b radio
Envoyer un SMS maintenant
$ adb logcat -b radio | grep "sendText: destAddr="
D/GSM     (  902): [SimSmsInterfaceManager] sendText: destAddr=+33xxxxxxxxx scAddr=null text='shdhzh' sentIntent=PendingIntent{40xxxxx8: android.os.BinderProxy@40xxxxx8} deliveryIntent=null

Comprendre logcat : limiter le bruit

Même si grep est un fidèle ami, il est utile de savoir filtrer la sortie de logcat. Cette optimisation est destinée à ménager le cpu du terminal Android, la bande passante USB et accessoirement le cpu de l'ordinateur.
Pour filtrer, il faut limiter au GSM (GSM:V) et ignorer(S) le reste (*:S)
$ adb logcat -b radio GSM:V *:S| grep "sendText: destAddr="
D/GSM     (  902): [SimSmsInterfaceManager] sendText: destAddr=+33xxxxxxxxx scAddr=null text='shdhzh' sentIntent=PendingIntent{40xxxxx8: android.os.BinderProxy@40xxxxx8} deliveryIntent=null

Conclusion

Cet article fait écho à l'analyse à distance d'un terminal compromis qui retransmettait secrètement les SMS reçus. L’intérêt de cette solution réside dans la lecture des données à un niveau interdit par défaut à l'API Android : elles sont en clair et l'application émettrice n'a aucune idée de leur exposition.

La lecture des envois des sms multiples, mms, sms binaires ET la lecture des réceptions des 4 types de SMS sont une simple déclinaison triviale de cet article.

Aucun commentaire: