Blog perso d'Ozwald - Geek category
Logo Blog perso d'Ozwald

Dissection de programmes sous linux

Par Oz le - Geek
Hack LD_PRELOAD Linux Reverse Engineering algo gdb objdump

J'ai longtemps pensé que pour s'initier au reverse le chemin "standard" consistait à apprendre à débuguer ses propres programmes autrement qu'à coup de printf. Avec le recul je me dit qu'il y a beaucoup d'autres chemins, et j'ai bien envie de partager avec vous celui qui consiste à observer les programmes des autres :) J'avais déjà parlé de python-ptrace qui permet de faire des trucs très sympa, mais aujourd'hui on va découvrir ensemble beaucoup d'autres astuces d'ingénierie inverse dans le but d'analyser le fonctionnement d'une application flash. Accrochez votre ceinture et re-mobilisez toutes les connaissances de base que vous avez sur le fonctionnement d'un PC parce qu'il va y avoir de la variété dans les techniques (mal) employées dans cet article-fleuve !

Engin de chantier - Based on a Creative Common photo published by Elvert Barnes on Flickr

Un avertissement s'impose : je suis pentesteur de métier, pas réverseur, donc j'opère dans ce domaine en amateur et j'utilise certainement beaucoup de détours peu efficaces pour arriver au même résultat que ce qu'un pro obtiendrait de façon plus élégante et rapide. Donc si vous retenez quoique ce soit de cet article vous êtes maintenant prévenus : c'est de l'amateurisme et il y a certainement pleins de choses qu'il ne faut pas faire comme je vais l'exposer.

Plantons maintenant le décor. Slow Frog (un français !) expliquait en 2011 comment il avait écrit un bot en python pour jouer à sa place à un petit jeu flash consistant à tenir une pizzeria. Le jeu était simple et l'article très amusant. J'avais plus ou moins oublié cet article jusqu'à ce que je tombe sur une conférence de deux gars ayant codés des bots pour des jeux plus conséquents1 et je me suis alors dit que j'allais tenter le coup sur un jeu flash plus compliqué.

Petite digression à cette étape du récit : écrire un bot est probablement contre les CGU du jeu donc je ne vais pas le citer publiquement dans cet article (même si je doute que qui que ce soit ai envie de me chercher des noises pour avoir "triché" à un petit jeu flash). Sachez juste qu'il s'agit d'un jeu assez largement plus complexe que celui de la pizzeria, et qu'il fait intervenir un nombre non-négligeable d'information textuelle et/ou numériques.

Bref, je me suis donc dit que j'allais tenter d'écrire un bot pour ce petit jeu flash juste pour vérifier si j'en étais bien capable. La première optique emprunté était la plus intuitive : l'approche graphique. Le concept est simple : je prends un screenshot, puis je l'analyse. Pour ce faire j'ai mobilisé 3 briques logicielles de bases :

Ce qui m'a posé le plus de problème s'est avéré sans conteste la lecture des textes. En effet, la police utilisée était petite ce qui rendait la lecture difficile et les erreurs n'étaient pas rare (l'OCR se plantait environ une fois sur dix...ce qui est énorme dans le contexte du jeu qui m'intéresse). J'ai donc codé des vérification de cohérences entre chaque intervention de l'OCR, ce qui a pas mal aidé mais ne donnait toujours pas un résultat parfait. Après plusieurs mois à améliorer le système de temps en temps j'ai obtenu un taux de succès de 100% d'identifications correctes sur les icones diverses du jeu, mais toujours pas un taux de reconnaissance acceptable sur les informations textuelles. J'ai donc laissé tombé le projet, lassé de voir mon bot prendre des décisions farfelues toutes les 5mn sur la base d'information mal lues.

Plusieurs mois après je me suis remis à la tache, en décidant d'intercepter les communications entre le jeux flash et le serveur du jeu plutôt qu'en essayant de "lire" l'écran. En effet, l'information transmise par le serveur est déjà dans un format compréhensible par un logiciel, autant en profiter. Autre avantage de cette technique : les outils impliqués sont ceux que j'ai l'habitude de manier lorsque je fais des pentests web. Aussitôt pensé aussitôt fait : j'intercale un proxy d'interception entre mon navigateur web et internet. Déception : le jeu refuse de démarrer. Je passe le proxy en transparent : même problème. Je fabrique ma propre autorité de certification racine, je l'intègre aux autorité de confiance de mon navigateur, puis de mon OS, et je l'utilise pour générer dynamiquement les certificats SSL bidons à présenter au navigateur4. Hélas, j'ai toujours le même problème : le jeu flash refuse catégoriquement de contacter le serveur de jeu via mon proxy. Tristesse, déception, et incompréhension.

Qu'à celà ne tienne, je n'ai qu'à descendre plus bas dans l'OS pour intercepter ces communications réseau ! L'astuce à laquelle je pense alors est celle de LD_PRELOAD. Sous linux la variable d'environnement LD_PRELOAD permet de forcer le chargement d'une librairie partagée avant toutes les autres, ce qui autorise la surcharge de fonctions issues des librairies légitimes. Par exemple si je code une fonction printf puis que je la compile dans une librairie partagée et que je force le chargement de cette librairie en priorité via LD_PRELOAD, l'ensemble des processus que je lancerai ensuite feront appel à mon implémentation de printf plutôt qu'à celle de la libc :) L'idée derrière cette astuce c'est de surcharger les fonctions de communication réseau pour intercepter les informations envoyées et reçues dans le tunnel SSL5.

Pour mettre en place cette astuce il va falloir que j'identifie quelles sont les fonctions susceptibles d'êtres utilisées par le jeu pour ses communications. Normalement la commande ltrace permet de tracer (i.e. afficher) l'ensemble des appels à des fonctions de librairies partagées qu'un programme effectue. L'utilisation aurait donc été immédiate dans mon cas : je lance ltrace sur le processus du plugin-container de firefox faisant tourner le jeu, je cherche dans les fonctions qui sont affichées celles qui ont un lien avec des communications SSL, puis je les re-code afin de les surcharger avec LD_PRELOAD pour intercepter les informations en transit. Oui mais voilà : chez moi ltrace fait systématiquement planter firefox :( . J'ai essayé de restreindre son champs d'investigation en ne demandant que les fonctions de certaines librairies (pensant que, peut-être, c'était la latence ajoutée qui faisait planter le soft) mais rien n'y fait : impossible d'utiliser ltrace :(

De déception j'ai alors tenté de tracer uniquement les appels systèmes (avec le plus standard strace), ce qui a marché à merveille. Malheureusement je me suis retrouvé noyé dans une tonne d'appel système avec peu d'espoir d'y trouver quoi que ce soit en rapport avec SSL de toute façon.

Après cette mini digression vers les appels système je suis retourné aux librairies partagées. Si je ne pouvais pas tracer les appels effectivement réalisés, je pouvais au moins tenter de les deviner. Pour ce faire j'ai dumpé la table de liaison dynamique du plugin flashplayer grace à la super commande objdump6 :

$ objdump -T /opt/Adobe/flash-player/flash-plugin/libflashplayer.so

/opt/Adobe/flash-player/flash-plugin/libflashplayer.so:     file format elf32-i386

DYNAMIC SYMBOL TABLE:
00000000      DF *UND*  00000000  GLIBC_2.1   iconv
00000000      DF *UND*  00000000              gtk_main_iteration
00000000      DO *UND*  00000000              gtk_major_version
...
00000000      DF *UND*  00000000  NSS_3.4     CERT_DecodeCertFromPackage
00000000      DF *UND*  00000000              gdk_window_set_back_pixmap
00000000      DF *UND*  00000000              gdk_screen_get_display
00000000      DF *UND*  00000000              XFreeGC
00000000      DF *UND*  00000000              gtk_entry_get_text

L'option "-T" d'objdump affiche la liste des fonctions importées par le soft ciblé, ainsi que, parfois, la librairie dont elle est tirée. En lisant ces importations je n'ai réussi à identifier qu'un seul appel interessant pour l'interception de communications chiffrées : PR_Read7. Tant mieux pour moi, ça ne fait qu'un "suspect" à auditionner ! Pour la petite histoire sachez que cette fonction fait partie des utilities fournis par le framework Mozilla et qu'elle permet de lire dans une socket abstraite (qu'elle soit dotée d'une couche SSL ou non ne change pas l'appel à la fonction). Voici donc la surcharge que j'ai écrite :

