
# كتابة تحليل لتحدي CTF من نوع pwn يستغل CVE-2021-4034 (pkexec) عبر التلاعب بالكومة، مع هندسة عكسية قائمة على Ghidra وملف مساعد ثنائي مخصص باسم shelly.so.
هذا تحدٍّ من نوع CTF pwn كتبته بلغة C ويتطلب من المستخدم استغلال ثغرة CVE-2021-4034. يحصل اللاعبون على ملفين ثنائيين في مجلد challenge في هذا المستودع. الملف الثنائي chal ينفّذ تحدّي CTF، وshelly.so ملف ثنائي مساعد.
في وقت كتابة هذه المقالة، لا يزال ملف Dockerfile غير مكتمل. ملف Dockerfile مطلوب لنشر هذا التحدي أثناء CTF مباشر، لكن ليس محليًا: لا يزال بإمكانك محاكاة هذا التحدي محليًا عن طريق إعداد صلاحيات المستخدم وتثبيت الحزم المعرّضة للثغرة كما يلي:
libpolkit-gobject-1-0=0.105-26ubuntu1 libpolkit-agent-1-0=0.105-26ubuntu1 policykit-1=0.105-26ubuntu1.flag.txt مملوكًا للمستخدم root:root في الدليل الحالي.challenge إلى الدليل الحالي.chal كمستخدم بصلاحيات محدودة.تشغيل الملف الثنائي chal يعطينا فكرة مبهمة عما يفعله هذا الملف الثنائي:```
WELCOME TO THE HUB CTRL+ALT+DELICIOUS
We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every byte
التلاعب بحجم الإدخال في دالة `Add` ومؤشرات دوال `Cancel` لا يعطينا أي شيء مميز (لا تجاوز سعة المخزن المؤقت ولا خطأ تجزئة). ومع ذلك، هناك بعض الأشياء المثيرة للاهتمام:
- يبدو أن الطلبات تُوضع في قائمة (ربما قائمة مرتبطة) وأنها ذات فهرسة تبدأ من الصفر؟
- يوجد هذا `Order number: 0x7ffde93681f0` والذي يبدو أنه يطبع موقعًا ما على المكدس؟
- أيضًا، يقوم البرنامج بإنشاء مجلد باسمنا الذي أدخلناه مع 3 ملفات تنفيذية، أحدها هو الملف الثنائي المساعد الذي حصلنا عليه:```sh
peasant@Tin-VM:~/Desktop$ ls
chal chal.c Dockerfile shelly.so solve.py tin
peasant@Tin-VM:~/Desktop$ ls -l tin/
total 28
-rwxrwx--- 1 peasant vboxsf 5 Feb 4 14:33 notes
-rwxrwx--- 1 peasant vboxsf 20 Feb 4 14:33 recipe
-rwxr-x--- 1 peasant vboxsf 16488 Feb 4 14:33 shelly.so
peasant@Tin-VM:~/Desktop$ cat tin/notes
0000
peasant@Tin-VM:~/Desktop$ cat tin/recipe
aaaa
bbbb
cccc
dddc
تحتوي الملفات التنفيذية على مدخلاتنا.
يمكنك العبث بالوظائف الأخرى وتأمل أن تصادف بعض الأخطاء (وهو أمر محتمل)، لكنني سأختصر الأمور وأفتح البرنامج في Ghidra.
بمقارنة السلاسل النصية التي تظهر أثناء تشغيل البرنامج مع السلاسل النصية الموجودة في Ghidra، يمكننا إعادة تسمية بعض دوال FUN_* إلى أسماء مألوفة:```C
undefined8 main(void)
{ int iVar1; size_t sVar2; undefined2 *puVar3; long in_FS_OFFSET; int opt; int local_1c; char *local_18; long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28); local_18 = "/recipe"; print("WELCOME TO THE HUB CTRL+ALT+DELICIOUS\n"); print( "We're not just a sandwich hub. We are the beacon of flavors, serving a symphony in every by te\n\n" ); while( true ) { print("1. ENTER THE HUB\n"); print("2. QUIT\n"); __isoc99_scanf(&DAT_001030c5,&opt); getc(stdin); if (opt != 1) break; printf("Order number: %p\n",&local_18); print("Enter your name: "); __isoc99_scanf(&DAT_0010334f,&DAT_00105120); sVar2 = strlen(&DAT_00105120); puVar3 = (undefined2 *)malloc(sVar2 + 2); DAT_00105100 = puVar3; *puVar3 = 0x2f2e; *(undefined *)(puVar3 + 1) = 0; strcpy((char *)(DAT_00105100 + 1),&DAT_00105120); iVar1 = FUN_001022f0(DAT_00105100,&DAT_00105060); if (iVar1 == -1) { mkdir((char *)DAT_00105100,0x1c0); } DAT_00105140 = 0; order_cnt = 0; for (local_1c = 0; local_1c < 10; local_1c = local_1c + 1) { *(undefined8 *)(&ptr_array + (long)local_1c * 8) = 0; } main_menu(); } print("Come again :)\n");
The `printf("Order number: %p\n",&local_18);` **يطبع موقع متغير محلي على المكدس.**
يُظهر فحص سريع في gdb أن العنوان المُسرَّب هو عنوان المؤشر إلى السلسلة الثابتة `/recipe`:```gdb
...
Order number: 0x7fffffffdfc0
...
gef➤ x/gx 0x7fffffffdfc0
0x7fffffffdfc0: 0x0000555555559020
gef➤ x/s 0x0000555555559020
0x555555559020: "/recipe"
نرى استدعاء mkdir، والذي ينشئ مجلدًا باسم مدخلاتنا في الدليل الحالي.
هذه متوافقة مع ملاحظتنا عندما قمنا بتشغيل البرنامج. ثم يقوم بتهيئة بعض المتغيرات قبل استدعاء دالة main_menu، والتي تبدو كالتالي:```C
while( true ) {
while( true ) {
while( true ) {
while( true ) {
while( true ) {
while( true ) {
print("1. ADD NEW ORDER\n");
print("2. EDIT ORDER\n");
print("3. SHOW ORDER\n");
print("4. CANCEL ORDER\n");
print("5. CHECKOUT\n");
print("6. DONE\n");
__isoc99_scanf(&DAT_001030c5,&local_40);
getc(stdin);
if (local_40 != 1) break;
add_order();
}
if (local_40 != 2) break;
edit_order();
}
if (local_40 != 3) break;
show_order();
}
if (local_40 != 4) break;
cancel_order();
}
if (local_40 != 5) break;
checkout();
}
if (local_40 == 6) break;
if (local_40 == 0x539) {
print(
"\nGORDON RAMSAY: Finally, a worthy opponent, our battle will be legendary! I BET YOU CAN 'T GUESS THE SECRET RECIPE.\n"
);
fgets(inp,0x20,stdin);
getrandom(random-bytes,0x10,0);
for (local_3c = 0; local_3c < 0x10; local_3c = local_3c + 1) {
if (inp[local_3c] != random-bytes[local_3c]) {
print("...Nuh Uh!...\n");
/* WARNING: Subroutine does not return */
exit(0);
}
print("...Ooh Yes.. sCruMpTioUs...");
}
print("Fine... I'll give you a taste.\n");
FUN_00101504();
}
إذا لم تكن معتادًا على REV، فهذا هو التفكيك (decompilation) لتعليمة `switch` في لغة C. يوجد خيار مثير للاهتمام يُعرف بـ `0x539`. يتيح للاعبين تخمين `0x10` بايتات عشوائية. إذا كانت جميع البايتات متساوية، فإنه يستدعي `FUN_00101504();` الذي يستدعي `system("cat flag.txt");`. وإلا، يخرج البرنامج.
ومع ذلك، فإن تخمين 16 بايتًا عشوائيًا بالقوة العمياء يعادل تجربة كل الاحتمالات البالغة 256**16 = 340282366920938463463374607431768211456 احتمالًا. حظًا موفقًا مع هذا ههه.