dimanche, novembre 9 2014

Avaro omnia desunt, inopi pauca, sapienti nihil

L'an dernier, j'avais relaté quelques unes de mes pérégrinations dans la réalisations de mes propres PCB et, depuis, je m'étais lancé dans la conception de ma propre planche de prototypage à base d'atmega(8), d'un cristal à environ 16MHz, sans doute d'un cristal de montre pour pouvoir utiliser le tout en RTC, et en gardant les ports I2C/SPI accessibles pour jouer avec des nRF24 facilement. Mais cette semaine, j'ai réalisé que ce n'était pas très malin...

Shenzen - CCBYSA by Britt Selvitelle on Flickr

Lorsque j'avais conçu un PCB pour contrôler des moteurs depuis un arduino et que je l'avais commandé (pour une bouchée de pain chez elecfreaks), je m'étais dit que c'était le début d'une longue liste de commande de PCB personnalisés. Sauf que le second dans lequel je me suis lancé (un PCB générique de prototypage) n'est toujours pas terminé. En raison de la vocation très générique de ce PCB, je n'arrive pas à me décider sur un design et je tourne en rond sans rien commander. Pourtant, j'ai trouvé un second fournisseur vraiment pas cher et je meurt d'envie de le tester. Finalement, j'ai donc décidé d'abandonner (pour l'instant) l'idée de concevoir mon propre PCB de prototypage et j'ai même décidé d'en faire un micro article afin de communiquer plus largement l'une des raisons de ce choix qui me semble méconnue : les clones d'arduino, ça ne coute rien.

L'été dernier, j'ai eu l'honneur de présenter une seconde conférence au SSTIC. Si vous regardez les slides (ou mieux : la vidéo) de cette conférence vous verrez que, lors d'un calcul de couts, je compte un arduino pour "environ 7€". Après cette conférences, plusieurs personnes sont venus me voir[1] pour me dire que si ça coute vraiment si peu ils veulent bien m'en acheter une centaine pour les revendre. J'aurai du accepter :p ! En effet, les prix français/européens sont bien ancrés dans nos esprits et, pour la majorité des gens que je croise, un arduino coute environ 20~25€. Moi-même, jusqu'à il y a peu, j'avais inconsciemment l'idée qu'un arduino "ça ne coute pas cher" mais qu'un microcontrolleurs ça ne coute vraiment rien. En réalité, et c'est en partie ce qui a motivé ma décision d'abandonner mon projet de PCB de prototypage, un clone d'arduino ça ne coute quasiment rien. Pour être précis, ça coute 3,02€ chez aliexpress ou 4,70€ sur ebay,..tout ça livraison incluse.

Autant vous dire qu'à 3€ le clone d'arduino, même en déployant des trésors d'ingénierie, mon PCB conçu maison n'atteindra jamais un tel rapport qualité prix[2] ! Bref : je me suis commandé une demi-douzaine de clones d'arduino (chez 3 vendeurs différents, pour limiter les risques de paquets s'évaporant en chemin) pour moins de 20€, et je peux à présent passer à la conception de PCB dédié à une tache spécifique :-D ! Les deux seuls inconvénients de cette solution découlent du format bien particulier de l'arduino : il fait un peu plus de 5cm de large (donc pour nos shields on rate, de quelques millimètres, le format le moins cher des graveurs de PCB : 5cmX5cm) et les deux rangées de connecteurs empilables ne sont pas alignées[3] ce qui est un peu chiant pour l'alignement dans gschem. En tout cas, j'espère avoir motivé certains d'entre vous à ressortir leurs arduino du tiroir/placard où il prend la poussière, et je conclurai ce billet ainsi : Happy Hacking !

Notes

[1] D'ailleurs : merci à tous pour vos retours ! C'est vraiment très sympa de savoir que la conférence a été appréciée, au moins par certains :)

[2] Sur le prix d'un PCB personnalisé on peut déjà compter 1€ pour le microcontrôleur et minimum 1€ pour le PCB. Il ne reste plus grand chose pour les connecteurs, les cristaux, les résistances/diodes, et la main d’œuvre

[3] En électronique amateur l'un des standard d'espacement les plus répandus est 0,1", soit 2.54mm. L'arduino utilise cet espacement partout sauf à un endroit, où il y a un décalage de 0,16" ce qui décale la moitié du PCB ...Pourquoi ont-ils fait ça?

dimanche, novembre 2 2014

Mes frameworks python du moment

Fin 2012, je vous avait parlé de deux modules standards de la librairie python que j'aimais beaucoup : logging et argparse. Presque deux ans plus tard, il est temps de rendre hommage à deux modules n'appartenant pas à la librairie standard python mais que j'aime tout particulièrement : peewee et bottle.

Swiss Knife - Creative Common by "focusforaword" on Flickr

Sqlite, MySQL, PostgreSQL ? Les trois !

Nombre des scripts python que j'écris ont besoin, lorsqu'ils atteignent une certaine importance, de stocker des informations de façon stable. Dans les cas les plus rustres je me contente d'un gros coup de pickle dans un fichier qui sera relu plus tard mais, parfois, le stockage et l'accès à mes données doit être plus performant et l'utilisation d'un outil dédié (à savoir : une base de donnée) semble trop évidente pour être ignorée. Avant que je ne découvre peewee, je réglais systématiquement ces besoins par la librairie sqlite3[1], qui a le bon gout d'être standard et, donc, de ne pas rajouter de dépendance à mes scripts (les laissant ainsi le plus portable possibles d'une machine à une autre). Malheureusement, cette approche avait deux défauts majeurs pour moi :

  • Si sqlite s'avérait, au bout de plusieurs semaines/mois d'utilisation, trop léger pour traiter mon volume de donnée je n'avais plus qu'à tout reprendre de zéro pour passer à une solution plus robuste
  • Je suis mauvais en SQL, donc taper les requêtes SQL moi-même était à la fois désagréable et peu efficace.

C'est pourquoi, lorsque j'ai découvert peewee, j'ai tout de suite adhéré au concept : c'est un orm (donc pas de SQL à écrire) capable d'utiliser un backend sqlite, mysql, ou postgresql sans avoir à changer le code :) ! En plus, je connais quelqu'un qui m'a dit récemment avoir testé peewee sur un backend sqlite avec une dizaine de thread concurrent sans le moindre problème. Étant assez fan du multithread et ayant déjà eu des problèmes avec sqlite ça serait pour moi la cerise sur le gateau si cette robustesse en multithread se confirmait :) ! En tout cas, j'ai déjà utilisé peewee sur une demi douzaine de projets (dont un qui commence à devenir assez imposant) et c'est toujours un plaisir de travailler avec cet ORM : je le conseille donc vivement.

Seul tout petit bémol, pour terminer le paragraphe : j'ai eu UN problème avec peewee. Je voulais utiliser un modèle ou une table A référence (foreign key) une table B; sachant que la table B doit, elle aussi, avoir une foreign key vers la table A. Oui, je sais, ça fait des références circulaires et ce n'est généralement pas le signe d'une bonne conception mais dans mon cas d'usage c'est ce que je voulais faire et il n'y avait pas vraiment d'autres alternative plus propre. Comme peewee crée les table en base au fur et à mesure qu'il lit la déclaration des objets, ce cas d'usage pose problème puisqu'au moment de créer la première table peewee n'a pas (encore) connaissance de la seconde et il échoue plante lors de la création de la foreign key vers une table qui n'existe pas encore. Si une seule des deux tables référençait l'autre, il me suffirait d'inverser l'ordre de déclaration des tables pour que peewee découvre la table cible en premier, mais comme j'ai une référence dans les deux sens cette astuce ne me sauvera pas. Tout n'est pas perdu pour autant : peewee prévoit ce cas de figure et propose d'utiliser un objet "proxy" qui sera la cible de la foreign key lors de la création des tables puis qui sera remplacé par les tables réellement ciblées une fois qu'elles auront toutes été créées. En pratique, ça marche probablement...SAUF quand le backend est sqlite.

En effet, pour réaliser ce tour de passe-passe, peewee crée les tables sans les foreign key lorsqu'il découvre la déclaration des objets, puis il utilise l'opération SQL "ALTER TABLE" pour ajouter les "CONSTRAINT" de foreign key au moment où l'objet proxy est remplacé par les bonnes tables une fois que ces dernières ont toutes été créées. Sauf que sqlite ne supporte pas cette fonctionnalité du SQL. Dans ce cas, il n'y a que deux solutions :

  • abandonner sqlite
  • créer (et gérer) soit même la relation de foreign key, en utilisant de simples champ "INTEGER" référençant un ID dans la table cible (c'est ce que j'ai choisi).

Nginx ? Apache ? IIS ? Aucun des trois !

Une autre fonction classique de mes script python lorsqu'il prennent de l'ampleur, c'est d'avoir une interface web (plus ou moins simpliste). Pour ce faire, j'ai longtemps surchargé BaseHTTPServer. J'ai bien tenté d'utiliser cherrypy ou django quand je prévoyais que mes scripts allaient demander un peu plus de complexité, mais j'ai vite abandonné[2]. Bref : j'utilisais toujours BaseHTTPServer, et j'étais contraint de ne faire que de petits sites un peu minables...

Puis, un jour, j'ai découvert wsgi sur l'excellent site de bortzmeyer. En résumé : c'est une spécification, dans un esprit assez similaire à CGI, qui définit une interface python très simple pour fabriquer des sites web. Pour offrir une interface web à votre script python il vous suffit donc de créer une classe d'objet "application" compatible avec la spécification WSGI, et à donner cette classe en argument à un serveur web compatible (par exemple, le "simple_server" de la librairie standard wsgiref). L'énorme avantage, c'est que si vous avez besoin de performance plus tard il vous suffit de changer le serveur web sans changer une seule ligne de votre code python (un peu comme lorsque vous changer de backend peewee). Par exemple, vous pouvez remplacer le serveur inclus dans la librairie standard par werkzeug ou cherrypy et vous obtenez immédiatement le support du multithread (qui est quand même un must pour tout ce qui est web).

Le seul inconvénient de WSGI (qui m'est tombé dessus assez vite), c'est que ça reste quand même assez rustre. On est à un niveau d'abstraction supérieure à BaseHTTPServer, mais il manque quand même des raccourcis pour réaliser certaines tâches classiques (comme mettre des fichiers statiques à dispositions, types CSS ou JS). On se retrouve donc rapidement à devoir passer du temps de développement sur des fonctions "inutiles" vis-à-vis de l'objet du script mais nécessaires au fonctionnement de l'interface web (et servir des fichiers static peut prendre beaucoup de temps de développement, en particulier quand on est paranoïaque et qu'on veut faire attention aux LFI et autres farces). C'est pourquoi, je conseille bottle ! Bottle est une librairie python non-standard typiquement dans l'esprit de ce que j'aime : elle tient en un seul fichier python (et vous pouvez donc l'embarquer partout sans sacrifier la portabilité de votre script :)). C'est un wrapper mince autour de la spécification WSGI qui vous offre des facilité pour tout un tas de besoins classiques, par exemple :

De plus, comme bottle est un wrapper mince, on ne perd pas la puissance de WSGI puisqu'on peut passer d'un simple serveur embarqué écrit en python à des choses plus musclées comme gunicorn ou bjoern sans avoir à ré-écrire son code ! Cerise (inutile mais agréable) sur le gateau : bottle accepte d'utiliser un serveur "auto" signifiant qu'il va, tout seul, sélectionner le serveur compatible WSGI le plus performant actuellement installé sur votre machine (sympa quand vous avez cherrypy sur vos machines de prod mais pas sur les machines de dev par exemple).

Notes

[1] et, par moments, pysqlite qui permet d'optimiser un peu l'utilisation du moteur sqlite

[2] Je conserve de ces tests une légère rancœur envers django, qui m'a laissé la très mauvaise impression de sortir une nouvelle version tout les 6 mois tellement incompatible avec les versions précédentes que même les tutoriaux "Hello World" ne fonctionnaient subitement plus

lundi, novembre 11 2013

shutdown -h -~1500

Hier, Cédric "Sid" Blancher est mort d'un accident de parachutisme. En plus d'être un hacker extrèmement compétent c'était quelqu'un de gentil qui communiquait ses passions à merveille. D'ailleurs, si ce blog existe c'est en grande partie grace à l'inspiration prodiguée par sa petite parcelle d'internet. Je ne le connaissais pas très bien mais j'avais l'habitude de le voir de loin aux conférences de sécurité informatique où je traine mes guètres, et j'ai même eu le privilège et le plaisir de discuter quelques fois avec lui à ces occasions. Il va manquer au paysage de la SSI française et à beaucoup de monde, dont moi. RIP Sid.

Black
Les mots me manquant je me contenterai, en conclusion, de lier ci-dessous quelques homages supplémentaires, sans prétention d'exhaustivité (loin de là) :

lundi, août 12 2013

Crypto à retardement : à la règle et au compas

Ce billet est le dernier d'une série de 4 portant sur l'analyse à postériori de l'étape 2 du challenge SSTIC 2013. Si vous n'avez lu aucun des billets précédents je vous invite à lire au moins l'introduction du premier avant de plonger dans celui-ci. Si vous avez lu les billets précédents : félicitations, vous arrivez au bout du tunnel :) !

enigma - Creative Common CCBY by "joncallas" on flickr

Les classiques

Dans les billets précédents j'ai avancé pas mal dans la cryptanalyse de data mais je reste encore relativement loin de la solution. Il est probable qu'en forçant dans cette voie je finisse par obtenir la bonne solution, mais il est temps d'explorer une autre voie ! Jusqu'à présent j'ai beaucoup avancé par recettes persos en tirant parti de mes connaissances sur la crypto du 19ème siècle et antérieur, mais je pense qu'il est temps de se pencher sur ce qui se fait dans les attaques crypto un peu plus modernes.

J'ai donc raffraichi pas mal de vieux souvenirs de cours et j'ai éliminé une à une toutes les attaques modernes.

Au final je me suis dit que je ne trouverai pas de protocole d'attaque "tout prêt" utilisable dans mon cas. Tout ce temps n'était pas perdu pour autant ! A force de lire toutes ces méthodes d'attaques, et les descriptifs des chiffrements attaqués, j'ai réalisé que le schéma SBox-PBox-XOR était vraiment très, très, très répandu et que les SBox tenait un rôle de première importance dans la force du chiffrement. Je me suis alors dit : "et si on était en face d'un algorithme de chiffrement de ce type, particulièrement nul ?". Le "particulièrement nul" était légitime, puisque c'était notre hypothèse de base depuis le départ (après tout c'est un challenge, on est censé pouvoir le casser !). Sur wikipédia on vous explique que l'une des caractéristiques les plus essentielles des SBox c'est de générer beaucoup de changements de bit (environ 1 sur 2) quand on change un seul bit en entrée; j'ai donc supposé qu'on avait à faire à une SBox tellement nulle qu'elle changeait un seul bit quand on changeait un seul bit en entrée :)


Ce bon vieux Hamming

