Merci tout d'abord à l'ANSSI pour leur challenge très intéressant et assez orienté actualité (forensic smartphone) et à Hurukan pour me prêter un petit espace de publication.
Cet article a pour but de présenter "rapidement" le challenge mais je vous encourage vivement à aller lire les solutions bien plus complètes des gagnants dès qu'elles seront disponibles.
Il est à noter qu'il manque la fin du challenge, ayant été bloqué à l'étape "déchiffrement du message GPG".
Dixit le site du challenge : "Le défi consiste à analyser la copie intégrale de la mémoire physique d'un mobile utilisant le système d'exploitation Android."
Analyse initiale
On récupère une archive au format 7zip qui contient le fichier de dump mémoire "challv2".
Un "strings" dessus nous fournit de nombreuses informations :
"[ro.build.description]: [sdk-eng 2.1 ERD79 22607 test-keys]"
- On a donc affaire à Android 2.1.
<package name="com.anssi.textviewer" codePath="/data/app/com.anssi.textviewer.apk" system="false" ts="1270823961000" version="1" userId="10024">
<package name="com.anssi.secret" codePath="/data/app/com.anssi.secret.apk" system="false" ts="1270823989000" version="1" userId="10025">
- On a apparemment 2 applications développées par l'ANSSI lancées lors du dump : textviewer et secret.
bmV3c29mdCwgdHUgZXMgaW50ZXJkaXQgZGUgY2hhbGxlbmdlIHBvdXIgc29jaWFsIGVuZ2luZWVyaW5nIGV4Y2Vzc2lmLg==
- La phrase "newsoft, tu es interdit de challenge pour social engineering excessif." encodée en base64. Un easter egg en somme

On trouve également un gros bloc de texte inintelligible :

On reconnaît vaguement des clés (aux nombreux --- ), et on suppose facilement que le texte CXZES JPW PVUEEH ENF BMHVG correspond à BEGIN PGP PUBLIC KEY BLOCK. De là, on en déduit rapidement que la clé de déchiffrement est -1 +7 +7 +4 -5 +6 -9 -7 0!
Amusez vous à essayer 
CXZES JPW PVUEEH ENF BMHVG
BEGIN PGP PUBLIC KEY BLOCK
Le texte en clair est alors :