#define _GNU_SOURCE //obligatoire pour utiliser "dlsym" qui n'est à priori pas POSIX. Plus d'info ici : http://linux.die.net/man/3/dlopen
#include <prio.h> //Pour avoir les headers mozilla
#include <stdint.h> 
PRInt32 PR_Read(PRFileDesc *fd, void *buf, PRInt32 amount) {
    // D'abord je retrouve la "vrai" fonction PR_Read
    static PRInt32 (*real_PR_Read)(PRFileDesc*, void*, PRInt32) = NULL;
    real_PR_Read = dlsym(RTLD_NEXT, "PR_Read");
    // Maintenant j'appelle la "vrai" fonction PR_Read
    PRInt32 res = real_PR_Read(fd, buf, amount);

    // Enfin je stocke les données reçus si jamais il y en a.
    if (res>0){
        FILE* f = fopen("/tmp/debug.log","a");
        fprintf(f, "PR_Read : %d\n",res);

        int i; char* mybuf = (char*) buf;
        fprintf(f, "<<<\n");
        for (i=0; i<res; i++) {
            if ((mybuf[i]>8) && (mybuf[i]<127)) {
                fprintf(f, "%c", mybuf[i]);
            } else {
                fprintf(f, "%x", mybuf[i]);
            }
        }
        fprintf(f, "\n>>>");
        fclose(f);
    }
    return res; // Et, bien sur, je retourne le résultat renvoyé par la "vrai" fonction PR_Read
}

La ligne de compilation ressemble à ça : gcc -O2 -I/usr/include/nspr -I/usr/include/nss -shared -ldl -fPIC -o surcharge.so surcharge.c

Les deux -I sont pour avoir les includes de prio.h (en fait je n'ai besoin que de l'un des deux, mais je ne sais plus lequel donc, de paresse, j'ai laissé les deux). Le -shared c'est parce que je veux compiler en librairie partagée, le -fPIC c'est pour obtenir du code qui fonctionne indépendament de sa position en mémoire (je me demande si le -shared n'inclus pas cette option d'ailleurs...) et le -ldl c'est pour être linké avec la librairie de link permettant de retrouver les fonctions originales que je surcharge via dlsym.8

Une fois compilé en *.so l'utilisation de ma fonction d'interception se fait comme ça :

$ export LD_PRELOAD="/home/ozwald/surcharge/surcharge.so"
$ firefox http://urldujeu.lan #Là on utilise normalement son programme
$ unset LD_PRELOAD # Une fois qu'on a fini on supprime la variable LD_PRELOAD pour que tout redevienne normal

Bonne surprise : quand j'utilise firefox mon fichier /tmp/debug.log contient bien des informations ! Mauvaise surprise : les informations contenues sont (très ? (trop ?)) nombreuses et à priori sans aucun rapport avec les actions du jeu (grace à un tail -f j'ai pu constater que, souvent, des actions se déroulaient au niveau du jeu mais ne déclenchaient aucune écriture dans le fichier de log). Déception...je n'ai visiblement pas intercepté la bonne fonction.

Appliquant le proverbe orc "quand ça ne marche pas en poussant, pousse plus fort" j'ai décidé de surcharger plus de fonctions. J'ai donc fait un plus simple objdump -x sur le plugin flash pour obtenir la liste des librairies linkées au lieu des fonctions. Dans le tas j'ai trouvé plusieurs librairies relatives à la communication et/ou au SSL :

Dynamic Section:
...
  NEEDED               libssl3.so
  NEEDED               libnss3.so
  NEEDED               libnssutil3.so
  NEEDED               libnspr4.so
...

Partant de cette liste j'ai identifié plusieurs fonctions appartenant à ces librairies et qui pourraient tremper dans de la communication. J'ai donc surchargé : PR_Read, PR_Recv, SSL_read, BIO_read, et BIO_gets !

