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

Aller plus loin avec gEDA

Par Oz le - Électronique
Hack arduino atmega gEDA robot électronique

J'avais déjà rapidement parlé de gEDA dans un précédent billet1, mais pour ceux qui ne l'ont pas lu voici un rappel : gEDA est une suite de logiciels Open Source permettant de concevoir et simuler des circuits électroniques. Dans ce billet je vais tenter de résumer comment on peut utiliser gEDA pour amener la conception d'un circuit électronique à partir de rien jusqu'à l'obtention d'un véritable circuit imprimé physique. Le but de cet article est, d'une part, de me servir de pense-bête pour plus tard et, d'autre part, d'apporter quelques astuces à ceux qui, comme moi, vont découvrir de nombreuses embuches en tentant de concevoir leur premier circuit avec gEDA. Contrairement au billet précédent il n'y aura donc pas d'informatique du tout dans ce billet-ci et il risque d'être assez indigeste pour ceux qui n'ont pas envie de concevoir de circuits imprimés...Promis je me rattraperai plus tard avec d'autres billets ;) !

Arduino motor shield by Ozwald - Creative Common CCBYSA by ozwald.fr

gschem pour créer le schéma fonctionnel

Schéma gschem - Creative Common CCBY by ozwald.fr

Comme rappelé en introduction, gEDA est une suite de logiciels cohérents les uns avec les autres, chacun ayant son utilité propre. L'outil de la suite gEDA par lequel nous allons commencer est gschem qui permet de dessiner le schéma logique du circuit que l'on est en train de concevoir2. Il n'est pas follement intuitif d'utilisation mais après 30mn à s'habituer au maniement de l'outil on parvient à faire à peu près ce que l'on veut.

