לדלג לתוכן

7.7 טכניקות אנטי הנדסה הפוכה תרגול

תרגיל 1 - ההשפעה של strip

קמפלו את התוכנית הבאה פעמיים - פעם עם סימבולים ופעם בלי:

#include <stdio.h>

int calculate(int a, int b) {
    return a * b + a;
}

void print_result(int val) {
    printf("Result: %d\n", val);
}

int main() {
    int x = calculate(5, 3);
    print_result(x);
    return 0;
}
gcc -o with_symbols program.c
gcc -o without_symbols program.c && strip without_symbols
  1. הריצו nm with_symbols ו-nm without_symbols. מה ההבדל?
  2. הריצו objdump -t with_symbols ו-objdump -t without_symbols. מה נשאר?
  3. הריצו strings על שניהם. האם יש הבדל במחרוזות?
  4. פתחו את שניהם בגידרה. האם הDecompiler מצליח לפענח את הפונקציות גם בלי סימבולים?
  5. מה המסקנה - האם strip באמת מגן על הקוד?

תרגיל 2 - זיהוי ועקיפת אנטי-דיבוג עם ptrace

קמפלו את התוכנית הבאה:

#include <stdio.h>
#include <stdlib.h>
#include <sys/ptrace.h>

void secret_function() {
    printf("The secret code is: 42-ALPHA-7\n");
}

int main() {
    if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
        printf("Nice try! No debugging allowed.\n");
        exit(1);
    }

    printf("No debugger detected. Proceeding...\n");
    secret_function();
    return 0;
}
gcc -o anti_debug anti_debug.c
  1. הריצו את התוכנית רגיל (בלי debugger). מה הפלט?
  2. הריצו עם GDB. מה קורה?
    gdb ./anti_debug
    (gdb) run
    
  3. עקפו את בדיקת ptrace באחת מהדרכים הבאות (נסו את שתיהן):

דרך א - שינוי ערך ב-GDB:

(gdb) catch syscall ptrace
(gdb) run
# כשנעצרים:
(gdb) continue
# כשנעצרים שנית (אחרי החזרה מ-syscall):
(gdb) set $rax = 0
(gdb) continue

דרך ב - LD_PRELOAD:
כתבו קובץ fake_ptrace.c:

long ptrace(int request, ...) {
    return 0;
}

קמפלו כ-shared library:
gcc -shared -o fake_ptrace.so fake_ptrace.c
LD_PRELOAD=./fake_ptrace.so ./anti_debug

  1. הסבירו למה כל אחת מהדרכים עובדת.

תרגיל 3 - זיהוי מחרוזות מוצפנות

קמפלו את התוכנית הבאה:

#include <stdio.h>
#include <string.h>

void decrypt(char *data, int len, char key) {
    for (int i = 0; i < len; i++) {
        data[i] ^= key;
    }
}

int main() {
    // "Secret Password: opensesame" XOR'd with key 0x42
    char encrypted[] = {
        0x11, 0x27, 0x25, 0x30, 0x27, 0x36, 0x62, 0x12,
        0x23, 0x31, 0x31, 0x37, 0x2d, 0x30, 0x26, 0x6a,
        0x62, 0x2d, 0x32, 0x27, 0x2c, 0x31, 0x27, 0x31,
        0x23, 0x2f, 0x27, 0x00
    };

    decrypt(encrypted, strlen(encrypted), 0x42);
    printf("%s\n", encrypted);

    return 0;
}
gcc -o encrypted_strings encrypted_strings.c
strip encrypted_strings
  1. הריצו strings encrypted_strings. האם מצאתם את הסיסמה?
  2. פתחו בגידרה. מצאו את פונקציית main ואת פונקציית ה-decrypt.
  3. מה פונקציית decrypt עושה? (הסתכלו ב-Decompiler)
  4. מהו המפתח (key)?
  5. הריצו עם GDB ושימו breakpoint אחרי הקריאה ל-decrypt. מה הערך של המחרוזת אחרי הפענוח?
    gdb ./encrypted_strings
    (gdb) break main
    (gdb) run
    (gdb) disass main
    # מצאו את הכתובת שאחרי call decrypt
    (gdb) break *<address_after_decrypt>
    (gdb) continue
    (gdb) x/s $rsp    # או הכתובת של המחרוזת
    

תרגיל 4 - זיהוי packing עם UPX

התקינו UPX:

sudo apt install upx-ucl

קמפלו תוכנית פשוטה וארזו אותה:

#include <stdio.h>

int main() {
    printf("Hello from packed binary!\n");
    for (int i = 0; i < 5; i++) {
        printf("Count: %d\n", i);
    }
    return 0;
}

gcc -o original program.c
cp original packed
upx packed
  1. השוו את הגדלים:

    ls -la original packed
    

  2. הריצו strings על שניהם. מה ההבדל? האם strings packed מוצא את "Hello from packed binary!"?

  3. הריצו readelf -h על שניהם. שימו לב לנקודת הכניסה (entry point) - האם היא שונה?

  4. הריצו readelf -S על שניהם. שימו לב לשמות הsections. מה שונה?

  5. פתחו את packed בגידרה. מה גידרה מראה ב-Decompiler?

  6. פרקו את הקובץ:

    upx -d packed
    

    ובדקו שוב עם strings - האם עכשיו מצאתם את המחרוזות?


תרגיל 5 - ניתוח תוכנית עם מספר טכניקות אנטי-RE

קמפלו את התוכנית הבאה:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <time.h>

// Anti-debug: ptrace check
int check_debugger() {
    if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
        return 1;
    }
    return 0;
}

// Anti-debug: timing check
int check_timing() {
    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);

    // Dummy work
    volatile int x = 0;
    for (int i = 0; i < 1000; i++) {
        x += i;
    }

    clock_gettime(CLOCK_MONOTONIC, &end);
    long diff = (end.tv_sec - start.tv_sec) * 1000000000 +
                (end.tv_nsec - start.tv_nsec);

    return (diff > 100000000);  // > 100ms = suspicious
}

// Encrypted flag
void print_flag() {
    char flag[] = {0x36, 0x2c, 0x23, 0x29, 0x44, 0x19, 0x0d,
                   0x1e, 0x0a, 0x1b, 0x16, 0x44, 0x13, 0x0e,
                   0x1b, 0x0c, 0x44, 0x19, 0x0a, 0x1b, 0x17,
                   0x44, 0x1b, 0x0e, 0x00};
    for (int i = 0; flag[i] != 0; i++) {
        flag[i] ^= 0x7A;
    }
    printf("%s\n", flag);
}

int main() {
    if (check_debugger()) {
        printf("Error: application integrity check failed.\n");
        exit(1);
    }

    if (check_timing()) {
        printf("Error: performance anomaly detected.\n");
        exit(1);
    }

    printf("All checks passed!\n");
    print_flag();
    return 0;
}
gcc -o challenge challenge.c
strip challenge
  1. הריצו את התוכנית רגיל. מה הפלט?
  2. נסו להריץ עם GDB. מה קורה?
  3. זהו את כל טכניקות האנטי-RE שהתוכנית משתמשת בהן.
  4. עקפו את כל הבדיקות והגיעו ל-flag. תעדו כל צעד שעשיתם.
  5. בונוס: מצאו את ה-flag בניתוח סטטי בלבד (בלי להריץ). רמז: מה מפתח ה-XOR?