La clé publique est une clé DSA de 1024 bits avec une sous-clé Elgamal de 1024 bits également, émise pour « Personne <invalide@nomdedomaine.tld> »
# gpg /tmp/cle.asc
pub 1024D/0CEE4650 2010-03-17 Personne <invalide@nomdedomaine.tld>
sub 1024g/96C9EAD0 2010-03-17 [expire: 2010-07-15]
Le message GPG est chiffré avec 2 clés publiques différentes : 1 avec une clé RSA, l'autre avec la sous-clé Elgamal que l'on nous fournit.
# gpg /tmp/msg.asc
gpg: chiffré avec une clé RSA, ID E1F67BBD
gpg: chiffré avec une clé de 1024 bits ELG-E, ID 96C9EAD0, créée le 2010-03-17
« Personne <invalide@nomdedomaine.tld> »
Pour résoudre le challenge, il faut donc :
- Résoudre les 4 énigmes (trouver les lieux mentionnés ainsi que leurs coordonnées GPS),
- Déchiffrer le message GPG pour récupérer le mot de passe,
- Rentrer les coordonnées GPS des 4 lieux dans l'application puis rentrer le mot de passe,
- Lancer le binaire résultant.
2 autres chaînes sont importantes :
- Bravo ! Va lancer le binaire pour voir si ca a marché
- 477689b3cb25eba2b9d6.....034d329324e36cfb09e63f9018f081df0fe3a2
La première est en fait le message apparaissant quand on rentre les bonnes coordonnées avec le mot de passe dans l'application.
La deuxième est un binaire chiffré en RC4, qui sera déchiffré puis écrit sur disque. C'est normalement ce binaire qui nous indique l'adresse mail de validation.
Enigmes
La partie énigme a été pour moi moins "marrante" car les références étaient pour certaines "capillotractées" (merci Guillaume) :
- Après les rump sessions, rendez-vous chez ce galliforme breton
Facile, c'est le "coq gadby" où se tiendra le social event. Niveau GPS, les coordonnées de Rennes suffisent
- L'abandonnée de Naxos y part pour d'autres cieux
Selon Wikipedia, Thésée abandonna Ariane sur l'ile de Naxos. "autres cieux" fait en fait référence à l'espace. L'endroit à trouver était donc Kourou, base de lancement de la fusée Ariane.
- Le frère de Marvin y est né, sous la grosse table
Pour moi l'énigme la plus tirée par les cheveux : Marvin est en fait l'androide de H2G2. Le "frère" de Marvin n'est pas tiré du livre/film : Marvin est un "android" et le challenge consiste à faire du forensic sur un téléphone Android.
Le frère de l'Android est donc logiquement un autre smartphone (iphone, gphone..). La "grosse table" est une référence à "Bigtable: A Distributed Storage System for Structured Data", un projet de Google. Le lieu à trouver est donc Mountain View, le siège de Google.
- Le nez de ce gigantesque capitaine ne fut libéré qu'en 1993
L'endroit est une falaise aux Etats-Unis.
Reconstruction mémoire virtuelle
A partir de là, il devient nécessaire de reconstituer la mémoire virtuelle du système à partir de la mémoire physique (au niveau mémoire virtuelle, les pages mémoires sont "vues" comme étant consécutives, mais elles peuvent être dispersées n'importe où dans la mémoire physique).
Au niveau forensic mémoire physique, un projet revient régulièrement : Volatility.
Malheureusement, celui-ci n'a pas l'air de gérer correctement l'analyse de dump Linux.
Des patchs ont été développés à l'occasion de DFRWS 2008, voir l'excellent papier des gagnants : http://sandbox.dfrws.org/2008/Cohen_Collet_Walters/Digital_Forensics_Research_Workshop_2.pdf, mais n'ont pas été réintégrés en totalité, et le framework nécessite différents fichiers décrivant les structures noyau, dépendants du système.
Personnellement je n'ai pas trop creusé cette voie.
Il a donc été nécessaire de créer une petite application qui parserait ce fichier de mémoire physique pour reconstituer les structures noyau au moment du dump et pouvoir analyser le système.
Android utilise le noyau Linux. Sous Linux, chaque processus est représenté au niveau du noyau par une structure "task_struct".
Ces task_struct sont chainées entre elles et forment une liste doublement chaînée (chaque process, représenté par une structure task_struct, est chaîné au process précédent et au process suivant).
Parmi les champs d'une task_struct à noter, on a le PID du process, l'UID et le GID de l'utilisateur l'ayant lancé et le champ MM (pointeur sur une structure mm_struct).

(Source de l'image: http://duartes.org/gustavo/blog/)
Une structure mm_struct recense les zones mémoire d'un processus (la pile (start_stack), le tas (start_brk), section de code (start_code), librairies mappées ...) grâce à une liste chainée de vm_area_struct .
Chaque vm_area_struct représente une zone mémoire avec :
- Des flags (Read, Write, Execute, Shared/Private),
- Optionnellement : un fichier associé (pas de fichier associé pour la pile et le tas du process, mais un fichier pour les zones de code des librairies par exemple).

(Source de l'image: http://duartes.org/gustavo/blog/)
Chaque process (task_struct) contient donc une structure mm_struct qui elle même contient une liste chainée de vm_area_struct (chaque zone mémoire du process, une zone mémoire pouvant contenir plusieurs pages mémoire de 4 Ko chacune ici).
Avec les vm_area_struct de chaque process, on peut reconstruire les zones mémoire du processus :
Exemple avec un "cat /proc/self/maps" sur un linux 2.6:

On voit ici la librairie libc (libc-2.10.1.so, inode 497) mappée en 4 vm_area différentes avec des droits différents sur chaque vm_area (r-xp, ---p, ...), et le tas (heap) sans fichier associé (inode à 0).
Il est à noter que le code développé ici ne marcherait probablement pas "out of the box" pour un dump d'un autre système linux (autre version de noyau sur x86 par exemple).
Les champs des structures (task_struct, mm_struct, vm_area_struct) ne sont pas forcément les mêmes selon les version de noyau, les options de compilation (modification des structures, ajout/suppresion de champs..)... donc l'offset d'un champ particulier par rapport au début de la structure change selon les versions.
Exemple avec mm_struct :
...
struct vm_area_struct * mmap; /* list of VMAs */
struct rb_root mm_rb;
struct vm_area_struct * mmap_cache; /* last find_vma result */
unsigned long (*get_unmapped_area) (struct file *filp, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags);
void (*unmap_area) (struct mm_struct *mm, unsigned long addr);
unsigned long mmap_base; /* base of mmap area */
Imaginons que le premier (mmap) et le dernier champ (mmap_base) nous intéressent :
Sur cette version d'Android, mmap est à l'offset 0 de la structure mm_struct, et mmap_base est à l'offset 20.
Si la structure mm_rb (struct rb_root mm_rb; 2ème champ de la structure mm_struct) venait à faire quelques octets de plus (on ajoute un champ dans la structure), l'offset de mmap_base en serait automatiquement modifié, ainsi que tous les champs suivants de la structure.
Il a donc été nécessaire de valider manuellement les offsets de chaque champ intéressant pour les 3 types de structure (grande partie de plaisir devant l'éditeur hexa
) .
Il aurait été possible également de faire un module noyau pour Android qui aurait accédé à tous les champs intéressants des structures, et en comparant l'adresse des champs par rapport à l'adresse de début des structures, on aurait eu facilement leur offset. Je n'ai personnellement pas développé cette piste.
Un autre problème :
On sait qu'on peut récupérer les informations complètes sur les process du système à partir des structures task_struct, et que ces structures sont chaînées entre elles.
Par contre, il faut arriver à repérer une de ces task_struct dans la mémoire physique pour arriver à accéder aux autres (liste doublement chainée de task_struct).
Chaque task_struct contient un tableau de 16 caractères qui contient le nom du processus
(char comm[TASK_COMM_LEN]; avec TASK_COMM_LEN = 16)
On pourrait donc chercher par exemple le process "init" (PID 1) et "hardcoder" l'adresse de sa task_struct dans le script.
Une autre méthode est d'utiliser le process de PID 0 : swapper. Ce processus n'apparaît pas lorsque l'on fait un "ps" donc il est moins connu, mais il a une particularité intéressante : l'adresse virtuelle de sa task_struct est disponible dans le fichier System.map du noyau: c'est l'adresse du symbole "init_task" :
...
c02a0f80 D init_task
...
L'adresse de init_task appartenant à l'espace noyau (adresse > à 0xC0000000), on peut obtenir facilement son adresse physique en soustrayant simplement 0xC0000000 à son adresse :
La task_struct de swapper est donc à l'offset 0x002a0f80 dans le fichier de dump de mémoire physique :

Par contre, le fichier System.map du noyau d'Android n'est pas disponible avec l'émulateur Android, il est donc nécessaire de le reconstruire :
- Sur un système Android 2.1 (dans l'émulateur par exemple), récupérer le fichier /proc/config.gz
- Récupérer les sources du noyau d'Android, et appliquer la config récupérée dans le config.gz
- Recompiler le noyau, le fichier System.map sera également généré
Une fois le script écrit, on est capable de récupérer la liste des process du système, ainsi que les zones mémoire associées:


Pour l'application secret, on voit que le "binaire" /data/com.anssi.secret.apk (sous Android, une application est un fichier .apk , qui est un .zip contenant différents fichiers) est mappé des adresses 0x426f3000 à 0x426f8000. (La partie mappée de 0x426f8000 à 0x426f9000 a un offset de 0x1000 dans le fichier, donc cette partie du fichier se retrouve déjà dans les parties mappées de 0x426f3000 à 0x426f8000. Idem pour la partie mappée de 0x426f9000 à 0x426fe000).
En récupérant les pages mémoires des adresses 0x426f3000 à 0x426f8000 (non compris), soit 5 pages mémoires (chaque page mémoire fait 4 Ko, soit 0x1000 octets), on peut donc récupérer le fichier apk associé.
Il suffit de faire une traduction adresse virtuelle <-> adresse physique de ces adresses et d'aller chercher les données dans le fichier de mémoire physique.
Update: en fait, les pages ne sont juste plus dans les tables de page du noyau, donc on a logiquement des fautes de pages pour celles-ci
Il est à noter que ma fonction de conversion virtuelle <-> physique a apparemment un problème car 2 des 5 adresses virtuelles n'ont pas pu être converties :
0x426f3000 ==> 0x00c25000
0x426f4000 ==> 0x00c26000
adresse 0x426f5000 non résolue
adresse 0x426f6000 non résolue
0x426f7000 ==> 0x00e09000
J'ai du "tricher" pour reconstituer le fichier en entier et pallier les 2 pages mémoires non converties: en regardant les data dans le fichier de mémoire physique, on se rend compte que la page 0x426f5000 est à l'adresse 0x00c27000 (juste après les 2 précédentes en fait) et que la page 0x426f6000 est à l'adresse 0x00e08000 (juste avant la dernière).
Ayant récupéré les applications secret et textviewer, on peut les installer dans un émulateur et les lancer :
- textviewer ne fait qu'afficher le texte donnant les instructions sur le challenge.

- secret permet de valider le challenge en rentrant les bonnes informations.

Analyse rapide de l'application secret
Un fichier .apk est un simple .zip qui contient différents fichiers (comme un .jar) dont un fichier classes.dex qui contient le code java compilé de l'application.
Le bytecode des fichiers .dex n'est pas le même que le bytecode des fichiers .class des classes java classiques, et les outils de décompilation java (jd, javad..) ne fonctionnent pas.
Il existe tout de même des applications pour décompiler ces fichiers .dex :
- dexdump, inclut directement dans l'émulateur Android,
- baksmali (http://code.google.com/p/smali/),
- dedexer (http://dedexer.sourceforge.net/).
En décompilant l'application, on voit que :
- La librairie libhello-jni.so est chargée.
- Les coordonnées (longitude et latitude) des 4 endroits à trouver sont mises dans un tableau de double (à chaque validation de lieu) et chaque valeur est multipliée par PI: on a donc un tableau de 8 doubles.
- La fonction "deriverclef" contenue dans la librairie libhello-jni est appelée avec en paramètre le tableau de 8 doubles et le mot de passe rentré.
- Cette fonction retourne une clef de chiffrement, qui va permettre de déchiffrer le binaire qui est stocké chiffré en RC4 dans l'application (Le binaire est la grande chaîne que l'on a repéré au départ en faisant un strings sur le fichier du challenge: 477689b3cb25eba...034d329324e36cfb09e63f9018f081df0fe3a2) via la fonction "dechiffrer"
- Ce binaire est ensuite écrit sur disque sous le nom "binaire", et l'on a plus qu'à le lancer.
En débugguant l'application, on peut également valider les coordonnées des 4 lieux.
En effet, la fonction "deriverclef" contenue dans la lib libhello-jni va se servir des coordonnées reçues en paramètre (via le tableau de 8 doubles) pour reconstruire dynamiquement la chaîne "com.anssi.secret/RC4" pour pouvoir appeler la fonction correspondante dynamiquement.
Dans le cas où l'on rentre des mauvaises coordonnées, la chaîne est mal reconstituée et ne correspond pas à une fonction existante. Lors de l'appel de cette chaîne, une exception est levée et l'application plante.
Au niveau de l'émulateur, cela se traduit par la fermeture de l'application sans aucun message juste après avoir appuyé sur le bouton "FINI!".
Etapes pour valider le challenge
Pour valider le challenge, il faut (faudrait, je n'ai malheureusement pas pu valider la fin) donc :
- L'application com.anssi.secret (à installer avec "adb install com.anssi.secret.apk" depuis votre machine),
- Les coordonnées des 4 lieux,
- Le mot de passe contenu dans le message GPG chiffré (sniff
).
Après avoir lancé l'application secret, il faut faire croire à l'Android que nous sommes aux bonnes coordonnées GPS. Pour ce faire, il faut se connecter en telnet sur l'émulateur sur le port 5554 (par défaut) et utiliser les commandes "geo fix longitude latitude" :
# telnet localhost 5554
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Android Console: type 'help' for a list of commands
OK
geo fix 1 2
OK
Il faut, pour chaque lieu, indiquer les bonnes coordonnées à l'émulateur et valider le lieu.
Puis on entre le mot de passe et on valide sur "FINI!".
L'application va normalement déchiffrer le binaire "binaire" sur le disque de l'émulateur, il suffit (normalement) d'aller l'exécuter.
Je dois malheureusement supposer que le binaire nous affiche l'adresse mail de validation, mais il y a peut être plus d'étapes derrière encore.. 
Notes sur les fichiers :
- System.map : comme son nom l'indique, le fichier System.map du noyau linux utilisé par Android 2.1.
- parse-challenge.py : le fichier qui permet de parser le dump mémoire (usage : parse-challenge.py challv2 System.map). En l'état, il n'affiche que les infos concernant les process en mémoire. Le dump des pages mémoire est à la fin et doit se configurer manuellement. Le script est assez sale/brut, mais marche suffisamment pour les besoins présents.