En formulant cette hypothèse de faiblesse de la SBox, je ne sais pas pourquoi, j'ai immédiatement pensé à la distance de hamming : si je prends deux octets qui diffèrent d'un seul bit, et que je les passe dans ma mauvaise SBox, leur représentation chiffrée diffèrerons, elles aussi, d'un seul bit ! Ce qui devient vraiment sympa c'est que, lorsque je passerai ces représentation chiffrées dans la PBox, les deux octets résultants diffèreront, eux aussi, d'un seul bit entre eux (une PBox se contente de ré-ordonner les bits de façon déterministe). La cerise sur le gateau c'est que si j'applique un XOR à mes deux sorties de SBox, les deux octets que j'obtiendrai diffèreront, encore une fois, d'un seul bit ! En bref : si la SBox est aussi nulle que je l'ai supposé, deux octets ayant une distance de hamming de 1 seront chiffrés (par tout l'algorithme de chiffrement) en deux octets ayant une distance de hamming de 1 entre eux.

Arrivé à ce point j'ai fait une petite généralisation illégitime : si la SBox est vraiment pourri, peut-être que la distance de Hamming est laissée invariante par l'application de l'algorithme de chiffrement. Formulé comme ça, ça n'a pas forcément l'air super excitant, mais en fait, ça pourrait devenir essentiel. En effet, je sais que mon texte d'origine est en base64 et qu'il contient donc un sous-ensemble des valeurs possibles d'un octet. Peut-être qu'avec un peu de chance l'ensemble des distances de Hamming d'un élément de l'alphabet base64 aux autres éléments de ce même alphabet est caractéristique ? Peut-être pourrais-je donc me servir de cet "ensemble des distances aux autres éléments de l'alphabet" comme d'une espèce d'empreinte digitale.

Pour vérifier cette seconde hypothèse (i.e. : que l'ensemble des distances est partiellement caractéristique) j'ai tracé, pour chaque élément de l'alphabet base64, une droite horizontale composé de rectangles colorés. Chaque rectangle coloré correspond aux éléments de l'alphabet distant de la distance X de mon élément de référence (X étant écrit sur le rectangle coloré). Je vous mets l'image ci-dessous et je vais expliquer la première ligne ensuite, ça sera sans doute plus clair comme ça :


Hamming fingerprinting of base64 charset - Creative Common CCBYSA by Ozwald from ozwald.fr

La première ligne de cette image montre "l'empreinte digitale" de la lettre de code ascii "0xa" (c'est à dire du caractère "retour charriot") dans l'alphabet base64. On voit que ce caractère:

  • n'est distant d'une distance 0 que d'un seul autre élément de l'alphabet base64 (à savoir : lui-même)
  • est distant d'un unique caractère de l'alphabet base64 d'une distance de 1
  • est distant de 7 caractères de l'alphabet base64 d'une distance de 2
  • est distant de 16 caractères de l'alphabet base64 d'une distance de 3
  • ...
  • est distant d'un seul caractères de l'alphabet base64 d'une distance de 7

En regardant l'image dans son ensemble on a l'impression qu'il n'y a pas deux lignes qui soient identiques. C'est une bonne nouvelle pour nous : notre élément caractérisant (i.e. : l'ensemble des distances de Hamming aux autres éléments de l'ensemble) est bel et bien caractérisant puisqu'unique à chaque symbole de l'alphabet base64 !

Je vais donc pouvoir tracer les "empreintes de Hamming" des octets chiffrés de data et en tirer des conclusions. Malheureusement, nous avons vu que nos buckets ne contiennent qu'environ 55 valeurs différentes (au lieu des 65 possibles) et mes empreintes seront donc partielles...Je ne vais ainsi sans doute pas pouvoir faire d'identification immédiates, mais je vais au moins pouvoir faire des éliminations. En effet je sais que si je trouve une distance de Hamming de 7 entre deux octets chiffrés ceux-ci appartiendront forcément à l'ensemble des clairs qui possèdent au moins une distance de Hamming de 7; de même si je trouve un octet qui a deux distance de Hamming de 1 il ne peut correspondre qu'aux clairs qui ont au moins deux distances de Hamming de 1; et ainsi de suite.

En appliquant cette méthode j'obtient une réduction drastique du problème. Pour beaucoup d'octets chiffrés je me retrouve avec moins de 10 clairs possibles. C'est donc une bonne avancée, mais c'est insuffisant. Comment aller plus loin ? En triangulant :) ! Après tout, nous avons une propriété de conservation d'une distance, et quand on cherche à placer des points les uns par rapports aux autres en connaissant la distance qui les sépare, moi ça me fait penser à de la triangulation. En substance je vais donc passer plusieurs fois sur l'ensemble de tous mes octets chiffrés en supprimant les clairs potentiels qui ne sont pas à une distance possible par rapport aux clairs possibles des autres chiffrés.

Formulé de façon plus "informatique" :

pour chaque octet chiffré, A :
    je vais étalibr une liste temporaire, L, contenant tous les clairs possibles vis à vis de la triangulation
    Cette liste est initialement vide
    pour chaque autre octet chiffré, B je vais :
        mesurer la distance de hamming, H, le séparant de A. 
        pour chaque clairs possible de B :
            je rajoute à ma liste L les clairs qui y sont distants de H. 
    la nouvelle liste de clairs possibles pour A est l'intersection de son ancienne liste de clairs possible et de la liste L.

Ca a l'air assez bourrin mais, vous savez quoi ? Ca marche :) En quelques itérations (à la louche une vingtaine) et moins de 5s de calculs je me retrouve avec un seul clair possible pour chacun des chiffré. En appliquant la substitution ainsi trouvée j'obtient bien la solution de l'étape 2 ! Comme quoi tous les essais relatés dans les billets précédents étaient inutiles...[1]

Comme quoi, il était aisé de passer cette étape sans regarder le FPGA, mais encore fallait-il faire LA bonne hypothèse...

Note

[1] Si on fait abstraction de l'expérience acquise et d'une petite entourloupe : avec la triangulation pure j'obtient en fait quelques milliers de combinaisons possibles et pas directement la bonne solution. Ca reste aisément bruteforçable, mais j'ai préféré hard-coder les relations chiffré<=>clair que j'avais trouvé pour les retours charriots tout au début de l'analyse puisque, là, je tombe directement sur le bon résultat par triangulation :)

dimanche, août 11 2013

Crypto à retardement : vers une solution

Ce billet constitue le troisième d'une série portant sur l'analyse a postériori de l'étape 2 du challenge SSTIC 2013. Si vous n'avez pas lu le premier de cette série et que vous souhaitez suivre mes diverses expériences correctement je vous conseille vivement de commencer par lui; et si, au contraire, vous voulez simplement connaitre le fin mot de l'histoire je vous recommande de sauter au dernier billet de la série :)

enigma - Creative Common CCBY by "joncallas" on flickr

Boostons l'analyse fréquentielle

En terminant le billet précédent par la comparaison de fréquences entre l'un des buckets chiffrés et un lorem ipsum encodé en base64 j'avais conclu que le clair original, bien que probablement ascii, n'était pas de l'anglais (ni probablement du français ou un autre dérivé de langue latine d'ailleurs). Pour autant, l'analyse fréquentielle est-elle totalement hors course ? Peut-être pas ! En effet, je sais que le clair original n'était pas similaire à du lorem ipsum, mais peut-être est il similaire à du code en C, ou à du HTML, ou à un autre truc potentiellement aussi exotique que du brainfuck. Pour identifier ça j'ai eu une idée toute bête : trouver pleins de textes variés et comparer leur analyse fréquentielle à celle de data. En pratique ça a donné un petit script python qui dumpait en boucle les dernier pastie de pastebin, les encodait en base64 et faisait l'analyse fréquentiel du blob base64 ainsi obtenu. Au bout d'environ 12h je me suis retrouvé avec 9322 textes différents et j'ai élu comme mon "champion" celui qui donnait l'erreur quadratique la plus faible quand il était comparé à data.

Pour les curieux voici à quoi il ressemble (il a déjà été supprimé de pastebin, il serait donc inutile de vous mettre le lien) :

RemoveBuildingForPlayer(playerid, 4024, 1479.8672, -1790.3984, 56.0234, 0.25);
RemoveBuildingForPlayer(playerid, 4044, 1481.1875, -1785.0703, 22.3828, 0.25);
RemoveBuildingForPlayer(playerid, 4045, 1479.3359, -1802.2891, 12.5469, 0.25);
RemoveBuildingForPlayer(playerid, 4046, 1479.5234, -1852.6406, 24.5156, 0.25);
RemoveBuildingForPlayer(playerid, 4047, 1531.6328, -1852.6406, 24.5156, 0.25);
...
CreateObject(1231, 1399.14575, -1845.25000, 15.20500,   356.85840, 0.00000, 3.14160);
CreateObject(1231, 1399.15137, -1838.06458, 15.20500,   356.85840, 0.00000, 3.14160);
CreateObject(1231, 1478.45117, -1862.11169, 15.20500,   356.85840, 0.00000, 3.14160);
CreateObject(1231, 1486.32324, -1862.03967, 15.20500,   356.85840, 0.00000, 3.14160);

Etant donné que nous sommes à posteriori et que nous "trichons" donc j'ai tracé l'analyse fréquentielle des buckets de mon "champion" et je l'ai comparé avec celle de la solution. Force est de constater que, contre toute attente, on trouve des éléments assez proche de la solution ! Les octets significatifs (i.e. apparaissant souvent) sont assez cohérents entre la solution du challenge et mon "champion" (bien qu'il y ait tout de même des différences). Les octets apparaissant peu sont, eux, complètement mélangés (mais ça, on pouvait s'y attendre). L'analyse complète prend plusieurs pages, mais voici un extrait de l'analyse du bucket 15 pour vous donner une idée de ce à quoi ça ressemble :

+++ data - bucket 15 +++
t [0xb] (0.04)	
L [0x17] (0.04)	
e [0x83] (0.04)	
o [0xd3] (0.04)	
K [0xf7] (0.04)	
...
z [0x7b] (3.67)	=======================
j [0x73] (4.58)	=============================
Z [0x7f] (4.76)	==============================
Y [0xbf] (5.01)	================================
T [0xf] (5.23)	=================================
D [0x7] (5.74)	====================================
N [0x57] (6.76)===========================================
M [0x97] (9.37)============================================================

== Champion.txt - bucket 15 ===
q [0x71] (0.05)	
e [0x65] (0.10)	
n [0x6e] (0.15)	=
7 [0x37] (0.20)	=
B [0x42] (0.20)	=
...
j [0x6a] (3.64)	===========================
N [0x4e] (3.89)	=============================
L [0x4c] (4.53)	==================================
C [0x43] (5.37)	========================================
D [0x44] (5.47)=========================================
A [0x41] (6.79)	===================================================
M [0x4d] (7.53)=========================================================
w [0x77] (7.88)============================================================

Il y a donc des similitudes assez flagrandes, mais pas assez pour que je puisse résoudre le challenge en identifiants uniquement les chiffrés à des clairs en fonction de leur similitude de fréquence avec mon champion. L'attaque fréquentielle se rapproche donc de la solution, mais n'y arrive pas tout à fait...


La génétique à la rescousse

Je me retrouve donc à ce point avec une approche en force brute trop longue, et une approche en analyse fréquentielle trop approximative. J'ai alors eu l'idée de tenter une méthode d'optimisation numérique issue du champs d'étude de l'intelligence artificielle : un algorithme génétique :)

Je ne vais pas rentrer dans les détails mais l'idée centrale c'est de simuler les phénomènes d'évolution naturelles des organismes sexués. Dans mon cas :

  • un individu était caractérisé par l'ensemble des relations chiffré=>clair
  • les mutations consistaient à changer le clair associé à un chiffré en le remplaçant par un autre chiffré possible pour ce clair
  • les croisements consistaient à prendre la moitié des association en provenance d'un parent et l'autre moitié de l'autre (sans déc ;) !)
  • la fonction de notation consistait à compter le nombre de caractères ascii "improbable" obtenus dans le clair décodé. Les caractères ascii "improbables" étant, en gros, l'ensemble des caractères de controles (tabulation VERTICALE, bip, etc.)

Le programme a tourné une petite après-midi, mais sans grand succès. La convergence ne semblait pas vraiment au rendez-vous et, surtout, je me suis rappelé d'une bonne résolution qui m'a semblé plus prometteuse, donc j'ai abandonné l'approche génétique.


Une bonne résolution refait surface

Ma bonne résolution c'était d'utiliser un solver SAT la prochaine fois que je n'aurai pas de meilleure idée qu'une approche en force brute ! En plus, je savais que ce type d'approche pouvait donner de bon résultats dans l'attaque de chiffrements faibles, donc je me suis dit qu'il fallait absolument que je tente le coup !

La traduction de mon problème au format SAT a d'ailleurs été plus facile que ce à quoi je m'attendais. En effet, ce que je cherche à trouver c'est : pour chaque valeurs d'octets chiffrés de chacun de mes 16 buckets je veux retrouver la valeurs des 6 bits qu'encode le caractère base64 clair correspondant. J'ai donc créé un petit dictionnaire python qui m'a permi de faire rapidement les conversiont entre "bucket, valeur d'octet, bit" et "numéro de variable booléenne pour le solveur SAT" :

runner = 0
for bucket_id in range(16):
    for octet in range(255):
        for bit in range(8):
            correspondance['%d_%d_%d'] = runner
            runner+=1

Je me retrouve donc avec un peu plus de 5000 variables utiles (chaque bucket ne contient qu'environ 54 valeurs d'octets chiffrés différents; donc avec mon range(255) j'en fais plus que nécessaire). Maintenant il va falloir écrire les règles à respecter... La première est évidente : je vais exprimer la contrainte des bits nuls causées par la nature ascii du texte clair original (cf billet précédent). Au format dimacs ça s'exprime très simplement -XXX 0 (où XXX est le numéro de la variable booléenne correspondant au bit forcé à nul).

Je lance minisat sur ce problème et il me trouve une solution en un rien de temps :) ! Malheureusement ce n'est, évidemment, pas la bonne solution :-/ Le texte obtenu contient, après décodage base64, beaucoup de caractères non-imprimables dont je parlais dans mon approche génétique. Qu'à cela ne tienne, l'approche SAT permet de rajouter allègrement des contraintes, en effet je voudrai exprimer que chaque octet décodé n'a pas le droit d'être l'un des caractères non-imprimables. En pseudo-logique ça va donner quelque chose comme ça : "(octetX n'a pas la valeur ascii du charactère de controle 1) et (octetX n'a pas la valeur ascii du charactère de controle 2) et (octetX n'a pas la valeur ascii du charactère de controle 3) ..." donc je n'ai qu'à réussir à traduire un "octetX n'a pas la valeur ascii BBBBBBBB" sous la forme d'une suite de "ou" logiques et ça ira bien. Ca tombe bien, la loi de de morgan me dit que : "non (bit1=1 et bit2=0 et bit3=1, ...)" est identique à "bit1=0 ou bit2=1 ou bit3=0...".

Bref : interdire à un octet de prendre une valeur donnée est trivial en dimacs. Aussitôt dit aussitôt fait, j'interdit l'apparition de caractères non imprimables et je relance minisat. En un clin d'oeil j'ai une solution...qui n'est toujours pas la bonne -_-

Je commence à être ennuyé mais je me dit que je vais profiter de la facilité qu'il y a à interdire que certains octets prennent certaines valeurs en clair pour m'aiguiller dans la bonne direction. Je ressort donc mon "champion" de l'analyse fréquentielle, dont je parle en début de billet, et j'interdit à chacun de mes octets apparaissant significativement dans data d'être traduit en clair par l'un des octets apparaissant moins de 1% du temps dans mon champion. Je relance minisat et j'obtient une nouvelle solution....toujours pas bonne -_-


L'anneau unique

C'est là que je réalise que j'ai oublié une contrainte essentielle : l'algorithme de chiffrement peut être vu comme 16 bijections indépendantes, or moi je n'ai imposé nulle part qu'un clair ne pouvait correspondre qu'à une seule valeur chiffrée. Pour preuve j'en veux la solution que minisat me donne quand je lui donne peu de contraintes : AAAAAAAAA....AAA Il faut donc que je trouve un moyen d'exprimer, en dimacs, le fait que chaque octets clair ne peut pas apparaitre plus d'une fois dans chaque bucket. Pas simple à réaliser à partir de simples ET et OU... Après réflexion je me suis dit qu'il me suffirait de dire que, pour chaque bucket, le XOR de chaque octets clair est non-null avec n'importe quelle autre octet clair du même bucket. La solution est dans l'esprit et fonctionne : ce sont des simples propositions logiques, et elles imposent qu'aucun clair n'apparaisse plus d'une fois dans chaque bucket. Sauf que voilà, bien que certains solveurs permettent d'utiliser des XOR sur des vecteurs de bits, la syntaxe dimacs "pure" ne permet d'utiliser que des OR et des AND sur de simples bits... Enfer et damnation, ne parviendrais-je pas à exprimer cette contrainte pourtant simple et essentielle ???

Et bien si ;) ! Il se trouve qu'on peut exprimer des XOR sur des vecteurs de bits avec de simples OR et AND sur des bits. L'explication est un peu longue donc je vous invite à aller la lire directement sur cette page web, là où je l'ai trouvé. On y apprend par contre un énorme inconvénient des XOR : un simple XOR entre deux vecteurs de X bits implique 2 puissance (X-1) clauses dimacs ! Un XOR entre deux octets implique donc 128 clauses dimacs différentes...Heureusement je travaille en base64 et je n'ai donc à faire des XOR qu'entre des vecteurs de 6 bits, ce qui n'induit que 32 clauses à chaque fois et qui reste donc gérable. Arrivé là je me suis même demandé si ça n'avais pas été fait exprès par les concepteurs du challenge[1]... Quoiqu'il en soit j'exprime mes (environ) 55*55*16 xor qui se traduisent en (environ) 1,5 millions d'expressions dimacs. Je donne mon nouveau fichier à manger à minisat et....j'attend.

Au bout d'une heure et demi de calcul minisat ne s'en sortait toujours pas. J'ai donc téléchargé, compilé, et testé cryptominisat. Pas de chance : au bout d'une heure cryptominisat n'avait toujours pas terminé lui non plus. J'ai donc cherché ailleurs et j'ai testé lingeling et, miracle, il m'a trouvé la solution en à peine une minute :-D !...Enfin...il m'a trouvé une solution, mais ça n'était toujours pas celle du challenge. Visiblement, je manquais de contraintes...


Markov à la rescousse

J'ai continué de jouer un petit peu avec lingeling en lui fournissant des dimacs un peu différents, basés sur diverses contraintes issues de l'analyse fréquentielle de mon champion (exemple : tous les octets chiffré apparaissant plus de 1% du temps dans data doivent forcément avoir leur clair présent dans le champion, etc.), mais sans succès. En effet, chaque nouvelle solution trouvée par lingelin n'était "visiblement" pas la bonne. Et c'est en réalisant ce que je venait de me dire que j'ai eu une idée : l'analyse fréquentielle du champion ne sera pas suffisante, il faut aussi que j'interdise les traductions "visiblement" improbables. En termes de techniques je me suis donc dit : j'identifie les suites de trois caractères les plus improbables (selon une mesure de markov) et je les interdit également ! Dans les faits j'ai plutôt implémenté un "pseudo-markov" capable de s'en sortir avec un échantillon de base aussi petit que mon texte "champion", mais l'idée reste la même et j'ai introduit des interdictions de plus dans mon dimacs : interdiction aux triplettes étrange ^^ !...Sauf qu'au bout d'une heure de calcul mon PC était encore en train de générer les règles d'interdiction :-/ Il y avait trop de triplettes improbables à interdire. J'ai donc opté pour un compromis en n'interdisant que certaines triplettes pour ne pas dépasser les 3 millions de règles dimacs et j'ai lancé lingeling. Quelques minutes de calcul ont suffit pour obtenir une solution...qui n'était toujours pas la bonne :-(


Destination finale ?

Bien...je commence à me dire que je suis dans une impasse et les tests prennent de plus en plus de temps (la moindre résolution demandée à lingeling prend environ 5mn, or avec la météo qu'on a en ce moment[2] 5mn avec le processeur qui chauffe à fond c'est presque une nouvelle forme de torture). Du coup j'ai décidé d'adopter une nouvelle approche : l'approche des autres (dont je vous parlerai dans le prochain billet).

Notes

[1] D'ailleurs si l'un d'entre eux voit ce blog je veux bien une réponse en commentaire et/ou par mail :)

[2] Je rédige cette ligne fin juillet 2013

samedi, août 10 2013

Crypto à retardement : La force brute

Dans le billet précédent (que je vous invite fortement à lire avant celui-ci) nous avons posé les bases du problème cryptographique de l'étape 2 du challenge SSTIC 2013. Dans ce billet-ci nous allons lancer nos premières attaques contre le mécanisme de chiffrement que nous avons réussi à identifier dans le billet précédent.

enigma - Creative Common CCBY by "joncallas" on flickr

Petite triche ?

Avant de poursuivre, nous allons tricher un peu pour montrer qu'on est dans la bonne direction et pour nous dérouiller avec une attaque fréquentielle qui marchera forcément. Notre "triche" va consister à tirer parti d'éléments connus de la solution pour émettre une hypothèse que nous avions peu de chance de formuler lors du challenge. On sait en effet que le contenu déchiffré est au format base64 (ça, c'est une information qu'on obtient sans tricher), mais en poussant un peu plus loin (puisqu'on connait le résultat) on peut supposer que le texte est formaté en lignes de 76 colones (ce qui constitue un formatage classique mais absolument pas systématique). Si on part de cette hypothèse on recherche, pour chaque bucket, les valeurs d'octets chiffrés dont la fréquence d'apparition se rapproche du facteur 1/76. Aussitôt dit aussitôt fait, on rajoute quelques lignes de python qui nous affichent, pour chaque bucket, les valeurs d'octets suceptible d'être des retours chariots (i.e. : dont la fréquence d'apparition est de 1/76, plus ou moins 5%) :

bucket : 0
	202 <=> \n ?
	0 <=> \n ?
	182 <=> \n ?
	42 <=> \n ?
	140 <=> \n ?
bucket : 1
	201 <=> \n ?
	149 <=> \n ?
	211 <=> \n ?
	233 <=> \n ?
bucket : 2
	150 <=> \n ?
	140 <=> \n ?
bucket : 3
	236 <=> \n ?
	254 <=> \n ?
bucket : 4
	208 <=> \n ?
	128 <=> \n ?
	70 <=> \n ?
	0 <=> \n ?
bucket : 5
	191 <=> \n ?
bucket : 6
	8 <=> \n ?
	190 <=> \n ?
	198 <=> \n ?
bucket : 7
	32 <=> \n ?
	150 <=> \n ?
bucket : 8
	47 <=> \n ?
	131 <=> \n ?
	39 <=> \n ?
	201 <=> \n ?
	53 <=> \n ?
bucket : 9
	252 <=> \n ?
	74 <=> \n ?
	0 <=> \n ?
bucket : 10
	115 <=> \n ?
	15 <=> \n ?
	143 <=> \n ?
bucket : 11
	31 <=> \n ?
	131 <=> \n ?
bucket : 12
	78 <=> \n ?
	178 <=> \n ?
	248 <=> \n ?
bucket : 13
	123 <=> \n ?
	61 <=> \n ?
bucket : 14
	141 <=> \n ?
	241 <=> \n ?
bucket : 15
	51 <=> \n ?
	117 <=> \n ?

L'affaire se profile bien puisque nous avons au moins un candidat par bucket, et rarement plus de 3. Voyons voir ce que cela donne si on affiche notre binaire "data" en remplaçant chacun des candidats possible par un retour de ligne, à la condition qu'il soit environ 76 caractères après le dernier début de ligne. Pour réaliser ce petit tour de passe-passe on utilise encore du python :

counter = 0
for index in range(len(data)):
	if ord(data[index]) in potentials[index%16] and counter>=76 and counter < 80:
		print '<%d>(%d[%d]){%d}'%(counter,ord(data[index]), index%16, index)
		counter = 0
	else:
		sys.stdout.write('X')
		counter+=1

Le résultat est sans appel :

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(78[12]){76}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(252[9]){153}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(8[6]){230}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(236[3]){307}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(182[0]){384}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(61[13]){461}
...
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(61[13]){43581}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(115[10]){43658}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(150[7]){43735}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(70[4]){43812}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(211[1]){43889}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(241[14]){43966}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<76>(131[11]){44043}
XXXXXXXXX

Le pavé de texte est parfaitement formaté avec des retours charriots à 76 caractères (à l'exception de la dernière ligne). Mieux encore : pour chaque bucket, un seul et unique candidat est utilisé tout au long du fichier pour représenter les retours à la ligne. Si j'étais arrivé à ce résultat lors du challenge je pense que ces éléments auraient suffit à valider toutes les hypothèses prises jusqu'alors :) !


Et ensuite ?

Retrouver les relations clair<=>chiffré pour chaque octet de la clef pour le caractère de retour charriot c'est bien mais insuffisant. Bien entendu, si nous connaissions l'algorithme utilisé (par exemple en ayant réverser le schéma FPGA) nous pourrions alors obtenir directement la clef grâce à cette identification, et ce sans avoir à lancer de brute force ( ;) ). Mais nous ignorons tout des détails de l'algorithme et nous allons donc devoir aller plus loin...

A cette étape, nous sommes en effet simplement confiant dans l'idée que nous avons à faire à un problème se ramenant à 16 substitutions simples. Le problème, c'est que la méthode usuelle pour casser ces chiffrement c'est d'identifier des relations "clair <=> chiffré" en se basant sur les fréquences d'apparitions des lettres en clair par rapport aux fréquences d'apparitions des lettres chiffrées. En effet, si nous faisons l'hypothèse que le document en clair est un texte anglais on va pouvoir supposer que les valeurs d'octets apparaissant le plus souvent correspondent en clair à des "e", puis "t", puis "a", et ainsi de suite. On remplacerait alors l'octet chiffré apparaissant le plus par un "e", le second par un "t", et ainsi de suite jusqu'à obtenir la solution. Nous avons pu utiliser cette méthode pour identifier les retours charriots en trichant mais nous n'allons pas pouvoir aller plus loin. En effet, nous pourrions tenter d'appliquer les correspondances chiffré/clair en nous basant sur du texte anglais, mais comme nous connaissons la solution nous savons que ça ne marchera pas. On pourrait alors, devant notre échec, supposer que nous avons à faire à un fichier PE et tenter d'extraire des fréquence à partir d'un corpus d'échantillon puis d'appliquer la même méthode de résolution. Là encore cette tentatives seraient inutiles (puisque nous connaissons le résultat et qu'il ne s'agit pas d'un fichier PE). Toujours dans l'hypothèse où nous ignorons la solution on pourrait se casser les dents longtemps en appliquant les fréquences de fichiers PNG, ELF, etc. ad nauseum.

Mais bref...comme nous connaissons la solution nous n'allons pas nous lancer dans des attaques n'ayant aucune chance de succès. Peut-être pouvons nous lancer une attaque par force brute ? Hélas non. L'attaque par force brute n'est pas envisageable puisque, par exemple pour le bucket 0, nous connaissons une unique correspondance entre l'une des 56 valeurs présentes dans le bucket et les 66 valeurs possibles de notre alphabet base64 (celle du retour à la ligne); il nous reste donc encore 55 correspondances à trouver dans un alphabet de 65 valeurs possibles. Une telle recherche offre un nombre de possibilité que l'on appelle en mathématique un "arrangement de 55 dans 65" et dont la modeste valeur est retranscrite ci-dessous :

>>> a(65,55)
2272831402139128821297169947857555184134220279801983436100673262342635520000000000000L

...tester toutes les combinaisons risque de prendre un peu de temps, et là on ne parle que d'un seul des buckets sur les 16 que l'on doit casser :-D !

Tentons donc de faire une hypothèse légitime et assez large pour que nous ne tombions pas dans un cas particulier : "le contenu qui a été encodé en base64 puis chiffré était soit un texte ascii, soit autre chose". On peut difficilement faire plus large, n'est-ce-pas ;) ? Si jamais c'était un texte ascii, qu'est ce que cela implique ? Cette hypothèse nous dit que tous les octets du texte clair ont leur bit de poids fort nul (en effet les valeurs ascii s'étalent de 0 à 126). Est-ce-que cette hypothèse peut nous avancer un petit peu ? Rappelons que, lorsqu'on encode en base64, on représente 3 octets quelconque par quatres valeur de l'alphabet base64 autorisé. Chaque élément de l'alphabet base64 représente ainsi une suite de 6 bits du clair. En appliquant l'hypothèse que le clair initial que nous recherchons est un texte ascii, on induit une contrainte sur sa représentation en base64 puisque nous savons que certains bits doivent être nuls. Le schéma ci-dessous (adapté par mes soins depuis un screenshot de la page wikipedia expliquant le base64 en anglais[1]) permet de visualiser les bits forcés à 0 quand on encode le mot "Man" en base64 (ce qui donne "TWFu") :



Grace à ces bits forcés nous avons un nombre de possibilité de clair qui se réduit pour certain octets chiffré. En effet, les octets chiffrés se trouvant en première, seconde, ou troisième position d'un groupe de 4 lettres base64 perdent un bit de variabilité. Chacun de ses octets ayant perdu un bit d'information possède alors 32 clair possibles, au lieu de n'importe lequel des 64 de l'alphabet. Cette particularité de réduction du nombre de clairs possibles (et donc de la variété de chiffrés possibles) pourrait nous permettre de vérifier l'hypothèse selon laquelle le clair original était bien un texte ascii.

Pour vérifier cette hypothèse nous allons faire des nouveaux buckets. Ces nouveaux buckets vont suivre les même contraintes que les buckets originaux (à savoir : chaque octets du bucket X a été chiffré avec l'octet X de la clef) mais, en plus, nous voulons que chaque octet d'un bucket soit au commencement d'un bloc de 4 caractères base64 (et donc au commencement d'un bloc de 3 octets du clair que nous cherchons). Voici comment j'ai construit mes nouveaux buckets :

new_buckets = [ [] for i in range(16) ]
new_line = 0
for i,c in enumerate(data):
	if i>0 and (i+1)%77==0:
		new_line+=1
	else:
		if (i-new_line) % 4 == 0:
			new_buckets[i%16].append(c)			

for i in range(16):
	print i,len(buckets[i]), len(new_buckets[i]), len(set(new_buckets[i]))

Puis on va afficher, pour chacun des buckets : son numéro, le nombre d'octets contenu dans le bucket initial, le nombre d'octets de ce bucket qui appartiennent au nouveau bucket réduit, et le nombre de valeurs chiffrées différentes présentes dans ce bucket réduit. Le résultat semble, encore une fois, encourageant :

0 2754 680 14
1 2754 679 14
2 2754 680 12
3 2754 679 16
4 2754 680 16
5 2753 680 15
6 2753 679 13
7 2753 679 12
8 2753 680 16
9 2753 679 17
10 2753 679 11
11 2753 679 12
12 2753 680 15
13 2753 679 14
14 2753 679 12
15 2753 680 14

On se retrouve, grosso modo, avec seulement 14 symboles base64 dans des groupes contenant 680 symboles. Quand on regarde la table ascii on se rend compte que les symboles imprimables commencent plutôt par "01" ou "001", mais quasiment jamais par "000" (dans la série des "000" on retrouve surtout les tabulations, retour charriots, etc.). Or il n'y a que 24 symboles de l'alphabet base64 qui codent des suites d'octets commençant par 01 ou 001. L'hypothèse semble donc, encore une fois, tenir l'épreuve de l'analyse statistique et nous pouvons juger que cette réduction de diversité prouve bien que le clair original était un texte ascii.

En guise de bonus, cette vérification ne se contente pas de valider notre hypothèse, elle implique également une simplification drastique du problème. En effet, pour tous les octets chiffrés se trouvant en tête d'un groupe de 4 octets de la représentation base64 nous avons vu que le problème se résume à trouver un arrangement de 14 symboles parmi 24 possibles (au lieu de 55 parmi 64) :

>>> a(24,14)
170978946685747200L

C'est beaucoup mieux que tout à l'heure ! Mais ça reste, hélas, trop important (d'autant que ce n'est qu'une petite partie du problème). On pourrait bien tenter d'utiliser les octets chiffrés qui apparaissent à la fois en première, seconde, et troisième place des groupes de 4 symboles base64 puisque ce sont ceux qui ont le plus de bits forcés à nul (3) et donc le moins de clairs possibles, mais le nombre de cas à explorer reste trop large pour une attaque exhaustive :-(

Nous ne parviendrons donc pas à résoudre le problème par simple force brute, et il va falloir revenir à des éléments d'analyse fréquentielle. En cherchant dans cette direction j'ai tracé l'analyse fréquentielle de l'un de ces nouveau sous-ensemble (à savoir l'ensemble des octets chiffrés l'ayant été par l'octet 0 de la clef et qui sont au début d'un groupe de 4 caractères du blob base64 et ont, par conséquent, leur premier bit forcément nul) puis je l'ai comparé avec l'analyse fréquentielle du même sous-ensemble sur un lorem ipsum encodé en base64:

0 2754 680 14 set(['a', 'c', 'b', 'e', 'd', 'I', 'K', 'M', 'L', 'O', 'N', 'U', 'Y', 'Z'])
K 	
e 	
U 	
L 	
c 	
a 	
d 	=
I 	==
b 	==
O 	=================
Y 	=======================
Z 	==========================
N 	=========================================
M 	============================================================

lorem b64 :  851 15 set(['a', 'c', 'b', 'd', 'I', 'L', 'Q', 'S', 'R', 'U', 'T', 'Y', 'C', 'Z'])	
S 	
C 	
R 	
U 	
Q 	==
T 	==
L 	=============
a 	==========================
Y 	==========================================
c 	===============================================
Z 	==================================================
I 	===================================================
b 	==========================================================
d 	============================================================

Ce qui m'a sauté à la figure en comparant ces deux résultats c'est à quel point ils sont différents ! La courbe provenant de data est beaucoup plus écrasée que celle du lorem ipsum, on ne peut pas considérer qu'elles sont similaires. L'analyse fréquentielle semble donc ne rien pouvoir apporter...


Bloqué ?

Dans ce billet-ci nous avons finalement pas mal piétiné...mais c'est pour vous faire partager les heures de galère que j'ai passé :-D[2] En tout cas nous en retirons tout de même quelques petites choses : nous avons un élément de preuve tendant à renforcer l'hypothèse que le texte de base est un texte ascii qui a été encodé en base64 puis chiffré, et nous savons également qu'une analyse fréquentielle classique seule ne nous permettra pas de résoudre le challenge, pas plus qu'une attaque par force brute seule. Sommes nous bloqués pour autant ? Pas tout à fait :) ! Dans le prochain billet je vous expliquerai comment j'ai tenté de forcer l'utilisation des analyses fréquentielles, comment j'ai fait tourner un peu d'algorithmie génétique, et comment je me suis rappelé d'une de mes bonne résolution.

Notes

[1] Je vous conseille d'ailleurs de lire certains articles wikipedia dans plusieurs langues. Par exemple le base64 en anglais propose ce petit tableau très explicatif sur le fonctionnement de l'encodage, et la version française, elle, offre un tableau de correspondance entre les lettres base64 et leur valeurs en binaire.

[2] NDLR à postériori : au final j'ai passé environ 1 mois et demi sur cette série de billets, mais j'étais loin d'y travailler tous les jours

vendredi, août 9 2013

Crypto à retardement : introduction

Cette année, plusieurs personnes ayant terminé le challenge SSTIC ont dit qu'il était sans doute possible de réaliser l'étape 2 sans analyser l'architecture logique qui était au coeur de l'étape. Pourtant, toutes les solutions que j'ai vu passent par l'analyse de ce schéma logique. Ayant moi-même calé lors de l'étape 2 et n'étant pas un grand fan de FPGA ni de schémas logiques, je me suis donc dit qu'il fallait que j'essaie de passer cette étape, à postériori, de façon purement cryptographique sans regarder le schéma logique. Cette série de billets va ainsi retranscrire mes différents essais (et ratés). Fan de crypto ou curieux ayant séché à l'étape 2 du challenge SSTIC, vos commentaires sont les bienvenus :) !

enigma - Creative Common CCBY by "joncallas" on flickr

Un peu de contexte

La première étape du challenge était très simple cette année, et en moins de 2h je me suis retrouvé avec l'archive de l'étape 2. Cette archive contenait les fichiers suivants :

data
decrypt.py
s.ngr
smp.py

En quelques dizaines de minutes à regarder ces fichiers le fonctionnement devient clair :

  • data contient des données chiffrées que l'on veut déchiffrer.
  • decrypt.py alimente un périphérique ("device") inconnu avec le contenu de data, le contenu de smp.py, et une clef de 16 octets passée en paramètre, puis il attend que le périphérique fasse son travail et il compare le md5 du résultat retourné avec une valeur connue et hard-codée. Une fois le résultat validé il le décode (c'est visiblement du base64) puis il enregistre le résultat décodé dans un fichier atad
  • s.ngr est un fichier au format propriétaire représentant l'architecture logique d'un FPGA; c'est probablement le périphérique inconnu auquel s'adresse decrypt.py.

L'objectif de l'approche 100% crypto que nous allons tenter de dérouler va donc être de faire totalement abstraction du FPGA, et de ne nous intéresser qu'aux données chiffrées ! A titre de rappel toutes les personnes ayant publié leur solution du challenge ont analysé le FPGA; au moment où j'écris cette ligne j'ignore donc s'il est vraiment possible de passer cette étape de façon purement cryptographique...croisons les doigts :)
( NDLR : maintenant que je suis en train de rédiger le dernier billet de la série je sais...et tout ce que je peux vous dire c'est qu'il va y avoir beaucoup d'échecs dans les billets à venir ;) )


Qu'est ce qui existe comme chiffrements simples ?

Pour que nous soyons en mesure de passer l'étape de façon purement cryptographique il faut que les chiffrements mis en oeuvres soient faibles et/ou mal utilisés, or nous n'allons pas essayer de décortiquer la façon dont l'algorithme a été implémenté sur le FPGA donc nous aimerions bien que ça soit la première hypothèse la bonne : les mécanismes de chiffrements utilisés sont faibles. Cherchons donc quel type de chiffrement a bien pu être utilisé en énumérant les chiffrements simples les plus classiques.

Qui dit chiffrement simple dit chiffrement de césar ! Malheureusement, un simple chiffrement de césar n'a pas besoin d'une clef de 16 octets, nous ne sommes donc pas face à celà. Si on a une clef on va plutôt penser à un chiffrement de césar à décalage variable avec une répétition de la clef; ou encore à un XOR avec répétition de la clef[1]; ou, plus généralement, à l'application d'une bijection simple entre l'ensemble des valeurs possibles d'un octet sur lui-même, cette bijection dépendant d'un octet de la clef (on aura donc 16 bijections simples, une par octet de la clef).

Si tel est le cas, on se retrouve à devoir casser 16 bijections indépendantes les unes des autres. Comment vérifier cette hypothèse ? Nous savons que le texte qui a été chiffré est en base64 et que ses octets ne peuvent donc prendre que 64 valeurs différentes (sur les 256 que peut prendre un octet dans le cas général). Si notre hypothèse est bonne (i.e. : que nous avons à faire à 16 bijections indépendantes) l'ensemble chiffré résultant de l'application de n'importe laquelle de ces bijection ne peut pas prendre, lui non-plus, plus de 64 valeurs différentes.

Pour vérifier ça on se pond un petit script qui compte combien de valeurs différentes il y a dans data en entier :

Number of different values in data    (of size 44053)    : 256

Bien, le document chiffré (d'une longueur de 44 053 octets) contient au moins un octet de chaque valeur possible. Maintenant, si je prend un octet sur 16 dans data(autrement formulé : "si je ne regarde que les octets qui ont été chiffré avec le premier octet de la clef"), combien de valeurs différentes vais-je avoir ?

Number of different values in bucket 0 (of size 2754)	: 56

Bingo :) ! J'ai moins de 64 valeurs différentes. Sauf erreur de ma part, la probabilité que les 2754 octets contenus dans mon échantillonage (appelé "bucket numéro 0") ne contienne que 56 valeurs différentes (sur les 256 que peut prendre un octet) est infinitésimale. J'ai un petit peu la flemme de faire le calcul là, mais si vous voulez le faire vous aurez sans doute besoin de la formule des arrangements. En tout cas ce résultat corrobore l'hypothèse des fonctions bijectives (qui sont un cas de boite de permutation crypto). Je peux d'ailleurs recommencer l'analyse en prenant toujours un octet sur 16, mais avec un décalage au démarrage de 1, 2, 3, etc. De cette façon, je vais vérifier les ensembles chiffré avec chacune de mes 16 bijections hypothétiques :

Number of different values in bucket 1 (of size 2754)	: 54
Number of different values in bucket 2 (of size 2754)	: 53
Number of different values in bucket 3 (of size 2754)	: 56
Number of different values in bucket 4 (of size 2754)	: 54
Number of different values in bucket 5 (of size 2753)	: 54
Number of different values in bucket 6 (of size 2753)	: 56
Number of different values in bucket 7 (of size 2753)	: 52
Number of different values in bucket 8 (of size 2753)	: 56
Number of different values in bucket 9 (of size 2753)	: 57
Number of different values in bucket 10 (of size 2753)	: 53
Number of different values in bucket 11 (of size 2753)	: 55
Number of different values in bucket 12 (of size 2753)	: 57
Number of different values in bucket 13 (of size 2753)	: 53
Number of different values in bucket 14 (of size 2753)	: 54
Number of different values in bucket 15 (of size 2753)	: 56

Le résultat est confirmé : nous sommes très probablement face à l'application de 16 fonctions bijectives codant un octets à la fois, indépendament les uns des autres.


Et si j'en savais moins ?

L'analyse précédente nous met sur la voie en nous confirmant un chiffrement "trivial", mais elle a un défaut : elle utilise 2 éléments dont nous avons pris connaissance grace au fichier decrypt.py (la taille de la clef, et le fait que le texte déchiffré est en base64). Dans un cas général nous ne pourrions donc pas l'utiliser. Comment arriver à la même conclusion sans nous servir de ces éléments ? Une méthode existe : l'analyse de Kasiski.

L'analyse de Kasiski est très bien expliquée sur wikipédia donc je ne vais pas tenter de faire mieux. Je résume donc uniquement le principe en une phrase : si nous avons une séquence d'octets qui se répète dans le texte clair on retrouvera cette répétition dans le texte chiffré si, et seulement si[2], les deux instance du schéma répété dans le texte clair sont séparées d'un nombre d'octets multiples de la longueur de la clef (puisque, du coup, ces deux répétition du clair seront chiffrés avec les même bijections et donneront donc le même résultat chiffré).

Donc, en cherchant des suites d'octets de taille raisonnable qui se répètent dans le texte chiffré, on peut supposer que le nombre d'octets qui les séparent est probablement un multiple de la taille de la clef. En cherchant beaucoup de ces suites d'octets et en notant à chaque fois leurs écarts on devrait alors voir apparaitre un chiffre qui revient beaucoup plus souvent que les autres, ça sera la taille de notre clef.

Jetons quelques lignes de pythons dans un fichier texte[3] ! D'ailleurs inutile de le lire, il n'est pas instructif et je l'ai mis ici uniquement pour que vous puissiez le copier/coller si l'envie vous prend de refaire les expériences à la maison :

def factors(n):
	"""
	Retourne la liste des facteurs premiers d'un entier.
	Merci a : http://stackoverflow.com/questions/6800193/what-is-the-most-efficient-way-of-finding-all-the-factors-of-a-number-in-python
	"""
	return set(reduce(list.__add__, ([i, n//i] for i in range(1, int(n**0.5) + 1) if n % i == 0)))
def sort_by_occurence(l, reduced_result = False):
	"""
	Prend une liste d'elements, retourne la liste dont j'ai supprime les repetitions et qui
	est triee par ordre d'apparence (les elements apparaissant le moins etant aux indices les plus faibles)
	Si "reduced_result" == False ce qui est retourne est en fait une liste de tuple (A,B) ou
	B est l'element de la liste passee en parametre, et A est son nombre d'occurence.
	"""
	tmp = {}
	for i in l:
		if i in tmp:
			pass
		else:
			tmp[i] = l.count(i)
	tmpl = [ (tmp[i],i) for i in tmp.keys() ]
	tmpl.sort()
	if reduced_result == True:
		return [ B for A,B in tmpl]
	else:
		return tmpl

def kasiski(text, part_len = 6):
	"""
	Tries to perform a kasiski analysis
	"""
	all_starts = []
	all_frequencies = []
	patterns_done = []
	for starter in range(len(data)-part_len):
		starts = []
		init = text[starter:starter+part_len]
		if (not init in patterns_done) and (init in text[starter+part_len:]):
			patterns_done.append(init)
			for compare in range(starter+part_len, len(text)-part_len):
				if text[compare:compare+part_len] == init:
					starts.append(compare)
			if len(starts)>1:
				print init,"\t", min( [ starts[i+1] - starts[i] for i in range(len(starts)-1) ] )
				all_starts += starts
				ecarts = [ starts[i+1] - starts[i] for i in range(len(starts)-1) ]
				most_probable_freq = sort_by_occurence(ecarts, reduced_result=True)[-1]
				all_frequencies += factors(most_probable_freq)
		#else:
		#	print init,"\t",starts
	return sort_by_occurence( all_frequencies )

Et quand on appelle kasiski(open('data').read()) voilà ce qu'on obtient :

[(1, 13), ... (19, 48), (24, 32), (64, 1), (64, 2), (64, 4), (64, 8), (64, 16)]

Cette liste doit se lire de droite à gauche et se traduit ainsi : le fichier 'data' contient 64 suites d'octets qui se répètent avec un espacement qui est un multiple de 16; il contient 64 suites d'octets qui se répètent avec un espacement multiple de 8 octets; ...; il contient 1 suite d'octet qui se répète avec un espacement multiple de 13 octets.

La différence d'apparition entre un espacement multiple de 16 et ses dauphins est tellement large que l'on peut conclure que nous sommes bien face à l'application de fonctions bijectives d'un octet, et que la taille de clef est de 16 octets. Tout ça sans aucune connaissance issue de decrypt.py, pas mal, non ^^ ?


Dernière confirmation

Histoire d'être vraiment certain de notre coup, on va se faire une dernière vérification. En effet, si on part du principe que l'on ignore tout du chiffré, l'analyse de Kasiski serait peut-être un peu légère pour nous convaincre. Pour enfoncer le clou nous allons donc carrément tracer l'analyse fréquentielle des sous-ensemble chiffré résultant chacun d'une unique bijection. Si l'hypothèse est bonne, les 16 analyses fréquentielles devraient être très similaires les unes aux autres.

J'ai donc pondu un mini script python qui trace la visualisation ascii des fréquences, et le résultat est assez parlant. Malheureusement, retranscrire ce tas de lignes de texte prendrait plusieurs pages donc, pour le blog, j'ai pondu une représentation graphique un peu moins parlante mais tout de même compréhensible. Sur la même image j'ai tracé le profil de fréquence en bar-graph de tous les buckets (i.e. de tout mes sous-ensembles de data) et du fichier data. Chaque colonne du bar-graph correspond à une valeur apparaissant dans le bucket, sa hauteur étant proportionnel à son taux d'apparition. On constate clairement que les tracés des buckets se chevauchent allégrement à gauche (i.e. : ils sont très similaires les uns aux autres et ils ne contiennent que peu de variété de valeurs différentes) alors que le fichier "data" entier, lui, contient une variété beaucoup plus grandes de valeurs (puisque son tracé est plus long) et que ces valeurs sont mieux réparties (le tracé est plus plat).

Analyse de fréquence du fichier "data" provenant du challenge SSTIC 2013 - Creative Common BYCCSA by ozwald from ozwald.fr

Arrivé à cette étape on a tout de même un bon nombre (deux ou trois) d'indices qui nous poussent dans la direction d'une clef de 16 octets paramétrant 16 bijections (ou "permutations") de l'ensemble des valeurs possibles d'un octet dans lui-même et qui sont appliquées successivement sur data. Maintenant il va falloir commencer à casser ces bijections...mais ça fera l'objet de prochains billets. Le prochain abordera d'ailleurs la possibilité des attaques en force brute, alors ne le manquez pas si vous aimez faire chauffer les [C|G]PU ;)

Notes

[1] Les XOR sont très communs quand on veut faire un chiffrement informatique faible

[2] A quelques cas pathologiques près.

[3] Ce code fonctionne, je l'ai utilisé, mais il est TRES approximatif dans la démarche employée. Si vous voulez utiliser la méthode de Kasiski sans connaitre à l'avance la taille de la clef je vous conseille de ré-implémenter le tout de façon plus propre; je m'en voudrais de vous induire en erreur avec les résultats approximatifs de ce code expérimental.

lundi, mai 27 2013

Level up

Quasiment deux ans après mon dernier "level up" relaté sur ce blog j'en ajoute un nouveau obtenu début Mai : j'ai gagné un niveau en robotique grâce à une homologation réussie à la coupe de France 2013. Si on veut aller plus loin j'ai même gagné un second niveau en robotique grâce à un match gagné en coupe de France :) ! Comme pour le dernier "level up" cet article ne contiendra rien de passionant techniquement; si il n'y a que l'informatique qui vous passionne je vous conseille donc de zaper ce billet pour vous concentrer sur le précédent.

Eurobot - Coupe de France de robotique 2013 - équipe Planet Express - Creative Common CCBYSA by ozwald from ozwald.fr
Du 8 au 11 Mai dernier se déroulait donc la coupe de France de robotique à la Ferté-Bernard. J'y avais déjà participé à deux reprises en 2006 et 2007 mais nous n'avions malheureusement pas réussi à homologuer nos robots et, en plus, je ne comprenais rien à l'électronique mise en oeuvre à l'époque :( Depuis ces lointaines années j'ai pas mal progressé en électronique et il se trouve que cette année deux amis étaient motivés pour participer à la coupe de France avec moi (pour eux c'étaient leur première participation), nous avons donc tenté le coup.

Nous sommes arrivés dans la Sarthe le premier jour de la coupe avec un robot "quasiment terminé"[1]. Après un rapide tour des stands nos soupçons étaient confirmés : nous avions probablement le robot dont la mécanique faisait le plus pitié[2] mais qu'importe, on était là pour s'amuser et homologuer. De façon prévisible nous avons rencontré pas mal de problèmes de dernières minutes et les "une ou deux heures" que nous estimions nécessaires pour rendre le robot opérationnel s'allongeaient tant et si bien que l'après-midi du deuxième jour de compétition nous n'avions toujours pas réussi à homologuer...

Eurobot - Coupe de France de robotique 2013 - équipe Planet Express, robot secondaire - Creative Common CCBYSA by ozwald from ozwald.fr

C'est alors que nous avons opté pour un "plan B" : le réglement de cette année autorisait l'utilisation de DEUX robots en même temps sur la table pour chaque équipe donc, pendant que notre electronicien-magicien s'acharnait à rendre notre premier (et unique) robot opérationnel, moi et le troisième larron nous sommes lancés dans la conception d'un second robot d'urgence qui pourrait nous permettre d'homologuer. En effet, pour que notre équipe soit homologuée il fallait qu'au moins un robot parvienne à quitter sa zone de départ et que l'équipe marque au moins un point. Notre robot principal n'avait aucun problème pour quitter sa zone de départ, mais il ne parvenait pas encore à marquer de points. Le cahier des charges du "robot" d'urgence a donc été réduit au strict minimum : inutile qu'il sache se déplacer, il doit juste marquer au moins un point. Là encore le réglement de cette année nous a facilité la tache puisqu'il était possible de marquer 12 points bonus juste en gonflant un ballon d'anniversaire[3] au bout de 90 secondes de match. La conception du robot secondaire s'est donc imposée d'elle-même : un ventillateur de PC va gonfler un sac plastique de supermarché au bout de 90s; pour obtenir la puissance en 12V demandée par le ventillateur de PC nous avons soudé entre-elles une pile 9V et une pile 4.5V achetées au leader-price du coin; pour compter les 90s nous avons utilisé un atmega8 qui trainait dans ma boite à malices; pour convertir l'ordre de l'atmega en puissance pour le ventillo nous avons utilisé un simple transistor également en provenance de ma boite à malices; et pour fabriquer le "bouton d'arrêt d'urgence" obligatoire sur tout les robots de la coupe nous avons collé un bouchon de Coca Cola sur un bouton poussoir :) Avec un peu de cartons et quelques chutes de plexy par dessus ce bric-à-brac le robot d'urgence remplissait parfaitement sa mission : il répond à tout les critères de sécurité nécessaire pour être autorisé à participer et il gonfle bien un "ballon" (marquant 12 points) au bout des 90s réglementaires.

Nous nous sommes donc présenté une ultime fois à l'homologation et la magie a opérée : notre robot principal a, comme d'habitude, parfaitement quitté sa zone de départ puis il a échoué à toutes ses tentatives de marquer des points; mais le robot d'urgence a correctement gonflé son ballon au bout de 90s marquant 12 points pour le compte de l'équipe et scellant ainsi une homologation réussie :-D ! Champagne[4] !

Une fois l'homologation en poche nous avons eu le droit de participer aux séries. Le premier match est sans appel : bug dans notre robot principal qui refuse de quitter sa zone de départ; aucun de nos deux robots n'ayant quitté sa zone de départ les points de gonflage de ballons ne sont pas comptabilisés et nous ne marquons donc aucun point. En face de nous un adversaire extrèmement sympathique avec un robot à la fois superbe et efficace nous colle une raclée 65 à 0. Quelques ajustements plus tard (en particulier une refonte mécanique complète du système de démarrage du robot principal et un recalibrage important de ses paramètres d'asservissements) nous participons à notre second match de série contre un adversaire tout aussi sympathique, avec un robot à la mécanique impressionante mais souffrant de gros problèmes électronico-informatique au moment de se présenter sur l'aire de jeu. Contraint par ses soucis électronico-informatique notre adversaire partait lui aussi sur une stratégie de secours consistant à uniquement sortir de sa zone de départ et gonfler un ballon; mais en n'utilisant qu'un seul et unique robot au lieu de deux. Enorme surprise de notre coté au bout de 20s de match : les ajustements faits sur le robot principal portent leur fruits et il parvient à marquer ses tout premiers 4 points[5] ! Notre robot de secours gonfle bien son ballon au bout des 90s, tout comme notre adversaire qui empoche lui aussi les 12 points correspondants. Le score s'élève donc à 16 points contre 12 à la fin du match, nous empochons les 5 points de la victoire et notre adversaire le point de "non-forfait" ce qui porte le score final à 21 contre 13 : nous venons de remporter une victoire en coupe de France[6] !

Cette victoire est d'autant plus savoureuse qu'en raison de contraintes diverses et variées nous devions quitter la coupe de France à l'issue de ce match, déclarant ainsi forfait pour les deux derniers matchs de série que nous aurions pu jouer. Au final nous resterons donc à la 136ème place (sur 146 possibles) ce qui dépasse notre objectif : nous avons homologué :) !

Qui sait, peut-être reviendrons nous l'an prochain avec un objectif plus ambitieux pour un nouveau level up ;) ...

Notes

[1] Expression consacrée de la coupe de France qui signifie "le robot ne marche pas mais avec beaucoup de café j'ai encore l'espoir de le faire marcher avant la fin de la compétition"

[2] Même les robots en LEGO avaient plus la classe que notre assemblage de bois/plexy/pistocolle

[3] Cette année le thème de la coupe était l'anniversaire puisqu'il s'agissait de la 20ième édition de la coupe

[4] Enfin...champomy en l'occurence puisque l'alcool était strictement interdit dans l'ensemble des batiments de la coupe :-(

[5] Quatres points qui illustrent d'ailleurs ce billet : sur la photo en tête de cet article notre robot vient d'ouvrir un "cadeau" en faisant basculer sa représentation en bois hors de la table

[6] Match "Robot-X" contre "Planet Express" : http://www.planete-sciences.org/robot/live/coupe2013/series/s3/

lundi, mai 20 2013

Dissection de programmes sous linux

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équents[1] 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 navigateur[4]. 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 SSL[5].

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 objdump[6] :

$ 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_Read[7]. 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évelopement[9] 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 python[10] 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 : \n" + 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 : \n" + 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 sentis[11] 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 milieu[12]" 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 top
$ 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 connait[13] : 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'était[14] :) ! 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 voulais[15] :-D !!!

Notes

[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

lundi, avril 29 2013

Pole Emploi : de l'argent public mal employé

Environ un an après la première démonstration d'incompétence que m'avait faite pole emploi ils remettent ça dans la joie et la bonne humeur :-D J'ai en effet appris le 18 avril que : "Votre inscription étant arrivée à échéance, vous cessez d'être inscrit sur la liste des demandeurs d'emploi à compter du 18 avril 2013, conformément aux dispositions du code du travail."

Voilà voilà voilà... Etant donné que je bénéficie du plafond de durée d'aide au retour à l'emploi (ayant travaillé plus de 36 mois consécutifs à temps plein avant de me retrouver sans emploi) je suis très heureux de savoir que je me retrouve désinscrit au bout de seulement 12 mois et sans aucune explication...

Le constat est donc clair : je peux témoigner qu'au moins depuis un an Pole Emploi est un ramassis d'incompétents et constitue une dépense d'argent public à mauvais escient[1]. En tant que citoyen je demande donc qu'ils remboursent l'argent ainsi volé pour faire tourner un service public fictif; mais je sais bien qu'il n'y a aucune chance pour que celà se produise un jour, snif :-(

Bref...en attendant j'ai demandé ma ré-inscription (qui d'ailleurs est une vrai plaie à faire ! J'ai rarement vu un service web aussi pourri :-/); on verra bien ce qu'il advient.

PS : pour me faire pardonner les derniers billets "non-techniques" (dont celui-ci fait évidemment parti) je prépare un micro article sur LD_PRELOAD et objdump afin d'intercepter des appels choisis et réaliser ainsi de l'analyse de binaire sous linux. C'est passionant comme méthode de jeux, même si je ne maitrise pas encore tout ce qui se passe[2]...

Notes

[1] J'aurai aimé pouvoir dire "dépense d'argent public inutile", mais étant donné qu'ils sont nuisibles le termes "inutile" est malheureusement trop gentil :-(

[2] en particulier j'essaie en ce moment d'intercepter, en clair, les communications réalisées par une animation flash dans un tunnel SSL. Malheureusement pour moi SSL_read, SSL_recv, BIO_read, PR_Read, et PR_Recv ne suffisent pas; recv ne me donne que le flux chiffré; et strncpy remonte trop de bruit par rapport au signal qui, en plus, semble incomplet.

mercredi, avril 10 2013

Aller plus loin avec gEDA

J'avais déjà rapidement parlé de gEDA dans un précédent billet[1], 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 concevoir[2]. 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émentale[3] (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 autre[4].
  • 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 routage[5] à 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 cartes[6] 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.frSi, 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 bien[7]; 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 main[8] 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 pcb[9] :

  • 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 marchent[10]. 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 conception[11] j'en suis très content et j'aurai peut-être l'occasion d'en faire bon usage :D !

Notes

[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 pourtnat 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 ;-)

jeudi, janvier 10 2013

Projet de Weekend : Tourelle de surveillance

Après plusieurs billets assez pauvre en contenu je me suis dit que résumer un micro-projet mettant en oeuvre plusieurs techniques différentes serait une bonne idée. Voici donc le résumé de la création d'un petit jouet qui m'a couté environ 7€ [1] ! Je vous laisse découvrir un à un les différents domaines que j'ai associés et essayer de deviner au fil de l'eau ce que j'obtient à la fin et qui constitue le but de ce projet (ainsi que le titre du billet...j'aurai d'ailleurs du y mettre des balises SPOIL ^^ ). Bonne lecture !

Tour de surveillance - Creative Common CC-BY-SA by Ozwald (me)

Un peu de vision

J'ai déjà expliqué sur ce blog comment on peut facilement programmer un micro-controlleur[2], et nous avons également déjà vu comment utiliser ces micro-controlleurs pour faire tourner des moteurs, ce qui permet (par exemple) de faire bouger un robot. Nous savons donc à présent comment fabriquer un robot qui agit sur le monde (en s'y déplaçant) mais nous n'avons pas encore vu de méthode pour que le robot prenne connaissance du monde (or d'aucun diront qu'il est intéressant de comprendre son environnement avant d'agir). La première connaissance nouvelle que nous allons aborder dans ce billet c'est justement comment mesurer la distance qui sépare notre robot d'un obstacle situé devant lui.

Beaucoup de méthodes existent pour mesurer des distances sans contact. On peut par exemple citer les mesures de déphasages entre un rayon laser émis et son reflet renvoyé par l'obstacle, les mesures de focalisation d'un signal lumineux (généralement infra-rouge) émis puis réfléchi par l'obstacle, ou encore la mesure de temps entre l'émission d'un signal acoustique (généralement ultra-son) et la réception de son écho produit par l'obstacle.

Dans ce mini-projet j'ai opté pour la mesure par ultra-son, pour deux raisons :

  • c'est la méthode la moins chère (on trouve des modules tout fait à moins de 1.50€ sur eBay, à moins de 6$ sur amazon, à moins de 10€ sur robotshop, ou à 15€ chez gotronic pour la classe au dessus).
  • Ces modèles peu onéreux permettent pourtant déjà de mesurer des distances allant, classiquement, de quelques centimètres à environ 4m, ce qui est parfait pour un usage en intérieur.

Plus précisément j'ai opté pour un HC-SR04, qui semble être l'un des modèles les plus répandus (mais pas l'un des plus fiables).
HC-SR04 - Creative Common by "scanlime" on Flickr
Le fonctionnement de ce module est simplissime : on branche la PIN "GND" du module à la masse de notre circuit, la PIN "VCC" à une alimentation 5V, et les PIN "trig(ger)" et "echo" chacune à une I/O de notre microcontrolleur. Pour mesurer la distance qui sépare ce module d'un obstacle situé devant lui on envoie simplement une impulsion positive d'environ 10us sur la PIN "trigger" du module, puis on mesure la longueur de l'impulsion positive que nous envoie le module en réponse sur sa PIN "echo". La longueur de l'impulsion de réponse (sur la PIN "echo") est proportionnelle au temps mis par le son pour faire l'aller-retour entre le module et le premier obstacle rencontré (la vitesse du son étant à peu près constante on en déduit alors la distance qui sépare le module du premier obstacle situé devant lui). Pour être précis on considère que 1cm se traduit en environ 58uS d'impulsion "echo"; si je reçois une impulsion retour qui dure 580uS j'en déduis donc qu'il y a un obstacle à environ 10cm devant mon module. Trivial[3] !


Un peu d'action (controllée)

La denière fois que nous avons parlé d'agir sur le monde depuis un microcontrolleur nous avions fait tourner des moteurs, dans un sens ou dans l'autre, à une vitesse arbitraire. C'est déjà très pratique, mais pas vraiment adapté à des mouvements finements controllés; en effet nous ignorons tout de la vitesse réelle de rotation du moteur (en fonction de la résistance qu'il rencontrera lors de sa rotation il tournera plus ou moins vite pour une même commande donnée). Pour palier ce manque de précision nous avons, là encore, de nombreuses options qui s'offrent à nous. Nous pouvons par exemple asservir la commande du moteur à la lecture de capteurs qui nous renseigneront sur la rotation réellement effectuée, ou nous pouvons opter pour des moteurs "pas à pas" plus compliqués à commander mais qui offrent une bien meilleure précision de commande, ou encore nous pouvons utiliser des servomoteurs qui permettent d'obtenir directement un déplacement asservi.

Pour ce petit projet nous allons opter pour un servomoteur. J'aurai pu avantageusement partir sur un moteur pas à pas mais j'avais besoin de jouer avec des servomoteurs pour un autre projet donc j'ai décidé de faire d'un pierre deux coups et de mettre également un servomoteur dans ce micro-projet. En plus, comme les capteurs ultrason hc-sr04, les servomoteurs ont le bon gout d'être simple à utiliser et peu cher (on en trouve à moins de 2€ sur eBay, à 3$ sur amazon, à un peu plus de 4€ chez gotronic, ou encore à une quinzaine d'euros chez selectronic pour la gamme au dessus).
Servo premier prix - Creative Common by "Nick Ames" on Flickr
Généralement les servomoteurs se connectent via 3 fils. Les couleurs varient selon les constructeurs mais vous pourrez trouver facilement les équivalences en cherchant sur internet. Les servomoteurs que j'ai (les même que celui de la photo ci-dessus) suivent les couleurs traditionnelles du constructeur Graupner :

  • un fil marron que je dois brancher à la masse
  • un fil rouge que je dois brancher à mon "VCC" (alimentation 5V);
  • un fil orange sur lequel je dois envoyer la commande.

Comme je vous l'ai déjà dit la commande d'un servomoteur est simple : on doit envoyer sur le fil de commande, à intervalle régulier (environ toutes les 20ms au plus), une impulsion positive dont la largeur est notre commande[4]. L'impulsion doit avoir une largeur comprise, environ, entre 0,5ms et 2,5ms. Lorsque la commande est correctement envoyée l'axe du servomoteur s'aligne sur un angle compris entre 0° et 180°, proportionnellement à la commande envoyée[5]. Donc si j'envoie, toutes les 20ms, des impulsions de 0,5ms sur la commande de mon servomoteur celui-ci va s'aligner sur -90° et ne plus en bouger. Si j'allonge mes impulsions jusqu'à 1,5ms l'axe du servomoteur va tourner environ jusqu'à 0° et ne plus bouger. Enfin si je rallonge mon impulsion jusqu'à 2,5ms l'axe va sagement s'aligner sur +90°. L'avantage d'avoir un asservissement directement dans le servomoteur c'est que son axe va s'aligner à un angle correspondant à ma commande quelque soit la résistance à son mouvement, je n'ai pas à me préoccuper d'asservir le bouzin, c'est prévu dans le forfait de base.


Un peu de dialogue

Les amateurs d'arduino sont habitués à communiquer avec leur joujou préféré depuis leur PC en le branchant simplement en USB, seulement voilà : les atmega8 n'ont pas de port USB et donc pas de méthodes native pour discutter avec un PC. Pour palier ce "problème" nous avons, encore une fois, plusieurs solutions :

Les microcontrolleurs incluant directement le support de l'USB ont l'inconvénient d'être plus cher que nos composants "low cost" (atmega8, atmega328, attinyX5, etc.), mais surtout ils sont plutôt disponible au format TQFP qu'au format DIP[6], donc j'exclus cette solution pour ce mini projet.

Implémenter la pile USB sur un atmega8 c'est sympa, mais comme je ne maitrise pas encore toutes les subtilités du débugage sur microcontrolleurs et que, de toute façon, la gestion USB pomperait quasiment toutes les resources du microcontrolleur, j'exclus aussi cette solution.

Il nous reste donc le passage par un "interprète". Ca tombe bien, sur internet on peut trouver des petits montages tout fait autour du chipset cp2102 pour moins de 2€ sur eBay ou moins de 9$ sur DX.

Convertisseur UART-USB à base de CP2102

Ces petits montages discutent en USB avec l'ordinateur, et en UART avec notre microcontrolleur. Avantage certain : les chipsets cp2102 sont pris en charge directement par le noyau linux depuis la version 2.6.12, il suffit donc de le brancher pour se retrouver avec un /dev/ttyUSBX fonctionnel qui fait le pont jusqu'à notre microcontrolleur :) Coté microcontrolleur l'UART est un protocole de communication série "universel" intégré en hardware dans les atmega8 et supérieurs[7] ce qui permet de ne consommer quasiment aucune ressource de calcul !

Une fois le montage branché il nous suffit donc de coder la communication. Coté microcontrolleur c'est supporté en hardware, donc après la routine d'initialisation le code d'envoi d'un octet se résume à vérifier que le flag "REady" est positionné, puis à inscrire l'octet à envoyer dans le registre dédié "UDR" (on fait difficilement plus simple...) :

/* Wait for empty transmit buffer */
while ( !( UCSRA & (1<<UDRE)) ) {};
/* Put data into buffer, sends the data */ 
UDR = data;

Coté PC c'est de la communication série standard, vous pouvez donc utiliser des softs "tout fait" comme minicom, ou dénicher une librairie de communication série pour dialoguer à partir de votre language de script préféré :

import serial
ser = serial.Serial('/dev/ttyUSB0', 4800, stopbits = serial.STOPBITS_TWO, parity = serial.PARITY_ODD)
while True:
    print ord(ser.read(1))

Bien entendu la communication peut être bi-directionnelle sans aucune modification matérielle et sans vraiment plus d'effort de code (il faut remplacer "ser.read" par "ser.write" en python et lire un registre au lieu d'en écrire un du coté de l'atmega...dur n'est ce pas ?).


Un peu de superficiel

Je suppose maintenant que vous voyez où on va avec ces différentes briquettes et le titre de ce billet : nous montons une "tourelle de surveillance" (visible sur la photo tout en haut de ce billet). Cette "tourelle" va être constituée d'un capteur de distance à ultra-son, monté sur un servomoteur, controllé par un atmega8, communiquant avec un PC via UART/USB. L'idée c'est que nous allons mesurer la distance qui nous sépare d'un obstacle "devant nous", puis nous allons faire tourner le servomoteur de quelques degrés et nous allons à nouveau faire une mesure de distance. En itérant ainsi le procédé nous pouvons obtenir une cartographie à 180° de la distance qui nous sépare du premier obstacle; obtenant ainsi une vision partielle de la pièce dans laquelle nous sommes. Grace à la communication avec le PC nous pouvons, en prime, obtenir une jolie visualisation de ces informations à la façon "radar".

Pour ne pas changer les bonnes habitudes j'ai codé tout le bouzin en python :

  • Pour réaliser la communication j'utilise la librairie "pyserial"
  • Pour faire une "jolie visualisation" j'utilise pygame.

Avec ces deux éléments, un peu d'huile de coude, et 93 lignes de python (commentaires inclus), j'obtiens un joli écran "radar" ^^ Grace à la fonction "pygame.image.save" c'est un jeu d'enfant de sauvegarder chaque frame affichée, et grace à la toute puissance de la ligne de commande linux je peux vous faire partager le résultat facilement :

Radar - Creative Common par ozwald

Pour ceux qui ne connaissent pas la pièce ça ne ressemble sans doute pas à grand chose, mais moi je vois bien les deux murs de ma pièce, la porte (ouverte) entre les deux murs, et mon armoire tout à droite (quand au gros obstacle collé tout à gauche du capteur...c'est moi -_-)

Conclusion

Et c'est tout pour ce résumé de la création d'un micro jouet conçu et bidouillé en quelques heures seulements :) Vous noterez que j'ai jeté un voile pudique sur la partie mécanique du projet, mais j'ai bien l'intention de revenir dessus dans un article ultérieur puisque c'est un domaine sur lequel j'ai également réalisé pas mal de recherches et progrès récemment, notamment grace à l'excellent "Guerrilla guide to CNC machining, mold making, and resin casting"[8] et à ce produit miracle que l'on appelle "polymorph plastic".

En guise d'ultime conclusion un petit rappel des couts de construction (hors mécanique; comptez moins de 5€ de mécanique) :

Atmega8		1.50
UART-USB	1.80
servo		2
HC-SR04		1.50
============
TOTAL		6.80 €

Notes

[1] Sans compter la main d'oeuvre, évidemment ;-)

[2] Vous pouvez également utiliser un arduino, c'est juste un peu plus cher

[3] Attention, le fonctionnement de ce module HC-SR04 vu "de l'extérieur" est trivial mais je vous rassure : son fonctionnement complet est un poil plus complexe. En effet, afin d'éviter d'être perturbé par des bruits parasites, le module se charge d'envoyer en réalité plusieurs trains d'ondes séparés pour fiabiliser sa mesure. C'est l'intérêt d'un module "tout fait" par rapport à un système bricolé à la main à partir des éléments d'émission et de réception ultra-son uniquement.

[4] Une méthode simple de faire ça sans consommer de temps de calcul de notre microcontrolleur c'est d'utiliser une PWM dont la fréquence avoisinne les 50/60Hz

[5] je ne parierai pas sur la linéarité de la relation entre l'angle du servomoteur et la largeur de l'impulsion ceci-dit...

[6] Petite parenthèse : le format "DIP" c'est le format standard du prototypage, c'est facilement manipulable et soudable à la main, les pattes des composants sont espacés de 2.54mm. Le format "TQFP" en revanche est nettement plus petit et, bien qu'il soit soudable à la main, vous allez galérer pour le manipuler et le mettre en place correctement puisque les pates peuvent s'y trouver espacées de seulement 0.4mm.

[7] Les attiny n'embarquent qu'une version "light" de l'UART que je n'ai pas testé, mais j'ose espérer que ça marche quand même

[8] Guide rédigé par Michal Zalewski alias lcamtuf qui ne se contente pas d'être un expert reconnu en sécurité informatique mais qui réalise aussi de bien jolis robots. Bref : un gars certainement très bien :-D !

mercredi, novembre 7 2012

Petites astuces vite fait

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

Swiss Knife - Creative Common by "focusforaword" on Flickr

Python2 ou Python3 ? Les deux !

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

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

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

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


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

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

import argparse, logging

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

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

Contre :

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

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

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

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

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

Note

[1] Même si ce mot n'existe pas

samedi, août 11 2012

SAT solvers

Ce mois-ci je suis tombé, en moins de 24h, sur deux articles traitant de l'utilisation des SAT solvers dans un contexte de sécurité informatique. La (re)découverte de ces outils dans ce domaine m'a donné envie de partager l'expérience et je fais donc, via ce petit billet, un peu plus de "bruit google" autour des SAT solvers en espérant qu'ainsi quelqu'un d'autre passe un bon moment à (re)découvrir les possibilités de ces outils :)

Puzzle - Creative Common from a work by "INTVGene" on Flickr
Un "problème SAT" c'est un problème de décision visant à savoir s'il existe une solution à une série d'équations logiques données[1]. Résoudre un problème SAT revient donc à décider s'il existe, ou non, une configuration d'affectation "VRAI/FAUX" à des variables d'entrées qui permette de satisfaire une expression logique du type : "(condition1 OU condition2 OU ... OU conditionX) ET (conditionY OU...OU condition Y++) ET ... ET (...)". Par exemple si on considère deux variables de bool "A" et "B" on peut évaluer le problème SAT suivant "(A) ET (nonA)" qui n'a, évidemment, pas de solution et qui est donc "UNSATIFIABLE"; en revanche le problème SAT "(A ou B) ET (nonA ou B)" est SATISFIABLE avec, par exemple, les affectations : B=VRAI, A=VRAI. Un solveur SAT est un programme informatique dont la tache est de décider automatiquement si un problème SAT et UNSATISFIABLE ou SATISFIABLE (et de donner une solution exemple dans ce dernier cas de figure).

On trouve aujourd'hui beaucoup de logiciels de résolution SAT plutôt très efficace[2] et on peut donc considérer que la technologie est assez mature pour jouer avec sans avoir à ramer dès l'installation ;) Par exemple "Minisat" est packagé dans toutes les bonne distributions Linux[3] et vous n'aurez donc aucun souci à vous en procurer une version en état de marche.

Une fois un solveur SAT fonctionnel installé il faut apprendre quelques rudiments du langage "DIMACS". C'est un formalisme qui est utilisé pour représenter les problèmes SAT de façon compréhensible par 99% des solveurs publiques et qui, coup de chance, est basé sur du texte brute et reste super simple à apprendre :

  • Toute ligne commençant par un "c" sera considérée comme du commentaire
  • En début de fichier on doit renseigner le nombre de clauses (i.e. nombre de : "condition1 OU condition2 OU ...OU ... conditionX" ) ainsi que le nombre de variables que l'on va utiliser. Pour celà on écrit une ligne commençant par "p cnf" puis contenant les informations demandées. Au final la ligne ressemble à ça : "p cnf NOMBRE_DE_CLAUSE NOMBRE_DE_VARIABLES"
  • Ensuite on écrit, sur chaque ligne, une clause du problème dans laquelle on remplace : les variables par un numéro d'indice l'identifiant uniquement, les mots "OU" par des espaces, et les les "non" par des moins. On termine chaque ligne de clause par un "0"

Ainsi notre problème SAT d'exemple UNSATISFIABLE ((A) ET (nonA)) devient[4] :

p cnf 1 2
1 0
-1 0

De même notre second problème SAT d'exemple ((A OU B) ET (nonA OU B)) devient :

p cnf 2 2
1 2 0
-1 2 0

Quand on donne à manger le premier fichier texte à minisat on obtient bien UNSATISFIABLE, et quand on lui donne à manger le second on obtient SATISFIABLE sur la sortie standard et la solution exemple -1 2 0 dans le fichier de résultat[5]

Bon c'est bien joli, mais on ne va pas très loin avec ça me direz-vous ! Et bien si, justement :) ! Là où ça devient intéressant c'est qu'on peut faire beaucoup plus que mes mini problèmes d'exemple. Parmi les fichiers cnf mis à dispositions ici on en trouve un qui compte 1040 variables et 3668 clauses que Minisat résoud (comme "UNSATISFIABLE") en...0m0.013s ! Dans la même catégorie on trouve un autre fichier de test contenant 1501 variables et 3575 clauses que Minisat résoud (en "SATISFIABLE" avec une solution exemple) en 0m0.008s.

Avec une telle puissance de résolution que peut-on faire ? On peut, par exemple, attaquer des algorithmes de hachage faibles, comme l'indique l'un des deux articles déclencheur de ce billet. On peut également aller beaucoup plus loin et se mettre à taquiner la recherche de vulnérabilité ou la génération de code d'exploitation comme le montre le second lien initiateur de ce billet. De plus, j'ai en mémoire d'avoir vu quelque part des outils de fuzzing qui utilisaient des SAT solver pour augmenter leur couverture de test, mais je ne parviens plus à retrouver de liens pertinents :( ...Enfin, pour ma part, je vais probablement tenter d'insérer une intervention de SAT solver dans mon analyseur de code PHP afin de décider plus précisément si un bout de code est accessible ou pas; ou encore tenter de transcrire au formalisme SAT le prochain problème que je rencontrerai et pour lequel je n'aurai pas de meilleure idée qu'un brute-forceur :)

Et vous, qu'en ferez-vous ;) ?

Notes

[1] Merci Wikipédia

[2] En tout cas beaucoup plus efficace qu'un être humain devant réaliser la même tâche

[3] Minisat est masqué sous gentoo par ~x86, mais il est tout de même packagé

[4] Vous devinerez que j'ai affecté 1 à la variable A, et 2 à la variable B

[5] On remarque au passage que Minisat nous propose la solution A=Faux, B=Vrai alors qu'en exemple moi j'avais donné A=Vrai, B=Vrai. Les deux solutions sont valides pour ce problème.

dimanche, juillet 8 2012

Packons (ou pas) avec miasm et elfesteem

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 machine[1] 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 jolies 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).

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 0x400[3]...Le mystère reste donc entier[4].

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 packer[5], à 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..