Comme on pouvait s'y attendre ça a été un échec pitoyable. Aucune de ces fonctions additionnelles n'est utilisée par le jeu :(

La méthode orc ne fonctionnant visiblement pas j'ai opté pour...la méthode orc ! "Quand ça ne marche pas en poussant, pousse plus fort" (je suis parfois tétu), donc j'ai surchargé strncpy, strcpy, recv, fread, read, strtok, XDrawString, et Xutf8DrawString.

Cette méthode "très subtile" m'a octroyé une petite victoire : j'obtient des informations qui semblent correspondre aux évènements du jeu. Visiblement certaines de ces fonctions sont utilisées pour peupler la petite fenêtre de log qui résume les évènements de jeu en bas d'écran; donc j'obtient pas mal d'informations! Malheureusement je me suis vite rendu compte que celà ne suffirait pas pour écrire un bot robuste. En effet certaines informations étaient manquantes, il y avait énormément de "bruit", et la cohérence temporelle n'était pas assurée (i.e. certains évènements m'étaient remontés dans un ordre différent de ceux dans lequel ils se produisaient dans le jeu...je blame le multithread sur le coup).

Là j'ai l'impression d'être face à un mur... Devant mon incapacité à expliquer l'échec de la surcharge de PR_Read & co ainsi que l'échec silencieux de la méthode d'interception via proxy avec un certificat SSL pourtant "valide" je me suis dit que, peut-être, c'était le plugin flash qui faisait de la magie noire. Je me suis donc renseigné et j'ai trouvé Gnash et Lightspark qui sont deux logiciels complémentaires réalisant une implémentation libre d'un interpreteur flash. Rien de tel qu'un logiciel libre pour comprendre le fonctionnement de quelque chose donc j'ai installé gnash / lightspark et j'ai tenté de lancer le jeu. Malheureusement lightspark a planté :-( C'était prévisible de la part d'un logiciel jeune et dont les spécifications proviennent en grande partie de difficiles efforts d'ingénierie inverse du plugin flash officiel. J'ai forcé un petit peu dans cette voie en récupérant les versions de dévelopement9 de gnash et lightspark et j'ai re-tenté le coup avec les même résultats (i.e. plantage de lightspark).

Je me retrouve donc encore une fois coincé...une nouvelle approche s'impose ! Je vais dumper la mémoire du processus grace à /proc/PID/maps et /proc/PID/mem dans l'espoir de trouver des choses intéressantes. Dans l'esprit j'étais parti pour faire quelque chose à la memory eye en fait.

Pour ceux qui l'ignore voici un petit résumé de l'esprit des fichiers que je vais utiliser : Sous linux le pseudo-système de fichier /proc/ contient pleins d'information sur le fonctionnement en cours du système. Par exemple /proc/cpuinfo contient des informations sur votre processeur. Chez moi il contient ça :

$ cat /proc/cpuinfo
processor   : 0
vendor_id   : AuthenticAMD
cpu family  : 16
model       : 6
model name  : AMD Athlon(tm) II X2 250 Processor
...

En l'espèce nous allons nous intéresser aux fichiers /proc/PID/maps et /proc/PID/mem. Le plus simple à comprendre est /proc/PID/mem puisqu'il contient simplement la mémoire totale du système telle qu'elle est vue depuis le processus d'id PID. La mémoire adressable étant gigantesque nous allons cibler nos recherches grace à /proc/PID/maps qui contient la liste des segments de mémoire adressable qui sont effectivement alloués et accessibles par le processus en question. Pour faire un test vous pouvez lancer une commande less sur un fichier quelconque (ou juste un top, comme vous préférez), récupérer son PID grace à ps aux | grep NomDeVotreCommande puis faire un cat sur le /proc/PID/map correspondant. Voilà ce que ça donne chez moi (j'ai lancé un top dans un autre shell):

$ ps aux | grep [t]op
ozwald    9064  0.0  0.0   2700  1136 pts/0    S+   12:33   0:17 top
$ cat /proc/9064/maps
08048000-08055000 r-xp 00000000 03:02 730853     /usr/bin/top
08055000-08056000 r--p 0000c000 03:02 730853     /usr/bin/top
08057000-0807b000 rw-p 00000000 00:00 0          [heap]
...
b76f1000-b76f2000 rw-p 00185000 03:02 938394     /lib/libc-2.15.so
...
b7739000-b773a000 rw-p 00044000 03:02 417799     /lib/libncurses.so.5.9
...
bfc5c000-bfc7e000 rw-p 00000000 00:00 0          [stack]
...

Comme vous pouvez le voir nous obtenons des informations pour chaque plage allouée. En particulier nous avons :

  • l'adresse de début (0xbfc5c000 pour la stack par exemple)
  • l'adresse de fin (0xbfc7e000 pour poursuivre notre exemple de la stack)
  • les permissions ("rw-p" pour la stack)

Nous pouvons donc à présent cibler toutes les plages qui sont à la fois "R"eadable et "W"ritable par le processus, et y chercher des élèments parlant. Pour lire le contenu de ces zones mémoire il suffit d'ouvrir /proc/PID/mem en lecture seule, puis de faire un seek jusqu'à l'offset de début de plage, et à y lire autant d'octets que la taille de la plage. Le petit script python10 ci-dessous permet de dumper les plages mémoire du processus dont on passe le PID en paramètre :

#! /usr/bin/env python
import re
import sys
if len(sys.argv) != 2 :
    print "Merci de donner un PID en argument"
    print "Usage : %s PID"%sys.argv[0]
    sys.exit(1)

mypid=sys.argv[1]
mypid=str(mypid)
sys.stderr.write("PID = " + str(mypid) )
maps_file = open("/proc/"+mypid+"/maps", 'r')
mem_file = open("/proc/"+mypid+"/mem", 'r', 0)
for line in maps_file.readlines():  # for each mapped region
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r][-w])', line)
    if m.group(3) == 'rw':  # if this is a writeable region
        sys.stderr.write("\nOK : \t" + line+"\n")
        start = int(m.group(1), 16)
        if start > 281474976710655 :
            continue
        end = int(m.group(2), 16)
        sys.stderr.write( "start = " + str(start) + "\n")
        mem_file.seek(start)  # seek to region start
        chunk = mem_file.read(end - start)  # read region contents
        open("%d.dump"%start,'w').write(chunk) # dump contents to a file
    else :
        sys.stderr.write("\nPASS : \t" + line+"\n")
maps_file.close()
mem_file.close()

Bon, avec un tel outil les perspectives d'analyse sont démultipliées ! Je lance donc le jeu, récupère le PID de l'interpreteur flash officiel via un ps aux | grep [f]lash, et dump le contenu de sa mémoire en invoquant le script ci-dessus. Je me retrouve avec quelques dizaines de fichiers de dump, chacun correspondant à une plage mémoire. Après quelques grep bien sentis11 j'identifie des structures JSON qui semblent contenir des éléments de jeu et qui ressemblent à ça :

GameEvent:ClientArrival { "ClientID":"123", "ClientName":"Roger", ..}
GameStatus:OrdersWaiting { "Orders":[ {"ClientID":"123", "Product":"Pizza", ...}, {}, ..., {}] }

L'espoir renait parce que ces structures sont très intéressantes : d'une part elles contiennent toutes les informations dont j'ai besoin pour écrire un bot, et d'autre part le fait qu'elles soient en JSON me laisse penser qu'il s'agit bien là de l'information échangée en réseau dans le tunnel SSL et sur laquelle j'essaie de mettre la main depuis le début. J'ai donc creusé dans cette voie pour, finalement, obtenir un script python qui fonctionne en deux temps :

  1. Il identifie la zone mémoire où sont les structures JSON
  2. Une fois la zone identifiée avec certitude il dump cette zone en boucle en guettant des changements (ce qui signifierait l'arrivée d'un nouveau paquet d'information en provenance du serveur et donc l'arrivée d'un nouvel élément de jeu tel qu'un client).

La qualité d'information que j'ai obtenu avec ce script est exemplaire puisque, contrairement à la lecture de l'écran par OCR, je n'ai aucune erreur sur le contenu des texte. Malheureusement, lorsque plusieurs évènements se suivent très rapidement dans le temps (comme deux clients qui rentrent quasiment en même temps dans la pizzeria) mon script ne perçoit que l'un des deux évènements et rate l'autre qui se fait écraser en mémoire entre deux dumps. Il fallait s'y attendre puisque je fait du "poll" sur la mémoire au lieu d'avoir obtenu un système en "push" comme me le permettrait la surcharge d'une fonction par LD_PRELOAD :(

N'ayant pas envie d'abandonner j'ai poursuivi dans une "voie du milieu12" tirant partie de plusieurs des travaux effectués jusqu'à présent. Lorsque je dumpais les structures JSON directement depuis la mémoire j'ai remarqué que l'adresse où étaient les structures intéressantes ne bougeait quasiment jamais lors d'une même partie. Ces structures JSON n'arrivant pas là par magie (à moins que le plugin flash officiel pratique réellement la magie noire...) je me suis dit qu'identifier la fonction responsable de l'écriture de cette structure serait très intéressant puisque je pourrait peut-être la surcharger avec LD_PRELOAD.

La démarche que j'ai adopté a donc été la suivante :

  1. Lancer une partie
  2. Identifier le PID de l'interpreteur flash par ps aux | grep [f]lash
  3. Dumper la mémoire du processus et identifier l'adresse d'une structure JSON d'intérêt
  4. Attacher gdb au processus de l'interpreteur flash
  5. Poser un breakpoint sur les écritures à l'adresse de la structure JSON
  6. Afficher, automatiquement lors du déclenchement de ce breakpoint, le contenu de la mémoire (pour vérifier si j'ai bien une structure JSON comme je le pensait) ainsi que la backtrace des 4 derniers appels.

L'objectif de ce protocole étant d'identifier quelle partie de code est responsable de l'écriture de ces structures. En termes de commande voici ce que ça a donné :

$ ps aux | grep [f]lash
ozwald    9064  0.0  0.0   2700  1136 pts/0    S+   12:33   0:17 flash-plugin
$ python memory_dump_and_seek.py 9064
Dumping...
Seeking json...
The json object is at address 0xaa72f000
$ gdb
(gdb) attach 9064
(gdb) watch *0xaa72f000
Hardware watchpoint 1: *0xaa72f000
(gdb) commands 1
>silent
>x/1s 0xaa72f000
>bt 4
>cont
>end
(gdb) cont
Continuing.
0xaa72f000: "GameEvent:ClientArrival { "ClientID":"123", "ClientName":"Roger", ..}...
#0  0xb584c026 in ?? () from /lib/libc.so.6
#1  0x00000277 in ?? ()
#2  0xb2b2c1d9 in ?? () from /opt/Adobe/flash-player/flash-plugin/libflashplayer.so
#3  0xb2b33f9b in ?? () from /opt/Adobe/flash-player/flash-plugin/libflashplayer.so
0xaa72f000: "GameEvent:ClientArrival { "ClientID":"124", "ClientName":"Paul", ..}...
#0  0xb584c026 in ?? () from /lib/libc.so.6
#1  0x00000277 in ?? ()
#2  0xb2b2c1d9 in ?? () from /opt/Adobe/flash-player/flash-plugin/libflashplayer.so
#3  0xb2b33f9b in ?? () from /opt/Adobe/flash-player/flash-plugin/libflashplayer.so
0xaa72f000: "GameEvent:ClientArrival { "ClientID":"125", "ClientName":"Jean", ..}...
#0  0xb584c026 in ?? () from /lib/libc.so.6
#1  0x00000277 in ?? ()
#2  0xb2b2c1d9 in ?? () from /opt/Adobe/flash-player/flash-plugin/libflashplayer.so
#3  0xb2b33f9b in ?? () from /opt/Adobe/flash-player/flash-plugin/libflashplayer.so
...

Voilà qui sent bon. A chaque interruption :

  • la zone mémoire contient bien ce qui ressemble à une structure JSON valide
  • la backtrace est systématiquement la même
  • cerise sur le gateau : la dernière instruction appelée (i.e. celle qui est responsable de l'écriture mémoire) appartient à /lib/libc.so.6 et sera donc surchargeable via LD_PRELOAD (alors que si ça avait été une fonction interne à l'interpreteur flash j'aurai été plus ennuyé).

Par contre ce qui me surprend à ce moment là c'est que j'avais déjà fait des tentatives infructueuses en surchargeant une bonne pelleté de fonctions de la libc (strcpy et strncpy en particulier, souvenez-vous du début de cet article...il y a 3 pages :D ). Quelle fonction de la libc peut donc être responsable de ces écritures ? A cette étape je suis certain qu'un gourou de gdb pourrait répondre en une commande. Malheureusement je ne suis pas un gourou de gdb :( J'ai bien tenté de demander gentimment, mais sans succès :

(gdb) info symbol 0xb584c026
No symbol matches 0xb584c026.

Bon..."Quand ça ne marche pas en poussant, pousse plus fort" donc je vais adopter, encore une fois, une technique très subtile :

  1. Tout d'abord on active l'enregistrement des sorties de gdb dans un fichier texte, en prévision d'un gros tas de donnée à traiter : set logging on
  2. On demande à gdb d'afficher TOUTES les fonctions que le processus connait13 : show functions.

Une fois ceci fait on quitte gdb et on se retrouve avec un fichier gdb.txt qui contient les logs de la session retranscrivant ce que nous avons eu sur la sortie standard et ressemblant à ça :

All defined functions:

Non-debugging symbols:
0x08049290  _init
0x080492b8  strerror_r@plt
0x080492c8  abort@plt
0x080492d8  sysconf@plt
...
=== NDLR : environ 40 000 lignes plus tard ===
...
0xb470e8f0  _nss_dns_getnetbyaddr_r
0xb470ec40  _nss_dns_getcanonname_r
0xffffe400  __kernel_sigreturn
0xffffe40c  __kernel_rt_sigreturn
0xffffe414  __kernel_vsyscall

Avec ces informations, retrouver la fonction que je cherche est simple comme bonjour puisqu'un grep et un sort suffisent. Pour rappel je cherche la fonction de la libc qui contient l'adresse 0xb584c026 puisque c'est l'instruction à cette adresse qui est responsable de l'écriture de la structure JSON que je recherche à l'adresse 0xaa72f000 :

$ grep -E 0xb584[bc] gdb.txt | sort -u
...
0xb584bea0  __strncasecmp_l
0xb584bea0  strncasecmp_l
0xb584bf20  memccpy
0xb584bf80  memcpy
0xb584c680  __strsep_g
0xb584c680  strsep
...

La coïncidence est trop belle : c'est memcpy le "coupable" (puisque 0xb584bf80 < 0xb584c026 < 0xb584c680) ! Il ne me reste donc plus qu'à surcharger memcpy pour vérifier la théorie. Attention cependant : surcharger strcpy et strncpy ne constituait pas vraiment un risque (ces fonctions sont censées traiter des chaines de caractères), mais surcharger memcpy est bien plus audacieux. En effet, memcpy est d'un usage beaucoup plus versatile et tout aussi courant (si ce n'est beaucoup plus). Quelques précautions s'imposent donc lors de l'écriture de la surcharge afin de s'assurer que l'on ne va intercepter que les mouvements de mémoire qui nous intéresse. D'une part ça nous facilitera le traitement des données interceptées et, d'autre part, ça va permettre de limiter le ralentissement des processus que nous surveillons même s'ils font très souvent appel à memcpy. J'ai donc adopté les précautions suivantes :

  • Je n'ai utilisé aucune tournure qui pourrait faire appel à une primitive memcpy lors de l'optimisation du compilateur (je ne sais pas s'il y a un risque d'appel récursif, mais je préfère ne pas tenter).
  • J'ai utilisé un fichier de dump différent par processus et par thread afin d'éviter les problèmes d'accès concurrents (j'aurai pu jouer avec des mutex, mais ça aurait potentiellement ralenti le schmilblick et puis c'est plus long à coder)
  • J'ai fait attention à ne pas trop introduire de bug dans mon code. Ca a l'air évident mais ça ne coute rien de le rappeler. Par exemple : avant d'accéder aux éléments de csrc pour vérifier si la zone mémoire copiée commence bien par la chaine de caractère que je veux ("Game") je m'assure que csrc contient au moins autant de caractères que ce que je vais lire...

Bref, voilà le code :

#include <sys/syscall.h> //for gettid
#include <sys/types.h> // this and below for getpid
#include <unistd.h>
void *memcpy(void *dest, const void *src, size_t n){
    static void* (*real_memcpy)(void* , const void*, size_t) = NULL;
    real_memcpy = dlsym(RTLD_NEXT, "memcpy");

    const char* csrc = (const char*) src;

    if ((n>4) && (csrc[0]==71/*G*/) && (csrc[1]==97/*a*/) && (csrc[2]==109/*m*/) && (csrc[3]==101/*e*/)){
        int write_this_much = 0;
        // I only want printable ascii :
        while ((write_this_much<n) && (csrc[write_this_much]>31) && (csrc[write_this_much]<127) ) { write_this_much++; }

        // Un fichier par processus et par thread :
        char filename[128];
        snprintf(filename,128,"/tmp/debug_%d_%d.log\0", (int)getpid(), (int)syscall(SYS_gettid) );

        FILE* f = fopen(filename,"a");
        fprintf(f, "memcpy : <<< ");
        fwrite(csrc, 1, write_this_much, f);
        fprintf(f, ">>>\n");
        fclose(f);
    }
    return real_memcpy(dest, src, n);
}

Malgré toutes ces précautions lorsque j'utilise ma surcharge en déclarant la librairie dans LD_PRELOAD firefox crashe lamentablement au démarrage :-( ...Et c'est là que j'ai un gros coup de chance : chromium-browser, lui, fonctionne comme si de rien n'était14 :) ! Je ne sais même pas pourquoi j'ai essayé sur chromium cette fois-là alors que je n'avais fait aucun test dessus auparavant, mais je m'en félicite ;)

Bon, avec un navigateur qui marche je vais sur le jeu flash, et je me fait une petite partie. Une fois la partie terminée j'ouvre /tmp/debug_8613_8613.log et je constate avec joie qu'il contient l'ensemble des structures JSON que je voulais ! Aucune corruption, à priori aucun manque, et l'ordre semble correct.

En conclusion donc : j'en aurai bien ch*é pour arriver à ce résultat et je ne comprends pas encore tout (pourquoi firefox plante-t-il ? Est-ce-que le plugin flash embarque sa propre librairie SSL ?? Pourquoi ltrace fait-il planter les softs qu'il surveille ? etc.), mais j'ai également appris beaucoup (dumper proprement la mémoire d'un process, définir les commandes à lancer automatiquement quand gdb déclenche un breakpoint, définir un breakpoint sur une écriture en mémoire, ...) et surtout : J'ai réussi à faire ce que je voulais15 :-D !!!

  1. Eux, avaient une motivation pécuniaire, contrairement à Slow Frog.
  2. En guise de rafinnement je prenais le screenshot dans un répertoire appartenant à un volume monté en tmpfs histoire d'être certain de ne pas fatiguer le disque par les écritures de screenshot répétées
  3. J'ai également testé tesseract, un autre logiciel de reconnaissance de caractère (OCR), mais j'ai trouvé qu'il donnait globalement des résultats moins bons.
  4. Merci OpenSSL et BURP :)
  5. Avec cette même fonctionnalité de LD_PRELOAD certains ont réalisé des keylogger, des rootkits, etc.
  6. Vous pouvez également utiliser nm -D à la place de objdump -T
  7. Je ne connaissais pas cette fonction avant de tomber dessus lors de ces recherche, je l'ai donc identifiée par tatonnement.
  8. Pfiou ça en fait des options de gcc...J'espère que je n'ai pas dit trop de bétises d'ailleurs :-D
  9. J'aime bien l'expression "bleeding edge" plutôt que "version de développement", mais bon...limitons les anglicismes quand ils ne sont pas nécessaires ^^
  10. Script largement pompé depuis le net; googlez pour retrouver la source originale, je ne l'ai malheureusement pas noté.
  11. Par exemple j'ai lancé des grep sur le nom des clients de la pizzeria que je pouvais voir à l'écran lors du dump de la mémoire.
  12. Comme le diraient certains sages.
  13. Attention, dumper l'ensemble des fonctions retourne beaucoup de résultats et ça peut donc prendre du temps. Dans mon cas pratique celà signifiait que le freeze du jeu était assez long pour me faire perdre la partie. Etant donné que l'adresse d'enregistrement des JSON changeait à chaque démarrage de partie, dumper les fonctions devait être la dernière étape de mon processus d'analyse :)
  14. En fait chromium-browser rame assez notablement, mais il fonctionne sans montrer la moindre envie de planter.
  15. Bon, il me reste à remplacer les fichiers par des tubes nommés (mkfifo) puis à écrire la partie logicielle qui va lire le flux d'information en temps réel dans ces tubes, puis à écrire la partie "IA" qui va décider comment jouer, puis à écrire la partie qui va envoyer les actions au jeu...Mais le plus difficile est fait, si si je vous jure ;) D'ailleurs, le plus difficile étant fait, je ne sais pas si ça m'intéresse encore de terminer ce projet lol

