Opcodes de fichiers PE

Je suis en train d’écrire un parsingur de fichiers PE et j’ai atteint le point où j’aimerais parsingr et interpréter le code réel dans les fichiers PE, ce qui est supposé être stocké sous la forme d’opcodes x86.

Par exemple, chacune des exportations au sein d’une DLL pointe vers les RVA (Relative Virtual Offsets) où la fonction sera stockée dans la mémoire et j’ai écrit une fonction pour convertir ces RVA en décalages de fichiers physiques.

La question est, est-ce que ce sont vraiment des opcodes, ou est-ce autre chose?

Est-ce que cela dépend du compilateur / éditeur de liens pour savoir comment les fonctions sont stockées dans le fichier, ou sont-elles un ou deux octets X86 des octets.

Par exemple, la DLL Windows 7 «BWContextHandler.dll» contient quatre fonctions chargées en mémoire, ce qui les rend disponibles dans le système. La première fonction exscope est ‘DllCanUnloadNow’ et se trouve à l’offset 0x245D dans le fichier. Les quatre premiers octets de ces données sont: 0xA1 0x5C 0xF1 0xF2

Donc, ces opcodes à un ou deux octets, ou sont-ils tout à fait autre chose?

Si quelqu’un pouvait fournir des informations sur la façon de les examiner, cela serait apprécié.

Merci!

Après quelques lectures et exécution du fichier via la version de démonstration d’IDA, je pense que j’ai raison de dire que le premier octet 0xA1 est un opcode d’un octet, ce qui signifie mov eax. Je l’ai eu ici: http://ref.x86asm.net/geek32.html#xA1 et je suppose que c’est correct pour le moment.

Cependant, je suis un peu confus quant à la façon dont les octets suivants constituent le rest de l’instruction. À partir de l’assembleur x86 que je connais, une instruction de déplacement nécessite deux parameters, la destination et la source. L’instruction consiste donc à déplacer (quelque chose) dans le registre eax, et je suppose que quelque chose vient dans les octets suivants. Cependant je ne sais pas encore lire cette information 🙂

L’encodage x86 est un encodage complexe à plusieurs octets et vous ne pouvez pas simplement trouver une seule ligne dans la table d’instructions pour la décoder telle qu’elle était dans RISC (MIPS / SPARC / DLX). Il peut y avoir même des codages de 16 octets d’une instruction: opcode de 1 à 3 octets + plusieurs préfixes (y compris VEX multi-octets ) + plusieurs champs pour coder l’adresse immédiate ou mémoire, offset, mise à l’échelle (imm, ModR / M et SIB; moffs). Et il y a parfois des dizaines d’opcodes pour une mnémonique unique. Et plus encore, pour plusieurs cas, il existe deux encodages possibles de la même ligne asm (“inc eax” = 0x40 et = 0xff 0xc0).

un opcode d’un octet, ce qui signifie mov eax. Je l’ai eu ici: http://ref.x86asm.net/geek32.html#xA1 et je suppose que c’est correct pour le moment.

Regardons la table:

po; flds; mnémonique; op1; op2; grp1; grp2; La description

A1; W; MOV; eAX; Ov; gen; datamov; Bouge toi ;