Notes

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

dimanche, juin 10 2012

SSTIC 2012 completed

Et voilà, le SSTIC 2012 c'est fini depuis hier soir. Cette année nous avons eu droit à quelques conférences mémorables et à plusieurs évènements retentissants à tout niveau de l'actualité. Ci-dessous ce que je retiendrait :

SSTIC 2012 - miasm presentation by F.DESCLAUX - Creative Common by Ozwald

Jour 1

20 years of Pax : Pour débuter le SSTIC on attaque sur les chapeaux de roues avec une présentation en anglais par un speaker de prestige sur un sujet d'un haut niveau technique. Globalement sympa mais, perso, je dirai "sans plus" à caude de l'équation suivante : (speaker qui ne parle pas dans le micro + place au fond de l'amphi) + anglais + contenu très technique que je ne maitrise pas = pas tout suivi :-/

SSL/TLS: état des lieux et recommandations par Olivier Levillain : Conf sympathique (sans plus) qui fait un état des lieux sur SSL/TLS (comment c'est déployé, quelles options existent, quelles sont les différentes versions, etc.). Pas grand chose à apprendre mais une jolie remise à niveau quand même. Il parait que le papier (le plus gros de cette année avec plus de 40 pages) contient beaucoup plus d'informations sur les attaques possibles sur SSL/TLS (volet assez absent de la présentation). A relire à tête reposée donc :)

Netzob : un outil pour la rétro-conception de protocoles de communication par Frédéric Guihery, Georges Bossert, et Guillaume Hiet (absent à la présentation :( ) : La conf débute par le constat qu'à l'heure actuelle l'ingénierie inverse de protocoles de communication est pénible et manuelle, puis elle enchaine sur la présentation de l'outil "netzob" qui est censé faciliter la démarche. L'outil a l'air assez stable et donne bien envie d'être testé si l'occasion se présente. Pour la forme de la conf : Les slides étaient amusant mais la conf manquait peut-être un peu de rythme et en tout cas, 3 jours plus tard, elle me laisse un peu le même sentiment que la conf précédente : sympa (mais sans plus).

Sécurité de RDP par Arnaud EBALARD, Aurélien Bordes et Raphaël Rigo : J'attendais cette conférence qui ne m'a pas (trop) déçu. Comme pour SSL/TLS c'était une bonne remise à niveau sur l'état de l'art RDP. Il y avait un peu plus d'informations sur les attaques (ce qui n'est pas plus mal), par contre le symptome ANSSI était bien présent : les outils présentés lors de la conf ne sont pas disponibles.

WinRT par Kévin Szkudlapski et Sébastien Renaud : Les speakers ont présenté les fonctionnalité du runtime "WinRT" présent dans windows 8 et qui est à la base des applications compatibles avec la nouvelle interface graphique Metro. Intéressant si on veut développer des applications pour "Metro" mais à part ça...

L'information, capital immatériel de l'entreprise par garance mathias : c'était, là encore, une conférence que j'attendais de pied ferme (tout en conservant une petite crainte) et le verdict est sans appel : j'aurai préféré être ailleurs qu'à cette conférence. Le message de fond (celui que j'ai retenu en tout cas) n'a rien de nouveau et pourrait se résumer ainsi : "le droit français a des décennies de retard sur l'évolution technologique du monde et il n'y a actuellement pas de texte pour traiter proprement les problématiques liés à l'information, donc en cas de litige c'est le magistrat qui décide". Mais le plus ch*ant n'est pas tant qu'on n'avait pas grand chose à retirer de cette conférence, c'était plutôt la forme : élocution lente, pas de référence pratique, et un déploiement outrancier de techniques rhétoriques grosses comme des maisons (on a eu droit à une dizaine de "vous le savez bien", "vous l'avez compris", "vous en êtes bien conscient", et à une vingtaines de questions rhétoriques bidons dont le seul but semblait être de ralentir la présentation plutôt que d'introduire le paragraphe[1] suivant). Bref : pas aimé.

Audit des permissions en environnement Active Directory par Géraud De Drouas et Pierre Capillon : Plusieurs idées très intéressantes dans cette présentation qui se penche sur l'audit offline des permissions qu'ont les objets d'un Active Directory afin de détecter une éventuelle compromission. On a droit à plusieurs exemples concret d'audits des permissions des utilisateurs dans un AD de taille "professionnelle", c'est sympa et pleins d'astuces. Petit bémol : passé la première moitié de la conférence les speakers ne présentaient quasiment plus que l'interface graphique de leur outil d'audit mais, syndrome ANSSI oblige, l'outil n'est pas disponible :-/

Windows 8 et la sécurité : un aperçu des nouvelles fonctionnalités par Bernard Ourghanlian : Présentation commerciale des fonctionnalités de sécurité de Windows 8. Pour tenter de résumer à l'arrache : boot sécurisé, TPM à gogo, et "carte à puce virtuelle" (celle là fait bien réver...). Ce qu'il faut retenir c'est surtout deux réponses aux questions de fin de conférences : "oui, un ordinateur certifié compatible Windows 8 pourra tout de même booter sur un autre OS malgré le boot sécurisé de bout en bout", et "faire tourner énormément de code pré-boot (anti-malware, longue chaine de boot sécurisé, etc.) dans un contexte ultra-privilégié (ce qui pourrait sembler à l'opposé de toutes les bonnes pratiques en terme de sécurité) ne pose pas de problème à Microsoft parce qu'il peuvent/vont prouver ce code mathématiquement"[2].

10 ans de SSTIC : très agréable présentation revenant sur l'histoire du SSTIC, sur les anecdotes les plus marquantes, les speakers les plus présents, etc.


Jour 2

Compromission d'une application bancaire JavaCard par attaque logicielle par Julien Lancia : Retour du speaker un an plus tard sur le même domaine mais avec une présentation bien différente. L'auteur explique cette année comment, en ayant les clefs permettant d'uploader du Java sur une Javacard bancaire, il est parvenu à totalement compromettre l'application bancaire présente de base. Très jolie présentation, même si les conditions de réalisation des attaques les rendent actuellement hautement délicate à mettre en oeuvre IRL. Petite remarque perso : ça me fait plaisir de voir, en 2012, que le monde Java se prend des vulnérabilité du type "j'accède à la mémoire au delà des limites normale de mon tableau et je l'écrase" <humour>et PAN dans tes dents langage pourri soit disant ultra-secure et portable :p</humour>.

IronHide: Plate-forme d'attaques par entrées-sorties par Fernand LONE SANG, Vincent Nicomette, et Yves Deswarte : Encore des revenants pour cette conférence. Ils présentent ici une plateforme matérielle permettant de lire et envoyer des trames PCI Express arbitraires (keylogger à la clef par exemple). Quelques vidéos de démonstrations d'attaques réelles. On est dans de l'étude de laboratoire, mais c'est sympa quand même, les speakers sont agréables à écouter, et les perspectives sont vastes :)

La qualité d'hébergeur en 2012 par Romain Beeckman : Comme quoi on pouvait faire une conférence orienté juridique au SSTIC 2012 sans être chiant. Le directeur juridique d'OVH explique clairement la notion "d'hébergeur" dans la Loi actuelle ainsi que les évolutions passées et potentiellement à venir de cette notion. On a des anecdotes concrètes démontrant bien les différents cas (hébergement mutualisé, dédibox, etc.) et un rythme normal sans artifice oratoire ou tour de manche. Bref : conf très agréable où aucune questions n'a été esquivée, que celà soit sur Megaupload ou sur Wikileaks.

Résultats du challenge par Axel Tillequin, Fabien Perigaud, et Florent Marceau (concepteurs du challenge) puis Julien Perrot (vainqueur du classement qualité) : Enorme claque. Forcément j'avais discutté du challenge avec quelques amis qui s'étaient penchés dessus avant cette conf et je savais donc un peu comment le challenge démarrait (réparation d'une partition ext, reverse d'un binaire elf/MIPS et analyse d'un algo dérivé de DES contenu dedans). Ce niveau d'avancement du challenge (que les gens avec qui j'avais discutté avaient mis entre une semaine et un mois à atteindre) a été dépassé en "un jour, un jour et demi" par le vainqueur (le reste lui ayant pris plus de deux semaines :D ). Je ne vais pas rentrer dans les détails que vous trouverez bien mieux expliqués sur le wiki du SSTIC mais, en gros, ça se termine par le flash du firmware d'une webcam USB pour faire tourner une VM sur son chipset CY16 afin de bruteforcer des softs embarqués dans l'efl/MIPS ce qui permet, finalement, de récupérer des bouts de clefs de chiffrement :-D Bref, pour citer Wayne's World, voilà à quoi quoi ça m'a fait penser d'être dans le même amphi que ces 4 personnes : "On mérite pas ! On est tout p'tits ! On est à chier !"

Présentation courte 1 Anthony Desnos présente deux outils (Elsim et Androguard) qui lui permettent, par mesure de similarité, d'identifier des reprises de code entre applications Android. Entre autres choses celà lui permet d'identifier les librairies de pub qu'on retrouve dans les jeux android. C'est amusant, ça semble efficace, et ça fait bien mal en mettant sous les yeux qu'on télécharge en très grandes quantité de la pub quand on télécharge certains jeux (de mémoire : plus d'un tiers du code d'Angry Bird c'est de la librairie publicitaire). Présentation vraiment agréable à suivre mais je soupçonne quand même l'auteur de nous avoir crapulé[3] sur des petits détails. En effet les résultats bruts de ses mesures de similarité ressemblent à ça : "Dans tel jeu on retrouve 80% du code de cette librairie de pub, 20% de telle autre librairie de pub, et 90% de cette troisième" or l'auteur nous présente ensuite, sans transition, le découpage de jeux en pourcentage de code "propre", de code provenant de librairies de pub, et de code provenant d'autres librairies. Le "crapulage" c'est que ces dernières mesures ne peuvent être que des estimations issues de décisions arbitraires type "on retrouve 79% du code de cette librairie de pub, donc on va dire qu'elle est présente et pèse environ autant que si je la télécharge seule; on retrouve 33% du code de cette autre librairie de pub, on va donc supposer qu'elle n'est pas présente", du coup le découpage présenté au final n'est qu'issu d'une estimation dont la recette n'a pas été expliquée. M'enfin je chipote ^_^

Présentation courte 2 Davide Canali présente un projet de honeypot web grande échelle. Ils ont achetés 100 noms de domaine, ont créé 5 sous-domaine pour chacun, et mis à disposition à ces 500 urls plusieurs CMS vulnérables et webshells, ensuite ils monitorent le comportement des attaques :) Ils ont actuellement générés plus de 10Go de données, mais leur analyse est en cours. Bon teaser donc, mais rien à se mettre sous la dent tant que l'analyse n'a pas été faite :-(

Présentation courte 3 Pierre Karpman parle de durcissement de programmes C avec SIDAN. Perso j'ai eu un peu de mal à suivre la présentation et à comprendre ce que voulait faire l'auteur...de ce que j'ai suivi il instrumente du code C pour rajouter des vérification d'invariant entre un appel de fonction et son retour (pour détecter d'éventuelles attaques s'étant déroulée dans l'appel et ayant modifié des bouts de mémoire imprévu); m'enfin ça ne m'a pas plus convaincu que ça. J'espère qu'il y aura un petit papier publié pour que je relise tranquillement ce qu'il voulait faire.

Contrôle d’accès mandataire pour Windows 7 par Christian Toinard, Damien Gros et Jérémy Briffaut : En résumé des 20~30 premières minutes "on tente de porter SELinux sur Windows 7 (mais on n'y arrive pas totalement)". Devant une telle déviance j'ai décroché ^_^

Expert judiciaire en informatique par Zythom : S'étant fait pwner le matin même son blog, et son compte twitter ( + on soupçonnait à ce moment là que le compte mail y était passé aussi) Zythom fait tout de même sa présentation, chapeau ! D'ailleurs il n'y avait pas à s'y tromper : c'est avant qu'il ne commence sa conférence qu'est parti le premier tonnerre d'applaudissement unanime de la salle. Pour le contenu on a eu droit à une petite explication de son piratage puis à la conférence telle qu'elle avait été prévue initialement. Ca parle des sujets qu'on retrouve sur le blog, mais en live : Qu'est ce qu'un expert judiciaire, comment on le devient, comment on le reste, quelques exemples de missions et des contextes dans lesquels elles se déroulent. On a également eu le droit à une liste des outils qu'il utilise, le tout ponctué de nombreux traits d'humour tout au long de la conf dispensée avec beaucoup d'humilité. Bref : conférence très agréable, et plus qu'impressionante quand on considère le contexte ! Monsieur Zythom vous avez tout mon respect[4]

Forensics iOS par Jean Sigwald et Jean-Baptiste Bédrune : On commence par un panorama complet des méthodes d'acquisition d'un dump mémoire d'un iOS avec leurs avantages et inconvénients respectifs (nécessité du code pin ou non, modification de la mémoire lors du dump ou non, etc.) puis on enchaine avec l'utilisation du chiffrement omniprésent dans iOS. Impression d'un prophane : c'est touffu et ça chiffre de partout mais les bougres arrivent quand même à déchiffrer tout ce qu'ils veulent étape par étape. Présentation d'un haut niveau technique, sur un sujet complexe, avec une démo, et réalisée par des spécialites du domaine, mais j'ai trouvé que c'était quand même un poil difficile à digérer.

Rump session : Super passage que ces rumps sessions ! On a eu du très très très bon mais également le petit plaisir sadique d'applaudir un présentateur plus de 30s avant la fin théorique de son temps[5]. Dans les mémorables on a Biondi qui, lui, a explosé son compteur de temps en présentant les pipes dans scappy et qui s'offre un magnifique "c'est dans scappy depuis un an" lorsqu'un membre de l'assistance lui demande si c'est disponible :D


Jour 3

Source Address Validation Improvements (SAVI) par Jean-Michel Combes et Maryline Laurent : je l'ai raté, social event oblige ^_^

Utilisation malveillante des suivis de connexions par Eric Leblond : Très bonne présentation de l'implémentation du suivi de connexions dans netfilter (permettant de gérer "proprement" les protocoles ouvrant dynamiquement d'autres canaux de communication, type FTP, IRC, ou SIP). Et le plat de résistance : un outil qui permet d'abuser ces fonctions pour ouvrir des trous dans les implémentations vulnérables (sous réserve que vous partagiez sur le même réseau ethernet que le FW cible, que le FW cible soit vulnérable, et que le serveur ciblé propose légitimement un protocol adéquat via le FW). Je reste impressionné que de tels bugs existent encore en 2012, mais c'est comme ça :) La présentation était en tout cas très bien, et l'auteur se paie le luxe de commiter son outil en live pour anticiper la question "est-ce-que le code est disponible ?", joli show :) !

Influence des bonnes pratiques sur les incidents BGP par Francois Contat, Guillaume Valadon et Sarah Nataf : Présentation à trois voix très bien menées pour ceux qui, comme moi, n'y connaissent rien en BGP. On nous explique ce que c'est, comment c'est utilisé pour soutenir internet entre les "grand acteurs de routage" possédant leur AS, quelles sont les bonnes pratiques, et comment ces bonnes pratiques répondent (ou auraient pu répondre) à des incidents (exemples concrets et réels à l'appui). Décidément cette troisième journée commence par deux très bonnes présentations, c'est bien parti pour être la meilleure journée du SSTIC :) !

Présentation courte 1 Clément Lecigne présente netusse. C'est un outil de fuzzing des implémentation socket ayant pour but de débusquer du 0day kernel. Le tool a commencé il y a plusieurs années lors d'un Google Summer of Code puis Clément l'a poursuivi. Après avoir présenté l'outil on passe à la dissection d'un bug kernel découvert sur FreeBSD puis sur l'impressionante explication du code d'exploitation. C'est d'un très haut niveau pourtant le speaker donne l'impression d'être aussi à l'aise que s'il était en train de faire une rump expliquant la recette de la pate à crèpe o_O ! En espérant le revoir en présentation longue l'an prochain :) !

Présentation courte 2 Étienne Millon nous parle de sa passion : l'analyse statique de code. Présentation vraiment sympa et qui s'enchainait bien avec la précédente. Clairement il y a des limitations à son outil qui demande encore pas mal d'aide manuelle, mais ça fonctionne et ça trouve du bug.

Présentation courte 3 Ronan Mouchoux nous parle de détection de nom de domaine "bizarre". Le concept de base c'est qu'un botnet doit contacter son C&C et que, souvent, les noms de domaine utilisés par les botnet sautent aux yeux des humains comme n'étant "pas normal" ("asbguocezbgiudzeujopnryeuocnbyo.ru", ce n'est pas "normal" :-D). Le principe de détection s'appuie sur 4 moteurs distincts (dont seulement 2 sont codés à l'heure actuelle) afin d'augmenter le taux de détection sans monter le taux de faux positifs grace à la diversification fonctionnelle[6]. Cette présentation ne m'a malheureusement pas convaincu; d'une part parce que les 4 outils présentés avaient l'air "relativement" simples mais que seulement deux avaient été codés (du coup on se demande un peu s'il n'a pas commencé ses recherches la veille ?), et d'autre part parce que le concept même est potentiellement bancal (l'auteur reconnait lui-même ne pas être capable de détecter des dns type "concaténation de plusieurs vrais mots" comme ceux qui sont utilisés par les tout derniers botnets, et qu'en plus il remonte des faux positifs avec les sous-domaines légitimes mais funkys type "grosrandom.updates.sitelegitime.com"). Bref pour moi l'idée est très intéressante mais c'est un truc à coder en une semaine grand max et dont l'utilité reste conditionnée par la confidentialité des méthodes de notation utilisées[7].

