Le petit monde d'un pentester

Aller au contenu | Aller au menu | Aller à la recherche

Tag - Challenge SSTIC

Fil des billets - Fil des commentaires

mardi, 15 juin 2010

Challenge SSTIC 2010 - Méthode alternative

Après quelques discussions au SSTIC concernant le challenge et suite à la publication des solutions, il semble que nombreuses ont été les façons de le résoudre.

Cependant je fus étonné de ne voir qu'aucune solution présentée n'utilisait la méthode qui m'a permis d'extraire les fichiers apk de la mémoire (peut-être est-elle trop tordue).

Cette méthode passe par la récupération des structures inodes des fichiers mappés en mémoire. La grande différence de cette manière de faire est qu’elle permet l’extraction des pages de façon « propre » sans avoir à passer par la conversion mémoire virtuelle -> mémoire physique.

Ce billet a donc pour but de décrire ma démarche pour résoudre cette première partie (ayant été bloqué par la clé Elgamal, je n'ai pu valider le challenge :) ).

Il n’a donc pas vocation à être exhaustif et ne présente que la partie d’extraction des fichiers apk. Le reste ayant déjà été très bien décrit {2}.

Je vous passe la partie file et 7zip! :)

On commence donc par chercher la chaine "init" dans le fichier jusqu'à tomber sur la task_struct (on aurait pu également chercher "swapper" mais vu que la liste des processus est cyclique, cela revient au même). La structure d’une tache se présente, ainsi :

1.JPG

Nous allons ici nous intéresser à la structure mm qui va nous permettre d’arriver à la description de la mémoire du processus. On code un petit tool qui nous affiche le pointeur vers cette structure pour chaque élément de la liste :

2.jpg

Nous avons donc nos deux processus provenant de l’ANSSI (La chaîne « comm » de la structure ne pouvant contenir que 16 caractères, leurs noms sont amputés).

Afin de visualiser le chemin à parcourir pour arriver aux inodes, voici un schéma représentant les liens entre les différentes structures du noyau {1} :

14.jpg

C’est ici que la solution diverge. En effet, au lieu, de nous contenter des adresses virtuelles des segments mémoires récupérés dans les structures vm_area et de procéder à la traduction d’adresse virtuelle -> physique, nous allons descendre plus profondément pour atteindre les descripteurs de pages des fichiers mappés en mémoire.

Le principe est alors simple, nous récupérons les pointeurs sur les structures vm_area et ensuite pour chacun des segments, successivement les structures file, dentry et inode.

Il est à noter que le pointeur vers la structure file n’existe ( != NULL ) que si la zone correspond à une zone « mappée ».

Depuis la task_struct nous commençons donc par récupérer la structure mm :

6.jpg

Celle-ci nous donne, via le pointeur mmap, le début de la liste des vm_area :

15.jpg

Le pointeur file pointe vers une structure file :

8.jpg

Qui contient un pointeur sur dentry :

16.jpg

Arrivé a ce point, nous avons l’inode et le nom du fichier mappé! :)

On code alors un autre outil qui nous donne tout ça automatiquement pour un processus donné :

17.jpg

Nous découvrons ainsi que le fichier com.anssi.secret.apk est mappé dans le processus et savons que son inode (la structure) se trouve à l’adresse c5462ad8.

Explorons maintenant cette structure inode. Elle contient une autre structure adress_space (incluse) fournissant la racine d’un arbre « radix » {3}.

Ce dernier contient un ensemble de pointeurs vers les descripteurs de page.

11.jpg

Exemple d’arbre radix a deux niveaux {1}:

18.jpg

A partir des pointeurs de descripteur récupérés, nous pouvons déduire le numéro de la page si nous connaissons l’adresse de base du tableau des descripteurs (mem_map).

Je n’ai pas trouvé de méthode « propre » pour obtenir l’adresse de début de ce tableau (si quelqu’un a une autre idée, je suis preneur).

L’accès a une adresse de descripteur arbitraire puis la remontée dans le tableau avec un éditeur hexa pour trouver le début n’est, je le conçois, pas des plus élégant.

Cependant les descripteurs ayant un motif reconnaissable, l’adresse est relativement facile à trouver. (Si vous aimez les méthodes originales, je ne peux que vous conseiller la lecture de la solution par Florent Marceau {4} ;) .

L’adresse de la mem_map trouvée (0x349000) et connaissant la taille d’un descripteur (32 octets) nous pouvons alors calculer l’index de chaque page à partir d’adresse de son descripteur via la formule :

Numéro de page = (adresse descripteur – adresse mem_map) / 32

Enfin, les pages commençant à l’adresse 0 et faisant chacune 4Ko, la récupération de l’offset de la page dans le dump s’effectue via une simple multiplication :

Offset de la page = numéro de la page * 4096.

A ce niveau, un peu de code est nécessaire pour automatiser la récupération des offsets de pages associées à une structure inode. Une fois développé, nous obtenons :

13.jpg

Quelques exécutions de dd et un cat avec les bonnes valeurs et nous récupérons le fichier apk correspondant.

19.jpg

L’extraction du deuxième fichier s’effectue exactement de la même façon. Ensuite il suffit de les exécuter sur l’émulateur, etc. Mais cela a déjà été décrit dans un précédent billet par Mat {5}.

Voilà c’est fini, merci a l’ANSSI pour ce challenge.

jeudi, 3 juin 2010

Challenge SSTIC 2010 in a nutshell

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 :

msg1.png

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 :

msg2.png

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).

mm_struct.png

(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 :

  • Une adresse de début,
  • Une adresse de fin,
  • Des flags (Read, Write, Execute, Shared/Private),
  • Offset dans le fichier,
  • 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).

memoryDescriptorAndMemoryAreas.png

(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:

struct1.png

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 :

tast_struct1.png

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:

processus-textviewer.png

processus-secret.png

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.

screenshot-textviewer.png

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

screenshot-secret.png

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. :)