CreateRemoteThread لنظام التشغيل Linux

Mitsuha يجلب تيارات جديدة يحتوي WinAPI على وظيفة CreateRemoteThread التي تسمح لك ببدء سلسلة رسائل جديدة في مساحة العنوان لعملية أخرى. يمكن استخدامه لمجموعة متنوعة من حقن DLL ، سواء للأغراض السيئة (الغش في الألعاب ، سرقة كلمة المرور ، وما إلى ذلك) ، ولإصلاح الخلل في برنامج قيد التشغيل ، أو إضافة مكونات إضافية إلى الأماكن التي لم تكن فيها المقدمة.


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


سوف أتحدث بالتفصيل عن كيفية ، بمساعدة مواصفات ELF ، كتابة بعض المعرفة بالهيكل x86_64 ومكالمات نظام Linux ، لديك قطعة صغيرة من مصحح الأخطاء التي يمكنها تحميل وتنفيذ تعليمات برمجية عشوائية في عملية قيد التشغيل والعمل بالفعل.


سيتطلب فهم النص معرفة أساسية حول برمجة النظام لنظام Linux: لغة C وبرامج الكتابة والتصحيح عليه ، وفهم دور رمز الجهاز والذاكرة في الكمبيوتر ، ومفهوم مكالمات النظام ، والإلمام بالمكتبات الرئيسية ، وقراءة الوثائق.



كنتيجة لذلك ، تمكنت من "إضافة" القدرة على معاينة كلمات المرور في مركز تحكم جنوم:


مظاهرة الحقن في مركز تحكم جنوم


الأفكار الرئيسية


إذا لم يكن هناك شرط في متطلبات تحميل الكود في عملية قيد التشغيل بالفعل ، فسيكون الحل بسيطًا للغاية: LD_PRELOAD. يسمح متغير البيئة هذا بتحميل مكتبة اعتباطية مع التطبيق. في المكتبات المشتركة ، يمكنك تحديد وظائف المنشئ التي يتم تنفيذها عند تحميل المكتبة.


معاً ، يسمح LD_PRELOAD والمنشئون بتنفيذ تعليمات برمجية عشوائية في أي عملية باستخدام أداة تحميل حيوية. هذه هي ميزة معروفة نسبيا وغالبا ما تستخدم لتصحيح الأخطاء. على سبيل المثال ، يمكنك تنزيل مكتبتك الخاصة من خلال التطبيق ، الذي يحدد الوظائف malloc () و free () ، مما قد يساعد في اكتشاف تسرب الذاكرة.


لسوء الحظ ، لا يعمل LD_PRELOAD إلا عند بدء العملية. لا يمكن استخدامه لتحميل مكتبة في عملية قيد التشغيل بالفعل. هناك وظيفة dlopen () لتحميل المكتبات أثناء تشغيل العملية ، ولكن من الواضح أن العملية نفسها يجب أن تطلقها لتحميل المكونات الإضافية.


حول الملفات التنفيذية الثابتة

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

في البرامج المجمعة بشكل ثابت ، يمكنك تضمين التعليمات البرمجية في وقت التشغيل ، ولكن يجب أن يتم ذلك بطريقة مختلفة قليلاً. وهذا ليس آمنًا تمامًا ، لأن البرنامج قد لا يكون جاهزًا لمثل هذا المنعطف.

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


من الناحية النظرية ، لإجبار عملية شخص آخر على تنفيذ نوع من التعليمات البرمجية ، تحتاج إلى تنفيذ الإجراءات التالية:


  1. الحصول على السيطرة في العملية المستهدفة.
  2. قم بتحميل الكود في ذاكرة العملية المستهدفة.
  3. قم بإعداد الكود الذي تم تنزيله للتنفيذ في العملية المستهدفة.
  4. تنظيم تنفيذ الكود الذي تم تنزيله بواسطة العملية المستهدفة.

دعنا نذهب ...


الحصول على السيطرة في هذه العملية


بادئ ذي بدء ، نحن بحاجة إلى إخضاع العملية المستهدفة لإرادتنا. بعد كل شيء ، عادة ما تنفذ العمليات فقط التعليمات البرمجية الخاصة بها ، أو رمز المكتبات المحملة ، أو نتائج تجميع JIT. ولكن بالتأكيد ليس لدينا رمز.


أحد الخيارات هو استخدام نوع من الضعف في العملية التي تتيح لك السيطرة. مثال كلاسيكي من البرامج التعليمية: تجاوز سعة المخزن المؤقت ، مما يسمح بإعادة كتابة عنوان المرسل في الحزمة. إنه ممتع ، حتى في بعض الأحيان يعمل ، لكنه غير مناسب للحالة العامة.


سوف نستخدم طريقة أخرى صادقة للسيطرة: مكالمات نظام تصحيح الأخطاء . يمكن أن تقوم مصححات الأخطاء التفاعلية بإيقاف عمليات الجهات الخارجية تمامًا وتقييم التعبيرات وأشياء أخرى كثيرة. يمكنهم - يمكننا ذلك.


على Linux ، استدعاء نظام التصحيح الرئيسي هو ptrace () . يتيح لك الاتصال بالعمليات وفحص حالتها والتحكم في تقدم تنفيذها. يتم توثيق ptrace () بشكل جيد من تلقاء نفسه ، ولكن تفاصيل استخدامه واضحة فقط في الممارسة العملية.


تحميل رمز في ذاكرة العملية


في حالة تجاوزات المخزن المؤقت ، عادةً ما يتم تضمين الحمولة النافعة ( كود القشرة ) في المحتويات التي تفيض على نفس المخزن المؤقت. عند استخدام مصحح الأخطاء ، يمكن كتابة التعليمات البرمجية الضرورية في ذاكرة العملية مباشرة. في WinAPI هناك وظيفة خاصة WriteProcessMemory لهذا الغرض. يتوافق Linux لهذا الغرض مع طريقة UNIX: لكل عملية في النظام ، يوجد ملف / proc / $ pid / mem ، يعرض ذاكرة هذه العملية. من الممكن كتابة شيء ما على ذاكرة العملية باستخدام المدخلات والمخرجات المعتادة.


إعداد رمز للتنفيذ


مجرد كتابة التعليمات البرمجية في الذاكرة لا يكفي. لا يزال يحتاج إلى أن يكتب إلى الذاكرة القابلة للتنفيذ . في حالة التسجيل من خلال مشكلة عدم الحصانة ، توجد صعوبات غير تافهة مع ذلك ، ولكن بما أننا نستطيع التحكم الكامل في العملية المستهدفة ، فلن نواجه مشكلة في إيجاد أو تخصيص الذاكرة "الصحيحة".


