Comment pousser des paramètres dans des méthodes sans paramètres dans un code sécurisé

Bonjour Cette fois, nous continuons de rire de l'appel à la méthode normale. Je propose de me familiariser avec l'appel de méthode avec des paramètres sans passer de paramètres. Nous allons également essayer de convertir le type de référence en un nombre - son adresse, sans utiliser de pointeurs et de code dangereux .


Clause de non-responsabilité


Avant de poursuivre avec l'histoire, je vous recommande fortement de lire le post précédent sur StructLayout . Ici, je vais utiliser certaines fonctionnalités, qui ont été décrites ici.

Je voudrais également avertir que cet article ne contient pas de matériel qui devrait être utilisé dans des projets réels.

Quelques informations initiales


Avant de commencer à pratiquer, rappelons-nous comment le code C # est converti en code assembleur.
Examinons un exemple simple.

public class Helper { public virtual void Foo(int param) { } } public class Program { public void Main() { Helper helper = new Helper(); var param = 5; helper.Foo(param); } } 

Ce code ne contient rien de difficile, mais les instructions générées par JiT contiennent plusieurs points clés. Je propose de ne regarder que sur un petit fragment du code généré. dans mes exemples, j'utiliserai du code assembleur pour les machines 32 bits.

 1: mov dword [ebp-0x8], 0x5 2: mov ecx, [ebp-0xc] 3: mov edx, [ebp-0x8] 4: mov eax, [ecx] 5: mov eax, [eax+0x28] 6: call dword [eax+0x10] 

Dans ce petit exemple, vous pouvez observer la convention d'appel rapide qui utilise des registres pour passer des paramètres (les deux premiers paramètres de gauche à droite dans les registres ecx et edx), et les paramètres restants sont passés dans la pile de droite à gauche. Le premier paramètre (implicite) est l'adresse de l'instance de la classe sur laquelle la méthode est appelée (pour les méthodes non statiques).

Dans notre cas, le premier paramètre est l'adresse de l'instance, le second est notre valeur int .

Donc, dans la première ligne, nous voyons la variable locale 5, il n'y a rien d'intéressant ici.
Dans la deuxième ligne, nous copions l'adresse de l'instance Helper dans le registre ecx. Il s'agit de l'adresse du pointeur vers la table des méthodes.
Dans la troisième ligne, il y a copie de la variable locale 5 dans le registre edx
Dans la quatrième ligne, nous pouvons voir la copie de l'adresse de la table de méthodes dans le registre eax
La cinquième ligne contient le chargement de la valeur de la mémoire à l'adresse 40 octets plus grande que l'adresse de la table des méthodes: le début des adresses des méthodes dans la table des méthodes. (La table de méthodes contient diverses informations qui sont stockées auparavant. Par exemple, l'adresse de la table de méthodes de la classe de base, l'adresse EEClass, divers indicateurs, y compris l'indicateur de garbage collector, etc.). Ainsi, l'adresse de la première méthode de la table des méthodes est maintenant stockée dans le registre eax.
Remarque: Dans .NET Core, la disposition de la table des méthodes a été modifiée. Il y a maintenant un champ (à un décalage de 32/64 bits pour les systèmes 32 et 64 bits respectivement) qui contient l'adresse du début de la liste des méthodes.
Dans la sixième ligne, la méthode est appelée à l'offset 16 depuis le début, c'est-à-dire la cinquième dans le tableau des méthodes. Pourquoi notre seule méthode est la cinquième? Je vous rappelle que l' objet a 4 méthodes virtuelles ( ToString (), Equals (), GetHashCode () et Finalize () ), que toutes les classes auront respectivement.

Goto Practice;


Pratique:
Il est temps de commencer une petite démonstration. Je suggère un si petit blanc (très similaire au blanc de l'article précédent).

  [StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public Test1 Test1; [FieldOffset(0)] public Test2 Test2; } public class Test1 { public virtual int Useless(int param) { Console.WriteLine(param); return param; } } public class Test2 { public virtual int Useless() { return 888; } } public class Stub { public void Foo(int stub) { } } 

Et utilisons ce genre de choses de cette manière:

  class Program { static void Main(string[] args) { Test2 fake = new CustomStructWithLayout { Test2 = new Test2(), Test1 = new Test1() }.Test2; Stub bar = new Stub(); int param = 55555; bar.Foo(param); fake.Useless(); Console.Read(); } } 

Comme vous pouvez le deviner, d'après l'expérience de l'article précédent, la méthode Useless (int j) de type Test1 sera appelée.

Mais qu'est-ce qui sera affiché? Le lecteur attentif, je crois, a déjà répondu à cette question. "55555" est affiché sur la console.

Mais regardons toujours les fragments de code générés.

  mov ecx, [ebp-0x20] mov edx, [ebp-0x10] cmp [ecx], ecx call Stub.Foo(Int32) mov ecx, [ebp-0x1c] mov eax, [ecx] mov eax, [eax+0x28] call dword [eax+0x10] 

Je pense que vous reconnaissez le modèle d'appel de méthode virtuelle, il commence après l' appel de Stub.Foo (Int32) . Comme nous pouvons le voir, comme attendu, ecx est rempli avec l'adresse de l'instance sur laquelle la méthode est appelée. Mais puisque le compilateur pense que nous appelons une méthode de type Test2, qui n'a pas de paramètres, rien n'est écrit dans edx. Cependant, nous avons un autre appel de méthode avant. Et là, nous avons utilisé edx pour passer le paramètre. Et bien sûr, nous n'avons pas d'instructions, ce clair edx. Ainsi, comme vous pouvez le voir dans la sortie de la console, la valeur edx précédente a été utilisée.

Il y a une autre nuance intéressante. J'ai utilisé spécifiquement le type significatif. Je suggère d'essayer de remplacer le type de paramètre de la méthode Foo du type Stub par n'importe quel type de référence, par exemple, une chaîne. Mais le type de paramètre de la méthode Useless () ne change pas. Ci-dessous vous pouvez voir le résultat sur ma machine avec quelques informations de clarification: WinDBG et calculatrice :)


Image cliquable

La fenêtre de sortie affiche l'adresse du type de référence en notation décimale.

Total


Nous avons actualisé les connaissances des méthodes d'appel en utilisant la convention fastcall et avons immédiatement utilisé le merveilleux registre edx pour passer un paramètre dans 2 méthodes à la fois. Nous avons également craché sur tous les types et en sachant que tout n'est que des octets facilement obtenus l'adresse de l'objet sans utiliser de pointeurs et de code dangereux. De plus, je prévois d'utiliser l'adresse reçue à des fins encore plus inapplicables!

Merci de votre attention!

Le code PS C # peut être trouvé ici

Source: https://habr.com/ru/post/fr447254/


All Articles