(CONSEIL: n’utilisez pas la table geek32, passez à http://ref.x86asm.net/coder32.html#xA1 – contient moins de champs avec plus de décodage, par exemple “A1 MOV eAX moffs16 / 32 Move”)

Il y a des colonnes op1 et op2, http://ref.x86asm.net/#column_op pour les opérandes. Le premier pour l’opcode A1 est toujours eAX et le second (op2) est Ov. Selon le tableau http://ref.x86asm.net/#Instruction-Operand-Codes :

O / moffs Original L’instruction n’a pas d’octet ModR / M; le décalage de l’opérande est codé sous la forme d’un mot, d’un mot double ou d’un mot quadruple (en fonction de l’atsortingbut de taille d’adresse) dans l’instruction. Aucun registre de base, registre d’index ou facteur de mise à l’échelle ne peut être appliqué (uniquement MOV (A0, A1, A2, A3)).

Ainsi, après A1 opcode, le décalage de la mémoire est codé. Je pense qu’il y a un décalage de 32 bits pour x86 (mode 32 bits).

PS: Si votre tâche est d’parsingr PE et de ne pas inventer le désassembleur, utilisez une bibliothèque de désassemblage de x86 comme libdisasm ou libudis86 ou toute autre chose.

PPS: Pour la question originale:

La question est, est-ce que ce sont vraiment des opcodes, ou est-ce autre chose?

Oui, “A1 5C F1 F2 05 B9 5C F1 F2 05 FF 50 0C F7 D8 1B C0 F7 D8 C3 CC CC CC CC” est le code machine x86.

Le désassemblage est difficile, en particulier pour le code généré par le compilateur Visual Studio, en particulier pour les programmes x86. Il y a plusieurs problèmes:

  1. Les instructions sont de longueur variable et peuvent commencer à n’importe quel décalage. Certaines architectures nécessitent un alignement des instructions. Pas x86. Si vous commencez à lire à l’adresse 0, vous obtiendrez alors des résultats différents si vous commencez à lire au décalage 1. Vous devez savoir quels sont les “points de départ” (points d’entrée de fonction) valides.

  2. Toutes les adresses dans la section de texte d’un exécutable ne sont pas du code. Certaines sont des données. Visual Studio placera des “tables de saut” (tableaux utilisés pour implémenter les instructions switch) dans la section de texte sous la procédure qui les lit. Une mauvaise interprétation des données sous forme de code vous amènera à produire un désassemblage incorrect.

  3. Vous ne pouvez pas avoir un désassemblage parfait qui fonctionnera avec tous les programmes possibles. Les programmes peuvent se modifier eux-mêmes. Dans ces cas, vous devez exécuter le programme pour savoir ce qu’il fait, ce qui aboutit au «problème d’arrêt». Le mieux que vous puissiez espérer est le désassemblage qui fonctionne sur “la plupart” des programmes.

L’algorithme généralement utilisé pour tenter de résoudre ces problèmes s’appelle le désassemblage “descente récursive”. Il fonctionne de manière similaire à un parsingur de descente récursif, en ce sens qu’il commence par un “point d’entrée” connu (soit la méthode “principale” d’un exe, soit toutes les exportations d’une DLL), puis commence à se désassembler. D’autres points d’entrée sont découverts lors du désassemblage. Par exemple, étant donné une instruction “call”, la cible sera supposée être un point d’entrée. Le désassembleur démontera de manière itérative les points d’entrée découverts jusqu’à ce qu’aucun autre ne soit trouvé.

Cette technique a cependant quelques problèmes. Il ne trouvera pas le code qui est seulement exécuté par indirection. Sur Windows, les gestionnaires des exceptions SEH en sont un bon exemple. Le code qui leur est envoyé est en fait à l’intérieur du système d’exploitation, donc le désassemblage récursif ne les trouvera pas et ne les démontera pas. Cependant, ils peuvent souvent être détectés en augmentant la descente récursive avec reconnaissance de forme (correspondance heuristique).

L’apprentissage automatique peut être utilisé pour identifier automatiquement les modèles, mais de nombreux désassembleurs (comme IDA pro) utilisent des modèles écrits à la main avec beaucoup de succès.

Dans tous les cas, si vous souhaitez démonter le code x86, vous devez lire le manuel Intel . De nombreux scénarios doivent être pris en charge. Les mêmes modèles de bits dans une instruction peuvent être interprétés de différentes manières en fonction des modificateurs, des préfixes, de l’état implicite du processeur, etc. Tout cela est traité dans le manuel. Commencez par lire les premières sections du volume I. Cela parcourra l’environnement d’exécution de base. La plupart des autres éléments dont vous avez besoin se trouvent dans le volume II.