Shellcode
=====================================================
هو مصطلح يطلق على الـ Code المستخدم بداخل الـ Exploit (يرجى مراجعة سلسلة Buffer Overflow) والذي يمكن المهاجم من التحكم بجهاز الضحية بصورة أو بأخرى وسمي Shellcode تحديدا لأنه عند تنفيذه على جهاز الضحية يقوم بإعطاء المهاجم Shell Access (وهي CMD في حالة الـ Windows و Terminal في حالة الـ Linux) والتي عن طريقها يستطيع المهاجم التحكم بالكامل في جهاز الضحية وهذه التسمية اصطلاحية. لكن في الحقيقة Shellcode قد يقوم بأي مهمة أخرى غير Shell Access. على سبيل المثال يمكن كتابة Shellcode عند تنفيذه على جهاز الضحية يقوم بإنشاء User جديد يتم إضافته إلى Administrators Group بحيث يتمكن المهاجم لاحقا من الدخول إلى جهاز الضحية وقتما شاء (يرجى مراجعة CTF 0x05 كمثال عملي على ذلك)
ولكن ما هو شكل الـ Shellcode في الحقيقة ؟ هذا مثال لـ Shellcode ممثل باستخدام Hex Format عند تنفيذه سيتم تشغيل برنامج Calculator:
"\x31\xC9\x51\x68\x63\x61\x6C\x63\x54\xB8\xC7\x93\xC2\x77\xFF\xD0"
ملحوظة: يمكنك مراجعة هذا الموقع لرؤية الأنواع المختلفة من الـ Shellcode وما يمكن تنفيذه من أوامر.
من المعلوم أن الـ Computer / CPU لا يفهم غير Binary والتي تتكون من 0 و 1 فقط. لذلك عند كتابة أي برنامج بلغة ما مثل C , نقوم باستخدام Compiler لتحويل الـ C Code إلى Machine Code يستطيع الـ Computer / CPU فهمه وتطبيقه مباشرة. بمعنى آخر، الـ Compiler ما هو إلا وسيط يقوم بتحويل الـ Source Code الذي نقوم بكتابته إلى Machine Code ليقوم الـ Computer / CPU بتنفيذه…... فالـ Machine Code هي اللغة الوحيدة التي يفهمها الـ Computer / CPU.
ولكن ما علاقة ذلك بالـ Shellcode ؟
الحقيقة أن الـ Shellcode هو نفسه Machine Code ولكن أطلق عليه لقب Shell للعرف فقط كما ذكرنا سابقا. لذلك كتابة Shellcode ماهي إلا كتابة Machine Code
و يبقى السؤال: كيف تمت كتابته؟
الإجابة أن كتابته صعبة و كذلك قراءته لأنه ممثل بصورة بدائية (Binary / Hex) يصعب على الإنسان تذكرها. في الحقيقة ذلك هو السبب في وجود لغات البرمجة المختلفة فنجد من السهل قراءة الـ Code المكتوب بصيغة C أو Java أو Pascal ولكن من الصعب علينا أن نقرأ الـ Machine Code ونفهم ما الذي يقوم بفعله.
لذلك عند الحاجة لكتابة Shellcode نقوم بكتابته بأبسط صيغة / لغة نستطيع قراءتها وكتابتها و فهمها بسهولة وهي الـ Assembly و من ثم تحويله/ترجمته إلى Machine Code - Hex Format ثم نستخدمه بصورة مباشرة داخل الـ Exploit ليستطيع الـ Computer / CPU تنفيذه بصورة مباشرة و سنتعرض لذلك لاحقا.
Assembly
=====================================================
هي low level programming language ويطلق عليها asm اختصارا وسميت low level لأنها أقرب للـ CPU من غيرها من اللغات الأخرى مثل C و JAVA كما يطلق عليها أيضا "close to the hardware" وهذا هو السبب الرئيسي في اختيارنا لها عند الحاجة لكتابة Shellcode.
في الحقيقة ، كل أمر في لغة Assembly (يطلق عليه mnemonic) يتم ترجمته إلى ما يسمى Opcode وهي Machine Code ممثل بصيغة Hex (وهي ما يتم استخدامها داخل الـ Exploit والتي نطلق عليها Shellcode) و تحول بعد ذلك من Hex إلى Binary ليتم تنفيذها بواسطة Computer / CPU. يمكننا مشاهدة ذلك بوضوح داخل الـ Debugger كما يلي
[Immunity Debugger Display of Mnemonics & Opcodes]
سنجد على اليمين أوامر الـ Assembly وعلى اليسار ما يعادلها من Opcodes. أيضا يوجد أداة داخل Metasploit تقوم بترجمة Assembly إلى Opcode يطلق عليها metasm والتي نستخدمها في بعض الأحيان خلال عملية كتابة الـ Shellcode
[Metasm Tool]
الخلاصة أن أي Source Code نقوم بكتابته يتم ترجمته في النهاية إلى Assembly (والذي يتكون من مجموعة من Mnemonics), بعد ذلك يتم ترجمته إلى Machine Code ممثل بصيغة Hex ( يطلق عليه Opcode) ، ثم في النهاية يتم تحويله من Hex إلى Binary ليتم تنفيذه بواسطة الـ Computer / CPU
=====================================================
Registers
=====================================================
هي وحدات تخزينية صغيرة موجودة بداخل الـ Processor تستخدم خلال العمليات الحسابية التي تتطلب سرعة عالية ويوجد منها أنواع عديدة ولكننا سنتعرض لأنواع محددة منها وهي:
EAX = Accumulator Register
EBX = Base Register
ECX = Counter Register
EDX = Data Register
ESI = Source Register
EDI = Destination
EIP = Instruction Pointer
EBP = Base Pointer
ESP = Stack Pointer
السعة التخزينية لكل Register منهم هي 32bits. و يتميز EAX,EBX,ECX,EDX عن غيرهم من الـ Registers بأنه يمكن استخدام الـ Register بأكمله أو جزء منه خلال العمليات الحسابية. فمثلا EAX يتكون من 32bits ولكن يمكن تقسيمه إلى نصفين كل منهما 16bits. كما يمكن تقسيم أحد النصفين إلى نصفين آخرين كل منهما 8bits.
[EAX Register Structure]
فيما يلي نبذة سريعة عن وظيفة كل Register
-الـEAX: يستخدم للاحتفاظ بالـ Return Value في حالة استخدام Functions كما يستخدم في العمليات الحسابية كالجمع والطرح.
-الـEBX: ليس له وظيفة محددة ويمكن استخدامه في سياقات متعددة كمكان للاحتفاظ بالـ .Data
-الـECX: يستخدم ككـ Counter في العمليات الحسابية التي يوجد بها تكرار مثل الـ Loops.
-الـEDX: يعتبر قرين للـ EAX فيستخدم في عمليات الضرب والقسمة كما يستخدم للاحتفاظ بالـ Function Variables.
-الـESI: يستخدم للاحتفاظ بعنوان الذاكرة الذي سيتم قراءة شئ ما منه.
-الـEDI: يستخدم للاحتفاظ بعنوان الذاكرة الذي سيتم كتابة شئ ما فيه. كمثال إذا أردنا نقل string من مكان لآخر، عندها سنستخدم ESI للاحتفاظ بالـ Memory Address الذي يوجد به الـ String حاليا ، ويستخدم EDI للاحتفاظ بالـ Memory Address الذي سيتم نقل الـ String إليه.
-الـEBP: يستخدم للاحتفاظ بالـ Memory Address الذي يشير إلى قاع الـ Stack.
-الـESP: يستخدم للاحتفاظ بالـ Memory Address الذي يشير إلى قمة Stack.
-الـEIP: يستخدم للاحتفاظ بالـ Memory Address الذي يشير إلي الـ Instruction القادمة التي سيقوم الـ Processor بتنفيذها.
=====================================================
Memory
=====================================================
بداية يجب علينا معرفة أن الـ Operating System يقوم بتقسيم الـ Memory إلى جزئين أساسيين وهما Kernel Space و User Space. كما يتضح من الإسم ، الجزء الأول مخصص لتحميل و تشغيل الـ Kernel والجزء الثاني مخصص لتحميل و تشغيل أي شئ آخر مثل البرامج التي يقوم الـ User باستخدامها
The kernel is a computer program that is the core of a computer's operating system, with complete control over everything in the system
يستطيع الـ Kernel التحكم في أي جزء من الـ Memory بما في ذلك الـ User Space. ولكن العكس غير مسموح به فلا يستطيع أي برنامج من الـ User Space التحكم بأي شئ داخل Kernel Space.
لنلقي نظرة سريعة الأن على شكل الذاكرة وكيف يتم تقسيمها عند تشغيل أي برنامج على نظام Linux 32 bit :
[Linux 32bit Memory Layout]
كما نرى فإن الذاكرة مقسمة لـ Kernel Space و User Space وكما ذكرنا فإن الأخير هو المسئول عن البرامج التي يقوم الـ User بتشغيلها.
هذا العزل / التقسيم لحماية نظام التشغيل من أي شئ يتم تشغيله بواسطة الـ User. تخيل ما الذي سيحدث إذا ترك الأمر للـ User للتحكم في الـ Code الخاص بالـ Operating System الموجود داخل الـ Kernel ؟ قد تكون النتائج كارثية ويحدث crash للـ Operating System بين الحين والآخر.
ولكن ما الذي يحدث إذا أراد برنامج يعمل في الـ User Space التواصل مع الـ Kernel وطلب بعض المهام منه ؟
يتم ذلك عن طريق استخدام ما يعرف بالـ System Calls. وهي الواجهة / نقطة الوصل بين User Space و Kernel Space. فلا يوجد سبيل لأي شئ موجود داخل User Space بالتواصل مع Kernel Space إلا عن طريق System Calls
A system call is the programmatic way in which a computer program requests a service from the kernel of the operating system.
مثال سريع يلخص ما سبق; عندما نقوم بكتابة برنامج بلغة C مثلا يقوم بطباعة Hello World. عند تشغيل هذا البرنامج سيتم تحميله داخل الذاكرة في الـ User Space. ولكن البرنامج يتضمن عملية طباعة وهذه العملية المسئول عنها نظام التشغيل أو بالادق الـ Kernel, عندها سيقوم البرنامج باستخدام System Call (في حالة Linux) ليخبر الـ Kernel أن يقوم بعملية الطباعة وسيقوم الـ Kernel بتنفيذ المهمة والرجوع للبرنامج مرة أخرى في الـ User Space وإخباره بالنتيجة.
قد يسأل البعض ولكن أنا لم أستخدم سوى عبارة Printf فقط داخل البرنامج ولم أتعرض لأي شئ مما سبق ، والإجابة أن ما شرحناه سابقا هو ما يحدث داخليا. فعند استخدامك لـ Printf يتم استدعاء syscall يسمى () write وهو ما ينفذ عملية الطباعة في الحقيقة.
أما في حالة الـ Windows فيوجد ما يسمى بـ Windows API بدلا من الـ System Call ، وهي الطريقة التي نستخدمها إذا أردنا من نظام التشغيل تنفيذ إحدى المهام المسئول عنها مثل الطباعة كما في المثال السابق. لا داعي للقلق الآن إذا لم تتضح الصورة بشكل كامل فسنتعرض لذلك بأمثلة عملية توضح ذلك بالتفصيل إن شاء الله.
الخلاصة أن المهام المسئول عنها نظام التشغيل عديدة ولاستخدام أي منها (عند كتابة الـ Shellcode باستخدام Assembly) لابد من استخدام System Call في حالة الـ Linux أو Windows API في حالة الـ Windows.
الجدول التالي يوضح مجموعة من أهم Linux System Calls وما يعادلها من Windows API's والوظيفة التي يقوم بها كل منهم.
بعد أن تعرفنا سريعا على بعض المفاهيم المطلوبة لكتابة الـ Shellcode , سنواصل في الدرس القادم ان شاء الله بالبدء في كتابة Shellcode بسيط يقوم بطباعة Hello World على الشاشة.
References:
http://www.linfo.org/machine_code.html
https://en.wikipedia.org/wiki/Low-level_programming_language
https://blog.malwarebytes.com/security-world/2012/09/so-you-want-to-be-a-malware-analyst/
http://what-when-how.com/pic-microcontroller/high-level-language-part-1-pic-microcontroller/
https://en.wikipedia.org/wiki/Kernel_(operating_system)
http://www.linfo.org/kernel_space.html