Question:
Qu'est-ce qu'un désassembleur correct?
perror
2013-03-25 22:02:44 UTC
view on stackexchange narkive permalink

Un désassembleur est censé produire une représentation lisible par l'homme du programme binaire. Mais les techniques les plus connues: balayage linéaire et traversée récursive (voir ce commentaire pour plus) sont connues pour être facilement trompées par des astuces spécifiques. Une fois trompés, ils sortiront du code qui ne sera jamais exécuté par le vrai programme.

Je pensais qu'il existe de nouvelles techniques et de nouveaux outils plus soucieux de l'exactitude (par exemple Jakstab, McVeto, ...), la notion de exactitude de la sortie n'a jamais été correctement définie, à ma connaissance, pour les désassembleurs.

Que serait un bonne définition d'un désassembleur, quelle serait une définition correcte de l'exactitude de sa sortie et comment classeriez-vous les désassembleurs existants au regard de cette définition de correction ?

Il me semble que vous répondez pratiquement vous-même à la question. En plus de cela, votre métrique d'exactitude est probablement entièrement subjective. Puisque vous ne pouvez pas faire une décompilation exacte, ce que vous faites à partir de là dépend juste.
Cela ressemble plus à un article de blog qu'à une question :)
Oui, veuillez le diviser en une question et une réponse. Vous * pouvez * répondre à vos propres questions, ce n'est pas un problème. De plus, la question à la fin ne correspond pas au titre.
@Emmanuel: veuillez séparer la réponse de la question avant qu'elle ne soit fermée.
fait (et désolé pour le désordre). Espérons qu'il y aura une meilleure réponse ...
Pourtant, ce n'est pas un bon choix pour les questions et réponses. C'est subjectif. Vote de clôture.
Je dirais que les deux questions que vous vous posez ("Quelle serait une bonne _définition_ d'un désassembleur" et "quelle serait une _definition_ appropriée de _correctness_ pour sa sortie"), pourraient mieux convenir à [cs.se] (http: //cs.stackexchange.com/)?
@Jesper: Oui, vous avez résumé les deux questions que je n'ai pas posées. Mais, comme un désassembleur est vraiment une pierre angulaire de la rétro-ingénierie logicielle, pourquoi ne devrait-il pas être placé ici?
@Emmanuel, Mon impression de la plupart des gens de RE (du moins du côté des logiciels, méfiez-vous des opinions colorées) est qu'ils sont pensifs et sacrément bons dans ce qu'ils font. Cependant, à cause de cela, ils ne sont pas bien fondés dans les universitaires et auraient du mal à donner une définition appropriée de l'exactitude. Ici, je vois plus l'accent sur la définition et l'exactitude des parties, et la partie désassembleur étant juste un exemple de ce qu'il faut définir l'exactitude, etc. Cela devrait se transformer en débat ou être vu comme une tentative de salir n'importe qui.
@Jesper, vous avez probablement raison (même si ce n'était pas du tout mon intention antérieure). Gardons cette question fermée, alors. Je suis d'accord avec cette décision.
J'ai oublié certaines négations. Évidemment, j'avais l'intention d'écrire quelque chose du genre: "Ceci n'est ** pas ** destiné à se transformer en débat ou devrait être considéré comme une tentative de salir n'importe qui".
Deux réponses:
#1
+16
endeavor
2013-03-26 00:02:55 UTC
view on stackexchange narkive permalink

Je suis l'auteur de rdis et j'ai réfléchi un peu à ce problème. Je vous recommande de consulter mon blog si vous avez d'autres questions après cela.

Je vous renvoie également au billet de blog d'Andrew Ruef Binary Analysis Is not. Ce qu'il faut retenir, c'est que nous essayons souvent de comprendre nos programmes dans le contexte des compilateurs, et pas nécessairement comme un continuum d'instructions. Il invente le terme «Analyse de sortie du compilateur», qui est plus ou moins ce que nous essayons de réaliser dans nos désassembleurs.

Termes et définitions

Recommencez avec vos définitions des termes courants au démontage. Nous avons des données, ou état, qui peuvent être composées de mémoire, de registres, de toutes les bonnes choses. Nous avons du code, qui est une étiquette que nous appliquons aux données que nous attendons de la machine à exécuter (nous reviendrons au code). Nous avons un programme, qui est un algorithme encodé dans les données qui, lorsqu'il est interprété par une machine, fait manipuler les données d'une certaine manière. Nous avons une machine qui est une cartographie d'un état à un autre. Nous avons des instructions qui, pour nos besoins, existent à un moment donné et sont composées d'éléments spécifiques de données qui contrôlent la façon dont notre machine manipule les données.

Souvent, nous pensons que notre objectif est la transformation de code, les données que nous attendons d'être exécutées par la machine, dans un démontage lisible. Je pense que nous faisons cela en raison de notre division de l'analyse de programme entre l'analyse de flux de contrôle (code) et l'analyse de flux de données (données). Dans l'analyse de programme, notre code est sans état et nos données ont un état. En réalité, notre code n'est que des données, tout a un état.

Récupération de programme

Au lieu de cela, notre objectif devrait être la récupération du programme par observation ou prédiction de la machine. En d'autres termes, nous ne sommes pas intéressés à transformer les données en un démontage lisible, mais à découvrir les instructions qui seront interprétées par notre machine.

De plus, notre représentation du programme doit être stockée séparément de notre représentation sans état des données, qui est généralement la configuration initiale de la mémoire qui nous est donnée par notre fichier exécutable (ELF / PE / MACH-O /etc). Vraiment, il devrait être stocké dans un graphe orienté. Quand je vois une représentation linéaire de la mémoire avec plusieurs emplacements étiquetés comme des instructions, je me ferme. Vous ne savez pas encore!

Je crois que la prochaine étape du démontage implique des processus qui font de meilleures prédictions sur les machines en permettant des changements d'état pendant le processus de démontage. Je crois que nous aurons à la fois un démontage émulé et un démontage abstrait. Certaines personnes le font déjà plus ou moins, bien que je ne sache pas si quelqu'un le fait expressément dans le but de créer des "récupérations de programme" utilisables et compréhensibles.

Vous pouvez voir un exemple de la différence entre un désassemblage récursif d'un programme et un désassemblage émulé d'un programme ici.

Qu'est-ce qu'un désassembleur correct?

Alors, maintenant pour répondre à votre question , "Qu'est-ce qu'un désassembleur correct?" Je crois qu'un désassembleur correct est celui qui définit clairement le comportement de son processus de récupération de programme et adhère à cette définition. Une fois que nous aurons des désassembleurs qui font CELA, les meilleurs désassembleurs seront ceux dont les définitions prédisent le mieux le comportement des machines pour lesquelles ils récupèrent des programmes.

#2
+1
perror
2013-03-25 23:29:05 UTC
view on stackexchange narkive permalink

Qu'est-ce qu'un désassembleur?

Je décomposerais un désassembleur en deux parties, d'abord un décodeur qui prend un code hexadécimal et affiche une instruction d'assemblage (éventuellement avec la longueur de l'instruction décodée si le langage d'assemblage a des instructions de longueur variable). Et puis un algorithme de désassemblage qui utilisera le décodeur pour parcourir le code exécutable.

L'objectif global d'un désassembleur, à mon humble avis, serait de récupérer tous les exécutions possibles qui peuvent être construites à partir d'un exécutable donné et pour le présenter dans un format concis et lisible par l'homme.

Problèmes de démontage

Un désassembleur peut rencontrer de nombreux problèmes lors du démontage un programme binaire. L'un des plus difficiles serait de gérer le code auto-modifiable . En effet, jusqu'à présent, il n'y a pas de véritable bonne représentation lisible par l'homme pour un programme auto-modifiable. Ainsi, tous les désassembleurs confrontés à un code auto-modifiable échouent lamentablement à produire quelque chose de clairement compréhensible.

Le deuxième problème qui peut arrêter un désassembleur est que de temps en temps le programme binaire passe à un autre endroit pour exécuter certains code (appels de fonction, if-then-else, commutateurs, ...). Et, si la plupart de ces sauts sont statiques (l'adresse où sauter est codée statiquement dans le code), il y a des sauts qui dépendent du contexte de l'exécution. Nous appelons généralement ces sauts des sauts dynamiques (par opposition aux sauts statiques ). Ces sauts dynamiques obligent le désassembleur à suivre non seulement la syntaxe des instructions mais aussi leur sémantique afin de ne pas se perdre lors de sa rencontre.

Enfin, un dernier problème est que tous les programmes binaires ne peuvent être supposés suivre une ABI (Application Binary Interface) précise, définissant une interface précise pour les appels de fonction ou un moyen de gérer les structures de données. En effet, certains programmes binaires sont soit fabriqués à la main, soit avec un compilateur modifié qui tentera de tromper les désassembleurs. Par conséquent, le désassembleur devra reconnaître un appel de fonction par sa sémantique et pas seulement par sa syntaxe.

Exactitude d'un désassembleur

Comme nous l'avons dit précédemment, le but ultime d'un désassembleur est de reconstruire toutes les traces d'exécution possibles à partir d'un programme binaire. Bien sûr, la plupart du temps c'est extrêmement difficile, nous pouvons donc définir trois types de désassembleurs:

  • Désassembleur exact : en théorie, il devrait émettre toutes les traces correctes qui peut être exécuté sur le programme binaire, et uniquement ces traces.
  • Désassembleur surestimé : La sortie de celui-ci doit inclure tous les traces, éventuellement avec quelques traces supplémentaires.
  • Désassembleur sous-estimé : La sortie de celui-ci doit être incluse dans les traces possibles mais ne pas en fournir non réalisables.

Techniques existantes et où les classer

Pour l'instant, les deux techniques les plus populaires sont le balayage linéaire et le parcours récursif (voir ici pour plus de détails).

Les deux sont assez largement utilisés dans la nature par de nombreux rétro-ingénieurs. Mais, en fait, aucune de ces techniques n'est ni exacte, ni sur-approximation, ni sous-approximation. Ils produisent tous les deux quelque chose qui ne correspond pas à ce que nous venons de voir auparavant (parfois, ils inventeront un chemin qui ne sera jamais atteint, et parfois ils en oublieront un autre).

Il existe des techniques plus avancées avec plus de souci d'exactitude (par exemple, Jakstab, McVeto, McVeto sur le code auto-modifiable, ... ), mais la recherche d'une récupération exacte est sûrement hors de portée.

Donc, choisir entre sous et sur-approximation dépend de ce que sera l'utilisation de la sortie du désassembleur.



Ce Q&R a été automatiquement traduit de la langue anglaise.Le contenu original est disponible sur stackexchange, que nous remercions pour la licence cc by-sa 3.0 sous laquelle il est distribué.
Loading...