Successes (and limitations) of (static) binary analysis par Halvar Flake : Du lourd ! Un grand monsieur qui nous explique, en anglais, les obstacles qui restent à surmonter dans le domaine de l'analyse statique de code. Les exemples sont clairements expliqués et s'appuient sur de vrais vulnérabilités. Bref la conférence est très bonnes et ça suffit ça tenir éveillé malgré le coup de barre post-social-event :)

Miasm: Framework de reverse engineering par Fabrice Desclaux : J'attendais impatiemment cette conférence et cette fois je n'ai pas été déçu ! Je pèse mes mots en disant que cette conférence était exceptionnelle. L'auteur était à 200% mais restait compréhensible (ce qui n'est vraiment pas simple quand on parle aussi vite) du coup l'audience s'est pris un torrent sans interruption d'informations ultra pointue et de blagues mélangées tout au long de la conférence. Du très très très lourd qui aurait mérité une standing ovation. On a eu droit à la présentation "torchée"[8] de l'architecture du framework python d'ingénierie inverse smiasm composé de miasm, grandalf, et elfesteem. Ensuite on a une présentation du langage intermédiaire utilisé par miasm qui permet de s'abstraire du matériel sous-jacent, et tout au long de la présentation on est accompagné par des exemples de-la-vraie-vie type exécution symbolique[9] identification de gadgets pour ROP, désobfuscation, etc. Vraiment ZE conf de ce SSTIC 2012.