Pour dessiner votre schéma il suffit de sélectionner les composants que vous voulez dans la bibliothèque ( menu add > components ), de les placer sur l'espace de dessin, d'éditer leurs propriétés, puis de les relier entre eux avec des "nets node". Rien de bien sorcier en somme, mais voici quelques points qu'il faut penser à vérifier si vous ne voulez pas être ennuyé plus tard :

  • Penser à donner un nom unique à chaque composant (via sa propriété refdes)
  • Pour les composants "redondants" (c'est à dire les composants qui répètent plusieurs fois la même fonction logique, par exemple les 7404 qui contiennent 6 fonctions logiques "NON") vous ne ferez apparaitre qu'une fonction logique sur le schéma (forcément, c'est un schéma logico-fonctionnel) et pas une empreinte réaliste du composant. Pour retrouver vos petits lorsque vous concevrez le plan physique de votre circuit il faut penser à cette étape à mettre le même nom (propriété refdes) à toutes les fonctions logiques que vous voulez prendre sur le même composant, et à les différencier uniquement en fixant une valeur incrémentale3 (unique) à leur propriété slot.
  • Penser à donner une valeur à l'attribut footprint de chaque composant. Cette valeur servira plus-tard à définir l'empreinte physique du composant sur le circuit imprimé. Quelques footprints utilisables peuvent être trouvés dans /usr/share/pcb/pcblib-newlib/geda sur Gentoo, mais personnellement j'ai fini par agréger ma propre librairie de composants afin d'obtenir des descriptions cohérentes utilisables sur l'ensemble des logiciels de la suite gEDA, les composants fournis d'office n'étant généralement plus utilisables dès qu'on veux passer d'un logiciel de la suite à un autre4.
  • Faite apparaitre vos connecteurs sur le schéma logique :) ! On n'y pense pas forcément lorsqu'on a le nez dans le schéma fonctionnel mais il est essentiel de se rappeler que votre "signal IN" devra bien venir de quelque part sur votre circuit imprimé physique. Vous devez donc absolument ajouter à votre schéma gschem les composants de connectique.

pcb pour créer le plan physique

Schéma pcb - Creative Common CCBY ozwald.fr

Une fois le schéma fonctionnel satisfaisant (.sch) vous pouvez lancer pcb, également inclus dans la suite gEDA. Si vous avez bien fait votre fichier ".sch" un petit clic dans le menu File > Import Schematics fera apparaitre l'ensemble des composants physique de votre schéma logique (dans un gros paté au centre de la fenêtre de pcb). Si ça ne marche pas pensez à bien vérifier les points exposés dans la partie gschem ci-dessus, et si ça ne marche toujours pas changez les noms de vos composants pour des noms plus courts et homogènes en casse. Ca a l'air tout con mais j'ai constaté que certains composants qui n'apparaissaient pas dans mon import sur PCB, ont subitement décidés d'apparaitre quand j'ai changé leur nom de quelque chose du type LED_batterie vers quelque chose du type LEDB...allez comprendre !

Une fois l'ensemble de vos composants et connexions correctement chargées dans PCB vous allez pouvoir les disposer sur l'espace qu'occupera votre futur circuit ainsi que faire le routage5 à proprement parler. Le routage est une étape qui peut prendre BEAUCOUP de temps, mais elle est malheureusement nécessaire pour obtenir une réalisation physique qui fonctionne. Ci-dessous je vous mets quelques astuces en vrac pour le routage :

  • Penser à dé-selectionner, dans la colonne de gauche de l'interface de pcb, les "layers" que vous ne voulez pas utiliser avant de faire appel à la fonction "Auto-route" de pcb. En effet, si vous prévoyez de réaliser votre circuit imprimé sur une seule face (par exemple si vous voulez le graver vous-même par la suite) il ne faut pas que pcb utilise deux couches pour router (or en mode "Auto-route" il utilise autant de couche que possible). Pour ma part je ne garde que 2 "layers" actifs (en plus des layer "silk" et "rat lines" qui ne correspondent qu'à des indices visuels et ne sont donc pas utilisés par la fonction "Auto root") parce que j'utilise plutôt du double couche que du simple. En effet le double couche reste encore raisonnable (on peut graver soit-même des circuits en double couche) et il permet de router des cartes avec beaucoup de connexions sans avoir à ajouter des tas de ponts disgracieux jouant à saute mouton au dessus des pistes comme celà arrive rapidement quand on se restreint au simple couche.
  • Penser à faire un gros plan de masse, voire un gros plan au potentiel VCC (pour ce faire vous pouvez, par exemple, utiliser les outils polygone ou rectangle puis les relier à des net via un pin grace à l'outil "therm(al)").
  • Jouez avec la largeur et les marges de vos pistes. Plus ces paramètres sont petits plus le routage est facile, mais plus la réalisation physique sera compliquée et moins vous pourrez faire passer de courant.

Création physique !

Une fois votre PCB correctement créé sur le logiciel éponyme il est temps de passer à l'étape physique ! Pour ce faire il y a deux chemins possibles : soit vous fabriquez votre circuit vous-même, soit vous le faite fabriquer par une entreprise spécialisée.

Si vous décidez de fabriquer votre PCB pensez à faire un routage avec des pistes larges et bien espacées. En effet, la réalisation manuelle de PCB est une opération délicate donc moins il y aura de petits détails sur votre plan de PCB moins vous risquerez de faire d'erreur et ce n'est pas du luxe ! Plusieurs méthodes existent pour la réalisation manuelle mais sachez qu'elles impliquent toutes l'utilisation de produits chimiques et peuvent vous prendre plusieurs heures. Ne paniquez pas pour autant, de nombreux amateurs réalisent régulièrement des cartes6 donc je vous assure que c'est réalisable. N'ayant moi-même pas choisi cette voie je ne vais cependant pas rentrer dans les détails, mais cherchez sur le net et vous trouverez pleins de gens prêts à vous expliquer comment ils font chacune des deux étapes principales du processus :

  • Mise en place d'une couche protectrice sur une plaque cuivrée vierge reprennant la forme de votre circuit à imprimer : Soit vous utilisez des PCB recouvertes d'une résine photosensible et vous détruisez sélectivement cette résine en exposant la plaque à la lumière derrière un transparent sur lequel vous avez imprimé votre circuit; soit vous imprimez une image mirroire de votre circuit avec une imprimante laser sur du papier de mauvaise qualité (en espérant ne pas tuer votre imprimante) puis vous tentez de faire un "décalcomanie" de votre impression sur une plaque de cuivre nue avec un bête fer à repasser.

  • Trempage de la plaque, avec son masque protecteur, dans une solution qui va dissoudre le cuivre non-protégé et donc "graver" les contours de votre circuit : soit vous utilisez le produit chimique "standard" que vous devrez acheter pour une dizaine d'euros en magasin spécialisé (ce produit chimique est réutilisable), soit vous tentez de concocter vous-même un autre produit chimique à base d'eau oxygénée à vos risques et périls.

  • Suppression du masque protecteur une fois la gravure terminée pour découvrir vos pistes de cuivres : selon les méthodes suivies aux étapes précédentes ça peut être aussi simple que de laisser la plaque au soleil, ou un poil plus compliqué type "tamponnage de disolvant à l'aide d'un coton tige".

Power Shield - Creative Common CCBYSA by ozwald.fr

Si, en revanche, vous décidez de faire fabriquer votre PCB par une entreprise spécialisée soyez averti : ça coute cher ! La conception de circuits imprimés amateur n'est pas encore follement répandue et les tarifs restent avant tout orientés vers des tirages en série, le mode de facturation/production n'est donc généralement pas adapté aux besoins d'amateurs souhaitant n'avoir qu'un ou deux prototypes. Vous trouverez ainsi souvent un montant forfaitaire d'une trentaine d'euros pour "préparation des outils", puis comptez une autre trentaine d'euros pour une impression sur des PCB carré double face de moins de 10cm de cotés, et ajoutez enfin à celà une dizaine d'euros de frais de livraison. Pour ma part j'ai eu énormément de chance et j'ai pu faire appel à un petit fournisseur asiatique qui m'a réalisé 10 exemplaires de mon circuit (5,50cm par 5cm en double face) pour seulement 25€, donc je n'ai pas hésité :) ! Bref, ce qu'il faut retenir si vous voulez faire fabriquer vos circuits :

  • La tarification est souvent élevée et parfois obscure, mais il existe tout de même quelques fournisseurs qui mérittent le coup d'oeil.
  • Les entreprises acceptent généralement le format "gerber" pour vos fichiers, c'est un format vers lequel pcb sait exporter donc tout va bien7; mais parfois ils n'acceptent que le format du logiciel Eagle qui est un logiciel non-libre...à moins de tout recommencer sous ce logiciel vous ne pourrez donc pas faire appel à ces entreprises.

Une fois votre PCB nu en main8 vous n'avez plus qu'à vérifier qu'il n'y a pas de court-circuit apparent sur les contacts accessibles, puis à souder les composants en place (cf la photo du paragraphe précédent) et à faire vos tests unitaires en croisant les doigts !

Conclusion sur le combo gschem/pcb

Le message essentiel à retenir sur la conception c'est qu'il est vraiment important d'avoir des librairies de composants cohérents à utiliser sous gschem et pcb. Il n'y a rien de plus rageant que de faire son circuit logique sous gschem pour se rendre compte ensuite que le "footprint" que l'on a mis à toutes ses résistances est inconnu de pcb (et donc inutilisable) ou encore que le "footprint" de vos diodes d'électronicien correspond en fait à un composant de puissance d'un mètre de diamètre pour pcb -_- ... Pour ma part j'ai honteusement pioché/tranché dans les composants fourni avec gEDA sous ma gentoo ainsi que dans les nombreux éléments téléchargeables sur internet grâce aux explications de ce site de référence. Ayant tranché comme un goret dans des parties de code (et de commentaires, dont certains contenaient le descriptif de la version de license utilisée) pour rassembler ma petite librairie de composants je ne vais pas la rendre téléchargeable avec ce billet; mais si vous la voulez envoyez moi un mail et je pourrai vous passer une copie "en privée".

Enfin, quelques dernières remarques en vrac qui pourront vous aider si vous rencontrez des problèmes de composants incompatibles entre gschem et pcb9 :

  • Favorisez les composants dont les PIN sont numérotées (plutôt que "lettrées") dans gschem. Par exemple préférez le modèle "capacitor" dont les pins sont labélisées "1" et "2" plutôt que "P" et "N". En effet les symboles dans pcb utilisent plutôt des numérotations que des lettres donc si vous voulez que pcb parvienne à retrouver les connexions rentrées dans gschem il faut que les références des pins des symboles pcb soient les même que celles des symboles gschem.

  • Quelques associations "composant <==> footprint" qui marchaient pas trop mal dans pcb et qui étaient fournis dans ma Gentoo :

capacitor-2.sym <==> RCY100P 
lm7805-1.sym <==> TO126W
connector2-1.sym <==> JUMPER2
resistor-1.sym <==> ACY800
led-3.sym <==> RCY100
  • Les connecteurs sont parmi les composants les plus pénibles à traiter. En effet leurs formes sont souvent assez spécifique et il est essentiel qu'ils aient la bonne représentation dans pcb sous peine de vous retrouver avec un circuit imprimé inutilisable parce que vous n'avez pas la place de souder vos connecteurs. Pour ma part j'ai été obligé de créer mes modèles de connecteurs de toute pièce afin d'en avoir qui marchent10. Parmi les problèmes que j'ai cru identifier : les "connectors" fourni avec mon gschem ont des pin de type pas(siv) qui, visiblement, ne plaisent pas à pcb; du coup j'ai refait les connecteurs avec des pins de type io. Ah et mes connecteurs étaient également particulièrement sensibles aux blagues de nommage dont je vous parlais plus haut : quasiment tout ceux dont le nom dépassait 5 caractères n'étaient pas importés dans pcb...

En guise de conclusion : concevoir ses circuits imprimés ex nihilo jusqu'à obtenir un circuit physique qui fonctionne c'est une grande satisfaction (surtout pour moi qui ne pigeait rien à l'électronique il y a 4 ans); et même si le "motor shield" compatible arduino que j'ai réalisé et qui illustre intégralement cet article est bourré de maladresse de conception11 j'en suis très content et j'aurai peut-être l'occasion d'en faire bon usage :D !


Dromi le 2013/09/10 13:24

Après avoir utilisé Kicad au boulot, je te confirme qu'il est vraiment plus efficace que gEDA! (Bon ok il y a toujours une part de subjectivité...)
J'en ai profité pour lire la réponse à mon commentaire d'il y a 2 ans (...) à propos de la simulation :
Un commentaire revient régulièrement chez Kicad : l'important n'est pas d'implémenter une fonction, mais de pouvoir exporter au bon format (si possible standard mais bon ^^ ) pour pouvoir travailler avec d'autres outils qui font bien le travail et qu'il est inutile de chercher à concurrencer...
Ensuite je te l'accorde, ajouter un bouton qui lance automatiquement la simu sous ngspice, ça pourrait peut-être ne pas coûter si cher que ça...

  1. Billet qui a déjà presque 2 ans...le temps passe vite :)
  2. Dessiner/Relire des schémas logique à peine plus haut niveau que ceux dont nous parlons ici semble justement amuser follement beaucoup de monde en France ces temps-ci ;-)
  3. En commençant à compter à 1, donc 1,2,3,4,5 et 6 pour un 7404...Bah ouais c'est de l'électronique, pas de l'informatique donc on commence à "1".
  4. Douce ironie: les logiciels qui comptent plusieurs dizaines de miliers de ligne de code sont compatibles entre eux, mais les composants comptant à peine 20 lignes de texte, eux, ne sont pas prévu pour fournir des informatios à tout les logiciels...
  5. C'est à dire tracer les connexions physiques entre les différentes pates de vos composants.
  6. J'en connais plusieurs, et pourtant ils ne sont pas masochistes.
  7. Vous pouvez ensuite utiliser le logiciel gerbv pour visualiser les fichiers gerber générés par pcb avant de les envoyer à votre fabricant
  8. PCB nu tel que celui qui illustre ce billet, tout juste sorti de sa boite de livraison.
  9. Remarques qui vont surtout me servir de pense-bête personnel pour le jour où je voudrait rajouter des composants à ma bibliothèque et que j'aurai tout oublié :D
  10. Et encore...Je m'étais trompé dans l'espacement entre les deux pates de mon connecteurs, si bien que j'ai du les tordres à la pince pour les espacer afin qu'ils puissent s'enfiler correctement dans les trous prévus à cet effet dans mes circuits imprimés reçus d'Asie. Trous qui, celà dit en passant, étaient largement surdimensionnés pour mes connecteurs, j'aurai du modifier mes empreintes pcb pour utiliser des trous de la même taille que ceux destinés à accueillir les pates de mes autres composants.
  11. Pas de plan de masse, pas de condensateurs filtrant les parasites, pas de résistance de pull-up, etc... Mais les fichiers sont quand même disponibles si vous me les demandez par mail ;-)

