אתגר הנדסה הפוכה - reverse engineering challenge¶
בפרויקט הזה תתמודדו עם אתגר הנדסה הפוכה מרובה שלבים. בכל שלב תקבלו תוכנית מקומפלת ותצטרכו למצוא את המפתח הנכון - או לעקוף את הבדיקה.
חשוב: בכל שלב, קודם קמפלו את הקוד (הוראות הקמפול בתחתית). אחרי שקמפלתם, אל תסתכלו על הקוד המקורי - התייחסו לבינארי כאילו אין לכם גישה לsource. הקוד המקורי כאן הוא "מפתח התשובות" לשימוש המרצה.
שלב 1 - ניתוח סטטי בסיסי¶
המשימה¶
הקובץ stage1 מבקש שם משתמש ומפתח רישיון. מצאו את המפתח הנכון בעזרת ניתוח סטטי בלבד (strings, objdump, Ghidra). אל תריצו את התוכנית לפני שמצאתם את המפתח.
הוראות¶
- קמפלו את
stage1.c(ראו הוראות קמפול בסוף) - הריצו
stringsעל הבינארי - פתחו בגידרה ומצאו את פונקציית הבדיקה
- מצאו את שם המשתמש ואת מפתח הרישיון הנכונים
- הריצו את התוכנית עם הערכים שמצאתם ווודאו שזה עובד
קוד מקור - stage1.c¶
#include <stdio.h>
#include <string.h>
int check_license(const char *username, const char *key) {
if (strcmp(username, "admin") != 0) {
return 0;
}
if (strcmp(key, "KCOR-7X42-PQZM") != 0) {
return 0;
}
return 1;
}
int main() {
char username[64];
char key[64];
printf("=== License Checker v1.0 ===\n");
printf("Username: ");
scanf("%63s", username);
printf("License Key: ");
scanf("%63s", key);
if (check_license(username, key)) {
printf("Valid! Welcome, %s.\n", username);
} else {
printf("Invalid license.\n");
}
return 0;
}
רמזים¶
stringsימצא את התשובה כמעט מיד- בגידרה, חפשו את הפונקציה
check_licenseותקראו את ה-Decompiler - הסיסמה והמפתח שמורים כמחרוזות רגילות בקוד
שלב 2 - ניתוח דינמי¶
המשימה¶
הקובץ stage2 גם מבקש שם משתמש ומפתח. הפעם המפתח מחושב בזמן ריצה - הוא לא מופיע כמחרוזת בקוד. תצטרכו להשתמש ב-GDB כדי למצוא אותו.
הוראות¶
- קמפלו את
stage2.c - נסו
strings- האם מצאתם את המפתח? (לא תמצאו) - פתחו בגידרה ונתחו את פונקציית הבדיקה - הבינו את האלגוריתם
- השתמשו ב-GDB: שימו breakpoint אחרי שהמפתח חושב ולפני ההשוואה
- בדקו את הערך שחושב ומצאו את המפתח הנכון
קוד מקור - stage2.c¶
#include <stdio.h>
#include <string.h>
void generate_key(const char *username, char *key_out) {
// XOR each byte of username with 0x55, then convert to hex
int pos = 0;
for (int i = 0; username[i] != '\0'; i++) {
unsigned char val = username[i] ^ 0x55;
pos += sprintf(key_out + pos, "%02X", val);
if (username[i + 1] != '\0') {
key_out[pos] = '-';
pos++;
}
}
key_out[pos] = '\0';
}
int check_license(const char *username, const char *key) {
char expected_key[256];
generate_key(username, expected_key);
return strcmp(key, expected_key) == 0;
}
int main() {
char username[64];
char key[128];
printf("=== License Checker v2.0 ===\n");
printf("Username: ");
scanf("%63s", username);
printf("License Key: ");
scanf("%127s", key);
if (check_license(username, key)) {
printf("Valid! Welcome, %s.\n", username);
} else {
printf("Invalid license.\n");
}
return 0;
}
רמזים¶
- הבינו את האלגוריתם: כל תו בusername עובר XOR עם 0x55, ואז ממיר להקס
- עבור username = "admin":
- 'a' (0x61) ^ 0x55 = 0x34
- 'd' (0x64) ^ 0x55 = 0x31
- 'm' (0x6D) ^ 0x55 = 0x38
- 'i' (0x69) ^ 0x55 = 0x3C
- 'n' (0x6E) ^ 0x55 = 0x3B
- המפתח:
34-31-38-3C-3B - ב-GDB, שימו breakpoint לפני ה-strcmp ובדקו את הערך של expected_key:
שלב 3 - עקיפת אנטי-דיבוג¶
המשימה¶
הקובץ stage3 הוא גרסה משודרגת שכוללת בדיקת אנטי-דיבוג. התוכנית מזהה אם GDB מחובר ומסרבת לרוץ. עקפו את בדיקת האנטי-דיבוג ומצאו את המפתח.
הוראות¶
- קמפלו את
stage3.c - נסו להריץ עם GDB - מה קורה?
- זהו את בדיקת האנטי-דיבוג (ptrace)
- עקפו את הבדיקה (LD_PRELOAD או שינוי ערך ב-GDB)
- מצאו את המפתח כמו בשלב 2
קוד מקור - stage3.c¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
void anti_debug() {
if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
printf("System error. Please try again.\n");
exit(1);
}
}
void generate_key(const char *username, char *key_out) {
int pos = 0;
unsigned char rolling_key = 0x42;
for (int i = 0; username[i] != '\0'; i++) {
unsigned char val = username[i] ^ rolling_key;
rolling_key = (rolling_key + username[i]) & 0xFF;
pos += sprintf(key_out + pos, "%02X", val);
if (username[i + 1] != '\0') {
key_out[pos] = '-';
pos++;
}
}
key_out[pos] = '\0';
}
int check_license(const char *username, const char *key) {
char expected_key[256];
generate_key(username, expected_key);
return strcmp(key, expected_key) == 0;
}
int main() {
char username[64];
char key[128];
anti_debug();
printf("=== License Checker v3.0 ===\n");
printf("Username: ");
scanf("%63s", username);
printf("License Key: ");
scanf("%127s", key);
if (check_license(username, key)) {
printf("Valid! Welcome, %s.\n", username);
} else {
printf("Invalid license.\n");
}
return 0;
}
רמזים¶
- ההודעה "System error. Please try again." היא הודעת שגיאה מתחזה - היא לא באמת שגיאת מערכת, אלא בדיקת אנטי-דיבוג
- עקפו ptrace עם:
- הפעם generate_key משתמש ב-rolling key שמשתנה בכל איטרציה
- הדרך הקלה: breakpoint לפני strcmp ובדיקת הערכים
שלב 4 - פריצה: Patching¶
המשימה¶
במקום למצוא את המפתח הנכון, הפעם שנו את הבינארי כך שכל קלט יתקבל כמפתח תקין. זה נקרא patching - שינוי של הוראות אסמבלי בקובץ הבינארי.
הוראות¶
- קמפלו את
stage4.c - פתחו בגידרה ומצאו את הבדיקה (ה-if שמחליט אם המפתח תקין)
- מצאו את הוראת הקפיצה המותנית (jne/je) שמחליטה אם להדפיס "Valid!" או "Invalid!"
- שנו את הבינארי באחת מהדרכים הבאות:
- אפשרות א: שנו
jneל-je(או להפך) כדי להפוך את הלוגיקה - אפשרות ב: החליפו את הוראת הקפיצה ב-NOP-ים (0x90) כדי שתמיד ייכנס לענף ה-"Valid!"
- אפשרות ג: שנו את ערך ההחזרה של check_license להיות תמיד 1
- שמרו את הקובץ המתוקן והריצו אותו - ווודאו שכל קלט מתקבל
קוד מקור - stage4.c¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
void anti_debug() {
if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
printf("Integrity check failed.\n");
exit(1);
}
}
void generate_key(const char *username, char *key_out) {
int pos = 0;
unsigned char rolling_key = 0x42;
for (int i = 0; username[i] != '\0'; i++) {
unsigned char val = username[i] ^ rolling_key;
rolling_key = (rolling_key + username[i]) & 0xFF;
pos += sprintf(key_out + pos, "%02X", val);
if (username[i + 1] != '\0') {
key_out[pos] = '-';
pos++;
}
}
key_out[pos] = '\0';
}
int check_license(const char *username, const char *key) {
char expected_key[256];
generate_key(username, expected_key);
return strcmp(key, expected_key) == 0;
}
int main() {
char username[64];
char key[128];
anti_debug();
printf("=== License Checker v4.0 (Final) ===\n");
printf("Username: ");
scanf("%63s", username);
printf("License Key: ");
scanf("%127s", key);
if (check_license(username, key)) {
printf("Valid! Welcome, %s.\n", username);
printf("Full access granted.\n");
} else {
printf("Invalid license.\n");
}
return 0;
}
רמזים¶
מציאת הקפיצה המותנית:
בגידרה, ב-main תראו משהו כזה אחרי הקריאה ל-check_license:
או:
אפשרות א - שינוי הקפיצה:
- je (0x74) -> jne (0x75), או להפך
- בגידרה: לחצו ימני על ההוראה -> Patch Instruction -> שנו
אפשרות ב - החלפה ב-NOP:
- je XX (2 בתים: 0x74 0xXX) -> nop nop (2 בתים: 0x90 0x90)
- ככה הקוד תמיד ימשיך לענף ה-"Valid!" בלי קפיצה
אפשרות ג - שינוי ערך ההחזרה של check_license:
- בתחילת check_license, שנו את הקוד ל:
- ככה הפונקציה תמיד תחזיר 1 (true)
שמירת ה-patch בגידרה:
1. אחרי שעשיתם את השינוי, לכו ל-File -> Export Program
2. בחרו פורמט Binary
3. שמרו כקובץ חדש
4. תנו לקובץ הרשאות הרצה: chmod +x patched_stage4
5. הריצו: ./patched_stage4
הוראות קמפול¶
כדי שהאתגר יהיה אפקטיבי, קמפלו עם strip (הסרת סימבולים) ובלי מנגנוני הגנה שמפריעים ללמידה:
# שלב 1
gcc -o stage1 stage1.c
strip stage1
# שלב 2
gcc -o stage2 stage2.c
strip stage2
# שלב 3
gcc -o stage3 stage3.c
strip stage3
# שלב 4
gcc -o stage4 stage4.c
strip stage4
הערה למרצה: אפשר לתת לתלמידים רק את הבינאריים המקומפלים (stage1, stage2, stage3, stage4) בלי קוד המקור. הקוד המקור כאן הוא "מפתח התשובות" שלכם.
קריטריונים להערכה¶
שלב 1 (25 נקודות)¶
- מצאו את המפתח (15 נקודות)
- תיעדו את תהליך המציאה (10 נקודות)
שלב 2 (25 נקודות)¶
- מצאו את המפתח (15 נקודות)
- הסבירו את האלגוריתם (10 נקודות)
שלב 3 (25 נקודות)¶
- זיהוי טכניקת האנטי-דיבוג (5 נקודות)
- עקיפת האנטי-דיבוג (10 נקודות)
- מציאת המפתח (10 נקודות)
שלב 4 (25 נקודות)¶
- הסבר על הpatch שביצעתם (10 נקודות)
- הpatch עובד - כל קלט מתקבל (15 נקודות)
סיכום הפרויקט¶
בפרויקט הזה השתמשתם בכל הכלים שלמדנו לאורך הפרק:
- strings ו-objdump לניתוח סטטי ראשוני
- Ghidra לניתוח סטטי עמוק עם Decompiler
- GDB לניתוח דינמי ולעקיפת מנגנוני הגנה
- Patching לשינוי התנהגות של בינארי
אלו הם בדיוק הכלים שחוקרי אבטחה משתמשים בהם כל יום. בהצלחה!