02 - x86 Shellcoding | Linux Shellcode Part 2

How to write linux shellcode

  1. Eng.Nour
    =====================================================
    JMP-CALL-POP Shellcode
    =====================================================​



    الطريقة الثانية JMP - CALL - POP:
    في الطريقة الأولى قمنا بتحميل الـ String في الـ Stack بأنفسنا ولكن في هذه الطريقة ، سنجعل البرنامج يقوم بهذه المهمة بدلا من ويتم ذلك عن طريق الاستفادة من إحدى خصائص الـ CALL Instruction.
    من المعلوم أن الـ CPU يقوم بتنفيذ أوامر البرنامج أمر تلو الآخر حتى يقابل إحدى الأوامر التي تتضمن توجيه الـ Execution لمنطقة أخرى من البرنامج مثل JMP و CALL. في حالة الـ CALL في المثال التالي يحدث ما يلي:


    11.png


    -أولا: يقوم البرنامج بالاحتفاظ بعنوان الذاكرة الذي يوجد به الأمر التالي للـ CALL Instruction (وهو 004011DD) لكي يستطيع العودة إليه مرة أخرى عندما ينتهي من تنفيذ الـ CALL. يتم ذلك عن طريق عمل PUSH لهذا العنوان في الـ Stack
    -ثانيا: يقوم البرنامج بتوجيه الـ Execution إلى الـ CALL وتنفيذ ما بداخله من أوامر.
    -ثالثا: عند الانتهاء من تنفيذ الـ CALL سيقوم البرنامج بتنفيذ أمر RETURN والذي سيقوم بإعادة توجيه الـ execution إلى العنوان المحتفظ به مسبقا في الـ Stack. يتم ذلك عن طريق عمل POP لهذا العنوان من الـ Stack.

    بعد أن مررنا سريعا على طريقة عمل الـ Call Instruction, لنرى كيف يمكننا الإستفادة من طريقة عملها في تحميل الـ String المراد طباعته في الـ Stack بطريقة أتوماتيكية داخل الـ Shellcode. سنقوم بتعديل الـ Code ليصبح كما يلي:


    13.png


    -أولا: سيقوم البرنامج بتنفيذ أول أمر وهو JMP وبناءا عليه سيتم الإنتقال إلى GetStringAddress لتنفيذ ما بداخله من أوامر.
    -ثانيا: عند تنفيذ أول أمر (وهو Call GetStringAddressReturn) سيتم الانتقال مرة أخرى إلى الأعلى ولكن النقطة المهمة هنا هي استخدامنا للـ Call Instruction ، والتي كما ذكرنا تقوم بالاحتفاظ بالـ Memory Address الخاص بالأمر الذي يليها في الـ Stack للعودة إليه عند الإنتهاء من الـ Call.
    إذا سيتم عمل PUSH للعنوان الذي يلى الـ CALL وهو نفسه العنوان الذي يوجد به الـ String المراد طباعته وسيصبح هذا العنوان في قمة الـ Stack ويمكننا استرجاعه متى أردنا عن طريق POP وهو فعلا ما قمنا بتنفيذه في POP ECX وبالتالي أصبح الـ ECX به عنوان الـ String المراد طباعته.

    لنقم بتجربة هذا الـ Code كما سبق (nasm-->ld-->objudmp) سنجد أن الـ Shellcode الناتج لا يوجد به Null Bytes وأيضا يحتوى على الـ String المراد طباعته كما نرى


    14.png


    لنختبر الـ Shellcode داخل الـ ShellcodeTester.c


    15.png


    كما نرى فإن الـ Shellcode يعمل بشكل جيد ويمكننا استخدامه حيثما أردنا.



    =====================================================
    "/bin/sh" Shellcode
    =====================================================​



    الآن لننتقل إلى شئ آخر أكثر واقعية مثل كتابة Shellcode يقوم بفتح Shell. هل تتذكر ما وضحناه من أن السبب في تسمية المصطلح Shellcode أنه يقوم بإعطاء Shell Access ؟ لنقم بتجربة ذلك عمليا. هذه المرة سنستخدم Syscall يدعى execve وكما تعودنا سنستخدم man 2 execve لفهم كيفية عمله. يتضح لنا أن وظيفة هذا الـ Syscall هي "execute a program" ويحتاج إلى ثلاث Pointers ليعمل:

    -الأول const char *filename وهو عنوان الذاكرة الذي يتواجد به String يمثل اسم البرنامج المراد تشغيله.
    -الثاني [ ] char *const argv وهو عنوان الذاكرة الذي يتواجد به array. عناصر هذه الـ array عبارة عن مجموعة من عناوين الذاكرة التي يتواجد بها Strings تمثل الـ Arguments المراد تشغيلها مع البرنامج. آخر عنصر في هذه الـ Array لابد أن يكون Null Pointer أو 00000000.
    -الثالث [ ] char *const envp وهو عنوان الذاكرة الذي يتواجد به array. عناصر هذه الـ array عبارة عن مجموعة من عناوين الذاكرة التي يتواجد بها Strings تمثل الـ Environment Variables المراد تشغيلها مع البرنامج. آخر عنصر في هذه الـ Array لابد أن يكون Null Pointer أو 00000000

    في المثال الذي سنستخدمه ، نحن نريد تنفيذ "bin/sh/" وبناءا عليه يجب علينا تحضير الـ Registers كما يلي:


    16-b.png



    قبل كتابة الكود ، يجب أن نتذكر أن الـ Shellcode يجب ألا يحتوي على أي Hard-Coded Memory Address لكي يعمل على أي نظام تشغيل ويكون Portable. لذلك سنستخدم طريقة JMP-CALL-POP كما في المثال السابق وسيكون Code البرنامج كما يلي:



    Code:
    ;This is a comment - ProgramA-execve.asm to execute "/bin/sh"
    
    global _start
    
    section .text
    _start:
    
    jmp short GetStringAddress      ;Redirect Execution to GetStringAddress Label
    
    GetStringAddressReturn:
    
          ;Execve() Syscall
           pop esi                 ;ESI now contains the address of "/bin/shNAAAABBBB" string.
           xor eax, eax            ;zeroing EAX register.
           mov [esi + 7], al       ;null terminate string "/bin/sh" by replacing "N" with null byte.
                                   ;Step 2
           mov [esi + 8], esi      ;setting the first element in argv[] array to the address of argv[0] ,
                                   ;argv[0] = "/bin/sh" ,
                                   ;end result is: address of "/bin/sh" will replace AAAA.
                                   ;Step 3
           mov [esi + 12], eax     ;Ending argv[] array with null pointer by replacing BBBB with 00000000 ,
                                   ;The same null pointer will be used by EDX to null envp[] array.
                                   ;Step 4
    
           ;Preparing Registers For Syscall
           mov al, 11              ;setting EAX to execve syscall number 11 decimal
           mov ebx, esi            ;setting EBX to the address of the string that represents the program to run ("/bin/sh")
           lea ecx, [esi + 8 ]     ;setting ECX to the address of argv[] array.
           lea edx, [esi + 12]     ;setting EDX to the address of envp[] array.
           int 0x80                ;invoke execve syscall
    
    GetStringAddress:
           call GetStringAddressReturn             ;Redirect execution again to GetStringAddressReturn
           db "/bin/shNAAAABBBB"                   ;AAAA will hold the address of argv[] array.
                                                   ;BBBB will hold the address of envp[] array.
    



    الرسم التالي يوضح شكل الـ Memory خلال مراحل تنفيذ البرنامج


    17.png


    لنقم الآن بعمل Assemble ثم link لتجربة البرنامج


    18.png


    سنلاحظ أن البرنامج لم يعمل كما هو متوقع. لكن ما السبب في ذلك ؟ قبل البحث عن سبب المشكلة ، لنقم باستخراج الـ Shellcode من البرنامج وتجربته بداخل ShellcodeTester.c


    19.png


    سنجد أن الـ Shellcode يعمل بشكل جيد وقام بفتح bin/sh/ كما هو متوقع ويمكننا استخدامه حيثما أردنا.



    ولكن ما السبب أن الـ Shellcode يعمل ولكن البرنامج لا يعمل ؟

    السبب في ذلك أن الـ Flags المتعلقة بالـ text section. الذي يوجد به الـ Code لا تتضمن Write Permission بمعنى أنه إذا حاول البرنامج كتابة أي شئ في الذاكرة فلن يعمل البرنامج

    كما ذكرنا ، هناك أي برنامج يتم تقسيمه إلى أجزاء أو ما يعرف بالـ Sections وهي التي تحدد المهام المختلفة لكل جزء من البرنامج ، فهناك الـ text section والذي يحتوي على الـ executable code. فكما يتضح من الاسم ، هذا الـ Section يحتاج لتنفيذ أوامر فمن الطبيعي أن يكون لديه صلاحيات Read و Execute ، ولكن ليس من الطبيعي أن يكون لديه صلاحية Write لحماية الذاكرة




    20.png

    وبما أن البرنامج يقوم بعمل Write لأجزاء من الذاكرة عند تجهيز الـ [ ] Argv و [ ] Envp ، إذا فلن يعمل البرنامج.


    حل هذه المشكلة أن نستخدم "N-" خلال عمل Linking باستخدام ld. والتي تعني ما يلي:



    -N
    --omagic
    Set the text and data sections to be readable and writable.



    حل هذه المشكلة أن نستخدم "N-" خلال عمل Linking باستخدام ld. والتي تعني ما يلي:



    21.png


    Write a piece of shellcode that executes netcat to listen on port 443 "nc -l 443" using both stack and jmp-call-pop techniques.



    بذلك نكون قد انتهينا من شرح مبسط لكيفية كتابة Portable Linux Shellcode باستخدام طريقة الـ Stack وطريقة JMP-CALL-POP. في السلسلة القادمة ، سنتعرض لكيفية كتابة Windows Shellcode بإذن الله.




    ملحوظات:
    -الشرح السابق تم على Ubuntu 12.04.5 LTS 32bit وكما ذكرنا سابقا كل من 32bit و 64bit يعمل بطريقة مختلفة.
    -تم استخدام Intel Syntax مع العلم أنه يوجد صيغة أخرى مثل AT&T.




    References:
    root_x90, ahmed.meko and ahmed harix like this.

Recent Reviews

  1. ahmed.meko
    ahmed.meko
    5/5,
    بوركت يا استاذنا