Packons (ou pas) avec miasm et elfesteem

Par Oz le - Sécurité
Hack Outil Reverse Engineering code source miasm python scripts

Comme beaucoup le savent à présent MIASM est un framework d'ingénierie inverse écrit en Python par Fabrice Desclaux. Pour ma part j'avais joué un petit peu avec il y a un an, mais j'étais finalement assez rapidement passé à autre chose devant mon incapacité à porter un "packeur" maison de ''pefile'' jusqu'à ''miasm''. Aujourd'hui, je re-tente la même tâche !

Engine - Creative Common by "cbowns" on Flickr

Il y a un an donc j'avais eu des soucis quand j'avais voulu porter un concept simple de ''packeur'' sur le framework miasm. En effet la base de tout ''packeur'' c'est d'ouvrir un fichier à traiter (dans mon cas un fichier PE), lui apporter des modifications, puis le sauvegarder pour obtenir un clone fonctionnel de notre fichier à traiter (clone dont la signature binaire sera pourtant différente). Le problème c'est que, même en n'apportant aucune modification à mon fichier cible, elfesteem semblait incapable de générer un clone fonctionnel. Par exemple le code suivant, qui est censé n'apporter aucune modification à l'exécutable passé en argument, me retournait systématiquement un clone que Windows refusait de démarrer :

import sys
from elfesteem.pe_init import PE

e = PE( open(sys.argv[1]).read() )

open( sys.argv[1]+'_modified.exe', 'wb').write(str(e))

