web-dev-qa-db-fra.com

Comment écrire un démonteur?

Je suis intéressé à écrire un dissembleur x86 comme projet éducatif.

La seule vraie ressource que j'ai trouvée est celle de Spiral Space, " Comment écrire un désassembleur ". Bien que cela donne une belle description de haut niveau des différents composants d'un désassembleur, je suis intéressé par des ressources plus détaillées. J'ai également jeté un rapide coup d'œil au code source de NASM mais c'est un peu lourd à apprendre.

Je me rends compte que l'un des principaux défis de ce projet est le jeu d'instructions x86 assez important que je vais devoir gérer. Je suis également intéressé par la structure de base, les liens de base du désassembleur, etc.

Quelqu'un peut-il m'indiquer des ressources détaillées sur l'écriture d'un désassembleur x86?

65
mmcdole

Jetez un œil à section 17.2 du Manuel de référence du programmeur 80386 . Un désassembleur n'est vraiment qu'un glorifié machine à états finis . Les étapes du démontage sont les suivantes:

  1. Vérifiez si l'octet actuel est un octet de préfixe d'instruction (F3, F2, ou F0); si c'est le cas, vous avez un préfixe REP/REPE/REPNE/LOCK. Passez à l'octet suivant.
  2. Vérifiez si l'octet actuel est un octet de taille d'adresse (67). Dans l'affirmative, décoder les adresses dans le reste de l'instruction en mode 16 bits si elles sont actuellement en mode 32 bits, ou décoder les adresses en mode 32 bits si elles sont actuellement en mode 16 bits
  3. Vérifiez si l'octet actuel est un octet de taille d'opérande (66). Si c'est le cas, décodez les opérandes immédiats en mode 16 bits s'ils sont actuellement en mode 32 bits, ou décodez les opérandes immédiats en mode 32 bits si vous êtes actuellement en mode 16 bits
  4. Vérifiez si l'octet actuel est un octet de remplacement de segment (2E, 36, 3E, 26, 64, ou 65). Si tel est le cas, utilisez le registre de segment correspondant pour décoder les adresses au lieu du registre de segment par défaut.
  5. L'octet suivant est l'opcode. Si l'opcode est 0F, alors c'est un opcode étendu, et lisez l'octet suivant comme l'opcode étendu.
  6. En fonction de l'opcode particulier, lisez et décodez un octet Mod R/M, un octet SIB (Scale Index Base), un déplacement (0, 1, 2 ou 4 octets) et/ou une valeur immédiate (0, 1 , 2 ou 4 octets). Les tailles de ces champs dépendent de l'opcode, du remplacement de la taille de l'adresse et des remplacements de la taille de l'opérande précédemment décodés.

L'opcode vous indique l'opération en cours. Les arguments de l'opcode peuvent être décodés à partir des valeurs du Mod R/M, du SIB, du déplacement et de la valeur immédiate. Il existe de nombreuses possibilités et de nombreux cas particuliers, en raison de la nature complexe de x86. Voir les liens ci-dessus pour une explication plus approfondie.

62
Adam Rosenfield

Je recommanderais de vérifier certains désassembleurs open source, de préférence distorm et surtout "disOps (Instructions Sets DataBase)" (ctrl + le trouver sur la page).

La documentation elle-même est pleine d'informations juteuses sur les opcodes et les instructions.

Citation dehttps://code.google.com/p/distorm/wiki/x86_x64_Machine_Code

Instruction 80x86:

Une instruction 80x86 est divisée en plusieurs éléments:

  1. Les préfixes d'instruction affectent le comportement du fonctionnement de l'instruction.
  2. Préfixe obligatoire utilisé comme octet d'opcode pour les instructions SSE.
  3. Les octets d'opcode peuvent être un ou plusieurs octets (jusqu'à 3 octets entiers).
  4. L'octet ModR/M est facultatif et peut parfois contenir une partie de l'opcode lui-même.
  5. L'octet SIB est facultatif et représente des formes d'indirection de mémoire complexes.
  6. Le déplacement est facultatif et c'est une valeur d'une taille variable d'octets (octet, Word, long) et utilisé comme décalage.
  7. Immediate est facultatif et il est utilisé comme une valeur numérique générale construite à partir d'une taille variable d'octets (octet, Word, long).

Le format se présente comme suit:

/-------------------------------------------------------------------------------------------------------------------------------------------\
|*Prefixes | *Mandatory Prefix | *REX Prefix | Opcode Bytes | *ModR/M | *SIB | *Displacement (1,2 or 4 bytes) | *Immediate (1,2 or 4 bytes) |
\-------------------------------------------------------------------------------------------------------------------------------------------/
* means the element is optional.

Les structures de données et les phases de décodage sont expliquées dans https://code.google.com/p/distorm/wiki/diStorm_Internals

Quote:

Phases de décodage

  1. [Préfixes]
  2. [Récupérer l'opcode]
  3. [Opcode de filtre]
  4. [Extraire opérande (s)]
  5. [Formatage du texte]
  6. [Décharge hexadécimale]
  7. [Instruction décodée]

Chaque étape est également expliquée.


Les liens originaux sont conservés pour des raisons historiques:

http://code.google.com/p/distorm/wiki/x86_x64_Machine_Code et http://code.google.com/p/distorm/wiki/diStorm_Internals

22
hannson

Commencez avec un petit programme qui a été assemblé et qui vous donne à la fois le code généré et les instructions. Obtenez-vous une référence avec architecture d'instruction , et parcourez à la main une partie du code généré avec la référence d'architecture. Vous constaterez que les instructions ont une structure très stéréotypée de inst op op op avec un nombre variable d'opérandes. Tout ce que vous devez faire est de traduire la représentation hexadécimale ou octale du code pour correspondre aux instructions; un peu de jeu le révélera.

Ce processus, automatisé, est au cœur d'un désassembleur. Idéalement, vous allez probablement vouloir construire un tableau n de structures d'instructions en interne (ou en externe, si le programme est vraiment grand). Vous pouvez ensuite traduire ce tableau dans les instructions au format assembleur.

6
Charlie Martin

Vous avez besoin d'une table d'opcodes à charger.

La structure de données de recherche fondamentale est un trie, mais une table fera assez bien si vous ne vous souciez pas beaucoup de la vitesse.

Pour obtenir le type d'opcode de base, commence par une correspondance sur la table.

Il y a quelques manières courantes de décoder les arguments du registre; cependant, il y a suffisamment de cas particuliers pour exiger la mise en œuvre de la plupart d'entre eux individuellement.

Puisque c'est éducatif, jetez un œil à ndisasm.

4
Joshua

Checkout objdump sources - c'est un excellent outil, il contient de nombreuses tables d'opcode et ses sources peuvent fournir une bonne base pour faire votre propre désassembleur.

2