نقطة أخرى مهمة للتحضير هو رمز قذيفة نفسها. في ذلك ، ربما نرغب في استخدام بعض الوظائف من المكتبات ، مثل المدخلات والمخرجات ، والأولويات الرسومية ، وهلم جرا. ومع ذلك ، يتعين علينا كتابة رمز الجهاز العاري ، والذي في حد ذاته ليس لديه فكرة عن عناوين كل هذه الوظائف الرائعة في المكتبات. من أين تحصل عليها؟


لتبسيط عمر نظام التشغيل وتعقيد عمر التعليمات البرمجية الضارة ، لا تستخدم المكتبات عادة عناوين ثابتة (وتحتوي على ما يسمى برمز مستقل عن الموضع ). لذلك لا يمكن تخمين العناوين.


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


بشكل عام ، مع المكتبات ، هناك أربعة خيارات:


  • لا تستخدم المكتبات على الإطلاق ، افعل كل شيء على مكالمات النظام النظيف
  • وضع نسخ من جميع المكتبات اللازمة في رمز قذيفة
  • قم بعمل المحمل الديناميكي بنفسك
  • العثور على محمل الإقلاع الديناميكي وجعله تحميل مكتباتنا

سنختار الأخير ، لأننا نريد المكتبات ، ونكتب محمل الإقلاع الكامل الخاص بنا لفترة طويلة. هذه ليست الطريقة الأكثر سرية ، وليست هي الأكثر إثارة للاهتمام ، ولكنها الأكثر بساطة وقوة وموثوقية.


نقل السيطرة على رمز


يسمح لك ptrace () بتغيير سجلات المعالج ، لذلك يجب ألا تكون هناك مشاكل في نقل التحكم إلى الكود الذي تم تحميله وإعداده: ما عليك سوى كتابة عنوان الكود الخاص بنا في سجل٪ rip - و voila! ومع ذلك ، في الواقع ، كل شيء ليس بهذه البساطة. ترتبط الصعوبات بحقيقة أن العملية التي تم تصحيحها في الواقع لم تختف وأن لديها أيضًا نوعًا من التعليمات البرمجية التي تم تنفيذها وستستمر عملية التنفيذ.


رسم الحل


المجموع ، سننفذ تدفقنا في عملية طرف ثالث على النحو التالي:


  1. نحن متصلون بالعملية المستهدفة لتصحيح الأخطاء.
  2. نجد المكتبات اللازمة في الذاكرة:
    • libdl - لتحميل مكتبة جديدة
    • libpthread - لبدء موضوع جديد
  3. نجد الوظائف اللازمة في المكتبات:
    • libdl: dlopen () ، dlsym ()
    • libpthread: pthread_create () ، pthread_detach ()
  4. نقدم كود shell في ذاكرة العملية المستهدفة:


     void shellcode(void) { void *payload = dlopen("/path/to/payload.so", RTLD_LAZY); void *entry = dlsym(payload, "entry_point"); pthread_t thread; pthread_create(&thread, NULL, entry, NULL); pthread_detach(thread); } 

  5. نعطي رمز قذيفة ليتم الوفاء بها.

نتيجةً لذلك ، ستقوم المكتبات بعمل الشيء الصحيح بالنسبة لنا: حيث ستقوم بتحميل مكتبة لدينا بالرمز الذي نحتاجه في الذاكرة وبدء سلسلة رسائل جديدة لتنفيذ هذا الرمز.


قيود


النهج الموصوف أعلاه يفرض قيودًا معينة:


  • يجب أن يكون لدى أداة تحميل التشغيل امتيازات كافية لتصحيح العملية الهدف.
  • يجب أن تستخدم العملية libdl (جاهزة للتحميل الديناميكي للوحدات النمطية).
  • يجب أن تستخدم العملية libpthread (جاهز للتعدد).
  • التطبيقات الثابتة غير مدعومة.

بالإضافة إلى ذلك ، أنا شخصياً كسالى للغاية لأزعجها بدعم من جميع الهياكل ، لذلك سنقتصر على x86_64. (حتى x86 32 بت سيكون أكثر تعقيدًا.)


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


الاستطراد: حول استخدام libdl و libpthread


قد يتساءل القارئ المحترف ذو الخبرة: لماذا يتطلب libdl ما إذا كانت الدالتان الداخليتان __libc_dlopen_mode () و __libc_dlsym () مدمجة بالفعل في glibc ، وما إذا كان libdl مجرد غلاف عليهما؟ وبالمثل ، لماذا تتطلب libpthread إذا كان يمكن إنشاء سلسلة رسائل جديدة بسهولة باستخدام استدعاء نظام clone ()؟


في الواقع ، لا يوجد على الإنترنت مثال واحد على كيفية استخدامها:



يتم ذكرها حتى في الأدب القراصنة الشعبية:


  • تعلم لينكس تحليل ثنائي
  • فن الطب الشرعي الذاكرة

فلماذا لا؟ حسنًا ، على الأقل لأننا لا نكتب تعليمات برمجية ضارة حيث يكون الحل مناسبًا لحذف 90٪ من عمليات الفحص ، يستغرق مساحة أقل 20 مرة ، ولكنه يعمل أيضًا في 80٪ من الحالات. بالإضافة إلى ذلك ، أردت أن أجرب كل شيء بيدي.


في الواقع ، ليس من الضروري libdl تحميل المكتبة في حالة glibc. يشير استخدامه من خلال العملية إلى أنه جاهز بشكل واضح لتحميل الكود الديناميكي. على الرغم من هذا ، من حيث المبدأ ، يمكنك رفض استخدام libdl (بالنظر إلى أننا سنظل بحاجة إلى البحث عن glibc لاحقًا أيضًا).


لماذا dlopen () داخل glibc على الإطلاق؟

هذا سؤال مثير للاهتمام بطريقته الخاصة. إجابة قصيرة: تفاصيل التنفيذ.

النقطة المهمة هي مفتاح خدمة الاسم (NSS) - أحد أجزاء glibc التي توفر ترجمة لأسماء مختلفة: أسماء الآلات والبروتوكولات والمستخدمين وخوادم البريد ، إلخ. وهي المسؤولة عن وظائف مثل getaddrinfo () للحصول على عناوين IP عن طريق اسم المجال و getpwuid () للحصول على معلومات حول المستخدم بواسطة معرفه الرقمي.