De mémoire j'avais testé sur "winmine.exe", sur "calc.exe", et sur "notepad.exe" : à chaque fois l'exécutable modifié ne fonctionnait plus :-(

Un an après, re-motivé par la super conf de serpi au SSTIC, je réinstalle smiasm sur ma machine1 et je re-tente. Tristesse : le bilan est le même. Les exécutables que j'ouvre puis que je sauvegarde avec elfesteem ne fonctionnent plus (enfin en tout cas "calc.exe", qui est ma cible de test préférée, donne systématiquement des clones "cassés"). Mais cette année, j'ai décidé d'investiguer un petit peu plus ! Retroussons nous les manches et voyons voir ça de plus près. D'abord assurons nous que "calc.exe" et "calc.exe_modified.exe" sont bien différents (et que nous n'avons donc pas à faire à un simple saute d'humeur de windows) : diff calc.exe calc.exe_modified.exe nous confirme que les fichiers sont différents.

Pour y voir de plus près on va regarder ces deux spécimens en hexa (xxd calc.exe > calc.exe.XXD && xxd calc.exe_modified.exe > calc.modified.XXD && diff *.XXD) :

5,14c5,14
< 0000040: 0e1f ba0e 00b4 09cd 21b8 014c cd21 5468  ........!..L.!Th
< 0000050: 6973 2070 726f 6772 616d 2063 616e 6e6f  is program canno
< 0000060: 7420 6265 2072 756e 2069 6e20 444f 5320  t be run in DOS 
< 0000070: 6d6f 6465 2e0d 0d0a 2400 0000 0000 0000  mode....$.......
< 0000080: 8745 1664 c324 7837 c324 7837 c324 7837  .E.d.$x7.$x7.$x7
< 0000090: 3907 3837 c624 7837 1907 6437 c824 7837  9.87.$x7..d7.$x7
< 00000a0: c324 7837 c224 7837 c324 7937 4424 7837  .$x7.$x7.$y7D$x7
< 00000b0: 3907 6137 ce24 7837 5407 3d37 c224 7837  9.a7.$x7T.=7.$x7
< 00000c0: 1907 6537 df24 7837 3907 4537 c224 7837  ..e7.$x79.E7.$x7
< 00000d0: 5269 6368 c324 7837 0000 0000 0000 0000  Rich.$x7........
---
> 0000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00000a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00000b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00000c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00000d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
21c21
< 0000140: 00f0 0100 0004 0000 fcd7 0100 0200 0080  ................
---
> 0000140: 00f0 0100 0004 0000 b16f 0200 0200 0080  .........o......
39,46c39,46
< 0000260: 0ffe 7d3b 3800 0000 0efe 7d3b 4400 0000  ..};8.....};D...
< 0000270: 0efe 7d3b 4f00 0000 0efe 7d3b 5c00 0000  ..};O.....};\\...
< 0000280: 0efe 7d3b 6900 0000 0efe 7d3b 7300 0000  ..};i.....};s...
< 0000290: 0000 0000 0000 0000 5348 454c 4c33 322e  ........SHELL32.
< 00002a0: 646c 6c00 6d73 7663 7274 2e64 6c6c 0041  dll.msvcrt.dll.A
< 00002b0: 4456 4150 4933 322e 646c 6c00 4b45 524e  DVAPI32.dll.KERN
< 00002c0: 454c 3332 2e64 6c6c 0047 4449 3332 2e64  EL32.dll.GDI32.d
< 00002d0: 6c6c 0055 5345 5233 322e 646c 6c00 0000  ll.USER32.dll...
---
> 0000260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000270: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000280: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 0000290: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00002a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00002b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00002c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
> 00002d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

Voilà donc l'intégralité des changements entre les deux fichiers. Sur 112ko ça fait peu, on devrait pouvoir être capable d'identifier précisément ce qui ne fonctionne plus :)

Le premier bloc de différence se situe entre les octets 0x0000040 et 0x00000d0. La position (en tout début de fichier) et le contenu ("...This program cannot be run in DOS mode...") de ce bloc me font penser qu'il s'agit uniquement du "DOS stub"2. Bref : pas intéressant.

Le second bloc de différence est beaucoup plus petit puisqu'il n'impacte que la ligne de notre dump commençant à l'octet 0x0000140. Sur toute cette ligne on a 3 octets de différence. Si jamais ces octets participent à une indirection quelconque on a peut-être trouvé notre coupable :) ! Après un petit tour dans "Lord PE" je réalise que, malheureusement, ce n'est probablement pas là que se situe le coupable. En effet la valeur du checksum de "calc.exe" est "0001D7FC" et celle de "calc.exe_modified.exe" est de "00026FB1". Cette suite d'octets correspond donc probablement au checksum du fichier contenu dans l'en-tête NT_HEADER. Comme le DOS-stub a été supprimé il est normal que la valeur de checksum soit différente; de plus il me semble qu'un checksum erroné n'empèche pas le lancement de programme simples (pour les drivers c'est une autre histoire, mais pour un soft user-land je ne crois pas que Windows vérifie). Pour avoir la conscience tranquille je modifie quand même la valeur du checksum dans "calc.exe.XXD" puis je reconstruit un binaire (xxd -r calc.exe.XXD > calc.exe.badchecksum.exe) et je peux confirmer que cette copie de calc.exe dont j'ai uniquement modifié le checksum pour en mettre un invalide fonctionne :)

Nous arrivons ainsi au troisième bloc, qui est devenu notre suspect numéro 1. En plus, les quelques chaines de caractères qui ont été remplacées par des octets nuls ont bien la tête d'informations provenant d'une table d'importation ("msvcrt.dll" , "KERNEL3.dll", etc.) ce qui pourrait expliquer le non-fonctionnement du programme si ces informations ont été altérées. Malheureusemnt "Lord PE" me donne des tables d'importations identiques entre les deux fichiers "calc.exe" et "calc.exe_modified.exe", et je ne trouve pas d'outil sympa pour disséquer simplement un PE :-( ...Qu'à celà ne tienne : on va en pondre un maison, à la porc bien entendu ! L'idée est simple : visualiser les structures du fichier PE simplement afin d'identifier à quelle structure appartient la zone d'octets modifiés.

Pour réaliser cet outil, quick&dirty rappelons-le, je vais faire appel à une poignée de fidèles compagnons :

  • VIM/gedit : parce que ce sont d'excellents éditeurs de code :)
  • C : parce que pour les manipulation "bas niveau" on n'a pas encore fait mieux que le bon vieux C.
  • Python : parce que pour les prototypages rapide c'est le must.
  • Python Imaging Library (PIL) : parce qu'on veut "visualiser les structures du fichier PE" et que c'est la meilleure librairie de manipulation d'image en python que je connaisse.

Enfin, en guise d'aide mémoire, on utilisera aussi les excellents tutos d'Iczelions sur les entrailles du format PE et quelques URL de références de chez Krosoft. Au final j'ai obtenu en un aprem de jeu :

* un prog en C qui analyse un fichier PE et dump ses caractéristiques sur la sortie standard (en prime j'ai rajouté un .h contenant les définitions des structures Windows, bien que j'aurai pu utiliser winnt.h qui doit se trouver quelque part sur ma machine étant donné que j'ai une gentoo avec de quoi compiler pour des centaines de systèmes exotiques, mais j'avais la flemme de le chercher :-D)
* un prog en python qui lance le prog en C sur un exécutable passé en argument, parse la sortie obtenue, et génère deux <s>jolies</s> images permettant de visualiser quelle partie du fichier appartient à quelle structure.

Toutes les sources sont en pièce-jointe de ce billet, donc n'hésitez pas à jouez vous aussi (mais n'oubliez pas : j'ai codé ça comme un goret donc si ça ne marche pas chez vous "c'est normal", et si vous le lancez sur des PE de plusieurs Mo il est probable que votre RAM soit intégralement vampirisée, vous êtes prévenus)(NDLR : utilisez plutôt la v4 qui génère directement un svg et qui est donc infiniment plus performante).