Rétroconception et débogage d'un baseband Qualcomm par Guillaume DELUGRE : Pas de chance pour cet orateur, il passe après Fabrice DESCLAUX. Le rythme est plus posé, mais en comparaison il apparait carrément lent :-/ Le contenu est néanmoins intéressant avec l'activation des canaux de communication série prévue pour débuggage dans une clef 3G qualcomm, l'utilisation de ce canal pour dumper l'ensemble du firmware, puis son analyse par ingénierie inverse. L'impression que j'en garde c'est que le firmware est assez "crado" (pour citer le speaker) et qu'il y a donc potentiellement pas mal de choses à aller regarder par là...

Protéger et défendre le cyberespace militaire : la démarche nationale par le contre-amiral Arnaud COUSTILLIERE : le premier élément qui saute aux yeux ce sont les slides, clairement issus d'un windows 95 ou antérieur. Les couleurs sont criardes, les images déformées, et les schémas semblent tout droit issus des bouquins d'enseignements de la biologie des années 90. M'enfin on va passer outre ^^ Concernant le fond du discours c'est une présentation sur la stratégie de défense informatiquecyber mise en place au niveau étatique. Dommage qu'il n'ai justement parlé que d'organisation de la défense et qu'il ai esquivé les questions offensives lors de la séance de questions[10].

