wtflol

נכתב על ידי Arie ו-Israel

(הפתרון הזה מבוסס על הגרסא wtflol_reflagged, אבל אנחנו נקרא לחידה wtflol)

wtflol_reflagged

באתגר הזה, קיבלנו קובץ יחיד, עם הוראה אחת: Can you get the flag?.

wtflol הוא דריבר לווינדוס בגרסאת 64 ביט, עם מעט מאוד קוד, אבל סגמנט .data ענק (כמעט 2MB)

מעט מאוד מחרוזות, למשל:

.text:0000000140005060 \Driver\Null .text:0000000140005080 This challenge is fully compatible with windows 8 and above.\n\n .text:00000001400050E0 writing %d bytes

מעבר מהיר על הפונקציות הראשונות מגלה לנו כמה פונקציות פענוח פשוטות. אנחנו מקבלים את המחרוזות הבאות, אבל זה עדיין לא ברור היכן משתמשים בהן:

as /e fuckthat PROCESSOR_ARCHITECTURE; .block {.if($spat(@"${fuckthat}","x86")== 0) { .writemem C:\Windows\Temp\kd.dll %p l?%x;!C:'\Windows\Temp\kd.dll.aaaa %p;g} .else {.writemem C:\Windows\Temp\kd.dll %p l?%x;!C:\Windows\Temp\kd.dll.aaaa %p;g}}',0

ו-

!C:\Windows\Temp\kd.dll.bbbb

בעזרת ניתוח סטטי גילנו שהאזור הענק של ה data מכיל שני dll-ים, מוצפנים בעזרת מפתח XOR מחזורי, המפתח שהשתמשו בו הוא: \xFD\xED\xDD\xCD\xBD\xAD\x0D\x00.

ה dll-ים הללו נראו דומים מאוד, מלבד העובדה שאחד הוא בגרסאת 32 ביט, ואילו השני בגרסאת 64 ביט. מופיעות שם שתי פונקציות מענינות בשםaaaa ו-bbbb, אך זה לא ברור בשלב זה כיצד הן נקראות ולצורך מה הן משמשות.

הפונקציה DriverEntry מכילה הפניות ל-\\Driver\\Null ול-This challenge is *fully* compatible with windows 8 and above.\n\n.

דבר מעניין נוסף שכדאי לציין הוא שבשני קבצי ה-dll קיימת הפונקציה WinDbgExtensionDllInit. המשמעות של זה היא שמדובר ב-extension dlls של WinDbg.

זמן ל-debugging

התחלנו לדבאג את הדרייבר בעזרת WinDbg (כאשר הדרייבר רץ בתוך מכונה ווירטואלית).

מכיוון שהיה מדובר ב-extension dlls של WinDbg ניסינו לטעון אותם אל תוך הדיבאגר ולהריץ את aaaa ו-bbbb, ולא התקבלה שום תוצאה.

כאשר בדקנו איזה מודלים נטענו אל תוך WinDbg כדי לראות שקובץ ה-dll נטען כראוי מצאנו משהו מעניין, קובץ ה-dll נטען פעמיים. פעם אחת מהתקייה שבה טענו אותו בצורה ידנית ופעם נוספת מתוך C:\Windows\Temp\kd.dll.

זה הזכיר לנו את המחרוזת as /e fuckthat PROCESSOR_ARCHITECTURE; .block {.if($spat(@"${fuckthat}","x86")== 0) { .writemem C:\Windows\Temp\kd.dll %p l?%x;!C:'\Windows\Temp\kd.dll.aaaa %p;g} .else {.writemem C:\Windows\Temp\kd.dll %p l?%x;!C:\Windows\Temp\kd.dll.aaaa %p;g}}',0 - מדובר בסקריפט של WinDbg שהורץ.

תקיפה של מחשב ה-host בעזרת remote kernel debugger היא לא דבר חדש, אולם, דבר זה לא קיבל את תשומת הלב לה היינו מצפים. להסבר מלא, אנא עיין ב הרצאה ב-Recon 2010 על ידי Alex Ionescu וב- מאמר מאת Mateusz ‘j00ru’ Jurczyk.

בשורה התחתונה, פרוטוקול התקשורת של הדיבאגר של ה-Kernel מאפשר ליעד להדפיס טקסט כלשהו אל תוך חלון הדיבאגר של ה-host, עוצר את ה-debug session על מנת להוציא מידע מסוים או להריץ פקודת דיבאג (לפרטים המלאים אנא עיין הרצאה לעיל).

הסקריפט בודק את הארכיטקטורה של ה-host (32 או 64 ביט), מעתיק את האזור הרלוונטי בזכרון שמכיל את ה-dll המפוענח הרלוונטי בהתאם (32 או 64 ביט) אל תוך קובץ ב-host, ומריץ את הפונקציה aaaa.

חשבנו שצריך להריץ את הפונקציה bbbb כדי להשיג את הדגל.

ראשית, ניסינו להריץ את bbbb באופן ידני באותה הצורה בה הפונקציה aaaa רצה - !C:\Windows\Temp\kd.dll.aaaa %p. אבל רגע, מה הוא המצביע שמופיע כאן?

כדי להשיג את הכתובת פתחנו מופע נוסף של WinDbg על מנת לדבאג את המופע הראשון והוספנו breakpoint בפונקציה aaaa, ובפונקציה bbbb כדי לוודא שהיא לא נקראת לפני הפונקציה aaaa.

אכן התבצעה קריאה לפונקציה aaaa עם כתובת כלשהי ולא נתבצע קריאה ל bbbb כלל. ניסינו להריץ את bbbb עם אותה הכתובת שבה הפונקציה aaaa נקראה, אך ללא הצלחה.

מכיוון שראינו את המחרוזת !C:\Windows\Temp\kd.dll.bbbb בזכרון של הדרייבר משהו אמר לנו שהסקריפט הנ"ל צריך להיקרא על ידי הדרייבר באותה הצורה בה הפונקציה aaaa הופעלה, רק היינו צריכים למצוא משהו שיגרום לכך.

[הערה לאריה: לבדוק את השורה הזאת] גילינו שהקוד שבו הופיעה המחרוזת היה בפונקציה בכתובת 0x140003740. תוך בדיקה על המיקום בו נעשה שימוש בפונקציה הנ"ל שמנו לב שב-DriverEntry מופיעה קריאה ל-ObReferenceObjectByName פונקציה לא מתועדת, המקבלת את \\Driver\\Null כפרמטר ואת IoDriverObjectType כסוג האובייקט המצופה.

האובייקט שהתקבל מהפונקציה הזאת הוא _DRIVER_OBJECT:

   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x008 DeviceObject     : Ptr64 _DEVICE_OBJECT
   +0x010 Flags            : Uint4B
   +0x018 DriverStart      : Ptr64 Void
   +0x020 DriverSize       : Uint4B
   +0x028 DriverSection    : Ptr64 Void
   +0x030 DriverExtension  : Ptr64 _DRIVER_EXTENSION
   +0x038 DriverName       : _UNICODE_STRING
   +0x048 HardwareDatabase : Ptr64 _UNICODE_STRING
   +0x050 FastIoDispatch   : Ptr64 _FAST_IO_DISPATCH
   +0x058 DriverInit       : Ptr64     long 
   +0x060 DriverStartIo    : Ptr64     void 
   +0x068 DriverUnload     : Ptr64     void 
   +0x070 MajorFunction    : [28] Ptr64     long 

המידע ב-offset של 0xE0 שונה על ידי הדרייבר אל הפונקציה 0x140003740. המידע ב-offset הזה הוא בעצם MajorFunction[0xe] (מכיוון ש-0x70 + 0xe*8 שווה ל-0xE0), כאשר 0xe הוא IRP_MJ_DEVICE_CONTROL. כלומר, הדרייבר שינה את פונקצית ה-IOCTL (input/output control) של \\Driver\\Null לפונקציה משלו. מחוכם! אז אנחנו צריכים לשלוח IOCTL אל \\Driver\\Null.

ה-alias ל-\\Driver\\Null הוא הקובץ NUL, בדומה ל-/dev/null ב-Linux, אולם לאחר השינוי של ה-IOCTL מתבצעים דברים מעניינים נוספים.

קיבלנו את הקוד של ה-IOCTL מהפונקציה C07FC004, והגענו למסקנה שה-buffer של הקלט צריך להיות בין 0x19 לבין 0x400 בתים.

בפונקצית ה-Handling של ה-IOCTL (0x140003740) יש השוואה בין קלט ה-IOCTL (כלומר, הסיסמא) לבין buffer מוערפל כלשהו.

הוצאנו את האלגוריתם והרצנו אותו כדי למצוא את התווים הנכונים, אחד אחרי השני, של הסיסמא באורך 25 התווים.

#include "stdio.h"
unsigned char str[25] ={0x0e, 0x47, 0xad, 0xa4, 0xe1, 0x13, 0x43, 0x3b, 
                        0xcd, 0x7b, 0xda, 0x2f, 0x78, 0xff, 0x24, 0x33, 
                        0xde, 0x6d, 0xb0, 0xcc, 0x1b, 0x14, 0x25, 0x6b, 
                        0xec};

void main(){
unsigned char temp;
for (int i = 0; i < 25; ++i ){
    for (int j=0;j<256;j++){
        temp = ((~((i ^ (((((((-(char)~((i ^ (i+ i + ~((i ^ ((i ^ ((i ^ (((~(i ^ (~(j + 60)- 54) ^ 0x1E)
                - i) ^ 0x71)  - 79))  + 1))  + 1))- i) - 28 - 1)) + 1) - 1) ^ 0x36)- i - 103) ^ 0xE6) + 32)
                 ^ 0x39) - i)) + 1) - 12 + 101) ^ 0xB1) - i;
        if (temp == str[i]){
            printf("%02x", j);
            break;            
        }
       }
  }
}

ואז שלחנו את ה-IOCTL עם הסיסמא:

    unsigned char InputBuffer[25] = {
        0xE5, 0x37, 0x48, 0xD4, 0x4A, 0x97, 0x26, 0x41, 0x12, 0xFB, 0x3F, 0x51,
        0xF7, 0x03, 0xC9, 0xB1, 0x65, 0xD1, 0x21, 0x0C, 0x58, 0x82, 0xA4, 0xC1,
        0x1F
    };

    hDevice = CreateFile("\\\\.\\NUL",
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    
    DeviceIoControl(hDevice,
        (DWORD)0xC07FC004,
        &InputBuffer,
        (DWORD)sizeof(InputBuffer),
        &OutputBuffer,
        sizeof(OutputBuffer),
        &bytesReturned,
        NULL);        

כאשר ה-IOCTL מתקבל, הדריבר משתמש באותו מכניזם כמו מקודם כדי להפעיל את הפונקציה bbbb מה-dll, בעזרת הסקריפט המוצפן השני. (דרך אגב, bbbb זה גם השם של ה-Break-in control packet של הפרוטוקול KD)

בשלב הזה, השתמשנו ב-DbgView כדי לראות את הדגל מודפס כ-Kernel DbgPrint.

ה-DbgPrint מנוהל על ידי הפונקציה 0x140003E40, שגם הכילה פענוח של מחרוזת לצורך format string שערכו Your Flag is: %s\n והקלט שהיא קיבלה.

הדגל המוחזר הוא פשוט ג'יבריש.

אבל קצת לאחר ההדפסה, ישנה השוואה נוספת ב-0z140003BF2, עם ערך שלא היה אמור להשתנות על ידי הדרייבר עצמו.

זה היה נראה חשוד ונראה כמו טכניקת anti-debugging, אז הסרנו את ה-breakpoints, ואז הודעה חדשה הופיעה אך ורק ב-debug console של WinDbg, לאחר הדגל השבור.

Please continue from here, the pointer to your flag is 00007ffb2de56010, remember to look at the bigger picture :)

הכתובת הזו הייתה ב-dll שנטען על ידי windbg ב-host. משם הוצאנו ... קובץ ELF!

היה שם איזשהו ascii art של חתול. אבל שום דבר בולט נוסף לא הופיע. Ascii steganography ;-D?

CAT ASCII

רובו של קובץ ה-ELF אכן היה פונקציה ענקית ומכוערת בכתובת 0x8048913.

כשניסינו לראות כיצד הפונקציה נראית במצב של graph view, IDA החלה לבכות שבפונקציה יש כמות גדולה מדי של צמתים בשביל להציג בעזרת graph view.

שינינו את האפשרויות של הגרפים כדי להגדיל את המספר המקסימאלי של צמתים ל-10000, ועל ידי zoom out (לפי הרמז look at the bigger picture), קיבלנו סוף-סוף את הדגל:BSidesTLV{Dont_Leak_This_One_Lol}

flag

בסך הכל, אתגר מדהים, עם מספר הפתעות, במיוחד לאנשים שמעולם לא התנסו בהתקפה תוך שימוש ב-remote kernel debugging.

Success