Objectif du chapitre
Ce chapitre présente les principales instructions utilisées en assembleur x86-64 pour manipuler des nombres à virgule flottante.
Dans les programmes modernes, les calculs sur les
Code: Select all
floatCode: Select all
doubleEn reverse engineering, reconnaître ces instructions permet notamment d'identifier :
- des calculs sur des coordonnées ;
- des calculs de distances ;
- des calculs graphiques ;
- des transformations de vecteurs ;
- des conversions entier ↔ flottant ;
- des comparaisons de valeurs flottantes ;
- des calculs scientifiques ou physiques.
Les premiers processeurs x86 utilisaient un coprocesseur spécialisé appelé
Code: Select all
8087Par la suite, les instructions x87 ont été intégrées directement aux processeurs.
Les processeurs modernes utilisent cependant principalement :
- ;
Code: Select all
SSE - ;
Code: Select all
SSE2 - et versions suivantes ;
Code: Select all
SSE3 - ;
Code: Select all
AVX - .
Code: Select all
AVX2
Code: Select all
XMMAVX étend ensuite leur taille avec les registres
Code: Select all
YMMIdée importante
Les anciennes instructions x87 existent toujours, mais dans du code C/C++ moderne en x86-64, on rencontre très souvent les registres XMM et les instructions SSE/AVX.
2. Les registres XMM
En x86-64, on dispose généralement des registres :
Code: Select all
xmm0
xmm1
xmm2
...
xmm15
Code: Select all
128 bitsIl peut contenir plusieurs types de données.
Par exemple :
Code: Select all
128 bits = 16 octets
- 4 nombres de 32 bits ;
Code: Select all
float - 2 nombres de 64 bits ;
Code: Select all
double - plusieurs entiers de différentes tailles.
Code: Select all
xmm0 = [ 1.0 | 2.0 | 3.0 | 4.0 ]
Code: Select all
xmm0Code: Select all
floatAvec deux
Code: Select all
doubleCode: Select all
xmm0 = [ 10.5 | 25.75 ]
Une instruction scalaire travaille sur une seule valeur du registre.
Une instruction packed travaille sur plusieurs valeurs en parallèle.
Exemple :
Code: Select all
addss xmm0, xmm1
Code: Select all
floatAlors que :
Code: Select all
addps xmm0, xmm1
Code: Select all
float3. Les registres YMM et AVX
AVX introduit les registres
Code: Select all
YMMUn registre YMM peut donc contenir :
- 8 ;
Code: Select all
float - 4 .
Code: Select all
double
Code: Select all
ymm0 = [f0 | f1 | f2 | f3 | f4 | f5 | f6 | f7]
Conceptuellement :
Code: Select all
ymm0
├── partie basse : xmm0
└── partie haute : 128 bits supplémentaires
Code: Select all
vExemple :
Code: Select all
vaddps ymm0, ymm1, ymm2
Code: Select all
ymm0 = ymm1 + ymm2
Les instructions les plus importantes à reconnaître sont :
Code: Select all
movss
movsd
movaps
movups
movapd
movupd
Code: Select all
movssCode: Select all
move scalar single precision
Code: Select all
floatExemple :
Code: Select all
movss xmm0, [rax]
Code: Select all
xmm0 = float situé à l'adresse rax
Code: Select all
movss [rbx], xmm0
Code: Select all
stocke le float de xmm0 à l'adresse rbx
Code: Select all
movsdCode: Select all
doubleExemple :
Code: Select all
movsd xmm0, [rax]
Code: Select all
doubleCode: Select all
xmm04.3 Données packed
Pour plusieurs valeurs en parallèle, on rencontre :
Code: Select all
movaps
movups
movapd
movupd
- = packed single precision = plusieurs
Code: Select all
ps;Code: Select all
float - = packed double precision = plusieurs
Code: Select all
pd.Code: Select all
double
Code: Select all
aCode: Select all
u- = aligned ;
Code: Select all
a - = unaligned.
Code: Select all
u
Code: Select all
movaps xmm0, [rax]
movups xmm1, [rbx]
movapd xmm2, [rcx]
movupd xmm3, [rdx]
Une adresse alignée sur 16 octets respecte typiquement :
Code: Select all
adresse % 16 == 0
5. Comprendre les suffixes
Les suffixes SSE sont extrêmement utiles en reverse engineering.
Code: Select all
ss = Scalar Single
sd = Scalar Double
ps = Packed Single
pd = Packed Double
Code: Select all
addss
Code: Select all
floatCode: Select all
addsd
Code: Select all
doubleCode: Select all
addps
Code: Select all
floatCode: Select all
addpd
Code: Select all
doubleRaccourci mental
Quand tu vois :
Code: Select all
ss
Code: Select all
1 float
Code: Select all
sd
Code: Select all
1 double
Code: Select all
ps
Code: Select all
plusieurs float
Code: Select all
pd
Code: Select all
plusieurs double
Les principales instructions sont :
Code: Select all
addss
addsd
addps
addpd
Code: Select all
movss xmm0, [a]
addss xmm0, [b]
Code: Select all
xmm0 = a + b
Code: Select all
doubleCode: Select all
movsd xmm0, [a]
addsd xmm0, [b]
Code: Select all
floatCode: Select all
movups xmm0, [a]
addps xmm0, [b]
Code: Select all
a = [1, 2, 3, 4]
b = [10, 20, 30, 40]
Code: Select all
xmm0 = [11, 22, 33, 44]
7. Soustraction
Les instructions correspondantes sont :
Code: Select all
subss
subsd
subps
subpd
Code: Select all
movss xmm0, [a]
subss xmm0, [b]
Code: Select all
xmm0 = a - b
Code: Select all
subss xmm0, xmm1
Code: Select all
xmm0 = xmm0 - xmm1
8. Multiplication
Les instructions principales sont :
Code: Select all
mulss
mulsd
mulps
mulpd
Code: Select all
movss xmm0, [a]
mulss xmm0, [b]
Code: Select all
xmm0 = a * b
Code: Select all
mulps xmm0, xmm1
Code: Select all
floatCode: Select all
xmm0Code: Select all
xmm1Exemple :
Code: Select all
xmm0 = [2, 3, 4, 5]
xmm1 = [10, 20, 30, 40]
Code: Select all
xmm0 = [20, 60, 120, 200]
Les variantes sont :
Code: Select all
divss
divsd
divps
divpd
Code: Select all
movss xmm0, [a]
divss xmm0, [b]
Code: Select all
xmm0 = a / b
Code: Select all
sub10. Tableau récapitulatif des opérations
Code: Select all
addss addition scalaire float
addsd addition scalaire double
addps addition packed float
addpd addition packed double
subss soustraction scalaire float
subsd soustraction scalaire double
subps soustraction packed float
subpd soustraction packed double
mulss multiplication scalaire float
mulsd multiplication scalaire double
mulps multiplication packed float
mulpd multiplication packed double
divss division scalaire float
divsd division scalaire double
divps division packed float
divpd division packed double
Il est fréquent de convertir un type flottant vers un autre.
Par exemple :
Code: Select all
cvtss2sd xmm0, xmm1
Code: Select all
floatCode: Select all
doubleConceptuellement :
Code: Select all
double d = (double)f;
Code: Select all
cvtsd2ss xmm0, xmm1
Code: Select all
float f = (float)d;
Les programmes font également beaucoup de conversions entre entiers et nombres flottants.
Exemples courants :
Code: Select all
cvtsi2ss
cvtsi2sd
cvtss2si
cvtsd2si
cvttss2si
cvttsd2si
Exemple :
Code: Select all
cvtsi2ss xmm0, eax
Code: Select all
eax = 10
Code: Select all
xmm0 = 10.0f
Code: Select all
cvtsi2sd xmm0, rax
Code: Select all
raxCode: Select all
double12.3 Float vers entier
Code: Select all
cvtss2si eax, xmm0
Code: Select all
floatCode: Select all
xmm012.4 Troncature
Les instructions commençant par
Code: Select all
cvttExemple :
Code: Select all
cvttss2si eax, xmm0
Code: Select all
xmm0 = 12.9
Code: Select all
eax = 12
13. Comparaison des nombres flottants
On ne compare pas toujours les nombres flottants avec un simple
Code: Select all
cmpLes instructions SSE importantes sont notamment :
Code: Select all
comiss
ucomiss
comisd
ucomisd
Code: Select all
ucomiss xmm0, xmm1
Code: Select all
floatEnsuite, le programme utilise souvent un saut conditionnel.
Exemple :
Code: Select all
ucomiss xmm0, xmm1
ja greater
Code: Select all
ucomiss xmm0, xmm1
jbe less_or_equal
Les comparaisons flottantes peuvent modifier notamment :
Code: Select all
ZF
PF
CF
Code: Select all
ja
jae
jb
jbe
je
jne
Code: Select all
ucomiss xmm0, xmm1
ja bigger
Code: Select all
if (a > b)
...
Quand tu vois :
Code: Select all
ucomiss
comiss
ucomisd
comisd
C'est généralement elle qui révèle la condition C/C++.
14. NaN
IEEE 754 définit des valeurs particulières appelées :
Code: Select all
NaN
Code: Select all
Not a Number
Exemples :
- certaines opérations invalides ;
- résultats indéfinis ;
- propagation d'une valeur NaN existante.
Code: Select all
QNaN
SNaN
C'est notamment pour cela que certaines comparaisons flottantes utilisent également le flag
Code: Select all
PF15. Minimum et maximum
SSE fournit des instructions permettant d'obtenir directement un minimum ou un maximum.
Exemples :
Code: Select all
minss
maxss
minsd
maxsd
minps
maxps
minpd
maxpd
Code: Select all
maxss xmm0, xmm1
Code: Select all
xmm0 = max(xmm0, xmm1)
Code: Select all
xmm0 = [1, 50, 3, 80]
xmm1 = [10, 20, 30, 40]
maxps xmm0, xmm1
Code: Select all
xmm0 = [10, 50, 30, 80]
16. Arrondis
Certaines instructions SSE permettent d'arrondir des valeurs flottantes.
Exemples :
Code: Select all
roundss
roundsd
roundps
roundpd
- arrondi au plus proche ;
- arrondi vers le haut ;
- arrondi vers le bas ;
- troncature vers zéro.
Code: Select all
1.5 -> 2
1.45 -> 1
1.99 -> 2
17. Racine carrée
SSE possède également des instructions de racine carrée.
Par exemple :
Code: Select all
sqrtss
sqrtsd
sqrtps
sqrtpd
Code: Select all
sqrtss xmm0, xmm1
Code: Select all
xmm0 = sqrt(xmm1)
18. Exemple : distance entre deux points 3D
Pour deux points :
Code: Select all
A = (x1, y1, z1)
B = (x2, y2, z2)
Code: Select all
distance = sqrt(
(x2 - x1)^2 +
(y2 - y1)^2 +
(z2 - z1)^2
)
Code: Select all
movss
subss
mulss
movss
subss
mulss
addss
movss
subss
mulss
addss
sqrtss
En reverse engineering, une succession :
Code: Select all
sub
mul
add
sqrt
19. Exemple : produit scalaire
Le produit scalaire de deux vecteurs 3D :
Code: Select all
A = (x1, y1, z1)
B = (x2, y2, z2)
Code: Select all
x1*x2 + y1*y2 + z1*z2
Code: Select all
mulss
addss
Code: Select all
result =
ax * bx +
ay * by +
az * bz;
- jeux vidéo ;
- moteurs 3D ;
- physique ;
- calculs géométriques ;
- IA ;
- traitement du signal.
Un polynôme :
Code: Select all
P(x) = a*x² + b*x + c
Une méthode très fréquente est la méthode de Horner :
Code: Select all
P(x) = (a*x + b)*x + c
Code: Select all
mulss
addss
mulss
addss
Code: Select all
sdCode: Select all
double21. AVX : syntaxe à trois opérandes
Une différence importante entre SSE classique et AVX est la syntaxe.
SSE :
Code: Select all
addps xmm0, xmm1
Code: Select all
xmm0 = xmm0 + xmm1
Avec AVX :
Code: Select all
vaddps ymm0, ymm1, ymm2
Code: Select all
ymm0 = ymm1 + ymm2
Cela simplifie souvent le code.
22. Reconnaître un float dans un débogueur
Un
Code: Select all
floatUn
Code: Select all
doubleDans x64dbg, WinDbg ou un autre débogueur, une suite d'octets peut représenter un entier ou un flottant.
Exemple :
Code: Select all
3F 80 00 00
Code: Select all
1.0f
Quand tu vois qu'une adresse mémoire est utilisée par :
Code: Select all
movss
addss
mulss
ucomiss
Code: Select all
floatDe même, si elle est utilisée par :
Code: Select all
movsd
addsd
mulsd
ucomisd
Code: Select all
double23. Reconnaître des structures contenant des floats
Supposons :
Code: Select all
struct Position
{
float x;
float y;
float z;
};
Code: Select all
offset +0 -> x
offset +4 -> y
offset +8 -> z
Code: Select all
movss xmm0, [rcx]
movss xmm1, [rcx+4]
movss xmm2, [rcx+8]
Code: Select all
floatC'est extrêmement utile en reverse engineering.
24. Exemple concret de reverse
Supposons que tu trouves :
Code: Select all
movss xmm0, dword ptr [rcx]
subss xmm0, dword ptr [rdx]
mulss xmm0, xmm0
movss xmm1, dword ptr [rcx+4]
subss xmm1, dword ptr [rdx+4]
mulss xmm1, xmm1
addss xmm0, xmm1
sqrtss xmm0, xmm0
Code: Select all
float dx = *(float*)(rcx) - *(float*)(rdx);
float dy = *(float*)(rcx + 4) - *(float*)(rdx + 4);
float result = sqrt(dx*dx + dy*dy);
C'est exactement le type de pattern qu'il faut apprendre à reconnaître en reverse.
25. Ce qu'il faut absolument retenir
Pour le reverse engineering, retiens surtout :
- servent très souvent aux
Code: Select all
xmm0-xmm15etCode: Select all
float;Code: Select all
double - = un float ;
Code: Select all
ss - = un double ;
Code: Select all
sd - = plusieurs floats ;
Code: Select all
ps - = plusieurs doubles ;
Code: Select all
pd - /
Code: Select all
movsschargent ou stockent des flottants ;Code: Select all
movsd - /
Code: Select all
addssadditionnent ;Code: Select all
addsd - /
Code: Select all
subsssoustraient ;Code: Select all
subsd - /
Code: Select all
mulssmultiplient ;Code: Select all
mulsd - /
Code: Select all
divssdivisent ;Code: Select all
divsd - /
Code: Select all
cvtsi2ssconvertissent entier → flottant ;Code: Select all
cvtsi2sd - /
Code: Select all
cvtss2siconvertissent flottant → entier ;Code: Select all
cvtsd2si - /
Code: Select all
ucomisscomparent des flottants ;Code: Select all
ucomisd - /
Code: Select all
sqrtsscalculent une racine carrée ;Code: Select all
sqrtsd - une suite ressemble souvent à un calcul de distance ;
Code: Select all
sub + mul + add + sqrt - plusieurs offsets ,
Code: Select all
+0,Code: Select all
+4avecCode: Select all
+8peuvent révéler une structure de vecteur 3D.Code: Select all
movss
Code: Select all
movss -> déplace 1 float
movsd -> déplace 1 double
addss -> float +
subss -> float -
mulss -> float *
divss -> float /
addsd -> double +
subsd -> double -
mulsd -> double *
divsd -> double /
ucomiss -> compare des float
ucomisd -> compare des double
cvtsi2ss -> int vers float
cvtsi2sd -> int vers double
cvtss2si -> float vers int
cvtsd2si -> double vers int
sqrtss -> racine carrée float
sqrtsd -> racine carrée double
Si tu vois beaucoup de :
Code: Select all
movss
mulss
addss
subss
Code: Select all
floatSi tu vois :
Code: Select all
movsd
mulsd
addsd
subsd
Code: Select all
doubleEt si tu vois :
Code: Select all
addps
mulps
subps
Code: Select all
float