בפוסט זה אסביר איך יצרתי, באמצעות ארדואינו, שידור תואם לפרוטוקול של PT2262, ואציג את הבדיקות שביצעתי עם המערכת הזו כדי לבחון אספקטים מסוימים בהתקפת OpenSesame החדשה של ההאקר סמי קמקר.
בפוסט הקודם (בקטע "שידור") הצגתי את פרוטוקול השידור של PT2262: שליחת "Frame" שמורכב מארבע חזרות על "Code Word", שמורכבת בעצמה משלושה-עשר אלמנטים: שנים-עשר של כתובת/נתונים, כל אחד באורך 32 מחזורי שעון, ולאחר מכן אחד לסנכרון באורך 128 מחזורי שעון. האלמנטים מועברים כרצף של HIGH ו-LOW לסירוגין, בתזמון לפי מספר מחזורי שעון:
"0" => 4-12-4-12 "1" => 12-4-12-4 "F" => 4-12-12-4 Sync => 4-124
כיוון השעון
תדר השעון של PT2262 תלוי, כפי שציינתי בעבר, בערך של נגד חיצוני. כאשר ערך הנגד הוא 2MOhm, תדר השעון קרוב ל-16.9KHz. אם נחלק את תדר העבודה של ארדואינו טיפוסי בתדר הנ"ל, נקבל 945 – מה שאומר שאפשר להגיע אליו בעזרת הטיימר Timer/Counter1 של המיקרו-בקר ATmega328, כי טיימר זה תומך בערכים בני 16 ביט. מכיוון התדר לא חייב להיות מדויק, אפשר להסתפק גם בספירה פחות מדויקת. במקרה שלי בחרתי לעבוד עם Timer/Counter2 בן ה-8 ביט, שהגדרתי לו Prescaler של 32. הפרמטרים האלה אומרים שכדי לחקות את שעון ה-PT2262, הטיימר יצטרך לספור כל פעם עד 29.5 – מספר לא שלם; אלא שכל זמני השידור שצוינו למעלה הרי מתחלקים יפה ב-4, אז אפשר פשוט לספור עד 118 ולקבל תקתוקי שעון בתדר שעדיין יתאים מצוין למשימה.
כדי לחסוך זמן, כתבתי קוד בסביבת הפיתוח של ארדואינו להגדרות הטיימר ולבדיקה ראשונית שלהן – הפקת גל ריבועי באחד הפינים. זה לא עבד, ואת הזמן שניסיתי לחסוך קודם שילמתי עם ריבית בניסיון להבין למה. הסתבר שאיפשהו מאחורי הקלעים, כנראה במסגרת ההכנות לפקודת analogWrite, ארדואינו מתעסק עם הטיימר הזה ומגדיר משהו שמפריע לעבודה חופשית. כדי לעקוף את הבעיה, במקום מבנה setup-loop הרגיל כתבתי קוד עם הפונקציה main – מה שמנטרל כמובן כל פונקציונליות ארדואינו סטנדרטית…
בניית ה-"Code Word"
כעת יצרתי מערך דו-ממדי שהכיל את התזמונים עבור כל אחד מסוגי האלמנטים שה-PT2262 שולח:
// PT2262-style "bit" definitions // Numbers are length (cycles) of High/Low // 0 means "End of bit" uint8_t bitPattern[4][5] = { {1, 3, 1, 3, 0}, // 0 = "0" {3, 1, 3, 1, 0}, // 1 = "1" {1, 3, 3, 1, 0}, // 2 = "F" {1, 31, 0, 0, 0} // 3 = Sync };
כדי לשלוח, נניח, אלמנט "F", אני מרים את פין הפלט ל-HIGH, ניגש לתת-המערך השלישי (אינדקס 2) ומתחיל לספור תקתוקי טיימר. בכל פעם שהספירה מגיעה לערך הנוכחי בתת-המערך, אני מחליף את מצב הפין ועובר לערך הבא. כך אני מקבל HIGH למשך תקתוק יחיד, LOW למשך שלושה (הכל מחולק בארבע, זוכרים?), HIGH למשך שלושה ו-LOW למשך אחד. כשאני מגיע לערך 0, סיימתי עם האלמנט הזה.
ה-"Code Word" היא בסך הכל רצף של מספרים שאומרים לי איזה אלמנט לשלוח כל פעם. הנה הגדרה של "Code Word" תקנית, שכל אלמנטי הכתובת/נתונים בה הם "F":
uint8_t msg[] = {2,2,2,2,2,2,2,2,2,2,2,2,3,99};
שימו לב ל-3 לקראת הסוף (אינדקס של אלמנט הסינכרון) ול-99, שמסמל לי את סוף השידור. בחרתי לשים "דגל" כזה ולא להשתמש ברשומות באורך קבוע, מכיוון שרציתי לערוך ניסויים גם עם שידורים לא-תקניים שעשויים להיות באורך שונה.
השיטה
החיבור של כל המרכיבים היה פשוט מאד: פקודת השידור מקבלת את הכתובת של תחילת ה-"Code Word" (במקרה זה, המערך msg), מרימה את פין הפלט ל-HIGH ומפעילה את הטיימר. בכל תקתוק של הטיימר, פונקציית הפסיקה שלו בודקת אם הגיע הזמן לעבור לחלק הבא של האלמנט הנוכחי, לעבור לאלמנט הבא, או לסיים את השידור. הנה קטעי הקוד הרלוונטיים:
volatile uint8_t isTransmitting = 0; volatile uint8_t *currBit; volatile uint8_t currBitPart; volatile uint8_t currCount; // We'll use PB4 (pin 12) for transmission #define tPort PORTB #define tDDR DDRB #define tMask (1 << 4) void sendMessage(uint8_t *m) { currBit = m; currBitPart = 0; currCount = bitPattern[*currBit][currBitPart]; TCNT2 = 0; // Reset timer isTransmitting = 1; tPort ^= tMask; // Start with pin HIGH // Enable Compare Match A interrupt TIMSK2 = 1 << OCIE2A; } // sendMessage ISR(TIMER2_COMPA_vect) { --currCount; // Is the current bit part finished? if (!currCount) { ++currBitPart; // Go to next bit part // End of current bit? if (bitPattern[*currBit][currBitPart] == 0) { ++currBit; // Go to next bit if (*currBit > 4) { // End of pattern? // We're done isTransmitting = 0; TIMSK2 = 0; tPort &= !tMask; // pin LOW return; } else currBitPart = 0; } currCount = bitPattern[*currBit][currBitPart]; tPort ^= tMask; // Toggle pin } }
את פין הפלט חיברתי לכניסת הנתונים של משדר מ-RF Link Kit, ואת היציאה של המקלט התואם חיברתי לכניסה של ג'וק PT2272. נורית LED שחוברה (עם נגד מתאים!) לפין ה"Valid Transmission" של ה-PT2272 הוכיחה שקוד הארדואינו עובד: היא הבהבה בקצרה, כצפוי, כששלחתי "Frame" שלם עם הכתובת הנכונה.
סומסום היפתח!
מתקפת OpenSesame היא ביסודה מתקפת Brute Force – היא משדרת את כל הקודים האפשריים עד שהשער, או מה שזה לא יהיה, נפתח. העניין הוא שגם אם עלינו על תדר השעון הנכון (אין שום אחריות שהוא יהיה דווקא 16.9KHz בכל המערכות), שידור "Frame" נפרד לכל קוד יהיה כל כך איטי, שהזמן הממוצע לפיצוח יהיה כשמונה שעות. סמי מצא וניצל מספר פרצות בתכנון ובפונקציונליות של המערכות האלה, וכן טריק מתמטי מתוחכם מאד, כדי לצמצם את הזמן הזה לשניות ספורות.
שתיים מהפרצות שנוצלו היו: 1) בפועל, סמי גילה שלא היה צורך באלמנט הסינכרון כדי שה-"Code Word" תיקלט, 2) הסתבר שאפילו אין צורך בחזרה על ה-"Code Word" – שידור נכון אחד ויחיד הספיק. גם הטריק המתמטי מסתמך על הפרצות האלה. אלא שכמובן, הן סותרות את מה שנאמר ב-datasheet של PT2272, מה שהותיר שתי אפשרויות: או שהמערכות שסמי פיצח השתמשו בג'וק סלחני יותר, או שה-datasheet משקר.
עם קוד הארדואינו שלמעלה בדקתי את ההתנהגות של PT2272 עם ובלי חזרות ואלמנט סינכרון, וגיליתי שהוא אכן מתנהג כמצופה ממנו: הג'וק הזה אינו פגיע להתקפת OpenSesame. ההתקפה מתאפשרת, כנראה, בגלל חוסר מחשבה וחיסכון-יתר מצד מתכנני מערכות, שבמקום ג'וק זול וסטנדרטי כמו ה-PT2272 ותואמיו בחרו לעבוד עם כלים ושיטות פגיעים במיוחד, שבוודאי היו זולים בסנט או שניים.