Petites astuces vite fait

Par Oz le - Geek
code source python

Pour renouer avec les quelques billets express que j'ai déjà pu faire voici, en vrac, une poignée d'astuces (principalement python) qui pourraient être utiles.

Swiss Knife - Creative Common by "focusforaword" on Flickr

Python2 ou Python3 ? Les deux !

J'ai l'impression que tout le monde code en python2, malheureusement "Python 3.x is the present and future of the language" donc il va bien falloir y passer. Pour ma part j'étais réticent à passer à Python3 principalement à cause d'une unique différence print "toto" en python2 (que je trouvais très pratique) devient print("toto") en python3, or des "print" j'en colle à toutes les sauces dans mes scripts. L'habitude étant ce qu'elle est je me retrouvait toujours à coller un print "toto" quelque part dans mon code et je me disait "oh et puis zut" et je basculais en 100% python2. Aujourd'hui je partage avec vous une "astuce" que j'utilise à présent pour rendre mes scripts compatibles python2 ET python3 : je remplace les print par les fonctions du module standard logging.

import logging
logging.basicConfig(level = logging.DEBUG, format="%(message)s")

//équivalent de 
// print "toto" # en python2
// print("toto") # en python3
logging.debug("toto")

Autre avantage du module logging : ça gère directement les différents niveau de verbosité1. Entre logging.debug, logging.info, logging.warning, et leurs consoeurs vous trouverez forcément le niveau de log qui vous plait; ainsi il n'est plus besoin de commenter en masse les print entre votre version de "dev" et de "prod" ^^

