Quelqu’un peut-il expliquer le sens de malloc (20 * c | – (20 * (unsigned __int64) (unsigned int) c >> 32! = 0))

Dans le code décompilé généré par IDA, je vois des expressions telles que:

malloc(20 * c | -(20 * (unsigned __int64)(unsigned int)c >> 32 != 0)) malloc(6 * n | -(3 * (unsigned __int64)(unsigned int)(2 * n) >> 32 != 0)) 

Quelqu’un peut-il expliquer le but de ces calculs?
c et n sont des valeurs int (entier signé).

Mettre à jour.
Le code C ++ d’origine a été compilé avec MSVC pour la plate-forme 32 bits.
Voici le code d’assemblage pour la deuxième ligne de code C décompilé ci-dessus (malloc (6 * ..)):

 mov ecx, [ebp+pThis] mov [ecx+4], eax mov eax, [ebp+pThis] mov eax, [eax] shl eax, 1 xor ecx, ecx mov edx, 3 mul edx seto cl neg ecx or ecx, eax mov esi, esp push ecx ; Size call dword ptr ds:__imp__malloc 

Je suppose que le code source original a utilisé l’opérateur new C ++ pour allouer un tableau et a été compilé avec Visual C ++. Comme le dit la réponse de user3528438, ce code est destiné à empêcher les débordements. Plus précisément, il s’agit d’une multiplication saturante non signée 32 bits. Si le résultat de la multiplication est supérieur à 4 294 967 295, valeur maximale d’un nombre non signé de 32 bits, le résultat est verrouillé ou “saturé” à ce maximum.

Depuis Visual Studio 2005, le compilateur C ++ de Microsoft a généré du code destiné à protéger contre les débordements . Par exemple, je peux générer du code d’assembly qui pourrait être décompilé dans vos exemples en compilant ce qui suit avec Visual C ++:

 #include  void * operator new[](size_t n) { return malloc(n); } struct S { char a[20]; }; struct T { char a[6]; }; void foo(int n, S **s, T **t) { *s = new S[n]; *t = new T[n * 2]; } 

Qui, avec le compilateur de Visual Studio 2015, génère le code d’assembly suivant:

  mov esi, DWORD PTR _n$[esp] xor ecx, ecx mov eax, esi mov edx, 20 ; 00000014H mul edx seto cl neg ecx or ecx, eax push ecx call _malloc mov ecx, DWORD PTR _s$[esp+4] ; Line 19 mov edx, 6 mov DWORD PTR [ecx], eax xor ecx, ecx lea eax, DWORD PTR [esi+esi] mul edx seto cl neg ecx or ecx, eax push ecx call _malloc 

La plupart de l’expression décompilée est en réalité destinée à gérer une seule instruction d’assembly. L’instruction d’assemblage seto cl CL sur 1 si l’instruction MUL précédente déborde, sinon elle définit CL sur 0. De même, l’expression 20 * (unsigned __int64)(unsigned int)c >> 32 != 0 évaluée à 1 si le résultat de 20 * c déborde et est évalué à 0 sinon.

Si cette protection de dépassement de capacité n’existait pas et que le résultat de 20 * c dépassait réellement l’appel, l’appel à malloc réussirait probablement, mais allouait beaucoup moins de mémoire que le programme le prévoyait. Le programme pourrait alors écrire au-delà de la fin de la mémoire effectivement allouée et supprimer d’autres bits de la mémoire. Cela équivaudrait à un dépassement de mémoire tampon, une possibilité qui pourrait être exploitée par des pirates.

Comme ce code est décompilé à partir d’ASM, nous ne pouvons donc que deviner ce qu’il fait.