NSS لديه بنية وحدات وتحميل وحدات بشكل حيوي. في الواقع ، لهذا ، يتطلب glibc أيضًا آليات لتحميل المكتبات ديناميكيًا. هذا هو السبب عند محاولة استخدام getaddrinfo () في تطبيق تم تجميعه بشكل ثابت ، يطبع رابط تحذير "غير مفهوم":
 /tmp/build/socket.o: في الوظيفة `Socket :: bind ':
 socket.o :(. text + 0x374): تحذير: استخدام "getaddrinfo" في الارتباط الثابت
 تتطلب التطبيقات في وقت التشغيل المكتبات المشتركة من إصدار glibc
 تستخدم للربط

بالنسبة إلى سلاسل الرسائل ، عادة ما لا يكون مؤشر الترابط رمزًا مكدسًا وقابلًا للتنفيذ ، ولكن أيضًا البيانات العالمية المخزنة في التخزين المحلي لمؤشر الترابط (TLS). يتطلب التهيئة الصحيحة لمؤشر ترابط جديد التشغيل المنسق لنظام التشغيل kernel ومحمل الكود الثنائي ووقت تشغيل لغة البرمجة. لذلك ، فإن استدعاء بسيط للاستنساخ () يكفي لإنشاء دفق يمكنه الكتابة إلى ملف "Hello world!" ، لكن هذا قد لا يعمل مع رمز أكثر تعقيدًا يحتاج إلى الوصول إلى TLS وأشياء أخرى مثيرة للاهتمام مخفية عن أعين مبرمج التطبيق.


هناك نقطة أخرى مرتبطة بتعدد العمليات وهي العمليات ذات العمليات الفردية. ماذا يحدث إذا قمنا بإنشاء سلسلة رسائل جديدة في عملية لم يتم تصورها على أنها ذات مؤشرات ترابط متعددة؟ صحيح ، سلوك غامض. في الواقع ، في هذه العملية لا يوجد تزامن للعمل بين مؤشرات الترابط ، والتي سوف تؤدي عاجلاً أم آجلاً إلى تلف البيانات. إذا طلبنا أن يستخدم التطبيق libpthread ، فيمكننا التأكد من أنه جاهز للعمل في بيئة متعددة الخيوط (على الأقل يجب أن يكون جاهزًا).


الخطوة 1. اتصال لهذه العملية


أولاً ، نحتاج إلى الاتصال بالعملية المستهدفة لتصحيح الأخطاء ، ثم فيما بعد قطع الاتصال به. هذا هو المكان الذي يأتي فيه استدعاء نظام ptrace ().


أول اتصال مع ptrace ()


في وثائق ptrace () يمكنك أن تجد كل المعلومات الضرورية تقريبًا:


   ربط وفصل
        يمكن ربط الخيط بالتتبع باستخدام المكالمة

            ptrace (PTRACE_ATTACH، pid، 0، 0)؛

        أو

            ptrace (PTRACE_SEIZE، pid، 0، PTRACE_O_flags)؛

        يرسل PTRACE_ATTACH SIGSTOP إلى هذا الموضوع.  إذا كان التتبع يريد هذا
        SIGSTOP ليكون لها أي تأثير ، فإنه يحتاج إلى قمعها.  لاحظ أنه إذا
        يتم إرسال إشارات أخرى بشكل متزامن إلى هذا الموضوع أثناء إرفاق ، و
        قد يرى التتبع التتبع يدخل إشارة توقف التسليم مع sig‐ الأخرى
        nal (s) أولاً!  الممارسة المعتادة هي إعادة هذه الإشارات حتى
        يتم رؤية SIGSTOP ، ثم قمع حقن SIGSTOP.  علة التصميم
        هنا هو أن ptrace نعلق و SIGSTOP تسليمها في وقت واحد قد
        سباق و SIGSTOP المتزامنة قد تضيع.

وبالتالي فإن الخطوة الأولى هي استخدام PTRACE_ATTACH:


 int ptrace_attach(pid_t pid) { /*     */ if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) return -1; /*    */ if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; return 0; } 

بعد ptrace () ، العملية المستهدفة ليست جاهزة تمامًا للتصحيح. لقد قمنا بالاتصال به ، ولكن لدراسة تفاعلية لحالة العملية ، يجب إيقافها. يرسل ptrace () إشارة SIGSTOP إلى العملية ، لكن لا نزال بحاجة إلى الانتظار حتى تتوقف العملية فعليًا.


للانتظار ، استخدم استدعاء النظام waitpid (). في الوقت نفسه ، العديد من الحالات الحدود مثيرة للاهتمام وتجدر الإشارة. أولاً ، قد تنتهي العملية أو تموت ببساطة دون تلقي SIGSTOP. في هذه الحالة ، لا يمكننا فعل شيء. ثانياً ، قد يتم إرسال بعض الإشارات الأخرى مسبقًا إلى العملية. في هذه الحالة ، يجب أن ندع العملية تعالجها (باستخدام PTRACE_CONT) ، وأنفسنا ، نواصل الانتظار أكثر من أجل SIGSTOP:


 static int wait_for_process_stop(pid_t pid, int expected_signal) { for (;;) { int status = 0; /* ,    -  */ if (waitpid(pid, &status, 0) < 0) return -1; /*      —   */ if (WIFSIGNALED(status) || WIFEXITED(status)) return -1; /*   ,     */ if (WIFSTOPPED(status)) { /* *  WSTOPSIG()   , *   ptrace()   *     . */ int stop_signal = status >> 8; /*    ,    */ if (stop_signal == expected_signal) break; /*        */ if (ptrace(PTRACE_CONT, pid, 0, stop_signal) < 0) return -1; continue; } /*   —   */ return -1; } return 0; } 

انقطاع العملية


إيقاف عملية التصحيح أبسط بكثير: ما عليك سوى استخدام PTRACE_DETACH:


 int ptrace_detach(pid_t pid) { if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) return -1; return 0; } 

بالمعنى الدقيق للكلمة ، ليس من الضروري دائمًا تعطيل المصحح بشكل صريح. عندما تنتهي عملية مصحح الأخطاء ، يتم قطع الاتصال تلقائيًا من جميع عمليات تصحيح الأخطاء ، وتستأنف العمليات إذا تم إيقافها بواسطة ptrace (). ومع ذلك ، إذا تم إيقاف عملية تصحيح الأخطاء بشكل صريح بواسطة مصحح الأخطاء باستخدام إشارة SIGSTOP دون استخدام ptrace () ، فلن تستيقظ بدون إشارة SIGCONT أو PTRACE_DETACH المقابلة. لذلك ، من الأفضل الانفصال عن العمليات الثقافية.


إعداد Ptrace_scope


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


لهذه الأسباب ، ولأسباب أمنية ، تعطل الأنظمة عادةً القدرة على تصحيح أي عمليات بشكل افتراضي. وحدة أمان Yama مسؤولة عن ذلك ، تتم إدارتها عبر الملف / proc / sys / kernel / yama / ptrace_scope. يوفر أربعة سلوكيات:


  • 0 - يمكن للمستخدم تصحيح أي عمليات بدأها
  • 1 - الوضع الافتراضي ، فقط العمليات التي بدأها مصحح الأخطاء يمكن تصحيحها
  • 2 - مسؤول النظام الجذر فقط يمكنه تصحيح العمليات
  • 3 - يحظر تصحيح الأخطاء للجميع على الإطلاق ، لا يتم إيقاف تشغيل الوضع حتى يتم إعادة تمهيد النظام

من الواضح ، لأغراضنا ، سيكون من الضروري أن تكون قادرًا على تصحيح العمليات التي تم تشغيلها قبل مصحح الأخطاء الخاص بنا ، لذلك في التجارب ستحتاج إما إلى تبديل النظام إلى وضع التطوير عن طريق كتابة 0 إلى ملف ptrace_scope خاص (يتطلب حقوق المسؤول):


 $ sudo sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope' 

أو قم بتشغيل مصحح الأخطاء كمسؤول:


 $ sudo ./inject-thread ... 

نتائج الخطوة الأولى


نتيجة لذلك ، في الخطوة الأولى ، أصبحنا قادرين على الاتصال بالعملية المستهدفة كمصحح أخطاء ثم قطع الاتصال بها لاحقًا.


سيتم إيقاف العملية المستهدفة ويمكننا التأكد من أن نظام التشغيل ينظر إلينا بالفعل على أنه مصحح أخطاء:


 $ sudo ./inject-thread --target $(pgrep docker) $ cat /proc/$(pgrep docker)/status | head Name: docker State: t (tracing stop) <---    Tgid: 31330 Ngid: 0 Pid: 31330 PPid: 1 TracerPid: 2789 <--- PID   Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 64 $ ps a | grep [2]789 2789 pts/5 S+ 0:00 ./inject-thread --target 31330 

الخطوة 2. البحث في المكتبات في الذاكرة


الخطوة التالية أبسط: عليك أن تجد في ذاكرة العملية المستهدفة المكتبة بالوظائف التي نحتاجها. ولكن هناك الكثير من الذاكرة ، من أين نبدأ بالبحث عن ماذا بالضبط؟


ملف / proc / $ pid / maps


سوف يساعدنا ملف خاص في هذا الأمر ، والذي من خلاله يُخبر النواة عن مكان ومكان العملية في الذاكرة. كما تعلم ، يوجد في الدليل / proc لكل عملية دليل فرعي. وهناك ملف فيه يصف بطاقة ذاكرة العملية:


 $ القط / بروك / الذات / خرائط
 00400000-0040c000 r-xp 00000000 fe: 01 1044592 / bin / cat
 0060b000-0060c000 r - p 0000b000 fe: 01 1044592 / bin / cat
 0060c000-0060d000 rw-p 0000c000 fe: 01 1044592 / bin / cat
 013d5000-013f6000 rw-p 00000000 00:00 0 [كومة الذاكرة المؤقتة]
 7f9920bd1000-7f9920d72000 r-xp 00000000 fe: 01 920019 / lib/x86_64-linux-gnu/libc-2.19.so
 7f9920d72000-7f9920f72000 --- p 001a1000 fe: 01 920019 / lib/x86_64-linux-gnu/libc-2.19.so
 7f9920f72000-7f9920f76000 r - p 001a1000 fe: 01 920019 / lib/x86_64-linux-gnu/libc-2.19.so
 7f9920f76000-7f9920f78000 rw-p 001a5000 fe: 01 920019 / lib/x86_64-linux-gnu/libc-2.19.so
 7fc3f8381000-7fc3f8385000 rw-p 00000000 00:00 0
 7fc3f8385000-7fc3f83a6000 r-xp 00000000 fe: 01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f83ec000-7fc3f840e000 rw-p 00000000 00:00 0
 7fc3f840e000-7fc3f8597000 r - ص 00000000 fe: 01 657286 / usr / lib / locale / locale-archive
 7fc3f8597000-7fc3f859a000 rw-p 00000000 00:00 0
 7fc3f85a3000-7fc3f85a5000 rw-p 00000000 00:00 0
 7fc3f85a5000-7fc3f85a6000 r - p 00020000 fe: 01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f85a6000-7fc3f85a7000 rw-p 00021000 fe: 01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f85a7000-7fc3f85a8000 rw-p 00000000 00:00 0
 7ffdb6f0e000-7ffdb6f2f000 rw-p 00000000 00:00 0 [مكدس]
 7ffdb6f7f000-7ffdb6f81000 r-xp 00000000 00:00 0 [vdso]
 7ffdb6f81000-7ffdb6f83000 r - p 00000000 00:00 0 [vvar]
 ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

يتم إنشاء محتويات هذا الملف أثناء التنقل بواسطة kernel لنظام التشغيل من الهياكل الداخلية التي تصف مناطق الذاكرة الخاصة بالعملية التي تهمنا ، وتحتوي على المعلومات التالية:


  • نطاق العنوان المخصص للمنطقة
  • حقوق الوصول إلى المنطقة
    • r/- : قراءة
    • w/- : الكتابة
    • x/- : التنفيذ
    • p/s : مشاركة الذاكرة مع العمليات الأخرى
  • إزاحة الملف (إن وجد)
  • رمز الجهاز حيث يوجد الملف المعروض
  • رقم الملف inode (إن وجد)
  • المسار إلى الملف المعروض (إن وجد)

يتم تعيين بعض مناطق الذاكرة على الملفات: عندما تقوم إحدى العمليات بقراءة هذه الذاكرة ، فإنها تقوم بالفعل بقراءة البيانات من الملفات المقابلة عند إزاحة محددة. إذا استطعت الكتابة إلى منطقة ما ، فإن التغييرات في الذاكرة يمكن أن تكون مرئية فقط للعملية نفسها (آلية النسخ عند الكتابة أو وضع p خاص) أو متزامنة مع القرص (يتم مشاركة وضع ( s )).


المناطق الأخرى مجهولة - هذه الذاكرة لا تتوافق مع أي ملف. يعطي نظام التشغيل ببساطة العملية قطعة من الذاكرة الفعلية التي يستخدمها. يتم استخدام مثل هذه المناطق ، على سبيل المثال ، لذاكرة العملية "الطبيعية": المكدس و الكومة. يمكن أن تكون المناطق المجهولة شخصية إما لعملية أو يمكن مشاركتها بين عدة عمليات (آلية الذاكرة المشتركة ).


بالإضافة إلى ذلك ، هناك العديد من المناطق الخاصة في الذاكرة المميزة بالأسماء الزائفة [vdso] و [vsyscall]. يتم استخدامها لتحسين بعض مكالمات النظام.


نحن مهتمون بالمناطق التي يتم فيها عرض محتويات ملفات المكتبة. إذا قرأنا بطاقة الذاكرة وقمنا بتصفية الإدخالات الموجودة بها باسم الملف المعروض ، فسنجد جميع العناوين التي تشغلها المكتبات التي نحتاج إليها. تم تصميم تنسيق بطاقة الذاكرة خصيصًا لمعالجة البرنامج ويمكن فهمه بسهولة باستخدام وظائف عائلة scanf ():


 static bool read_proc_line(const char *line, const char *library, struct memory_region *region) { unsigned long vaddr_low = 0; unsigned long vaddr_high = 0; char read = 0; char write = 0; char execute = 0; int path_offset = 0; /*    /proc/$pid/maps */ sscanf(line, "%lx-%lx %c%c%c%*c %*lx %*x:%*x %*d %n", &vaddr_low, &vaddr_high, &read, &write, &execute, &path_offset); /* ,       */ if (!strstr(line + path_offset, library)) return false; /*           */ if (region) { region->vaddr_low = vaddr_low; region->vaddr_high = vaddr_high; region->readable = (read == 'r'); region->writeable = (write == 'w'); region->executable = (execute == 'x'); region->content = NULL; } return true; } 


, libc-2.19.so, :


libc-2.19.so


2 - ? 51? ? ?


, , .


, , . , , , (, , ).


, ( 4 ). , .


, . — — . 2 — , ( x86_64 4 , 2 , 1 ). .



, :


  • libdl: dlopen() dlsym()
  • libpthread: pthread_create() pthread_detach()

, , . Linux ( address space layout randomization , ASLR). (- , ), — - .


, , , /proc/$pid/maps. , .


3. ELF-


, , , .


:


 $ nm -D /lib/x86_64-linux-gnu/libdl-2.19.so | grep dlopen 0000000000001090 T dlopen 

nm . .


- , nm , . , dlsym().



— ELF-, . procfs. UNIX way, /proc/$pid/mem , — ( /proc/$pid/maps).


Linux mmap(), ( , ). :


 static int map_region(pid_t pid, struct memory_region *region) { size_t length = region->vaddr_high - region->vaddr_low; off_t offset = region->vaddr_low; char path[32] = {0}; snprintf(path, sizeof(path), "/proc/%d/mem", pid); /*     */ int fd = open(path, O_RDONLY); if (fd < 0) goto error; /*      */ void *buffer = malloc(length); if (!buffer) goto error_close_file; /*   */ if (read_region(fd, offset, buffer, length) < 0) goto error_free_buffer; region->content = buffer; close(fd); return 0; error_free_buffer: free(buffer); error_close_file: close(fd); error: return -1; } static int read_region(int fd, off_t offset, void *buffer, size_t length) { /*      */ if (lseek(fd, offset, SEEK_SET) < 0) return -1; size_t remaining = length; char *ptr = buffer; /* *     .   , *      ,  . */ while (remaining > 0) { ssize_t count = read(fd, ptr, remaining); if (count < 0) return -1; remaining -= count; ptr += count; } return 0; } 

ELF- . , -, , -, .


ELF


ELF — Linux. , , .


ELF . ELF . — , . , — . ELF-.


, libdl-2.19.so :


libdl-2.19.so


( readelf --headers .)


, , (29 9). — , , . ELF — , . Linux, , LOAD, ( ).


ELF- , . , .


, . «» . .bss, , ( ).


, ELF — , . ...


?


() . , dlsym(), . - .


ELF (. 2-10). , .dynamic , DYNAMIC . .dynamic , :


  • .dynsym — ;
  • .dynstr — ;
  • .hash — -, .

, , ELF:


DYNAMIC


ELF, (1), (2), (3), (4) , .


ELF →


() ELF <elf.h>, , , . , ELF — . 32- 64- , , , . x86_64, ELF .


ELF- ( Elf64_Ehdr ). ( program headers ), e_phoff e_phnum :


ELF


— , , ELF- — , , , , .


e_phoff, , . e_phnum e_phentsize .


( ), ELF — 64 .


→ DYNAMIC


. — Elf64_Phdr ( 64- ELF-), . PT_DYNAMIC p_type :


ELF


:


  • p_vaddr — , ;
  • p_memsz — .

.dynamic 0x2D88 ( ). DYNAMIC — 0x202D88. 0x210 (8448) . .


DYNAMIC → .dynsym, .dynstr, .hash


.dynamic, DYNAMIC, . Elf64_Dyn , :


DYNAMIC


8 d_val d_ptr , 8- d_tag , , . :


  • DT_HASH (4) — .hash ( d_ptr)
  • DT_STRTAB (5) — .dynstr ( d_ptr)
  • DT_SYMTAB (6) — .dynsym ( d_ptr)
  • DT_STRSZ (10) — .dynstr ( d_val)
  • DT_NULL (0) —

. .dynamic : , , , .


, DYNAMIC , . , , - , .


.dynamic , . -, .dynstr , ? .



. , .dynsym , . ( «» .symtab, , , . .)



Elf64_Sym , ELF — , , , . dlopen :


ELF


:


  • st_name — ,
  • st_info — ( )
  • st_value

( , nm , dlopen() .text, 0x1090 .)


, .



— - , . ( ). .dynstr , libdl-2.19.so :


ELF


, ( «dlopen», 0xA5) , . .


-


.hash - , . - — — ELF-, . , .dynsym, , . ( ) - .


- <elf.h>, (. 2-19). - , :


- ELF


حيث


  • nbuckets — buckets
  • nchains — chains ( )
  • buckets —
  • chains —

- :


  1. h .
  2. i buckets[h % nbuckets] , .
  3. ( ) , .
  4. chains[i % nchains] .
  5. 3—4 , .

-, ELF:


 static uint32_t elf_hash(const char *name) { uint32_t h = 0; uint32_t g; while (*name) { h = (h << 4) + *name++; g = h & 0xF0000000; if (g) h ^= g >> 24; h &= ~g; } return h; } 

, "dlopen" - 112420542 :



libdl — , 39 , . - .



, :


  • dlopen() dlsym() libdl
  • pthread_create() pthread_detach() libpthread

, .


. . , .


ELF- . , ( ). , . , , . .


4. -


, , - , : , . - .


-


, -:


 void shellcode(void) { void *payload = dlopen("/path/to/payload.so", RTLD_LAZY); void (*entry)(void) = dlsym(payload, "entry_point"); pthread_t thread; pthread_create(&thread, NULL, entry, NULL); pthread_detach(thread); } 

?


, — . , , , - — - ! .


— - . , , : .


 /* *      .rodata:   * .         , *        . */ .section .rodata /* *   .       . *      -:    ,  *  ,       . */ .global shellcode_start .global shellcode_address_dlopen .global shellcode_address_dlsym .global shellcode_address_pthread_create .global shellcode_address_pthread_detach .global shellcode_address_payload .global shellcode_address_entry .global shellcode_end /* *   dlopen().     #include <dlfcn.h>, *       . */ .set RTLD_LAZY, 1 .align 8 shellcode_start: /* * void *payload = dlopen(shellcode_address_payload, RTLD_LAZY); * *        x86_64: * * -     %rdi, %rsi, %rdx, %rcx * -     %rax * -      * *         . * *       %rax,    *     . */ lea shellcode_address_payload(%rip),%rdi mov $RTLD_LAZY,%rsi mov shellcode_address_dlopen(%rip),%rax callq *%rax /* * void (*entry)(void) = dlsym(payload, shellcode_address_entry); */ mov %rax,%rdi lea shellcode_address_entry(%rip),%rsi mov shellcode_address_dlsym(%rip),%rax callq *%rax /* * pthread_t thread; * pthread_create(&thread, NULL, entry, NULL); * *            * ,     pthread_create(). */ sub $8,%rsp mov %rsp,%rdi xor %rsi,%rsi mov %rax,%rdx xor %rcx,%rcx mov shellcode_address_pthread_create(%rip),%rax callq *%rax /* * pthread_detach(thread); * *    ,   ,  *     . */ mov (%rsp),%rdi add $8,%rsp mov shellcode_address_pthread_detach(%rip),%rax callq *%rax /* *   - —    ,     *      ret.    *     ,  *      . */ int $3 /* *       ,   *   ,    - *     .   “  *  ” (global offset table, GOT),   *           . */ .align 8 shellcode_address_dlopen: .space 8 shellcode_address_dlsym: .space 8 shellcode_address_pthread_create: .space 8 shellcode_address_pthread_detach: .space 8 shellcode_address_payload: .space 256 shellcode_address_entry: .space 256 /* *  - . */ shellcode_end: .end 

, . :


 $ as -o shellcode.o shellcode.S 

, , , . : (procedure linkage table, PLT), .


- , (, ) . - .


-


- . , , , . ?


-


, . , . , . , .


(- ), : , , . , , JIT- , . ?



:


  • - ,
  • - ,

, . -, - , . -, . -, , - -, .


, . . x86_64 int $3 — 0xCC — . ptrace() PTRACE_POKETEXT — , 8 , . , , .


, , , : . - , .


?


, ! malloc()!


. , -, . . , mmap():


 void inject_shellcode(const void *shellcode_src, size_t shellcode_size) { void *shellcode_dst = mmap(NULL, shellcode_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); copy_shellcode(shellcode_dst, shellcode_src, shellcode_size); } 

, ptrace() , .



, ? , . Linux x86_64 :


  • %rax
  • — — %rsi, %rdi, %rdx, %r10, %r8, %r9
  • SYSCALL,
  • %rax

ptrace() PTRACE_GETREGS PTRACE_SETREGS. , . - SYSCALL.


: , %rip. , , SYSCALL.


SYSCALL


SYSCALL? , . - , - . — libc. , , , :


 unsigned long find_syscall_instruction(struct library *library) { for (size_t i = 0; i < library->region_count; i++) { struct memory_region *region = &library->regions[i]; if (!(region->readable && region->executable)) continue; const uint8_t *region_data = region->content; size_t region_size = region->vaddr_high - region->vaddr_low; if (region_size < 2) continue; /* * 0F 05 syscall */ for (size_t offset = 0; offset < region_size - 1; offset++) { if (region_data[offset + 0] == 0x0F && region_data[offset + 1] == 0x05) { return region->vaddr_low + offset; } } } return 0; } 

, /proc/$pid/maps . x86_64 , - . , 0x0F 0x05. , , ARM, 0xDF 0x00 ( SVC #0), .


PTRACE_{GET,SET}REGS


:


 int get_registers(pid_t pid, struct user_regs_struct *registers) { int err = 0; if (ptrace(PTRACE_GETREGS, pid, registers, registers) < 0) err = -errno; return err; } 

struct user_regs_struct , <sys/user.h>. . . , varargs :


 static int set_regs_for_syscall(struct user_regs_struct *registers, unsigned long syscall_insn_vaddr, long syscall_number, int args_count, va_list args) { registers->rip = syscall_insn_vaddr; registers->rax = syscall_number; for (int i = 0; i < args_count; i++) { switch (i) { case 0: registers->rdi = va_arg(args, long); break; case 1: registers->rsi = va_arg(args, long); break; case 2: registers->rdx = va_arg(args, long); break; case 3: registers->r10 = va_arg(args, long); break; case 4: registers->r8 = va_arg(args, long); break; case 5: registers->r9 = va_arg(args, long); break; default: return -E2BIG; } } return 0; } static long perform_syscall(pid_t pid, unsigned long syscall_insn_vaddr, long syscall_number, int args_count, ...) { struct user_regs_struct old_registers; struct user_regs_struct new_registers; /* *    ,   *      . */ get_registers(pid, &old_registers); /* *      ,   * ,     . */ new_registers = old_registers; va_list args; va_start(args, args_count); set_regs_for_syscall(&new_registers, syscall_insn_vaddr, syscall_number, args_count, args); va_end(args); set_registers(pid, &new_registers); /* *    ,    *   ,    * (  ),    . *     . */ wait_for_syscall_completion(pid); /* *       *    . *        . */ get_registers(pid, &new_registers); long result = new_registers.rax; set_registers(pid, &old_registers); return result; } 

PTRACE_SYSCALL


: , ?


PTRACE_SYSCALL. PTRACE_CONT, . , - : , .


PTRACE_SYSCALL SIGTRAP : ( ) ( ). , ptrace() , , .


, SIGTRAP:


 static int wait_for_syscall_enter_exit_stop(pid_t pid) { if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) return -1; if (wait_for_process_stop(pid, SIGTRAP) < 0) return -1; return 0; } void wait_for_syscall_completion(pid_t pid) { wait_for_syscall_enter_exit_stop(pid); wait_for_syscall_enter_exit_stop(pid); } 

— , — (wait_for_process_stop() ). . , .


PTRACE_O_TRACESYSGOOD


, PTRACE_SYSCALL : , , - . , SIGTRAP ( ).


SIGTRAP . PTRACE_O_TRACESYSGOOD, :


  • SIGTRAP — -
  • SIGTRAP | 0x80 —

  int ptrace_attach(pid_t pid) { if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) return -1; if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; + /*     */ + unsigned long options = PTRACE_O_TRACESYSGOOD; + if (ptrace(PTRACE_SETOPTIONS, pid, 0, options) < 0) + return -1; return 0; } static int wait_for_syscall_enter_exit_stop(pid_t pid) { if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) return -1; - if (wait_for_process_stop(pid, SIGTRAP) < 0) + if (wait_for_process_stop(pid, SIGTRAP | 0x80) < 0) return -1; return 0; } 

-


- :


 void write_shellcode(void) { char shellcode_text[SHELLCODE_TEXT_SIZE]; size_t shellcode_size = shellcode_end - shellcode_start; /*   ,  ,  . . */ prepare_shellcode(shellcode_text, shellcode_size); /*   -   */ write_remote_memory(target, shellcode_text_vaddr, shellcode_text, shellcode_size); } 

- : dlopen(), .


 static inline void copy_shellcode(char *shellcode_text, const char *shellcode_addr, const void *data, size_t length) { ptrdiff_t offset = shellcode_addr - shellcode_start; memcpy(shellcode_text + offset, data, length); } static void prepare_shellcode(char *shellcode_text, size_t shellcode_size) { copy_shellcode(shellcode_text, shellcode_start, shellcode_start, shellcode_size); copy_shellcode(shellcode_text, shellcode_address_dlopen, &dlopen_vaddr, sizeof(dlopen_vaddr)); copy_shellcode(shellcode_text, shellcode_address_dlsym, &dlsym_vaddr, sizeof(dlsym_vaddr)); copy_shellcode(shellcode_text, shellcode_address_pthread_create, &pthread_create_vaddr, sizeof(pthread_create_vaddr)); copy_shellcode(shellcode_text, shellcode_address_pthread_detach, &pthread_detach_vaddr, sizeof(pthread_detach_vaddr)); copy_shellcode(shellcode_text, shellcode_address_payload, payload, sizeof(payload)); copy_shellcode(shellcode_text, shellcode_address_entry, entry, sizeof(entry)); } 

, , -:


 extern const char shellcode_start[]; extern const char shellcode_address_dlopen[]; extern const char shellcode_address_dlsym[]; extern const char shellcode_address_pthread_create[]; extern const char shellcode_address_pthread_detach[]; extern const char shellcode_address_payload[]; extern const char shellcode_address_entry[]; extern const char shellcode_end[]; 

, .


- . /proc/$pid/mem, :


 int write_remote_memory(pid_t pid, unsigned long vaddr, const void *data, size_t size) { char path[32] = {0}; snprintf(path, sizeof(path), "/proc/%d/mem", pid); /*       */ int fd = open(path, O_WRONLY); if (fd < 0) return -1; /*     */ if (lseek(fd, vaddr, SEEK_SET) < 0) { close(fd); return -1; } /*    */ int err = do_write_remote_memory(fd, data, size); close(fd); return err; } static int do_write_remote_memory(int fd, const void *data, size_t size) { size_t left = size; /* *    ,  ,     *   ,       *      . */ while (left > 0) { ssize_t wrote = write(fd, data, left); if (wrote < 0) return -1; data += wrote; left -= wrote; } return 0; } 


, - — « » . . - , .


5.


- . , : %rip -, PTRACE_SETREGS, PTRACE_CONT . .


, , . -? ?



, . , « » . , . :


  • (async-signal-safe)

— . dlopen() pthread_create() . - dlopen(), dlopen() ?


-, , , . , pthread_create() . , ( ). clone().


pthread_create()?

, - , ?

: clone().

, (libc) (pthread). clone() (thread control block, TCB) (thread-local storage, TLS), , . . pthread_create() , .

«», clone() libc pthread. , .


clone() :


  • ?
  • ?
  • -?


: -?


, - : , , , .


. , , . ? : exit(). , .


. exit() -:


 +.set __NR_exit, 60 .set RTLD_LAZY, 1 @@ - /* - *  . - */ - int $3 + /* + * exit(0); + */ + xor %rdi,%rdi + mov $__NR_exit,%rax + syscall 

: exit() — exit() . exit() , exit() — . Linux exit_group().



. . , , PROT_EXEC:


 shellcode_stack_vaddr = remote_mmap(target, syscall_vaddr, 0, SHELLCODE_STACK_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN, -1, 0); 

, Linux x86_64 — «» , . mmap() , clone() . , mmap() MAP_GROWSDOWN, , .


PTRACE_O_TRACECLONE


. , - . waitpid(), : , .


— PTRACE_O_TRACECLONE. . , . , , , . , PTRACE_ATTACH , .


-, :


 - unsigned long options = PTRACE_O_TRACESYSGOOD; + unsigned long options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE; if (ptrace(PTRACE_SETOPTIONS, pid, 0, options) < 0) return -1; 

-, clone(), PTRACE_EVENT_CLONE, , PTRACE_SYSCALL. :


 -void wait_for_syscall_completion(pid_t pid) +void wait_for_syscall_completion(pid_t pid, long syscall) { wait_for_syscall_enter_exit_stop(pid); + + /*  clone()   PTRACE_EVENT_CLONE */ + if (syscall == __NR_clone) + wait_for_clone_event(pid); wait_for_syscall_enter_exit_stop(pid); } 

:


 static int wait_for_clone_event(pid_t pid) { if (ptrace(PTRACE_CONT, pid, 0, 0) < 0) return -1; int event = SIGTRAP | (PTRACE_EVENT_CLONE << 8); if (wait_for_process_stop(pid, event) < 0) return -1; return 0; } 

clone() PID , . :


 void clear_ptrace_options(pid_t pid) { ptrace(PTRACE_SETOPTIONS, pid, 0, 0); } 

, clone() ptrace(), PTRACE_O_TRACECLONE. , , - .



, - . clone() :


 static int spawn_shell_thread() { shell_tid = remote_clone(target, syscall_ret_vaddr, CLONE_FILES | CLONE_FS | CLONE_IO | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD | CLONE_VM, /*   **  */ shellcode_stack_vaddr + SHELLCODE_STACK_SIZE); if (!shell_tid) return -1; return 0; } 

clone() : , , , . , .


CLONE_FILES, CLONE_FS, CLONE_IO, CLONE_SIGHAND, CLONE_SYSVSEM, CLONE_VM — . , CLONE_FILES , ( fork()). — — , . . , CLONE_VM , , .


CLONE_THREAD : Linux — « », . , , getpid() , kill() — - , execve() — , .


, clone() fork(): , . clone() : , — . . ( , , .)


, pthread_create() , , . ?



fork() :


 pid_t child = fork(); if (child < 0) { /* fork() ,    */ } if (child == 0) { /*     execve() */ } /*     */ 

, . clone() . .


. , clone() , . syscall ret, , . .


SYSCALL + RET


, . , syscall ret:


 -if (region_size < 2) +if (region_size < 3) continue; /* * 0F 05 syscall + * C3 retq */ -for (size_t offset = 0; offset < region_size - 1; offset++) { +for (size_t offset = 0; offset < region_size - 2; offset++) { if (region_data[offset + 0] == 0x0F && - region_data[offset + 1] == 0x05) + region_data[offset + 1] == 0x05 && + region_data[offset + 2] == 0xC3) { return region->vaddr_low + offset; } } 

, .



. prepare_shellcode() , , :


  void write_shellcode(void) { char shellcode_text[SHELLCODE_TEXT_SIZE]; size_t shellcode_size = shellcode_end - shellcode_start; /*   ,  ,  . . */ prepare_shellcode(shellcode_text, shellcode_size); /*   -   */ write_remote_memory(target, shellcode_text_vaddr, shellcode_text, shellcode_size); + /*    «»   */ + unsigned long retaddr_vaddr = + shellcode_stack_vaddr + SHELLCODE_STACK_SIZE - 8; + write_remote_memory(target, retaddr_vaddr, + &shellcode_text_vaddr, sizeof(shellcode_text_vaddr)); } 

, , .


, , . System V ABI , ( %rsp) 16 . shellcode_stack_vaddr + SHELLCODE_STACK_SIZE : ( 4096 ), 1 . 8 , , retq, - . - :


 - sub $8,%rsp + sub $16,%rsp /*   */ mov %rsp,%rdi xor %rsi,%rsi mov %rax,%rdx xor %rcx,%rcx mov shellcode_address_pthread_create(%rip),%rax callq *%rax 

, %rsp 16 pthread_create(). SIGSEGV, — pthread_create() , .



, - , clone():


  static int spawn_shell_thread() { shell_tid = remote_clone(target, syscall_ret_vaddr, CLONE_FILES | CLONE_FS | CLONE_IO | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD | CLONE_VM, /*   **  */ - shellcode_stack_vaddr + SHELLCODE_STACK_SIZE); + shellcode_stack_vaddr + SHELLCODE_STACK_SIZE - 8); if (!shell_tid) return -1; return 0; } 

ptrace() SIGSTOP, :


 int ignore_thread_stop(pid_t pid) { return wait_for_process_stop(pid, SIGSTOP); } 

. ptrace():


 void resume_thread(pid_t pid) { ptrace(PTRACE_CONT, pid, 0, 0); } 


, , , exit(). waitpid(). — CLONE_THREAD wait() ,— PTRACE_O_TRACECLONE, :


 int wait_for_process_exit(pid_t pid) { int status = 0; if (waitpid(pid, &status, 0) < 0) return -1; if (!WIFEXITED(status)) return -1; return WEXITSTATUS(status); } 

pthread , , pthread_join() pthread , . , — . , , .


ذاكرة حرة


, - . , - , munmap():


 void remote_munmap(pid_t pid, unsigned long syscall_insn_vaddr, unsigned long addr, size_t len) { perform_syscall(pid, syscall_insn_vaddr, __NR_munmap, 2, (long) addr, (long) len); } static void unmap_shellcode() { remote_munmap(target, syscall_ret_vaddr, shellcode_text_vaddr, SHELLCODE_TEXT_SIZE); remote_munmap(target, syscall_ret_vaddr, shellcode_stack_vaddr, SHELLCODE_STACK_SIZE); } 

, , , — ptrace() . (, SIGSTOP), , ( ):


 int stop_thread(pid_t pid) { if (kill(pid, SIGSTOP) < 0) return -1; if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; return 0; } 


, , . PTRACE_DETACH:


 int ptrace_detach(pid_t pid) { if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) return -1; return 0; } 


, . , . , .


استنتاج


? . , , .


Gnome Control Center


Linux . GTK+ . , make:


 libpayload.so: payload.c $(CC) $(CFLAGS) $(shell pkg-config --cflags --libs gtk+-3.0) -shared -o $@ $< 

entry() GTK- — GTK UI , :


 #include <glib.h> #include <gtk/gtk.h> static gboolean actual_entry(gpointer _arg) { /*       : */ hook_gtk_entry_constructor(); /*   FALSE,       */ return FALSE; } void entry(void) { /*    -,   */ g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, actual_entry, NULL, NULL); } 

, GTK , GtkEntry . "input-purpose" . «», , .


GTK glib — — GtkEntry . constructed(), . :


 static void (*old_gtk_entry_constructed)(GObject *object); static void new_gtk_entry_constructed(GObject *object) { GtkEntry *entry = GTK_ENTRY(object); /*    */ old_gtk_entry_constructed(object); /*    ,  ,   entry */ } static void hook_gtk_entry_constructor(void) { /*     GtkEntry */ GTypeClass *entry_type_class = g_type_class_peek(GTK_TYPE_ENTRY); GObjectClass *entry_object_class = G_OBJECT_CLASS(entry_type_class); /* *     "constructed"     . */ old_gtk_entry_constructed = entry_object_class->constructed; entry_object_class->constructed = new_gtk_entry_constructed; } 

GtkEntry :


  • , ,

, GtkEntry , , . , :


 static void new_gtk_entry_constructed(GObject *object) { GtkEntry *entry = GTK_ENTRY(object); old_gtk_entry_constructed(object); /*       */ g_signal_connect(entry, "notify::input-purpose", G_CALLBACK(input_purpose_changed), NULL); /*      */ g_signal_connect(entry, "icon-press", G_CALLBACK(icon_pressed), NULL); /*      */ g_signal_connect(entry, "icon-release", G_CALLBACK(icon_released), NULL); } 

. , . .


 static void input_purpose_changed(GtkEntry *entry) { GtkInputPurpose purpose = gtk_entry_get_input_purpose(entry); if (purpose == GTK_INPUT_PURPOSE_PASSWORD) { gtk_entry_set_icon_activatable(entry, GTK_ENTRY_ICON_PRIMARY, TRUE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-remove"); } else { gtk_entry_set_icon_activatable(entry, GTK_ENTRY_ICON_PRIMARY, FALSE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, NULL); } } 

: , , , - , :


 static void icon_pressed(GtkEntry *entry, GtkEntryIconPosition position) { if (position != GTK_ENTRY_ICON_PRIMARY) return; gtk_entry_set_visibility(entry, TRUE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-add"); } static void icon_released(GtkEntry *entry, GtkEntryIconPosition position) { if (position != GTK_ENTRY_ICON_PRIMARY) return; gtk_entry_set_visibility(entry, FALSE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-remove"); } 

هذا كل شيء.


GitHub (GPLv2).


, . gdb :


 $ gdb --pid $(pgrep target) \ --batch \ -ex 'compile file -raw shell-code.c' 

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


All Articles