 Aujourd'hui, je vais vous parler d'un petit projet que j'ai fait il y a quelques semaines. Donc, c'est un décompilateur pour des programmes en C, c'est plus ou plus écrit en piton. Donc, je suis étudiant en génie logiciel à l'OTS. Je m'intéresse beaucoup à la sécurité. Puis, j'aime mieux briser des logiciels plutôt que les écrire. J'aime mieux trouver des problèmes de sécurité. Oui, parler plus fort. J'aime mieux trouver des problèmes de sécurité plutôt qu'écrire un logiciel. Donc, c'est ça, mon compte get-om. Donc, la présentation aujourd'hui, on ne tient pas beaucoup de piton. Il y a plus d'assembleurs et d'AC dedans, donc désolé pour ça. Donc, c'est à propos d'un décompilateur qui est écrit en piton. Puis, possiblement que vous allez pouvoir utiliser des choses que je vais vous apprendre aujourd'hui si vous voulez écrire un décompilateur pour du piton plutôt que pour du C ou du C++. Donc, de quoi on parle quand on dit décompilateur? Donc, un compilateur évidemment, ça prend un fichier source, ça le compile, puis on obtient un binaire qu'on peut exécuter. Un décompilateur, ça fait exactement l'inverse. On essaie d'avoir une espèce de pseudo-code qui est plus facile à lire que de l'assembleur. Donc, pourquoi est-ce que vous voudriez faire ça? Peut-être que vous voulez comprendre comment un logiciel fonctionne. Donc, vous n'avez pas le code source. Peut-être que vous avez perdu le code source. Donc, peut-être que vous voulez comprendre un bug dans un logiciel. Donc, vous n'avez pas le code source. Ou, juste comme moi, je fais beaucoup de compétitions de sécurité informatique. Puis, il y a beaucoup de problèmes que c'est des logiciels qu'il faut trouver des bugs dedans. Donc, ça devient utile. Donc, ça, c'est avec ça qu'on travaille. Dans le fond, c'est une petite fonction qui est désassemblée. Donc, quand on regarde ça, à première vue, on ne comprend pas grand-chose. Ce n'est pas très intuitif. Ce n'est pas du code qu'on peut lire. On voit en bas ici qu'il y a un appel à printf. On voit peut-être que ça, ça va être le format string qui est passé à printf. En haut, il y a un addition ici. On voit qu'il y a le keyword add. Ça peut être un indice. On voit qu'il y a une loop et un jump qui revient en haut dans la fonction. Donc, on se doute qu'il y a une loop. Mais à part de ça, si on veut comprendre ce que ça fait, il va falloir lire chaque instruction puis comprendre un peu plus. C'est pas facile. Donc, ce qu'on va essayer de faire, c'est, dans le fond, essayer de simplifier ça pour retrouver la sens qui était dans le programme original. Donc, première étape, c'est qu'on va aller chercher une représentation intermédiaire. Au lieu d'avoir les instructions qui sont ici, au lieu d'avoir les instructions, on va transformer ça en une représentation qui est déjà un petit peu plus comme le code original, qui va avoir des assignations, qui va avoir des références avec l'étoile, puis il va avoir des go-to au lieu des jumps. C'est déjà un petit peu plus lisible. Deuxième étape, on va prendre la représentation intermédiaire puis on va trouver qu'est-ce qui est utilisé à quelle place puis ces registres-là sont définis où. Donc, par exemple, si on a AX qui est défini ici puis il est utilisé plus loin, même chose pour ADI est utilisé ici et défini là. Puis, on va les tagger. On va faire en sorte qu'il soit différent des autres déclarations qu'il y avait avant. Donc, AX ici est déclaré une fois. C'est différent de AX5 et différent de AX6, par exemple. Ensuite d'autre ça, le reste du décompilateur, c'est un espèce de server d'équation. C'est vraiment super simple le reste. Ce qu'on va faire, c'est avec des règles, on va remplacer les utilisations des registres par leur définition. C'est un peu comme si on voulait simplifier ça ici. X égal A plus 1, Y égal X moins 4. Ça donne, intuitivement, c'est vraiment facile. Y égal A moins 3. Donc, ça n'importe qui qui a fait les mathématiques de secondaire 3 est capable de dire ça. Mais dans un programme, c'est un peu plus compliqué que ça. Donc, c'est beaucoup plus compliqué dans un logiciel qui roule sur un ordinateur parce qu'il peut avoir des embranchements. Par exemple, des conditions ou des loupes. Ça rend le processus de taguer les registres beaucoup plus durs. Donc, par exemple ici, c'est quoi la définition de ce AX? AX là, il y en a deux. Il y a AX égal 0, AX égal 1. Donc, il y a deux définitions. Ça devient beaucoup plus dur de remplacer ici. On ne peut pas remplacer AX. On ne peut pas remplacer l'utilisation par sa définition. Même chose pour une loop. Ici, quand la condition dans la loop est évaluée pour la deuxième fois, le nouveau AX qui a été défini à l'intérieur de la loop va devoir être pris en compte. Donc, ça devient vraiment plus compliqué avec ce genre de construction-là dans du code. Donc, on va revenir au bout de code que je vous ai présenté plus tôt. Donc, première chose, on va commencer à combiner certaines expressions. Par exemple ici, on peut voir qu'il y a un emplacement sur la stack qui est incrémenté de 1. Donc, si on peut le combiner avec ce qui vient avant, la ligne juste avant, puis faire un post incrément avec ça. Donc, on efface la deuxième ligne, on remplace celle du haut. Puis, déjà, ça simplifie. Même chose pour le AX. Ici, c'est un artefact que le compilateur a laissé dans le code. AX est égal à 0 et jamais réutilisé après. Donc, on peut l'enlever. Ça simplifie encore un peu. Donc, ici, AX est défini. AX est utilisé la ligne d'après. On peut remplacer. Puis, ça simplifie encore un peu plus le code. On est rendu avec juste trois lignes. Puis, on fait ça dans un paquet de fois. Puis, à un moment donné, ça simplifie qu'il y ait une seule expression qui est notre printf originale qui est tel dans le code qu'on a compilé à l'origine. Donc, ici, vous avez un go-to. Mais le go-to, ça fait juste l'OP. Donc, on peut remplacer ça par une loupre. Il y aura juste un while 1. Si il y avait une condition, il y aurait un if en haut du go-to. On pourrait remplacer. On pourrait enlever le if, remplacer ça par la condition du while. Mais dans ce cas aussi, c'est juste un while infini. Donc, ça donne ça. Donc, si on compare un peu avec le code original, on peut jouer un peu à spot les différences. Premièrement, ici, on peut voir qu'on n'a pas les types. Donc, on n'a pas le type de retour. Ça, c'est parce que c'est pas implémenté dans ce que j'ai écrit. On peut voir que le return zero en bas a été enlevé. Ça, c'est parce que le compilateur l'a enlevé, l'a pas mis, vu que c'est une boucle infinie qui n'a pas de chemin pour sortir de la boucle. Mais le compilateur a tout simplement pas mis le return dans la fonction. Donc, il n'y en a pas dans notre fonction compilée. Ensuite de ça, on peut voir que, évidemment, les commentaires sont disparus parce qu'ils ne sont pas compilés dans l'exécutable. On peut voir que les nombres de variables sont disparus. J'ai juste renommé ici le ebp-4 par s1. J'ai donné un nom généré incrémentalement. Les nombres de variables sont évidemment disparus. Les nombres de fonctions sont disparus aussi. Le processus de décompilation est vraiment là aussi. Il y a beaucoup de choses qui vont disparaître. Mais on va pouvoir aller rechercher le sens original du code qui va être beaucoup plus facile à lire qu'est-ce qu'on avait, l'instinct désassemblé qu'on avait avant. Donc, les limitations, évidemment, comme je viens de dire, pas mal toutes est partie. Entre autres, les noms, mais aussi si on avait les structures, si on avait des types complexes, des unions, des choses comme ça, ce serait toute partie. On ne pourrait pas retrouver ça avec un décompilateur. Mais, à moins que les debug symbols soient dans l'exécutable. Il y a aussi beaucoup de choses qui vont être transformées en quelque chose d'autre qui est équivalent. Par exemple, une condition comme ça, E plus petit que 10, ça pourrait être transformé par le compilateur en E plus petit, égal à 9. C'est deux trucs équivalents. Puis ça, on ne pourra jamais aller retrouver ce qui était dans l'exécutable originalement. On va juste avoir quelque chose qui est peut-être équivalent. Donc, si vous voulez l'essayer, vous pouvez dansnoder la version démon de ce logiciel-là. Ce que j'ai écrit, c'est un plugin pour ce logiciel-là. Donc, le plugin est disponible. Puis, c'est ça. Merci.