Calculateur assembleur JMP E9 / FF – calcul d’offset x86
Calculez rapidement le déplacement relatif d’un saut assembleur, générez les octets little-endian, vérifiez la plage signée et comprenez la différence fondamentale entre E9, EB et FF /4.
Guide expert: comprendre le calcul d’offset pour assembleur JMP E9 FF
Quand on recherche assembleur jmp e9 ff calcul offset, on tombe souvent sur deux besoins très concrets: d’une part calculer correctement le déplacement relatif d’un saut machine pour l’opcode E9, et d’autre part comprendre pourquoi le cas FF ne se traite pas avec la même formule. Cette différence est capitale en reverse engineering, en création de patchs binaires, en instrumentation de code et en développement de loaders ou de trampolines. Une erreur de quelques octets suffit à provoquer un crash, un saut dans une zone invalide, ou un comportement impossible à déboguer. Le but de cette page est de rendre ce calcul fiable, rapide et compréhensible.
Sur architecture x86, l’instruction JMP existe sous plusieurs encodages. Le plus connu pour les modifications binaires est E9, qui représente un saut relatif near. Cela signifie que l’opérande n’est pas une adresse absolue stockée telle quelle, mais un déplacement signé calculé par rapport à l’adresse de l’instruction suivante. C’est exactement ce point qui piège les débutants: on ne fait pas destination – source, mais destination – (source + longueur_instruction).
La formule correcte pour E9
Pour un saut E9 rel32, la formule standard est:
offset = adresse_cible – (adresse_jmp + 5)
Pourquoi 5? Parce que l’instruction fait 1 octet d’opcode plus 4 octets de déplacement signé. Le processeur ajoute d’abord la longueur de l’instruction au pointeur d’instruction, puis applique le déplacement. Pour un cas réel, si le JMP commence à 0x401000 et que la cible est 0x401250, alors:
- Adresse de l’instruction suivante: 0x401000 + 5 = 0x401005
- Déplacement: 0x401250 – 0x401005 = 0x24B
- Octets machine: E9 4B 02 00 00
Les 4 octets de déplacement sont écrits en little-endian. C’est un autre point important. La valeur logique est 0x0000024B, mais les octets sont stockés sous la forme 4B 02 00 00.
Que signifie FF dans un JMP x86?
L’opcode FF couvre plusieurs instructions selon le champ ModR/M. Dans le cas FF /4, on parle d’un JMP near absolu indirect. En clair, le processeur ne lit pas un déplacement immédiat placé après l’opcode, mais va chercher la destination dans un registre ou en mémoire. Par exemple:
- jmp eax
- jmp dword ptr [0x00402000]
- jmp qword ptr [rip+disp32] en x86-64 selon le contexte d’assemblage
Dans ce cas, il n’existe pas d’offset immédiat équivalent à celui de E9. C’est pourquoi un calculateur sérieux doit distinguer ces familles d’encodage. Si vous avez besoin d’écrire un patch de 5 octets avec un saut direct, E9 rel32 est généralement l’outil adapté. Si vous utilisez FF /4, vous êtes dans une logique d’indirection, souvent choisie pour contourner des limites de portée ou pour sauter vers une destination résolue dynamiquement.
| Encodage | Taille typique | Type d’adressage | Plage signée / portée | Usage principal |
|---|---|---|---|---|
| E9 rel32 | 5 octets | Relatif | -2 147 483 648 à +2 147 483 647 | Patching, detours, trampolines, saut inter-fonctions dans la même portée 32 bits |
| E9 rel16 | 3 octets | Relatif | -32 768 à +32 767 | Cas historiques ou contextes spécifiques 16 bits |
| EB rel8 | 2 octets | Relatif | -128 à +127 | Sauts courts, optimisation de taille |
| FF /4 | Variable | Absolu indirect | Pas d’offset immédiat direct | Appui sur registre ou mémoire pour la cible |
Statistiques pratiques qui comptent vraiment
Les chiffres de portée ci-dessus ne sont pas théoriques au sens vague du terme: ce sont les bornes exactes imposées par la taille du champ signé. En pratique, cela a des conséquences immédiates sur les stratégies de hooking. Un EB rel8 n’offre qu’une fenêtre de 256 valeurs possibles, soit 128 en arrière et 127 en avant si l’on raisonne en signé. Un E9 rel32, lui, ouvre une portée de plus de 4,29 milliards de positions possibles au total. Cet écart colossal explique pourquoi E9 est la solution dominante pour les redirections dans les binaires 32 bits et pour de nombreux patchs 64 bits quand la cible reste dans une portée compatible.
Autre donnée importante: la taille occupée par l’instruction. En instrumentation, chaque octet compte. Remplacer un prologue par un E9 suppose en général d’avoir au moins 5 octets sûrs à écraser. Si le bloc à détourner ne fournit que 2 ou 3 octets avant une frontière critique, il faut désassembler précisément, déplacer des instructions complètes et construire un trampoline. Les outils de hooking robustes sont justement conçus pour garantir cette sécurité.
Exemples concrets de calcul d’offset
Voici plusieurs exemples pour éviter les confusions les plus fréquentes:
- Saut en avant avec E9 rel32
Source: 0x1000
Cible: 0x1100
Instruction suivante: 0x1005
Offset: 0x1100 – 0x1005 = 0xFB
Octets: E9 FB 00 00 00 - Saut en arrière avec E9 rel32
Source: 0x2000
Cible: 0x1F00
Instruction suivante: 0x2005
Offset: 0x1F00 – 0x2005 = -0x105
Le déplacement est encodé en complément à deux sur 32 bits. - Saut court EB rel8
Source: 0x3000
Cible: 0x3070
Instruction suivante: 0x3002
Offset: 0x6E
Valide car compris entre -128 et +127. - Cas FF /4
jmp dword ptr [0x00402000]
Ici, la cible n’est pas déduite via destination – (source + longueur). Le processeur lit la valeur stockée à l’adresse mémoire indiquée ou dans le registre visé.
| Scénario | Adresse JMP | Adresse suivante | Cible | Offset calculé | Encodage type |
|---|---|---|---|---|---|
| Patch forward | 0x401000 | 0x401005 | 0x401250 | +0x24B | E9 4B 02 00 00 |
| Retour arrière | 0x402000 | 0x402005 | 0x401F20 | -0xE5 | E9 1B FF FF FF |
| Short jump valide | 0x5000 | 0x5002 | 0x5070 | +0x6E | EB 6E |
| Short jump invalide | 0x5000 | 0x5002 | 0x5200 | +0x1FE | Hors plage rel8 |
Pourquoi les erreurs arrivent si souvent
La première cause d’erreur est l’oubli de la longueur de l’instruction. Beaucoup de personnes calculent cible – source alors que le processeur raisonne depuis l’adresse suivante. La deuxième cause est l’endianness. Une valeur hexadécimale juste peut produire des octets faux si l’ordre little-endian n’est pas respecté. La troisième cause est le mauvais choix d’encodage: on tente parfois un EB alors que la cible est hors plage, ou l’on croit qu’un FF se calcule comme un E9. Enfin, sur x86-64, la confusion entre adresse absolue, relative et indirecte via mémoire est encore plus fréquente.
Méthode fiable pour patcher un JMP E9
- Identifiez l’adresse exacte du premier octet de l’instruction remplacée.
- Déterminez la longueur réelle du saut encodé, en général 5 octets pour E9 rel32.
- Calculez l’adresse de l’instruction suivante.
- Soustrayez cette adresse de la cible.
- Vérifiez que le résultat tient dans la taille signée voulue.
- Encodez en little-endian.
- Contrôlez au désassembleur que la destination reconstruite correspond bien à la cible attendue.
Quand choisir E9 plutôt que FF /4
Choisissez E9 quand vous voulez un saut direct compact, simple à calculer, stable et immédiat. C’est le meilleur choix pour de nombreux détournements de flux de contrôle. Choisissez FF /4 quand vous avez besoin d’une destination indirecte, par exemple stockée dans une table, calculée dynamiquement, ou chargée dans un registre. Dans certains systèmes de hooking avancés, l’indirection est utile pour gérer des cibles variables ou pour contourner certaines contraintes de portée.
Références académiques et techniques utiles
Pour approfondir la sémantique des instructions x86, vous pouvez consulter des ressources universitaires sérieuses comme le guide x86-64 de Stanford, le support d’introduction x86 de l’Université de Virginie, et des glossaires techniques gouvernementaux pour le vocabulaire machine code:
- Stanford University – x86-64 Guide
- University of Virginia – Guide to x86 Assembly
- NIST – Définition de machine code
Bonnes pratiques finales
Si votre objectif est de calculer un offset assembleur JMP E9, retenez la règle d’or: destination – (adresse_instruction + taille_instruction). Si votre objectif concerne FF /4, ne cherchez pas un offset immédiat qui n’existe pas; analysez plutôt le registre ou l’opérande mémoire utilisé comme source de destination. En reverse engineering, cette distinction fait gagner un temps considérable. Elle permet aussi d’éviter des patchs instables, particulièrement dans les environnements où l’ASLR, les trampolines, les loaders personnalisés ou les mécanismes anti-tamper rendent les erreurs d’encodage très coûteuses.
Le calculateur ci-dessus automatise précisément cette logique. Il vérifie la plage selon le type de saut, affiche les octets attendus et rappelle le statut de validité. Vous pouvez vous en servir comme aide de vérification rapide avant d’écrire vos patchs, de documenter un rapport d’analyse binaire ou de préparer un exercice d’assembleur avancé.