recherche de vulnérabilités / 3. les opcodes

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.

Encodage d'une instruction L'instruction assembleur, ses octets colorés par rôle, et le détail de l'octet ModR/M.

opcodeModR/Mdéplacementvaleur immédiate

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).

Code000001010011100101110111
RegistreEAXECXEDXEBXESPEBPESIEDI

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 :

Little endian0x08049000 est rangé 00 90 04 08 aux adresses croissantes. adresse on lit la valeur « à l'envers » : 08 04 90 00

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

OctetsInstructionIntérêt
90nopNOP sled : une piste d'atterrissage avant un shellcode
CCint3point d'arrêt logiciel, posé par gdb
CD 80int 0x80appel système Linux 32 bits
C3 / C9ret / leavefins de fonction, base des gadgets ROP
E8 rel32calldéplacement relatif, pas d'adresse absolue
EB rel8jmp courtsaut de -128 à +127 octets
31 C0xor eax, eaxmettre à zéro sans octet nul
FF E4jmp espgadget 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.

← 2. Les registres4. Le format ELF →