wtflol
נכתב על ידי Arie ו-Israel
(הפתרון הזה מבוסס על הגרסא wtflol_reflagged, אבל אנחנו נקרא לחידה wtflol)
באתגר הזה, קיבלנו קובץ יחיד, עם הוראה אחת: 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?
רובו של קובץ ה-ELF אכן היה פונקציה ענקית ומכוערת בכתובת 0x8048913
.
כשניסינו לראות כיצד הפונקציה נראית במצב של graph view, IDA החלה לבכות שבפונקציה יש כמות גדולה מדי של צמתים בשביל להציג בעזרת graph view.
שינינו את האפשרויות של הגרפים כדי להגדיל את המספר המקסימאלי של צמתים ל-10000, ועל ידי zoom out (לפי הרמז look at the bigger picture
), קיבלנו סוף-סוף את הדגל:BSidesTLV{Dont_Leak_This_One_Lol}
בסך הכל, אתגר מדהים, עם מספר הפתעות, במיוחד לאנשים שמעולם לא התנסו בהתקפה תוך שימוש ב-remote kernel debugging.