Commençons par le formater afin de déterminer la priorité:

 malloc(20 * c | -(20 * (unsigned __int64)(unsigned int)c >> 32 != 0)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //this is first evaluated, promoting c to //64 bit unsigned int without doing sign //extension, regardless the type of c malloc(20 * c | -(20 * (uint64_t)c >> 32 != 0)) ^^^^^^^^^^^^^^^^ //then, multiply by 20, with uint64 result malloc(20 * c | -(20 * (uint64_t)c >> 32 != 0)) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ //if 20c is greater than 2^32-1, then result is true, //use -1 to generate a mask of 0xffffffff, //bitwise operator | then masks 20c to 0xffffffff //(2^32-1, the maximum of size_t, input type to malloc) //regardless what 20c actually is //if 20c is smaller than 2^32-1, then result is false, //the mask is 0, bitwise operator | keeps the final //input to malloc as 20c untouched 

Que sont 20 et 6?

Celles-ci proviennent probablement de l’usage courant de malloc(sizeof(Something)*count) . Ces deux appels à malloc sont probablement passés avec sizeof(Something) et sizeof(SomethingElse) évalués à 20 et 6 au moment de la compilation.

Alors qu’est-ce que ce code fait réellement:

À mon avis, il s’efforce d’empêcher que sizeof(Something)*count ne déborde, ce qui provoque le succès du malloc et provoque un dépassement de la mémoire tampon lorsque la mémoire est utilisée.

En évaluant le produit en 64 bits unsigned int et en testant contre 2^32-1 , lorsque la taille est supérieure à 2^32-1 , l’entrée dans malloc est définie sur une très grande valeur qui en garantit l’échec (No 32 bit le système peut allouer 2 ^ 32-1 octets de mémoire).

Quelqu’un peut-il expliquer le but de ces calculs?

Il est important de comprendre que la compilation change la signification sémantique du code. Le comportement non spécifié du code d’origine est spécifié par le processus de compilation.

L’IDA n’a aucune idée de l’importance ou non du code assemblé généré. Pour plus de sécurité, il essaie de reproduire parfaitement le comportement du code de l’assembly, même dans les cas qui ne pourraient pas se produire si le code est utilisé.

Ici, IDA est probablement en train de reproduire les caractéristiques de débordement que la conversion des types a sur la plate-forme. Il ne peut pas simplement reproduire le code C d’origine car ce dernier avait probablement un comportement non spécifié pour certaines valeurs de c ou n , probablement négatives.

Par exemple, disons que j’écris ce code C: int f(unsigned j) { return j; } int f(unsigned j) { return j; } . Mon compilateur transformera probablement cela en un code assembleur très simple, donnant le comportement des valeurs négatives de j que ma plate-forme donne.

Mais si vous décomstackz l’assembly généré, vous ne pouvez pas le décomstackr en int f(unsigned j) { return j; } int f(unsigned j) { return j; } parce que cela ne se comportera pas de la même façon que le code d’assembly que mon assemblage a fait sur des plates-formes ayant un comportement de débordement différent. Cela pourrait comstackr en code (sur d’autres plates-formes) qui renvoie des valeurs différentes de celles de mon code d’assemblage pour les valeurs négatives de j .

Il est donc souvent littéralement impossible (en fait, incorrect) de décomstackr le code C en code original. Ce type de bizarreries “reproduira de manière portable le comportement de cette plate-forme”.

il arrondit à la taille de bloc la plus proche.

pardonne-moi. Il calcule un multiple de c tout en vérifiant simultanément une valeur négative (débordement):

 #include  #include  size_t foo(char c) { return 20 * c | -(20 * (std::uint64_t)(unsigned int)c >> 32 != 0); } int main() { using namespace std; for (char i = -4 ; i < 4 ; ++i) { cout << "input is: " << int(i) << ", result is " << foo(i) << endl; } return 0; } 

résultats:

 input is: -4, result is 18446744073709551615 input is: -3, result is 18446744073709551615 input is: -2, result is 18446744073709551615 input is: -1, result is 18446744073709551615 input is: 0, result is 0 input is: 1, result is 20 input is: 2, result is 40 input is: 3, result is 60 

Pour moi, le numéro 18446744073709551615 ne signifie pas grand-chose en un coup d'œil. Seulement après l'avoir vu exprimé en sortilège je suis allé "ah". - Jongware

ajout de << hex:

 input is: -1, result is ffffffffffffffff