Le C c'est bien, mais parfois il faut l'oublier

J'aime beaucoup le C, mais sur ce coup là ça m'a joué un mauvais tour : quand j'ai voulu parser les arguments de la ligne de commande de mes scripts python je me suis intéressé au module getopt qui ressemble furieusement à ce qui existe en C. Erreur grossière : j'aurai mieux fait de regarder argparse qui est beaucoup plus pythonique et infiniment plus pratique ! A titre de comparaison voilà un exemple typique avec les deux modules. Les arguments possibles sont -h/- -help pour afficher l'aide, ou -v/- -verbose suivi d'un niveau de 0 à 4 pour régler la verbosité du programme. Voilà deux équivalents fonctionnels, l'un avec argparse, l'autre avec getopt :

import argparse, logging

parser = argparse.ArgumentParser(description='Super script de la mort qui tue')
parser.add_argument('--verbose', '-v', type=int, dest='verb', help='verbosity level [0-4]', default=4, choices=[0,1,2,3,4])
args = parser.parse_args()

logging.basicConfig(level = logging.CRITICAL-args.verb*10, format="%(message)s")
logging.debug("Debug") # cette ligne et les 2 suivante servent juste a voir que le setting du niveau de verbose fonctionne
logging.info("Info")
logging.warning("Warning")

