كيفية دفع المعلمات إلى أساليب دون معلمات في رمز آمن

مرحبا. هذه المرة نستمر في الضحك على استدعاء الأسلوب العادي. أقترح التعرف على استدعاء الأسلوب مع المعلمات دون تمرير المعلمات. سنحاول أيضًا تحويل نوع المرجع إلى رقم - عنوانه ، دون استخدام مؤشرات ورمز غير آمن .


تنصل


قبل متابعة القصة ، أوصي بشدة أن تقرأ المنشور السابق حول StructLayout . هنا سأستخدم بعض الميزات ، التي تم وصفها هناك.

أود أيضًا أن أحذر من أن هذه المقالة لا تحتوي على مواد يجب استخدامها في مشاريع حقيقية.

بعض المعلومات الأولية


قبل أن نبدأ التدريب ، دعونا نتذكر كيف يتم تحويل رمز C # إلى رمز المجمع.
دعونا دراسة مثال بسيط.

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); } } 

لا يحتوي هذا الرمز على أي شيء صعب ، لكن التعليمات التي أنشأتها JiT تحتوي على عدة نقاط أساسية. أقترح أن ننظر فقط على جزء صغير من الشفرة التي تم إنشاؤها. في الأمثلة الخاصة بي ، سأستخدم رمز المجمّع لأجهزة 32 بت.

 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] 

في هذا المثال الصغير ، يمكنك ملاحظة اصطلاح استدعاء fastcall الذي يستخدم السجلات لتمرير المعلمات (المعلمتان الأوليان من اليسار إلى اليمين في سجلات ecx و edx) ، ويتم تمرير المعلمات المتبقية عبر المكدس من اليمين إلى اليسار. المعلمة الأولى (الضمنية) هي عنوان مثيل الفئة التي تسمى فيها الطريقة (بالنسبة للطرق غير الثابتة).

في حالتنا المعلمة الأولى هي عنوان المثيل ، والثاني هو القيمة int لدينا.

لذلك كثافة العمليات في السطر الأول نرى المتغير المحلي 5 ، لا يوجد شيء مثير للاهتمام هنا.
في السطر الثاني ، نقوم بنسخ عنوان مثيل Helper في سجل ecx. هذا هو عنوان المؤشر إلى جدول الأسلوب.
في السطر الثالث ، يتم نسخ المتغير المحلي 5 في سجل edx
في السطر الرابع ، يمكننا رؤية نسخ عنوان جدول الطريقة في سجل eax
يحتوي السطر الخامس على تحميل القيمة من الذاكرة عند عنوان 40 بايت أكبر من عنوان جدول الطريقة: بداية عناوين الطرق في جدول الطريقة. (يحتوي جدول الأسلوب على معلومات متنوعة تم تخزينها من قبل. على سبيل المثال عنوان جدول أسلوب الفئة الأساسية ، وعنوان EEClass ، وأعلام متعددة ، بما في ذلك علامة أداة تجميع مجمعي البيانات المهملة ، وما إلى ذلك). وبالتالي ، يتم الآن تخزين عنوان الطريقة الأولى من جدول الطريقة في سجل eax.
ملاحظة: في .NET Core ، تم تغيير تخطيط جدول الطريقة. يوجد الآن حقل (عند إزاحة 32/64 بت للأنظمة 32 و 64 بت على التوالي) يحتوي على عنوان قائمة بداية الطريقة.
في السطر السادس ، يتم استدعاء الطريقة عند الإزاحة 16 من البداية ، أي الخامسة في جدول الطريقة. لماذا هي طريقتنا الوحيدة الخامسة؟ أذكرك أن هذا الكائن يحتوي على 4 طرق افتراضية ( ToString () و Equals () و GetHashCode () و Finalize () ) ، والتي ستكون جميع الفئات بها ، على التوالي.

غوتو الممارسة ؛


practive:
حان الوقت لبدء مظاهرة صغيرة. أقترح هذا الفراغ الصغير (مشابه جدًا للفراغ من المقال السابق).

  [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) { } } 

ودعونا نستخدم هذه الأشياء بهذه الطريقة:

  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(); } } 

كما قد تعتقد ، من تجربة المقالة السابقة ، سيتم استدعاء طريقة عديمة الفائدة (int j) من النوع Test1 .

ولكن ما الذي سيتم عرضه؟ أعتقد أن القارئ اليقظ قد أجاب على هذا السؤال. يتم عرض "55555" على وحدة التحكم.

ولكن دعونا لا نزال ننظر إلى شظايا رمز ولدت.

  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] 

أعتقد أنك تتعرف على نمط استدعاء الأسلوب الظاهري ، يبدأ بعد استدعاء Stub.Foo (Int32) . كما نرى ، كما هو متوقع ، يتم تعبئة ecx بعنوان المثيل الذي تم استدعاء الطريقة عليه. ولكن نظرًا لأن المترجم يعتقد أننا نسمي طريقة من النوع Test2 ، والتي لا تحتوي على معلمات ، فلا شيء مكتوب على edx. ومع ذلك ، لدينا استدعاء طريقة أخرى من قبل. وهناك استخدمنا edx لتمرير المعلمة. وبالطبع ليس لدينا تعليمات ، هذا الأمر واضح. لذلك ، كما ترون في إخراج وحدة التحكم ، تم استخدام قيمة edx السابقة.

هناك فارق بسيط آخر مثير للاهتمام. لقد استخدمت على وجه التحديد نوع ذو معنى. أقترح محاولة استبدال نوع المعلمة للأسلوب Foo لنوع الروتين بأي نوع مرجعي ، على سبيل المثال ، سلسلة. لكن نوع المعلمة للأسلوب عديمة الفائدة () لا يتغير. أدناه يمكنك رؤية النتيجة على الجهاز الخاص بي مع بعض المعلومات التوضيحية: WinDBG و Calculator :)


صورة قابلة للنقر

تعرض نافذة الإخراج عنوان نوع المرجع بترميز عشري.

مجموع


قمنا بتحديث معرفة طرق الاتصال باستخدام اصطلاح fastcall واستخدمنا على الفور سجل edx الرائع لتمرير معلمة بطريقتين في وقت واحد. نحن بصق أيضا على جميع الأنواع ومع العلم أن كل شيء هو بايت فقط بسهولة الحصول على عنوان الكائن دون استخدام مؤشرات ورمز غير آمن. كذلك أخطط لاستخدام العنوان المستلم لأغراض غير قابلة للتطبيق!

شكرا للاهتمام!

يمكن العثور على رمز PS C # هنا

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


All Articles