ببساطة ، شعرت بالملل ذات يوم وطلبت من صديقي ، مكسيموس هاكيرمان ، أن أعطيني شيئًا لأفعله. على الفور ، أرسل من خلال عملية تنفيذية بسيطة تسمى "Reberveseme.exe" (رابط virustotal) مع تعليق بسيط عن "طباعة الرسالة المخفية في التطبيق الخاص بك". وبطبيعة الحال ، لم يتم توفير ملفات رأس CPP. عند التشغيل القابل للتنفيذ ، يتم افتتاحه ببساطة كتطبيق يعتمد على وحدة التحكم ، يطبع "إرسال جميع الحزم السحرية ، والخروج قريبًا ، Beep Boop!" ثم يخرج على الفور تقريبا. أعتقد أن "قريبًا" شخصي.
لحسن الحظ ، لا يبدو أن هناك معاداة للانقادة ، لذلك أعتقد أن هاكيرمان كان لطيفًا في اليوم.
بعد إرفاق Windbg و disassembler ، يمكننا أن نرى أن التطبيق يلقي ، ما يبدو في البداية ، divide by zero .
ومع ذلك ، عند الفحص الدقيق ، نجد أن هذا الاستثناء يتم طرحه قبل طباعة التطبيق مباشرة رسالته المرئية. من المهم أن نلاحظ أن اثنين من المعالجات (معالجات الاستثناءات المتجهة) مسجلة قليلاً قبل هذه النقطة ، لذلك من المحتمل أن يكون هذا الاستثناء مقصودًا ، ويستخدم لتدفق التحكم. خدعة رخيصة حقا لمحاولة التخلص منا حقا!
مع نسيان السيارات في الوقت الحالي ، من الآمن افتراض أنه إذا كان التطبيق "يرسل" حزمًا ، فإنه يستخدم وظيفة إرسال Winsock للقيام بذلك (على الرغم من أن التطبيق يقول بوضوح أنها "حزم سحرية" ، لذلك سنرى).
كما هو متوقع ، اتضح أنه لا يمكنك إرسال حزم حسب السحر ، لذلك يتم بالفعل استيراد وظيفة send Winsock.
من خلال وضع نقطة توقف على وظيفة الإرسال ، يمكننا أن نلقي نظرة ومعرفة ما إذا كانت هناك أي بيانات ذات صلة يمكننا تجريدها في وقت الإرسال. للأسف ، بحلول الوقت الذي يتم فيه تحميل المخزن المؤقت في الوظيفة ، يتم تشفير البيانات بالفعل. ومع ذلك ، يمكننا تحديد أن المخزن المؤقت هو دائمًا 3 بايت في الطول ، ويرتبط المقبس دائمًا قيمة متشددين من 0x69 ( لطيفة ) ، وأن نقطة توقف دالة الإرسال يتم ضربها 14 مرة في المجموع.
هناك بعض الطرق للتجول حول التشفير المذكور. أحدهما هو عكس ذلك تمامًا ، والذي قد يتحول إلى الكثير من الجهد ، والآخر هو تحديد موقع البيانات المطلوبة قبل التشفير وتجريدها قبل أن يتم تشفيرها. هذا الأخير أسهل بكثير من السابق ، لذلك نحن نذهب مع ذلك ؛ لا تتردد في عكس التشفير ، لقد ألقيت نظرة موجزة وليست مروعًا. بدلاً من ذلك ، إذا كنت تشعر بالتوهم ، فيمكنك دائمًا الكتابة فوق قيمة مقبض المقبس ولديك صفقة تطبيق استلام معها.
للأسف ، لا توجد سلاسل مفيدة في قطاع البيانات للقراءة فقط ، بصرف النظر عن الرسالة الأولية ، لذلك لا توجد مؤشرات مفيدة من هذا الجانب!
بالعودة إلى المركبات ، يمكننا أن نرى أن كلا من المعالجات المسجلة تستخدم لنسخ بعض الكائنات غير المعروفة في مخزن مؤقت للذاكرة المخصصة. يبدو أن هذا يتم باستخدام memcpy ، باستخدام دالة كمعلمة ثانية ، والتي بدورها تستخدم عدد صحيح متشددين كمعلمة ثانية لها. تجدر الإشارة إلى أن هذه القيم المتشددين لا تتجاوز 14 في أي وقت. لقد قمت بتضمين واحدة فقط من المركبات في لقطة الشاشة ، لأنها متطابقة بشكل أساسي من الكود باستثناء القيم المتشددين.
من خلال الانهيار في إحدى وظائف MEMCPY وفحص الوظيفي الفرعي في المعلمة الثانية ، يمكننا أن نرى أن عدد الأعداد المتشددين (13 / 0DH في المثال أدناه) تم تعيينه على البايت الأول من المعلمة A1 ، وأن A1+1 يحتوي على حرف ، مباشرة بعد أن يتم تأجيل المؤشر.
إذا تحققنا من بعض المكالمات الـ 14 الأخرى إلى هذه الوظيفة ، فيمكننا العثور على نفس السلوك يتكرر ؛ ترتيب الأحرف من 1 إلى 14 ، باستخدام الرقم المقابل كمؤشر للطلب ، يمكننا أن نرى أنها تبدأ في توضيح بعض الكلمات المقروءة. الآن ، يمكن أن نكون كسولًا للغاية ونجعل تطبيق وحدة تحكم لطباعة الرسالة ، بمجرد أن نعرف ما هو عليه ، ولكن هذا يبدو وكأنه غش. بالإضافة إلى ذلك ، قد تتغير الرسالة في مرحلة ما. لذلك ، دعنا نكتب كهف رمز لاعتراض القيم قبل تشفيرها.
آسف ، لكننا نستخدم C ++ لهذا! إذا كنت تفضل استخدام C#، فلا تتردد في الذهاب إلى عملية استدعاء النظام الأساسي بأكملها لمدة 9.7 مليون وظيفة والعودة إلى هنا عند الانتهاء. على أي حال ، أولاً ، نحتاج إلى تحديد مكان كهف كهف. لحسن الحظ ، نحن نعرف بالفعل! من خلال تحليل الوظيفة أعلاه ، ولكن باختصار ، نعلم أنه من خلال النقطة المميزة RDX تحتوي على الفهرس ، ويحتوي RDX+1 على الحرف المقابل. فيما يلي رمز التجميع للوظيفة التي تمت مناقشتها.
الآن ، من الناحية المنطقية ، سيكون أفضل مكان للاتخاذ القفزة في mov [rsp+arg_8], rdx ، لأننا لا نهتم حقًا بالمعلمة الثالثة ، ولكننا نريد اعتراض RDX و RDX+1 . للقيام بهذا الأطفال ، سنحتاج إلى عدد قليل من البايتات: 10 بايت لتعليمات MOV ، لنقل عنوان كهف الكود الخاص بنا إلى سجل (سنستخدم RAX ، وأكثر من ذلك في وقت لاحق خلال أخبار الساعة العاشرة) ، و 2 بايت لتعليمات JMP ، للقفز إلى السجل. لأولئك منكم الذين لديهم اضطراب ما بعد الصدمة من المزيد من الرياضيات ، هذا الإجمالي في 12 بايت. الآن ، قبل أن نذهب إلى جميع العمة Bessie و Spaghettify هذا الرمز مع WriteProcessMemory ، نحتاج إلى النظر في أنه لا يوجد مكان مثالي لاستبدال 12 بايت في التجميع أعلاه. إذا كنا نريد القفز من الإزاحة 0x2905 ( mov [rsp+arg_8], rdx ) ، ونحن بحاجة إلى 12 بايت للقيام بذلك ، فهذا يأخذنا إلى تعويض 0x2917 ، وهو ضجة بين تعليمين MOV . لسوء الحظ ، إذا أردنا ببساطة كتابة بايتنا إلى هناك ، فسيؤدي ذلك إلى تجميع التجميع تمامًا ، ومن المحتمل أن تسبب بعض الآثار الجانبية "المثيرة للاهتمام". نتيجةً لذلك ، سيكون الأمر أسهل (ربما أكثر قليلاً من الاختراق ، آسف لا آسف) لإضافة بعض الإرشادات البانية للحشو والانتقال إلى نهاية التعليمات. مرحبًا بك على متن ، 0x90.
على أي حال ، والآن بعد أن عرفنا ماهية خطتنا ، لذلك دعونا نكتب بعض التعليمات البرمجية: جديلة موسيقى Hacker Man !
فيما يلي ما يبدو عليه القفزة الأولية إلى كهف الكود الخاص بنا بمجرد كتابة البايتات في تجميع التطبيق ، مع استكمال شريحة NOP الخاصة به.
ومع ذلك ، قبل أن نكتب أي تجميع واستبداله بالفعل ، نحتاج إلى بدء التطبيق في حالة مع وقف التنفيذ ؛ سيؤدي ذلك إلى إيقاف وقت تشغيل التطبيق في مرحلة مبكرة ، بحيث يمكن إجراء تغييرات في الذاكرة قبل أن يصل التطبيق إلى التعليمات التي مهتمون بها. يوضح مستخلص الرمز أدناه هذه العملية ، ولن أتغلب عليها كثيرًا حيث يمكنك عرضها في الملفات المصدر لهذا الريبو ، ويتحدث في الغالب عن نفسه.
الآن وقد ولدت العملية ، سنحتاج إلى الحصول على العنوان الأساسي للعملية. في العادة ، يمكنك استخدام EnumProcessModules لهذا الغرض ، ولكن نظرًا لأننا قمنا على الفور بتعليق مؤشر ترابط العملية الرئيسي ، فإن PEB لا يحتوي على بنية PEB_LDR_DATA المأهولة بالسكان ، وعلى وجه التحديد InMemoryOrderModuleList ، لذلك لا يمكننا حاليًا الحصول على العنوان الأساسي. بالمناسبة ، لا يبدو أن هذا موثق في أي مكان على MSDN. لحسن الحظ ، من السهل نسبيًا التحايل ؛ من خلال استئناف العملية بسرعة كبيرة ، والاستعلام عن الوحدات ، ثم إعادة بناء العملية يمكننا الحصول على المعلومات التي نحتاجها دون تقدم العملية كثيرًا. كما هو الحال مع معظم الأشياء في Windows ، تحب Microsoft أن تكرر أن نظام التشغيل هو نظام التشغيل المتفوق مرة أخرى لا توثق الوظائف التي نحتاجها: NtSuspendProcess و NtResumeProcess . مريح ، والدي صديق مع بيل غيتس ، ويخبرني أن هذه الوظائف تعيش إلى جانب ntdll.dll ، حتى نتمكن من جلبها باستخدام الفصل أدناه الذي قمت به سابقًا:
الآن وبعد أن أصبح لدينا وظيفتين نحتاجهما ، يمكننا استئناف العملية ، والاستعلام عن الوحدات ، وإعادة توسيع العملية:
قد تتساءل ، لماذا تنتظر حلقة الاكتشافين في الوحدة النمطية بدلاً من واحد؟ حسنًا ، تتطلع Microsoft إلى تسجيل هاتريك مع كرة اللحم في الجزء الخلفي من شبكة السباغيتي غير الموثقة من خلال عدم الإشارة أيضًا إلى أن الوحدة الأولى التي عثر عليها من قبل EnumProcessModules ستكون ntdll.dll ، والثانية ستكون قابلة للتنفيذ. على الرغم من أن هذا يبدو معقولًا ، إلا أنه بمجرد العثور على التنفيذ ، فإنه سيباد الفهارس باستخدام ntdll.dll. هذا مثال:
النتيجة بعد الاستعلام عن الوحدة الأولى فقط:
النتيجة بعد الاستعلام عن وحدتين:
قبل أن نذهب إلى أبعد من ذلك ، نحتاج إلى كتابة رمز التجميع الذي يقوم بالقفز الأولي إلى كهف الكود ، والرمز نفسه. في الأساس ، سنقوم بالكتابة على بعض الذاكرة بدءًا من الإزاحة 0x2905 ، ونقفز ، ونقوم بالتجسس في الكود ، ثم القفز مرة أخرى إلى 0x2911 لمواصلة تدفق البرنامج العادي. في البداية ، نعلن أن نقل العنوان إلى سجل RAX باسم MOV RAX, 0x0 ، لأن العنوان الذي سنقفز إليه هو ديناميكي ولا نعرف ما هو عليه بعد. RAX هو سجل آمن للاستخدام ، لأنه سجل متقلبة ويتم الكتابة فوقه بعد فترة وجيزة على أي حال. حقيقة ممتعة ، هذه هي الطريقة التي برمجت Nintendo منصة Mario Bros الأصلية ، مع الكثير من القفزات (أخبرني أنا مضحك)! فيما يلي كيف يبدو الرمز في المترجم ؛ يمكن إنشاؤه بطرق أخرى ، لكنني اخترت نشر تعليمات التجميع المطلوبة في رمز bytecode. إذا كنت تتطلع إلى القيام بذلك في المنزل ، فاستخدم هذا الموقع.
رمز كهف الكود الفعلي أكثر تعقيدًا بعض الشيء ، ويتم التعليق على المنطق أيضًا في هذا الملف ، ولكن إليك العملية الخشنة:
R10RDX ، والذي يحتوي على فهرس الحزمة ، إلى R11BRDX ، والذي يحتوي على الحرف ، إلى R11B+12 ) إلى R10 (وهو 0x2911 )R10 نحتاج أيضًا إلى إعادة كتابة رمز التجميع الذي نكتاله (مع شريحة NOP) في كهف الكود الخاص بنا للحفاظ على المكدس وما إلى ذلك ؛ تتم الإشارة إلى هذا الرمز في منطقة " Predetermined Assembly " ، باستثناء NOPs. فيما يلي كهف الكود في مجده القبيح:جزء صعب ، في الغالب.
في هذه المرحلة ، لدينا بشكل أساسي كل ما نحتاجه لإخراج الرسالة المخفية من الذاكرة ، نحتاج فقط إلى تنفيذها. للتلخيص ، لدينا مقبض لعملية معلقة التي أنتجناها ، 2 صفيف بايت تمثل منطق التجميع ، والعنوان الأساسي للعملية المعلقة ، المخزنة في modules[0] ، وإزاحة المكان الذي نحتاج إلى كتابة منطق القفز. يقوم مقتطف الرمز أدناه بإنشاء عنوان القفز منه ، وعنوان كهف الكود (للقفز إلى) ، وعنوان تخزين الذاكرة المكون من 3 بايت ، يكتب العناوين في رمز التجميع ، ثم يكتب رمز التجميع العملية المعلقة ، قبل استئنافها:
يتمثل صب نمط Harry Potter السحري لـ codeCaveStorageAddr في تحويل العنوان إلى بايت ، والقيم المتشددين في الحلقات هي مواضع العنوان الديناميكي في صفائف التجميع. بالطبع ، يمكن القيام بذلك بطريقة أنظف كثيرًا (لا تكتب أرقامًا سحرية للأطفال) ، فأنا كسول فقط بعد الاضطرار إلى كتابة كل تلك البايتات يدويًا. البتات القليلة الأخيرة التي يجب كتابتها هي الحلقات التي يجب قراءتها ، باستخدام ReadProcessMemory ، والذاكرة ، وتخزين الأحرف والفهجة في صفيف البايت المطلوب ، والإشارة إلى البايت ، باستخدام WriteProcessMemory ، عند الانتهاء من قراءة الذاكرة الحالية ، وطباعة الرسالة المخفية. نحن نعلم أن وظيفة الإرسال تحدث فقط 14 مرة ، لذلك نخرج من الحلقة بمجرد ملء صفيف البايت ؛ يمكن تغيير ذلك مع المزيد من تعديلات الذاكرة للإشارة إلى تطبيقنا بأن العملية "تخرج" ويمكن إيقاف حلقتنا ، بدلاً من استخدام قيمة متشددين قدرها 14 ، ولكن هذا يعمل لهذا المثال.
النتيجة؟ تتم طباعة رسالتنا المخفية في تطبيق وحدة التحكم الخاصة بنا! إذا كان أي شخص فضوليًا ، فإن NPT هو إشارة إلى جزء آخر من البرمجيات القصوى التي كتبها هايم.