La première image est une représentation à l'échelle des différentes structures du fichier PE sur disque (pas tel qu'il sera mappé en mémoire donc). La seconde image n'est qu'une représentation "compressée" de l'image précédente où j'ai inséré des séries de "petits points" pour remplacer visuellement les énormes applats de couleurs unis représentant des zones de fichier où "rien de spécial ne se passe" et qui rendent le premier type d'image carrément gigantesque (1200x14688 pour représenter "calc.exe" dans mon environnement de test ;) ). Voilà comment on s'en sert :

$ gcc -o a.out oz_pedissector.c
$ python oz_pedissector3.py calc.exe
INFO:root:PE zone discovered [ZONE : IMAGE_DOS_HEADER   En-tete DOS 0   64]
INFO:root:PE zone discovered [ZONE : IMAGE_NT_HEADERS   En-tete NT  240 488]
INFO:root:PE zone discovered [ZONE : SECTION_HEADERS[0] En tete de section ".text"  488 528]
INFO:root:PE zone discovered [ZONE : SECTION[0] Contenu de section ".text"  1024    76800]
(...)
INFO:root:Compression second pass 98%
INFO:root:Compression second pass 99%
INFO:root:Compression second pass finished.

Nous voilà à présent avec deux fichiers ".png", l'un étant la représentation à l'échelle, l'autre étant la représentation compressée. Voyons voir ce que donne l'image "compressée" générée automatiquement à partir du "calc.exe" de mon environnement de test (je vous invite à l'avoir sous les yeux en taille normale lorsque vous lirez le paragraphe suivant) :

Oz PE dissector (Compressed Example file) - Creative Common CC-BY-SA by Ozwald

En la regardant de haut en bas on retrouve bien :

  • L'en-tête MS-DOS commençant à l'adresse 0x0 et allant jusqu'à 0x40
  • Une zone noire (représentation graphique de "on ne sait pas ce que c'est") juste en dessous. C'est le DOS Stub.
  • l'en-tête NT commençant à 0xF0 et allant jusqu'à 0x1E8 (on se rappelle à ce moment là que le second bloc de différence était situé aux alentours de l'adresse 0x140 et que nous avions supposé qu'il s'agissait du checksum contenu dans l'en-tête NT, nous avons à présent confirmation que cette adresse est bien dans l'en-tête NT ce qui conforte notre hypothèse s'il en était besoin)
  • Les trois en-têtes de sections qui suivent l'en-tête NT
  • Une seconde zone noir juste en dessous...on en reparlera.
  • La première section (".text") qui va de 0x400 à 0x12C0. On notera (sur la droite de l'image) que cette section contient les zones mémoires suivantes appartenant au DATA_DIRECTORY : Import Address Table, Debug, Import Symbols.
  • La seconde section (".data") qui ne contient rien de spécial.
  • La troisième section (".rsrc") qui contient, on s'en doutai, la zone spéciale "Resources" du DATA_DIRECTORY.

Bref : tout va bien :) Tout va tellement bien d'ailleurs que je tombe d'accord avec elfesteem : il ne devrait rien y avoir d'important entre l'adresse 0x260 et 0x4003...Le mystère reste donc entier4.

Edit du lundi 9 Juillet 2012, 14h53 : je remplace les 3 fichiers sources joints au billet par une archive unique contenant le tout, ça évitera les 403 imposés par mon hébergeurs sur les fichiers d'extension ".py" -_-

Edit du mardi 10 Juillet 2012, 17h30 : après vérification sur d'autres versions de "calc.exe" (provenant de diverses versions de WinXP) on retrouve toujours ce bloc non nul entre la fin des en-tête de section et le début de la première section... Etrange... Par contre je viens seulement maintenant de vérifier un exécutable qui serait plus typiquement ce que j'aurai besoin de packer5, à savoir gsecdump, et il ne contient bien que des octets nuls entre la fin de ses en-tête de section et le début de sa première section. Donc à priori il devrait pouvoir être ouvert/enregistré par elfesteem sans être "cassé". Je teste ce soir :) ! Accessoirement je rajoute en PJ la version 4 du script dégueu programme permettant la visualisation des structures de fichiers PE. Il est maintenant infiniment moins gourmand en mémoire (forcément: il génère du svg directement compressé au lieu de générer un bitmap "taille réelle" puis de le compresser...); je l'ai testé sans douleur sur un binaire de 13Mo (en lui demandant cependant de n'afficher que les structures de plus de 40 octets).

Edit du mardi 10 Juillet 2012, 20h30 : A force d'avoir l'évidence sous les yeux (à savoir que je ne vois aucune raison pour lesquelles elfesteem ne pourrait pas légitimement écrire des bytes nuls sur ces zones du fichier) j'ai re-testé l'ouverture/sauvegarde simple mais sur plusieurs binaires ne provenant pas de Microsoft cette fois (à savoir : gsecdump-v2b5.exe, ftpserv.exe, notepad++.exe, 7zFM.exe, python.exe) et ils ont tous fonctionné à merveille après leur passage dans elfesteem. La conclusion du premier chapitre de ce mystère s'écrit donc d'elle même : elfesteem fonctionne très bien, c'est le compilateur utilisé par microsoft qui produit des résultats "exotiques" (et je suis un gros c*uillon de ne pas m'en être rendu compte il y a un an -_- ...).

Edit du mercredi 11 Juillet 2012 : Serpi a trouvé la solution (lisez les commentaires du billet pour les détails). Le problème venait de bound imports..


Gorgonite le 2012/07/09 10:53

Allez, je tente ma chance :

open(sys.argv[1],'rb')
au lieu de
open(sys.argv[1])

nb: je ne peux pas tester au boulot ^^

Ozwald le 2012/07/09 12:02

La vache tu es rapide à répondre :-D !
Alors l'idée était intéressante mais je viens de tester et, malheureusement, j'obtiens le même résultats avec et sans le 'rb' :

$ md5sum  calc.exe\*
829e4805b0e12b383ee09abdc9e2dc3c  calc.exe
bab1aecd69fa7e93d03da67b8601faf4  calc.exe_modified.exe
bab1aecd69fa7e93d03da67b8601faf4  calc.exe_gorgonite.exe

Décidément plus j'y réfléchi plus je me dit que ça doit avoir à faire avec la table d'importation. Vivement que j'ai un peu de temps pour me re-pencher sur le souci :) !

Baboon le 2012/07/09 13:48

