Les opcodes : de l'assembleur aux octets
Le CPU ne lit pas mov ebp, esp mais 89 E5. On assemble à la main la fonction foo de l'animation de la pile, instruction par instruction.
La structure d'une instruction
Une instruction x86 fait de 1 à 15 octets, toujours dans cet ordre, chaque partie étant optionnelle sauf l'opcode :
[préfixes] [opcode 1 à 3 octets] [ModR/M] [SIB] [déplacement] [immédiat]
L'octet ModR/M se découpe en 3 champs : mod (2 bits : registre direct, ou mémoire avec 0, 8 ou 32 bits de déplacement), reg (3 bits : un registre, ou une variante de l'opération) et r/m (3 bits : l'autre opérande).
| Code | 000 | 001 | 010 | 011 | 100 | 101 | 110 | 111 |
|---|---|---|---|---|---|---|---|---|
| Registre | EAX | ECX | EDX | EBX | ESP | EBP | ESI | EDI |
Attention à l'ordre : EBX vaut 3 et ECX vaut 1. D'où push eax = 50, push ecx = 51… push ebp = 55, et pop ebp = 5D (58 + 5).
Little endian
Les valeurs de plusieurs octets sont stockées octet de poids faible en premier. L'adresse 0x08049000 en mémoire :
C'est pour ça qu'en Python on écrit une adresse de retour avec struct.pack("<I", 0x08049000) ou p32() de pwntools, et que 05 00 00 00 vaut 5.
Opcodes à connaître en exploitation
| Octets | Instruction | Intérêt |
|---|---|---|
90 | nop | NOP sled : une piste d'atterrissage avant un shellcode |
CC | int3 | point d'arrêt logiciel, posé par gdb |
CD 80 | int 0x80 | appel système Linux 32 bits |
C3 / C9 | ret / leave | fins de fonction, base des gadgets ROP |
E8 rel32 | call | déplacement relatif, pas d'adresse absolue |
EB rel8 | jmp court | saut de -128 à +127 octets |
31 C0 | xor eax, eax | mettre à zéro sans octet nul |
FF E4 | jmp esp | gadget classique pour sauter dans un shellcode sur la pile |
Pour vérifier : objdump -d -M intel ./prog, rasm2 -a x86 -b 32 "mov ebp, esp", ou asm("mov ebp, esp") dans pwntools.