Conclusion : je dirai que ce SSTIC était moyen jusqu'à l'entame de la troisième journée qui a remonté le niveau pour rendre cette édition mémorable. Je rentre de Rennes avec des heures de sommeils de retard, trois grammes dans chaque bras, mais également PLEINS d'idées de nouveaux terrains de jeux à explorer. Et puis si jamais je présente à nouveau au SSTIC un jour j'essaierai de garder à l'esprit qu'une conférence peut avoir un contenu techniquement super mais ne pas décoller du "mouaif" dans le ressenti du public si le speaker n'est pas à 200%.

Notes

[1] et je dit bien "paragraphe", pas "idée"

[2] Celle là c'est quand même ma citation préférée du SSTIC Q:"ça ne vous semble pas à l'opposé de toutes les bonnes pratiques?" R:"Et si on le prouve mathématiquement le code ?!" ...mais oui bien sur :-D

[3] http://www.ozwald.fr/index.php?tag/SSTIC#pnote-36-2

[4] Et j'ai eu l'honneur de lui serrer la main en plus ! Youhouuuuuu !!! Même si pour ça je l'ai intercepté un peu comme un goujat alors qu'il quittait le social event (probablement pour la rue St Michel). Si vous me lisez un jour : désolé :-/

[5] En même temps c'est un peu gonflé de commencer une rump SSTIC par "ça c'est une photo des produits qu'on vend" puis d'enchainer 2mn plus tard par "le code est libre mais on a tout fait pour que vous ne puissiez pas l'utiliser sans nous acheter de prestation de toute façon"