File les binaires ! (les 2 calcs)
J'ai jamais réussit à installer miasm chez moi et le SDK de win veux même plus s'installer non plus...)
(vais regarder ton précédent post tient)

serpilliere le 2012/07/10 15:38

Yop!
C'est peut être normal. Sous windows 7:
* tu copies calc.exe de c:\\windows\\system32
* tu la colles sur le bureau
* tu lances celle sur le bureau
* *surprise* => segfault.

il a un problème pour retrouver ses petits dans les ressource de LANG FR s'il n'est pas lancé depuis c:\\windows\\system32

Après si tu es sous XP, ca devrait fonctionner :/

Ozwald le 2012/07/10 20:51

Merci à tous pour vos réactions !

@baboon> Tu as les deux binaires dans tes mails (si l'adresse que j'ai utilisé est la bonne).

@serpi> Je testais sous WinXP :-/ En fait (je répète un peu mon dernier edit, mais s'pas grave) je ne vois toujours aucune raison pour lesquelles les binaires générés ne marchent pas. Du coup j'ai testé avec pas mal de PE non-Microsoft et ils fonctionnent tous à merveille. On peut donc plus ou moins dire que mon problème est résolu :) Mais niveau curiosité intellectuelle le mystère reste quand même entier : toutes les versions de "calc.exe" ou de "winmine.exe" sur lesquels j'ai pu mettre la main (WinXP, et 7), avaient cette caractéristique étrange d'embarquer, entre la fin des headers de section et le début de la première section, une partie binaire non-nulle qui semble essentielle au fonctionnement de l'appli (et qui, entre autre, répète la liste ASCII des DLL utilisées dans la table d'importation). Bref on peut renommer le mystère de "pourquoi elfesteem ne parvient pas à ouvrir/sauvegarder mes binaires de tests ?" en "pourquoi le compilateur/linkeur de microsoft fait des choses étranges ?" ^_^

serpilliere le 2012/07/11 08:53

Ok. c'est les bound imports.
En fait de base, elfesteem ne parse pas (encore) les bound import, du coup quand il rebuild, il les met à 0, par contre, il laisse l'entrée dans l'import directory.
résultat: quand tu lances, certaines fonctions ne sont pas fixée dans l'iat.

petit fix rapide: il faut transformer ton code en :

import sys
from elfesteem.pe_init import PE, pe


fname = sys.argv[1]
e = PE(open(fname).read())
e.NThdr.optentries[pe.DIRECTORY_ENTRY_BOUND_IMPORT].rva = 0
e.NThdr.optentries[pe.DIRECTORY_ENTRY_BOUND_IMPORT].size = 0
open(fname+".mod", "w").write(str(e))

bon ok: je vais fixer le tripou soit pour qu'il les parse, soit qu'il supprime l'entrée dans le header.
note: je n'ai pas remarqué au premier coup d'oeil, parce que *tout* mes scripts d'unpacking ou autre ont ce fix... oui, j'avoue c'est *painfull*.

du coup corolaire: Avec miasm, on apprend plein de truc dans la bonne humeur... Désolé pour le temps perdu!

serpilliere le 2012/07/11 09:09

Au passage, pour se marrer, la fameuse ligne de code est dans la doc (les slides du repository) de miasm: slide 9/105. (comme quoi je ne suis pas totalement un escroc :p)

Concernant le packer, j'en ai également un basé sur miasm développé pour les pentest, et j'avoue il y a encore 2/3 "blagues" du même style, du coup tu me donnes presque envie de le releaser (ce qui diminuera peut être son efficacité). Mais l'exercice reste intéressant.
Après j'avais une amie juriste qui m'avait glissé à l'oreille que la publication de code de ce genre reste illégale. Dieu ait pitié de l'âme des juristes.

Ozwald le 2012/07/11 11:56

Bound Import, nice catch !! Je ne maitrise pas du tout cette structure là des PE...je vais y jeter un oeil du coup (comme quoi tu as raison : c'est un exercice intéressant et instructif).

Pour la petite histoire : wine, lui, réussi à lancer les versions modifiées de "calc.exe" dont les bound imports sont "pourris" :-)

"Désolé pour le temps perdu!" <= c'est une blague ^_^ ? Tu ponds un framework super, tu le mets à dispo en open source, et tu t'excuse qu'il ne soit pas parfait ? C'est plutôt à moi de te payer une bière si on se croise un jour pour te remercier de l'opportunité d'en apprendre plus sur les bound imports :-p