Contre :

def usage():
    print "La on a un souci puisqu'on doit utiliser 'print'..."
    print "Et puis surtout on doit ecrire son 'usage' soit-meme alors qu'argparse l'ecrit automatiquement"
    print "Usage : %s [--verbose/-v LEVEL]"%sys.argv[0]
    print " --verbose/-v LEVEL\\tVerbosity level between 0-5 [default to 4]" 

import sys, getopt, logging
try:
    opts, args = getopt.getopt(sys.argv[1:], "v:h", ["verbose", "help"])
except getopt.GetoptError, err:
        print str(err) # will print something like "option -a not recognized"
        usage()
        sys.exit(2)

verbose = 4
for o, a in opts:
    if o in ["-v","--verbose"]:
        try:
            verbose = int(a)
        except:
            usage()
            sys.exit(2)
        if not verbose in [0,1,2,3,4]:
            usage()
            sys.exit(2)
    elif o in ['-h', '--help']:
        usage()
        sys.exit(0)

logging.basicConfig(level = logging.CRITICAL-verbose*10, format="%(message)s")
logging.debug("Debug") # cette ligne et les 2 suivante servent juste a voir que le setting du niveau de verbose fonctionne
logging.info("Info")
logging.warning("Warning")

L'avantage d'argparse est évident pour tout le monde j'espère ! Par contre, bien qu'argparse soit censé être un module standard depuis python2.7, et qu'il soit par défaut présent sur ma ubuntu 12.04, j'ai été obligé de l'installer explicitement sur ma gentoo emerge dev-python/argparse...dommage.

  1. Même si ce mot n'existe pas

Brèves du jour

Par Oz le - Geek
I.A. Outil Réseau python scripts

Un mini billet pour deux petites brèves : une astuce python (déjà bien connue, mais plus on réplique ce genre d'info moins on a de mal à la retrouver quand on l'a oublié), et un pointeur sur un jouet sympa.

Tools - Creative Common by mtneer_man on Flickr

Tout d'abord l'astuce python : comment changer son user-agent quand on utilise la librairie urllib. C'est un problème ultra-classique puisque pas mal de sites font du filtrage sur ce user-agent et refusent de répondre quand ils repèrent un script python1. En fouillant sur le net on trouve pleins de méthodes plus ou moins bidons (certaines se contentent carrément de rajouter un second header "user-agent", ce qui n'est évidemment pas ce que l'on cherche à faire), et après en avoir testé au moins une demi-douzaine voici celle que j'utilise à présent (et qui marche :D) :

import urllib
# ============ USER AGENT MAGIC ===========
class UAOpener(urllib.FancyURLopener):
        version = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11'
urllib._urlopener = UAOpener()
# =========================================

Et voilà :) Comme pour le passage via TOR c'est tout ce qu'il y a à faire. Accessoirement j'ai réalisé récemment que le dernier paramètre de "setdefaultproxy" que j'utilise pour passer par TOR souffre un bug ennuyeux : il est ignoré :-D Donc que vous mettiez à "True" ou à "False" les résolutions DNS ne passeront JAMAIS par votre proxy socks (et donc par TOR)...ennuyeux n'est-ce-pas ? En tout cas maintenant vous êtes prévenus :)

Le petit jouet maintenant : ça n'a rien de nouveau non plus, mais c'est tout de même un jouet rigolo. Il s'agit du WOrdnet Libre du Français2. Pour ceux qui connaissent Wordnet : c'est Wordnet pour le Français ;) Pour ceux qui ignorent ce qu'est Wordnet c'est une base de donnée concernant le sens des mots et leurs relations les uns avec les autres. Un usage immédiat de Wordnet c'est de trouver des synonymes (dans la langue française avec WOLF, dans la langue anglaise avec le Wordnet original), un autre usage consiste à mesurer la proximité de sens entre deux mots par exemple. Les possibilités sont vraiment nombreuses, notamment en data-mining. Bref vous pouvez le télécharger ici au format XML, et ces trois mini remarques pourraient vous êtes utiles si vous voulez travailler avec :

  • Le document ne contient pas de balise racine, ajoutez donc en une si vous voulez que xmllint --format ou xml.dom.minidom.Parse ne vous insulte pas. Par exemple un petit <wolf> en début de document et un petit </wolf> en fin de document feront parfaitement l'affaire.
  • Les & ont tendance à faire crier le module python xml.dom.minidom. Pour ma part je les ai donc purement et simplement supprimés grâce à un petit sed.
  • Le XML pèse 38Mo sur disque, une fois parsé par xml.dom.minidom mon interpréteur Python prend plus de 1Go ;-) Veillez donc à avoir de la RAM disponible.

Gorgonite le 2011/10/22 13:45

MiniDOM... tu cherches aussi les ennuis pour traiter une telle quantité de données :p

  1. Par exemple wikipedia.
  2. WOLF

Petite visite du guérisseur

Par Oz le - Geek
Hack Réseau android python

Monsieur Soleil peut tout faire. Retour de l'être aimé, prédiction des résultats du loto, choix infaillible de la couleur de la cravatte, conjuration des problèmes d'impuissance, etc... Bon plus sérieusement je ne vais pas parler de ce type de guérisseur là, rassurez-vous. Je vais vous parler de Jason, figure de la mythologie grecque connu sous le surnom "le guérisseur"...bon non plus, ça c'était juste pour le jeu de mot (bien pourri). En fait je vais vous parler de JSON et, plus précisément, des JSON-RPC que j'ai eu la surprise de découvrir accessibles sur mon serveur...

Marabout - Based on a Creative Common photo published by "Ludovic Hirlimann" on Flickr