[6] Même si ça reste à prouver sérieusement...

[7] Et encore...on pourrait pousser la mise à mort en s'interrogeant comment son outil va être utile pour les botnets qui utilisent des C&C alternatifs type Skype, Facebook, pastebin, etc.

[8] Mais super bien torchée !

[9] Quote : "là je suis en train de vous expliquer que j'ai recodé qemu"

[10] Parce que répondre "attaquer c'est illégal donc on ne fait pas" c'est au mieux pas crédible du tout et au pire inquiétant à la lumière de Flame/STUXNET/etc.

mercredi, mai 23 2012

SSTIC 2012 en approche

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

dimanche, mai 13 2012

Culture automatisée

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
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 provence[1]"

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 cerises[2]). 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 test[3].

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és 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 UV[4].

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-controlleur[5]). 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/an[6]) 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 ?

Notes

[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

samedi, mai 5 2012

De l'incompétence à Pole Emploi

Ca fait plus d'un mois que je suis sans emploi et que je tente de m'inscrire à Pole Emploi. Le problème c'est que je me heurte à un mur inébranlable d'incompétence crasse...De lassitude je rend publique le dernier courrier que je leur ai envoyé, la réponse sera également rendue publique (si tant est que j'en ai une un jour). D'une part ça soulage, et d'autre part peut-être qu'un miracle pourra se produire et que quelqu'un de compétent à Pole Emploi lira ce message et débloquera la situation :

Bonjour,

Lors de mon rendez-vous d'inscription on m'a demandé des documents qui n'avaient pas été précisés dans les convocations, en particulier une fiche "relative au portage salarial", mais le problème c'est que je n'ai JAMAIS été en portage salarial. J'ai eu beau m'expliquer de toute les façons possible à la dame qui me recevait elle n'a rien voulue entendre et j'ai donc du repartir chez moi avec mon dossier de demande d'allocations sous le bras. J'ai réalisé les photocopie du contrat de travail qui m'avait également été demandé et j'ai retourné tout le dossier en ajoutant une phrase qui expliquait que, n'ayant jamais été en portage salarial (ce que vous pouvez vérifier en lisant le contrat de travail joint) il m'était impossible de faire remplir l'attestation "A remplir exclusivement par l'employeur en cas de rupture du contrat de portage salarial" (je cite le titre de la fameuse fiche que vous me réclamez...).

Je viens de recevoir par la Poste l'ensemble du dossier de demande d'allocation que vos services m'ont retournés, sous prétexte que l'attestation "à remplir exclusivement par l'employeur en cas de rupture du contrat de portage salarial" (je cite toujours le titre du fameux formulaire) n'avait pas été remplie par mon ancien employeur.

JE DEMANDE DONC A AVOIR RENDEZ-VOUS AVEC QUELQU'UN DE COMPETENT OU DE RESPONSABLE. De compétent parce qu'il pourra comprendre qu'il est complètement con de s'acharner à me demander un formulaire que je ne PEUX PAS faire remplir (je veux bien lui faire lecture à haute voix de mon ancien contrat de travail puis de la définition légale du portage salarial autant de fois qu'il sera nécessaire à ce qu'il comprenne); ou de responsable pour qu'il puisse me signer une attestation de refus d'indemnisation sous l'unique prétexte que je ne fourni pas cette fiche, ainsi je pourrai aller porter plainte et, peut-être, vous obliger à appliquer la Loi.

Je reste dans l'attente d'un retour RAPIDE de votre part.

---

Edit du Vendredi 11 Mai 2012,11h30 : en réponse à mon courrier j'ai reçu un accusé de réception automatique stipulant "Nous vous répondrons sous 48 heures (2 jours ouvrés), soit le Jeudi 10 Mai 2012 au plus tard ou 7 jours calendaires s'il s'agit d'une réclamation.". Sauf que nous sommes déjà à 6 jours calendaires et que je n'ai toujours eu aucune réponse...le suspens est presque palpable : vont-ils, encore une fois, mal faire leur boulot en ne répondant pas dans les temps :-D ? Ca semble plausible sachant que la dernière fois que j'ai essayé d'avoir un conseiller au téléphone j'ai appris qu'ils ne répondaient pas le vendredi après-midi (horaires officiels...).

Edit du Dimanche 13 Mai 2012, 15h12 : Bon bah toujours aucune réponse de la part de Paul en Bois. Ca aurait été surprenant aussi qu'ils respectent une échéance quelconque (qu'ils se sont pourtant eux même fixés)... Allez, disons que je vais quand même attendre jusqu'à lundi ou mardi soir avant de les relancer.

Edit du vendredi 18 Mai 2012, 00h32 : Toujours aucune réponse.

Edit du mercredi 23 Mai 2012, 22h30 : Toujours aucune réponse à mon mail. Par contre j'ai été expliquer le problème à mon employeur qui a gentiment accepté de me remplir le formulaire à la c*n que Paul en Bois s'acharne à me demander. Du coup j'ai été le porter moi-même ce matin où on m'a annoncé "un délai de quinze jour pour la relecture du dossier"... Délai qui a finalement été réduit à 5h grace à l'intervention d'une agente qui a eu la sympathie de traiter mon dossier dans la journée[1] ce qui me permet de poursuivre mes démarches diverses et variées dans les temps (une autre administration m'avait fixé une date limite à vendredi pour obtenir la confirmation de prise en charge par Paul en Bois). Bref, j'espère que je peux enfin écrire les trois dernières lettres de ce billet : F.I.N.

Edit du jeudi 31 Mai 2012, 17h26 : J'ai un peu anticipé avec le "F.I.N." de la semaine dernière...Chez pole emploi ils manquent de compétence mais visiblement pas d'humour ! Vingt-six jours[2] après avoir envoyé le message que je relate en haut de ce billet je viens enfin de recevoir une réponse ! Comme promis je vous le copie/colle intégralement (avec l'apostrophe manquante et sans aucun retour à la ligne...bref : exactement comme je l'ai reçu) :

C est un imprimé que nous donnons systématiquement selon certains code NAF et métiers. Ce document est justement a faire remplir quand nous avons un doute et seul l'employeur peut lever ce doute en remplissant cette demande. Votre dossier a été validé le 230512.

Comme c'est mauvais pour ma tension je ne reviendrai pas une fois de plus sur la connerie profonde qu'il y a à exiger un document "à remplir en cas de rupture du contrat de portage salarial" quand il n'y a jamais eu de contrat de portage salarial, mais je n'en pense pas moins ! Ahlala...si on ne les payait pas déjà à nuire aux demandeurs d'emploi j'aurai presque été d'accord pour qu'on les paie à prendre des cours de français et de logique.

Notes

[1] C'est dingue quand même d'en arriver à être profondément reconnaissant envers quelqu'un qui a juste fait le travail pour lequel nos propres taxes/impots paient...

[2] Ca fait une vitesse de réponse d'environ 10 lettres par jour (en comptant les espaces...). Si j'avais su j'aurai demandé une réponse en langage SMS ^^

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)

- page 1 de 8