Pour ce qui est de releaser un packeur il y a en effet un problème légal (une sombre histoire de "motif légitime" en rapport avec l'article 323-3-1 du code pénal si j'ai tout suivi...http://www.legifrance.gouv.fr/affic...).En tout cas c'est pour ça que je n'ai pas non plus releasé le mien :-/

  1. L'installation n'est d'ailleurs toujours pas plus facile, je vous recommande la lecture de mon billet de l'époque, ça peut éventuellement aider
  2. Le DOS stub est,en résumé, un programme MSDOS censé être lancé à la place du programme PE lorsqu'il est exécuté sous DOS. Le but de ce mini programme DOS est principalement d'avertir l'utilisateur que le programme ne peut pas fonctionner sous DOS.C'est une zone totalement facultative de nos jours.
  3. Ce genre de zone "poubelle" pouvant apparaitre naturellement dans le fichier suite à des jeux de padding puisque les structures PE imposent des tailles multiples de certaines constantes renseignées dans les en-tête
  4. En attendant un autre après-midi de libre (où je pourrait me re-pencher sur le problème), ou que quelqu'un poste un commentaire expliquant ce bien étrange phénomène
  5. Pour rappel : je suis pentesteur pro, si je pack des binaires c'est pour mon taff et pour sensibiliser mes clients au fait qu'un AV ce n'est pas l'outil miracle qui les protègera de tout et n'importe quoi; ce n'est pas pour faire du pr0n, monter un botnet, ou pire : boire dans votre YOP.

SSTIC 2012 en approche

Par Oz le - Sécurité
Hack SSTIC

L'été arrive et avec lui un bon lot de conférences sur la sécurité informatique, parmi lesquelles le SSTIC version 2012. Si tout se passe bien je devrai avoir la chance d'y assister (coté public exclusivement cette année) et il faut bien avouer que j'ai hâte. Bien que certains regrettent les choix réalisés par le comité de programme j'ai personnellement déjà repéré quelques conférences tout particulièrement intéressantes...

Tarot - Creative Common by "MShades" on Flickr

Jour 1 - Mercredi 6 Juin :

12h30 - Sécurité du RDP : étant donné le pédigré des auteurs le contenu risque d'être intéressant, et vu le nombre de fois où on croise du RDP en pentest il est certain que si j'apprend quelque chose je pourrai l'utiliser rapidement :)

15h15 - L'information, capital immatériel de l'entreprise : Là c'est un pari puisqu'on risque d'avoir soit une étude passionante sur l'influence réelle de l'information en entreprise, soit un insipide exposé de platitude sans possibilité de se rattraper puisque ne parlant même pas d'informatique...à voir sur pièce donc (mais avec un peu d'apréhension quand même).

Jour 2 - Jeudi 7 Juin :

9h30 - IronHide: Plate-forme d'attaques par entrées-sorties : Le sujet peut être très intéressant mais tout de même risqué (dans une moindre mesure que la conf sur le capital immatériel tout de même). Le minimum que j'en attend c'est d'avoir des idées de nouveaux terrains de jeux (à base de teensy-like par exemple); et ce que j'espère éviter c'est un lent exposé de normes de communications matériels sans applications "sécu".

14h45 - Expert judiciaire en informatique : Depuis le temps que je lis ses billets d'humeurs je suis curieux de voir ce que donne un "Zythom" en direct :)

Jour 3 - Vendredi 8 Juin :

12h15 - Successes (and limitations) of (static) binary analysis : Ca sent bon la présentation technique et j'espère bien que cette conf me permettra de rattraper mon retard en analyse statiques (ou au moins qu'elle me fournira des pointeurs pour que je puisse rattraper mon retard après le SSTIC). Et si, par la même occasion, je peux récupérer des idées pour l'amélioration en cours de mon vieux bidouilleur de binaires ça sera tout bonus !

14h30 - Miasm: Framework de reverse engineering : SLUUUURP ! Là c'est très clairement la conf que j'attend le plus. Je parlais déjà de ce framework il y a presque un an, et il m'intéresse toujours autant. Durant l'année passée j'ai eu quelques soucis d'utilisation (du fait de sa jeunesse) mais j'ai continué de garder un oeil dessus et j'ai carrément pondu un article d'initiation au packing ou 50% des exemples utilisent Miasm. Si cette conf peut compenser un peu le manque de documentation et m'éclairer sur les fonctions funkys du framework je serai donc ravi de la session SSTIC 2012 :) !

Voilà pour ma première sélection "à la gueule". Mais de toute façon j'ai bien l'intention d'assister à toutes les conférences (sauf peut-être les premières du vendredi matin, social-event oblige) et je m'attend donc à être agréablement surpris par une ou deux confs dont le titre ne m'as pas sauté aux yeux à l'heure actuelle. Affaire à suivre dans un prochain billet donc :)


Gorgonite le 2012/05/25 15:02

Le jour 3 a l'air passionnant (petite larme d'émotion pour l'analyse statique, si belle mais souvent raillée dans ce monde de fuzzers :P )

Culture automatisée

Par Oz le - Biologie
Hack Nepenthes atmega barbu botanique code source robot

Ce qu'il y a de bien avec l'état d'esprit du "hack" c'est qu'on peut l'employer dans de nombreux domaines. Et ce qu'il y a de très bien c'est qu'on peut faire des combinaisons de plusieurs domaines ! Par exemple on peut faire un combo programmation/électronique/biologie et obtenir des petites expériences amusantes comme celle que je vais retranscrire ici.

Creative Common by Ozwald from ozwald.fr, mai 2012

L'idée de départ c'est qu'habitant en ville je ne peux planter aucun végétal "naturellement". A la rigueur je dispose de deux rebords de fenêtre pour poser des petits pots ou des jardinières mais c'est quand même super limité. Du coup comment faire pour améliorer autant que possible la pouse de jolies plantes carnivores ? La solution simple c'est d'abandonner la méthode "naturelle" (i.e. : fabriquer une tourbière de quelques mètres carrés avec écoulement d'eau permanent pour recréer des conditions marécageuses) et passer à de l'artificiel.

"Artificiel" ça peut recouper beaucoup de choses et du coup je me suis renseigné. J'ai ainsi découvert et approfondi les méthodes de cultures allant de la simple serre, à la culture aéroponique/ultraponique, en passant par l'hydroponique et l'aquaponique. Toutes ces méthodes semblent intéressantes mais il y a un point que j'ai trouvé systématiquement mal traité : l'éclairage. En effet on comprend vite que les méthodes en ultraponie, en hydroponie, ou en aquaponie, adressent principalement des problématiques d'arrosage; mais le problème de l'éclairage, lui, est souvent traité d'une façon qui m'a laissé sur ma faim. Bien souvent en effet les seuls remarques que l'on peut trouver sur l'éclairage se résument à quelque chose du genre : "une lampe au sodium super puissante, puisque c'est ce qui s'approche le plus de la vrai lumière du soleil et que ça chauffera super bien ta culture en placard où tu pourra planter plein d'herbe de provence1"

Ce genre de réponse ne me satisfait pas du tout puisqu'elle vise une problématique qui n'est pas la mienne. En effet ce type de réponse vise le problème de recréer des conditions naturelles dans un espace à l'abri des regards (à savoir un placard -_-); ma problématique à moi c'est de faire pousser des plantes carnivores (et des tomates cerises2). Ayant quelques bases en optique je me disais que les plantes n'avaient pas besoin de tout le spectre lumineux du soleil pour bien pousser, et du coup j'ai été faire un petit tour sur wikipédia pour obtenir le spectre d'absorbtion de la chlorophyle :

Spectre d'absorbtion de la chlorophyle - From Wikimedia

En regardant ça je me suis dit qu'il était probablement superflus de vouloir recréer un spectre lumineux continu, et je me suis dit qu'il était certainement énergétiquement plus intéressant de faire un éclairage par LED à des fréquences de couleur bien choisies :) ! J'ai farfouillé sur le net pour savoir si quelqu'un avait déjà fait ce genre d'expérience mais ce ne fut pas très fructueux. J'ai bien trouvé quelques personnes qui disaient qu'il fallait faire pousser sous du rouge, d'autre sous du rouge et du bleue, mais aucune référence sérieuse pour étayer le propos. Devant ce manque d'information pour me guider dans le choix (dois-je prendre des LED bleues ? Rouge ? Un mélange des deux ?) j'ai pris la décision le plus "simple" : faire un test3.

Le protocole de test est simple : je vais réaliser en même temps 5 cultures de graines issus d'un même lot et semées dans un même terreau. L'une de ces cultures sera laissée à la lumière naturelle, l'une sera plongée dans le noir total, et les trois autres seront respectivement éclairés exclusivement par une diode rouge, bleue, et UV4.