Pour poser le cadre donc : j'ai un serveur multimédia (allumé H241, comme tout bon serveur qui se respecte) et l'autre jour alors que je faisais le petit nmap routinier de mon réseau local apparait une nouveauté sur ce serveur :

PORT     STATE SERVICE     VERSION
9090/tcp open  zeus-admin?

Autant vous dire que, sur le moment, j'ai un peu fait la gueule :-D Après enquête rapide il s'avère que c'est en fait le XBMC qui tourne sur mon serveur qui expose là l'une de ses nouvelles interfaces de commande... Dans les anciennes versions, XBMC exposait uniquement (pour autant que je sache) une interface de commande HTTP accessible après authentification et qui permettait ainsi de controller les lectures multimédia via le réseau (super pratique pour transformer votre téléphone android en télécommande wifi). Dans les dernières version l'interface HTTP est deprecated, et c'est l'interface JSON-RPC qui devrait la remplacer.

Pour ceux qui l'ignorent (comme moi il y a quelques semaines), le JSON-RPC c'est une méthode de RPC qui utilise la syntaxe JSON (on ne s'en douterai pas hein ?). Et pour la conclure l'explication : le JSON c'est une méthode de formatage de données dont l'esprit est de se rapprocher de la syntaxe....javascript. Je ne plaisante pas, JSON c'est même l'acronyme de "JavaScript Object Notation" si vous voulez tout savoir. Du coup une requête JSON-RPC ça ressemble à ça :

{"jsonrpc": "2.0", "method": "subtract", "params": {42, 23}, "id": 1}

Bon...c'est pas très joli mais ça reste compréhensible :) Du coup je me suis dit "voyons ce que l'on peut faire avec ça", mais avant d'exposer les résultats je dois faire un disclaimer : "j'ai peut-être été trop permissif dans ma configuration d'xbmc (même si j'en doute), donc ne blamons pas trop les réglages qu'on peut/doit faire pour utiliser une télécommande, c'est peut être ma faute". Maintenant répondons à "que peut on faire avec ça ?" :

Première chose : survoler la liste des procédures accessibles. La première qui a une tête sympa c'est "JSONRPC.Ping". Aussitôt identifiée aussitôt testée :

echo '{"jsonrpc": "2.0", "method": "JSONRPC.Ping", "id":"5"}' | nc serveurmultimedia 9090

Et réponse immédiate :

{
   "id" : "5",
   "jsonrpc" : "2.0",
   "result" : "pong"
}

Cool ça marche :-D !!!...Pas cool : j'ai pas eu besoin de m'identifier :-/

La paranoïa aidant la seconde procédure que je teste c'est "Files.GetDirectory"...forcément.

echo '{"jsonrpc": "2.0", "method": "Files.GetDirectory", "id":"peur", "params":{"directory":"/"}}' | nc serveurmultimedia 9090

Ah bah....ça marche :

{
   "id" : "peur",
   "jsonrpc" : "2.0",
   "result" : {
      "directories" : [
         {
            "fanart" : "special://masterprofile/Thumbnails/Video/Fanart/cfb24076.tbn",
            "file" : "/var/",
            "label" : "var"
         },
         {
            "fanart" : "special://masterprofile/Thumbnails/Video/Fanart/b1fe6366.tbn",
            "file" : "/usr/",
            "label" : "usr"
         },
         {
            "fanart" : "special://masterprofile/Thumbnails/Video/Fanart/dafd2084.tbn",
            "file" : "/tmp/",
...ETC...

Et vous savez le mieux ? Y'a une procédure Files.Download :-D !...Après l'avoir testé dans tout les sens il semblerait qu'en fait cette procédure ne soit, pour l'instant, pas implémentée (ouf !) et que de toute façon elle ne permette de télécharger que des ressources générées par XBMC (vignettes, etc.). M'enfin déjà le simple Files.GetDirectory c'est pas très joli2...Du coup une seule chose à dire : faites attentions aux permissions que vous mettez sur votre XBMC si vous voulez utiliser une télécommande réseau, il se peut que vous ouvriez des portes à n'importe qui ayant accès au port TCP.

Ceci étant dit, on va considérer que ça cloture la partie "sécu" de ce billet, et on va voir si on peut jouer avec ça sans vilaines pensées, par exemple essayons de faire les requêtes en python :

>>> import json, socket, random
>>> req = {'jsonrpc':'2.0', 'id':str(random.randint(0,100)), 'method':'JSONRPC.Ping'}
>>> s =  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.connect(("serveurmultimedia",9090))
>>> s.send(json.dumps(req))
>>> reponse = s.recv(1024)
>>> print reponse
{
   "id" : "82",
   "jsonrpc" : "2.0",
   "result" : "pong"
}

Intéressant...Du coup si jamais un script python sur android qui est en train d'exécuter time.sleep() ne consomme pas de CPU (donc de batterie) je pourrai m'écrire un script, utilisable sur mon téléphone, qui me réveille à l'heure de mon choix en jouant le média de mon choix sur mon serveur !!! Intéressant non ?

Pour que ça soit intéressant j'ai envie de lire un mp3 aléatoire. La logique de XBMC étant ce qu'elle est3 je dois d'abord récupérer les "sources" de music :

>>> req = {'jsonrpc':'2.0', 'id':str(random.randint(0,100))}
>>> req['method'] = "Files.GetSources"
>>> req['params'] = {'media':'Music'}
>>> s.send(json.dumps(req))
>>> reponse = s.recv(10*1024)
>>> shares = json.loads(reponse)['result']['shares']
>>> for s in shares:
>>>    print s['file']
/mnt/raid0/musiquelegalementachetee/
...

Une fois les sources récupérées (et surtout les répertoires associées, dans l'attribu bien nommé "file"...) on peut utiliser Files.GetDirectory dont on parlait plus haut afin de récupérer l'ensemble des morceaux directement à la racine de notre source (j'ai la flemme de coder le récursif pour l'instant) :

>>> req = {'jsonrpc':'2.0', 'id':str(random.randint(0,100))}
>>> req['method'] = "Files.GetDirectory"
>>> req['params'] = {'directory':shares[0]['file']}
>>> s.send(json.dumps(req))
>>> reponse = s.recv(100*1024)
>>> musics = json.loads(reponse)
>>> random.shuffle(musics['result']['files'])
>>> musique = musics['result']['files'][0]["file"]
>>> print musique
/mnt/raid0/musiquelegalementachetee/rickroll.mp3

Maintenant j'aurai adoré utilisé AudioPlaylist.Insert pour insérer la chanson à l'endroit de mon choix dans la playlist courante puis en lancer la lecture, sauf que quand je fais appel à AudioPlaylist.Insert j'ai une réponse Method not found....On va donc supposer que ce n'est pas encore implémenté :-( Résultat il ne me reste plus qu'à me rabattre sur AudioPlaylist.Add puis prier le guérisseur pour que lors de l'appel à AudioPlaylist.Play ce soit le bon morceau qui soit joué...Je l'ai donc fait :

>>> req = {'jsonrpc':'2.0', 'id':str(random.randint(0,100))}
>>> req['method'] = "AudioPlaylist.Add"
>>> req['params'] = {"item": {"file":musique}}
>>> s.send(json.dumps(req))
>>> reponse = s.recv(1024)
>>> print reponse
{
   "id" : "2",
   "jsonrpc" : "2.0",
   "result" : "OK"
}
>>> req = {'jsonrpc':'2.0', 'id':str(random.randint(0,100))}
>>> req['method'] = "AudioPlaylist.Play"
>>> s.send(json.dumps(req))

Et là ça ne joue pas le bon morceau :-S ... Vérification de la playlist : mon morceau n'a même pas été ajouté en fait... Heureusement la communauté des dev de XBMC est plutôt très active et j'ai rapidement trouvé le problème :

The current playlist implementation/representation in json rpc has many (design) flaws and there are lots of unsupported things and it will hopefully be completely rewritten for the next release (if there's enough time). Until then it might be the safest approach not to use the Playlist methods (except SkipPrevious, SkipNext, GetItems, Clear, Shuffle and UnShuffle which should work as expected).

We're sorry for the inconvenience and hope to provide a much better solution in the next version

Donc voilà : actuellement il est impossible de faire ce que je voulais :-( ..Snif. La suite peut-être à un prochain numéro :) !

  1. Que les amis de la planète se rassurent : c'est une machine à base d'atom qui consomme moins en H24 qu'une seule heure de jeu sur les dernières cartes graphiques à la mode
  2. Même si, comme moi, vous faites tourner votre XBMC avec des droits minimalistes
  3. et comme je n'utilise pas les fonctionnalitées "library" d'XBMC

Android c'est bon, mangez en !

Par Oz le - Geek
android python

Avec l'été qui revient le soleil se fait de plus en plus présent et c'est donc le moment de ressortir...son android ! Bah oui, avec le beau temps on se retrouve plus souvent à glander en plein air et pour geeker il n'y a alors rien de plus pratique/portable qu'un android :)

Droid Has Landed

J'avais déjà expliqué dans un vieux billet comment installer un environnement d'émulation Android1, et bien cette fois je vais parler de SL4A. SL4A c'est une application qui vous permet d'utiliser des langages de scripts populaires (LUA, ruby, python, etc.) sur votre android. Pour en profiter il suffit d'installer SL4A (soit depuis le market soit en téléchargeant l'APK directement depuis leur site. Pour l'installer sur emulateur ca fera donc adb install ~/droid/sl4a_r4.apk), de le lancer, de choisir "ajouter" dans le menu de l'application, puis de choisir l'interpretteur du langage de votre choix (pour ma part j'ai choisi Python ;) ).

Une fois SL4A installé et les interpretteurs de votre choix téléchargés et installés depuis SL4A vous avez accès à toute une API qui vous permet d'interragir, depuis vos scripts, avec le terminal Android. Vous pouvez par exemple accéder à la liste des contacts enregistrés, lire les capteurs divers et variés, utiliser l'apareil photo, envoyer/lire des sms, etc. Par contre, coder sur son téléphone ça devient vite fatiguant, donc un tout petit peu de préparation est utile pour coder à son aise.

Tout d'abord nous décidons de coder sur émulateur, ça évitera de mettre trop la grouille sur notre téléphone. Ensuite il faut savoir que les scripts que SL4A est capable de lancer sont stockés par défaut dans /mnt/sdcard/sl4a/scripts. Enfin il faut savoir un petit peu se servir de l'outil adb qui est fourni avec le SDK Android (mais vraiment juste "un peu" je vous rassure).

Pour coder à notre aise donc, nous allons commencer par lancer un AVD2 (je vous conseille de lui accorder au moins 512Mo de mémoire vive, sinon ça rame vraiment trop !). Ensuite nous choisissons un répertoire sur notre machine hote qui servira de répertoire de développements3. Enfin, on va y coder notre premier petit script python pour android (avec VIM bien entendu) :

import android
droid = android.Android()
droid.makeToast("Bonjour le monde")

Pour info on aurait pu se contenter de print "Bonjour le monde" mais dans ce cas nous n'aurions pas eu d'interfaçage avec l'UI android et donc nous n'aurions pas pu afficher "Bonjour le monde" dans une petite info-bulle stylée en bas de l'écran. Bref, une fois le script écrit il faut le pousser sur l'AVD adb push bonjour.py /mnt/sdcard/sl4a/scripts/bonjour.py, il apparait alors dans le répertoire "scripts" de SL4A et en cliquant dessus ça fait bien apparaitre une mini info-bulle contenant "Bonjour le monde" :)

Script PYTHON sous emulateur Android.

Maintenant on peut se pencher sur une amélioration de tout ça. Par exemple optimiser le push. Pour cette étape j'ai bêtement fait un Makefile :

all:
    ls *.py | xargs -I 'X' ../tools/adb push X /mnt/sdcard/sl4a/scripts/X
    ls *.py | xargs -I 'X' ../tools/adb shell rm /mnt/sdcard/sl4a/X.log

Grâce à cette "astuce" il me suffit de faire un "make" avant chaque test pour que l'ensemble de mes scripts soient poussés sur l'AVD, et ça ne me change pas trop des habitudes prises en C/CUDA4.

Une fois qu'on sait afficher du texte dans une info-bulle on peut se pencher sur toutes les autres fonctions de l'API et laisser courir notre imagination. Pour les besoins de la cause je conclue d'ailleurs ce billet par quelques exemples de scripts inutiles mais drôles (et donc indispensables) qui m'ont servi de tests :

IRC-like :

import android
droid = android.Android()

victime = droid.pickPhone()
droid.smsSend(victime.result , "I slap you with a large trout")

IRC-like++:

import android
droid = android.Android()

armes = ['Truite', 'Chaussette', 'Andouillette']
droid.dialogCreateAlert("Choix des armes")
droid.dialogSetItems(armes)
droid.dialogShow()
response = droid.dialogGetResponse().result['item']
try:
  response = armes[int(response)]
except:
  response = armes[0]

victime = droid.pickPhone()
message =  "Je viens de te baffer avec une "+response
droid.smsSend(victime.result , message)
droid.makeToast("ENVOYE : "+message)

Gorgonite le 2011/06/06 12:24

C'est assez sympa de pouvoir développer des applications sur Android dans le langage de son choix, mais quid de la pérennité et l'éventuel frein à leur diffusion... tout le monde ne voudra pas forcément installer des machines virtuelles supplémentaires.

Enfin ça l'avantage d'être rapide pour réaliser des PoC sur cette plate-forme :)

  1. si tant est que quelqu'un aurait eu besoin d'explications pour ça...
  2. AVD = instance d'android virtuel
  3. Répertoire dans lequel vous pourrez faire un petit bzr init && bzr add * && bzr commit si le coeur vous en dit.
  4. Pour en savoir plus sur ce que j'ai fais récemment en C/CUDA (et pourquoi j'ai délaissé ce blog pendant des mois) je vous invite à aller suivre le SSTIC puisque j'ai l'immense honneur d'y donner une conférence mercredi prochain :) !

Page 1 / 3 »