Pour cette expérience je vais donc utiliser 4 pots en plastique noir. L'un sera directement retourné sur la culture qui ne doit pas avoir de lumière, et les trois autres seront préparés pour éclairer au mieux d'une unique couleur les cultures qu'ils recouvreront. La préparation est simple : j'ai recouvert les bords intérieurs de papier aluminium afin que le maximum de lumière atteigne les plants, j'ai planté une diode sur le fond du pot pour que ses connections soient accessible de "l'extérieur", et j'ai calibré une résistance série pour que chaque diode consomme autant de puissance électrique malgré la différence de chute de tension à leur bornes :

Pot opaque vu du dessus.

Intérieur d'un pot pour éclairage mono-couleur.

Pour garder les diodes allumées 12h par jour, et éteintes 12h par jour j'avais deux solutions : les allumer moi-même à heure fixe tout les matins et les éteindre tout les soirs; ou opter pour une méthode de feignasse et utiliser un microcontrolleur pour qu'il fasse ça à ma place. Bien évidemment j'ai opté pour la solution micro-controlleur (ce qui m'a permit de m'entrainer à la gestion des timers et des interruptions, ainsi que de constater que si je veux faire une "vrai" horloge un jour il faudra que j'utilise un chip RTC pour compenser la dérive de l'oscillateur interne du micro-controlleur5). Si vous êtes curieux vous pouvez aller jeter un oeil au code que j'ai mis en pièce jointe de ce billet, m'enfin il n'est pas très intéressant puisque 70% du code porte sur l'affichage et le réglage de l'heure (affichage réalisé par un afficheur 7 segment, et réglé par un unique bouton permettant d'avancer le temps).

Il ne reste donc plus qu'à mettre le terreau dans un grand bac en plastique, à y semer des lentilles vertes (parce que, de mémoire de classe de CP, ça pousse super facilement et rapidement ces trucs là) et à lancer l'expérience. Voilà donc à quoi ressemble le montage expérimental :

Expérience en place.

Après une dizaine de jours les résultats sont surprenants :) !

Pendant la germination nous avons deux cultures qui ont nettement pris la tête : la culture obscure (!!), et la culture rouge. Ces deux cultures étant suivi de prêt par les cultures bleue et UV, elles-même tallonnées par la culture au soleil.

Une fois la germination effectuée et les premières feuilles sorties on assiste à un équilibrage des 5 cultures en termes de longueur de pousse (y compris de l'obscure donc !). La seule culture qui se détache nettement des 4 autres reste néanmoins l'obscure puisqu'au lieu d'avoir de jolis plants verts elle a des plants blanchatre qui ont un peu de mal à tenir vertical.

Si vous voulez plus de détails sur le déroulement précis de la pousse de chaque échantillon vous pouvez télécharger le fichier ZIP joint à cet article, il contient quasiment une photo pour chaque jour d'expérience.

A partir de cette expérience super simpliste les questions ouvertes sont pourtant nombreuses :

  • Pourquoi le pot obscur germe-t-il aussi vite ? Est-ce grace à l'effet "serre" du pot opaque qui augmente l'humidité ?
  • J'ai visé du bleue, du rouge, et de l'UV, qui sont tout les trois normalement assez bien exploités par la chlorophyle; cependant le relatif "match nul" au bout de l'expérience me fait penser que j'aurai pu tenter une culture sous lumière verte pour vérifier si, comme la théorie l'indique, j'aurai obtenu les même résultats que dans l'obscurité (ou pas ?!)
  • Mes cultures sous lumière artificielle ont poussé quasiment à la même vitesse que celle exposée à la lumière du jour; que se passe-t-il si je double la puissance lumineuse artificielle ? Et si je la divise par deux ?
  • J'ai imposé des cycles jours/nuit de 12h/12h à mes cultures sous lumière artificielle. Que se passe-t-il si j'accélère ou si je ralenti ce cycle ?
  • Enfin une question évidente : que se passe-t-il sur une durée de vie plus longue de la plante (là j'ai abandonné l'expérience avant la maturation complète des lentilles faute de parvenir à bricoler des serres opaques assez grandes) ? De même que se passe-t-il sur d'autres plantes (par exemple sur les tomates cerises et les drosera/nepenthes que je vise) ?

Enfin, en guise de conclusion, je constaterait la chose suivante : Chaque LED utilisait environ 54mW de puissance électrique (51.5mW pour le rouge, 54.4mW pour le bleue, et 57.8mW pour l'UV), les pots faisant 7x7cm à la base (mesure intérieure) on est donc sur une consommation de 11W/m² pour l'éclairage, c'est à dire une énergie consommée de 48kWh pour un an d'éclairage. Si on recoupe avec les infos de Wikipédia sur les panneaux solaires (à savoir qu'un m² de panneau solaire a une puissance d'environ 150Watt Crete et qu'en Europe 1Watt Crete permet de produire environ 1kWh/an6) on obtient qu'un panneau solaire de 1m² pourrait, sur une année, fournir assez de puissance (150kWh) pour éclairer environ 3m² de culture :) Intéressant, non ?


dromi le 2012/06/13 20:29

J'ai lu un article qui m'a fait penser au tien bien qu'il n'a qu'un rapport éloigné :
http://passeurdesciences.blog.lemon...
Pour celui qui a la flemme de cliquer sur le lien, l'article parle de la communication entre les plantes pouvant être chimique, visuelle ou sonore (ou autre). Leur communication visuelle dépendant de la longueur d'onde de la lumière reçue (ce n'est pas le sujet, c'est le lien avec le présent article)
Et la conclusion du texte, c'est pour t'inciter à poursuivre tes expériences, en comparant la croissance des plantes soumises à du Bach ou du Ramstein... (tu as pensé emmener tes plantes à Clisson demain ?)

  1. J'ai mis "herbe de provence" mais en réalité internet est submergé de conseils pour faire pousser un tout autre type de plante (dont, perso, la méthode de pousse m'intéresse encore moins que celle des herbes de provence).
  2. j'ai été obligé par ma copine
  3. On peut également dire "expérience" pour faire plus scientifique.
  4. Référez-vous à la courbe d'absorbtion de la chlorophyle, vos verrez que le rouge vise un pic important sur la chlorophyle A, la bleue vise un pic important de chlorophyle B, et l'UV tape un plateau correcte pour la A et un peu la B
  5. Sur la durée de l'expérience, à savoir un peu plus de 10 jours, l'horloge de l'Atmega8 avait pris un peu plus d'une heure de décalage.
  6. Avec une forte variance, en gros entre 0.5 et 1.4

Page 1 / 3 »