Reverse Engineering — Ghidra, IDA ו-Binary Analysis
Reverse Engineering — Ghidra, IDA ו-Binary Analysis
Reverse Engineering (RE) היא האמנות לפרק תוכנה ולהבין מה היא עושה — בלי קוד מקור. שיעור זה מלמד ניתוח Binary בכלים Ghidra ו-IDA Free, קריאת Assembly, זיהוי Patterns, עקיפת Anti-Analysis, ו-Dynamic Analysis עם GDB.
בשיעור 43 הכרת את כלי ה-RE (IDA, Ghidra) ואת אמנות עקיפת ההגנות — Anti-Debug, Packing ו-Keygens. שיעור זה משלים אותו מהצד השני: היסודות שעליהם הכל נשען — פורמטי ה-Binary (ELF/PE), ניתוח דינמי עם GDB, ופתרון Crackme מלא מקצה לקצה. יחד, שני השיעורים מכסים את מלוא ה-RE.
- ELF ו-PE Format — מבנה Binary, Magic Bytes, Sections
- Ghidra — NSA Tool חינמי: Import, Analysis, Decompiler, Xref
- IDA Free — השוואה ל-Ghidra, קיצורי מקלדת, Pseudocode
- Function Graph — קריאת Control Flow, זיהוי if/loop/switch
- String Search — מציאת מחרוזות וניווט ל-Xrefs
- Anti-Analysis — Obfuscation, UPX Packing, Anti-Debug, Self-Modifying
- GDB — Dynamic Analysis, Breakpoints, checksec
- Crackme Walkthrough — פתרון מלא מקצה לקצה
פורמט Binary: ELF ו-PE
לפני שפותחים Ghidra, צריך להבין איך binary בנוי. קיימים שני פורמטים עיקריים: ELF (Linux) ו-PE (Windows).
| היבט | ELF (Linux) | PE (Windows) |
|---|---|---|
| קיצור | Executable and Linkable Format | Portable Executable |
| סיומות | .elf, ללא סיומת, .so (lib) | EXE, DLL, SYS, OCX |
| Magic Bytes | 0x7F 'ELF' (4 bytes ראשונים) | 'MZ' = 0x4D5A (2 bytes ראשונים) |
| Sections חשובות | .text (קוד), .data, .bss, .rodata (strings) | .text, .data, .rdata (strings), .rsrc |
| כלי קריאה | readelf, objdump, file | PEview, CFF Explorer, dumpbin |
| נפוץ ב-CTF | כן — RE ו-Pwn challenges | פחות — נפוץ ב-Malware RE |
# ─── בדיקת סוג קובץ ───────────────────────────────────────
$ file mystery_binary
mystery_binary: ELF 64-bit LSB executable, x86-64, dynamically linked
# ─── Magic Bytes בהקס ─────────────────────────────────────
$ xxd mystery_binary | head -2
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
↑ 7f 45 4c 46 = \x7fELF (ELF Magic!)
# ─── Sections ב-ELF ───────────────────────────────────────
$ readelf -S mystery_binary | grep -E '\.text|\.data|\.rodata'
[ 14] .text PROGBITS 0x401060 ... # קוד!
[ 16] .rodata PROGBITS 0x402000 ... # strings קבועים
[ 25] .data PROGBITS 0x404040 ... # משתנים גלובליים
# ─── מה strings מסתיר? ────────────────────────────────────
$ strings mystery_binary
Enter password:
Correct!
Flag: CTF{...}
Wrong password, try again.
# ← strings מוצא טקסט קריא בתוך binary! כלי RE ראשון!xxd binary | head -1 וראית: 4d5a 9000 0300 0000. מה הקובץ?strings crackme | grep -i flag ואין פלט. מה המסקנה הנכונה?Ghidra — Reverse Engineering חינמי של NSA
Ghidra הוא כלי RE בקוד פתוח שפורסם על ידי NSA ב-2019. מאפשר פירוק Binary לAssembly וייצור Pseudocode קריא (Decompiler). נחשב לחלופה הטובה ביותר לIDA Pro היקר.
ממשק Ghidra: 4 חלונות קריטיים
| חלון | שם | מה רואים |
|---|---|---|
| Symbol Tree | עץ הסמלים | רשימת כל הפונקציות, Labels, Classes |
| Code Browser | מרכז המסך | Assembly + Pseudocode זה לצד זה |
| Decompiler | ימין | C-like Pseudocode שנוצר אוטומטית |
| References | References | מי קורא לפונקציה הנוכחית (Xrefs) |
# ─── פתיחת Binary ב-Ghidra ────────────────────────────────
# 1. File → New Project → Non-Shared Project → שם לפרויקט
# 2. File → Import File → בחר קובץ binary
# 3. Double-click → YES לאנליזה אוטומטית
# האנליזה מבצעת: Function detection, String detection, Xref analysis
# ─── Workflow מקצועי ──────────────────────────────────────
# שלב 1: Search → For Strings → חפש 'flag', 'password', 'correct'
# שלב 2: לחץ ימני על String → References → Show References
# שלב 3: Double-click → קפוץ לפונקציה שמשתמשת בstring
# שלב 4: קרא Pseudocode, Rename (L) משתנים חשובים
# שלב 5: Data Type Fix — לחץ ימני → Retype Variable
# ─── Pseudocode שDecompiler מייצר ────────────────────────
int check_password(char *param_1) {
int iVar1;
char local_29 [25];
strcpy(local_29, param_1); // ← DANGER: Buffer Overflow!
iVar1 = strcmp(local_29, "s3cr3t_p4ss"); // ← הסיסמה!
if (iVar1 == 0) {
puts("Access Granted!");
return 1;
}
puts("Wrong password");
return 0;
}
# ─── קיצורי מקלדת Ghidra ──────────────────────────────────
# L → Rename label/function/variable
# T → Retype variable
# Ctrl+L → Go to address
# X → Show references לאיבר הנוכחי
# Search → For Strings → חיפוש Strings! מלכודת — Pseudocode לא זהה לקוד המקורי ›
הDecompiler מנסה לשחזר C Code מAssembly. התוצאה קרובה לקוד המקורי, אבל לא זהה. שמות משתנים יהיו param_1, local_2c. לולאות יכולות להראות כ-while במקום for.
if (strcmp(param_1, "G4MM4_CTF") == 0) {
puts("You Win!");
}מה הסיסמה?// Pseudocode מ-Ghidra:
char enc[] = {0x26, 0x22, 0x2d, 0x2d, 0x22};
char key = 0x47;
for (int i = 0; i < 5; i++)
enc[i] ^= key; // decrypt
printf("Password: %s\n", enc);מה הסיסמה שתודפס?IDA Free ו-Function Graph
IDA Pro הוא הכלי המקצועי הקלאסי לRE. IDA Free חינמי ומוגבל. Function Graph מציג פונקציה כגרף של Basic Blocks — הדרך המהירה ביותר להבין Control Flow.
השוואה: Ghidra vs IDA
| היבט | Ghidra (NSA) | IDA Free | IDA Pro |
|---|---|---|---|
| מחיר | חינם ✅ | חינם (מוגבל) ⚠️ | $1,000–$4,000+ ❌ |
| Decompiler | ✅ מצוין | ⚠️ x86 32-bit בלבד | ✅ HexRays — מעולה |
| Scripting | Python/Java API | Python/IDC מוגבל | Python/IDC מלא |
| שימוש ב-CTF | ✅ מומלץ | ⚠️ ישן | ✅ מקצועי |
קיצורי מקלדת IDA
; ─── קיצורי IDA Free ────────────────────────────────────────
F5 → Decompile (Pseudocode) — הפקודה הכי חשובה!
G → Go to address
N → Rename function/variable
X → Cross-references (מי קורא לזה?)
Space → Toggle Graph/Linear view
Shift+F12 → Strings window
Ctrl+F → Search in strings/names
; ─── Function Graph ─────────────────────────────────────────
; כל בלוק = Basic Block (קוד רציף ללא קפיצות)
; חץ ירוק = JMP/JE שמתקיים (True branch)
; חץ אדום = JMP שלא מתקיים (False branch / Fall-through)
; חץ כחול = JMP ללא תנאי
; ─── דוגמה: Function Graph ────────────────────────────────
[ENTRY BLOCK]
push ebp
mov ebp, esp
cmp [ebp+8], 0 ; האם arg == NULL?
jz .fail ; אם כן → כשלון
│
[BLOCK 2] ────────→ [FAIL BLOCK]
call strlen mov eax, 0
cmp eax, 8 ; len==8? ret
jne .fail
│
[BLOCK 3]
call check_chars ; בדיקת תווים
test eax, eax
jz .fail
│
[SUCCESS BLOCK]
call print_flag
mov eax, 1
ret
; ← מהגרף: צריך arg != NULL, len == 8, check_chars מצליחהזיהוי Patterns בגרף
| Pattern בגרף | משמעות | מה לחפש |
|---|---|---|
| Block עם שני יוצאים | if/else | מה ה-condition? JE/JNE/JL/JG |
| Loop (קשת חוזרת) | for/while loop | מה counter? מה condition יציאה? |
| Block יחיד רב-יוצאים | switch/case | לכל case block נפרד |
| Block "בלתי מושג" | Dead code / Anti-Debug | מוסיפים לבלבל את הReverse Engineer |
| CALL לפונקציה חיצונית | API call | strncmp? malloc? fopen? |
| Block עם XOR loop | Decryption | מצא המפתח ופרק את הנתון |
cmp eax, 5 / jge .else ושני Blocks יוצאים. מה זה מייצג?sub_401230 ב-IDA. לחצת X. מה קרה?; Block A:
cmp eax, 7
jne .block_c
; Block B (Fall-through):
call success_func
jmp .end
; Block C:
call fail_func
; Block D (.end):
retעם eax=7, לאיזה Block נגיע?Anti-Analysis Techniques
מפתחים ומחברי Malware משתמשים בטכניקות כדי להקשות על RE. ב-CTF מופיעות גרסאות פשוטות יחסית — חשוב לזהות ולעקוף.
| טכניקה | מה זה | איך מזהים | איך עוקפים |
|---|---|---|---|
| String Obfuscation | Strings מוצפנות ב-XOR/Base64 | strings ריק / חסר הגיון | מצא פונקציית decrypt, הרץ דינמית |
| Packing (UPX) | כל הקובץ דחוס, נפרק ב-Runtime | entropy גבוה, UPX header בstrings | upx -d לunpack, או dump מזיכרון |
| Anti-Debug | בודק אם רצים תחת Debugger | IsDebuggerPresent, ptrace calls | Patch את הcall ל-XOR EAX,EAX |
| Control Flow Obfuscation | קפיצות מיותרות, junk code | גרף מסובך מאוד, NOP sleds | עקוב רק אחרי executed paths |
| Self-Modifying Code | קוד ששינה את עצמו ב-Runtime | Sections עם RWX permissions | הרץ דינמית ו-dump לאחר שינוי |
# ─── UPX Unpacking (נפוץ ב-CTF) ──────────────────────────
$ strings packed_binary | grep UPX
UPX! ← Packer זוהה!
$ upx -d packed_binary -o unpacked_binary
Ultimate Packer for eXecutables
Unpacked 1 file.
$ file unpacked_binary
ELF 64-bit LSB executable ... ← עכשיו ניתן לניתוח!
# ─── Anti-Debug: IsDebuggerPresent (Windows) ─────────────
int main() {
if (IsDebuggerPresent()) {
printf("No debugging allowed!\n");
exit(1);
}
// ... קוד אמיתי
}
// Patch ב-Ghidra: מצא CALL IsDebuggerPresent
// שנה ל: XOR EAX,EAX (מחזיר 0 תמיד = 'לא מדבגים')
# ─── entropy בדיקה ────────────────────────────────────────
$ binwalk -E binary
# Binary רגיל: entropy 5-6 bits/byte
# Packed/Encrypted: entropy 7.5-8 bits/byte (כמו random!)if (IsDebuggerPresent() != 0) { exit(1); }. מה הפעולה הנכונה?strings crackme | grep -i upx וקיבלת: UPX!. מה הצעד הבא?; ב-Ghidra רואים:
char hidden[] = {0x4b, 0x4f, 0x5a, 0x50};
char key = 0x20;
for(int i=0; i<4; i++)
hidden[i] ^= key;
printf("%s\n", hidden);מה יודפס?GDB — Dynamic Analysis
GDB הוא debugger ל-Linux. בעוד Ghidra/IDA הם Static Analysis (ללא הרצה), GDB מאפשר Dynamic Analysis — מריצים binary ומפסיקים תוך כדי לבחון זיכרון ורגיסטרים.
# ─── GDB בסיסי ────────────────────────────────────────────
$ gdb ./crackme
(gdb) info functions # רשימת פונקציות
(gdb) disassemble main # Assembly של main
(gdb) break check_password # Breakpoint בפונקציה
(gdb) break *0x08048470 # Breakpoint בכתובת ספציפית
(gdb) run # הרץ
(gdb) run AAAA # הרץ עם argument
# כשמגיעים לBreakpoint:
(gdb) info registers # כל הרגיסטרים
(gdb) x/20x $esp # 20 מילים מה-Stack (hex)
(gdb) x/s 0x08048680 # קרא string מכתובת
(gdb) p $eax # ערך EAX
(gdb) set $eax = 0 # שנה ערך EAX (לעקיפת checks!)
(gdb) next # צעד אחד (לא נכנס לפונקציה)
(gdb) step # צעד אחד (נכנס לפונקציה)
(gdb) continue # המשך עד Breakpoint הבא
# ─── checksec: בדיקת הגנות ───────────────────────────────
$ checksec --file=./crackme
Arch: amd64-64-little
Stack: No canary found ← ⚠️ פגיע ל-Buffer Overflow!
NX: NX enabled ← Stack לא executable
PIE: No PIE (0x400000) ← כתובות קבועות!
# ─── pwndbg: GDB on steroids ──────────────────────────────
# pip install pwndbg
# מוסיף: telescope, checksec, vmmap, heap viewscheck_password. איך תראה את ה-Stack?No canary found, NX enabled, PIE: No PIE. מה אפשרי?Crackme Walkthrough מלא + סיכום
Crackme הוא תרגיל RE קלאסי: Binary שמבקש סיסמה. המשימה — למצוא את הסיסמה. נעבור על Walkthrough מלא מקצה לקצה.
# ─── שלב 1: Reconnaissance ────────────────────────────────
$ file crackme01
crackme01: ELF 32-bit LSB executable, Intel 80386
$ strings crackme01 | head -20
Enter password:
Nope.
You Win!
# ← לא רואים ישירות את הסיסמה. צריך Ghidra.
# ─── שלב 2: פתיחה ב-Ghidra ───────────────────────────────
# Symbol Tree → Functions → main
# Pseudocode:
int main(void) {
char local_2c [32];
printf("Enter password: ");
fgets(local_2c, 32, stdin);
local_2c[strcspn(local_2c, "\n")] = 0; // trim newline
if (check(local_2c) == 1) {
puts("You Win!");
} else {
puts("Nope.");
}
return 0;
}
# ─── שלב 3: ניתוח check() ─────────────────────────────────
int check(char *s) {
char key[] = {0x67, 0x34, 0x6d, 0x6d, 0x34, 0x5f, 0x70, 0x77, 0x6e};
if (strlen(s) != 9) return 0;
for (int i = 0; i < 9; i++) {
if (s[i] != (key[i] ^ 0x00)) return 0; // XOR 0 = no-op
}
return 1;
}
# ─── שלב 4: פיצוח ──────────────────────────────────────────
# key bytes: 0x67 0x34 0x6d 0x6d 0x34 0x5f 0x70 0x77 0x6e
# XOR 0x00 = הbytes עצמם
# convert: g 4 m m 4 _ p w n
# → הסיסמה: g4mm4_pwn
$ ./crackme01
Enter password: g4mm4_pwn
You Win!- ELF (Linux) / PE (Windows): פורמטים בינאריים. Magic: \x7fELF / MZ. Sections: .text (קוד), .rodata (strings)
- strings binary | grep flag: כלי ראשון — מציא טקסט קריא. לא מוצא? → Strings מוסתרות
- Ghidra: Import → Analysis → Symbol Tree → Decompiler. Rename: L. Xref: X (ב-Decompiler)
- Pseudocode: Decompiler מייצר C-like code. לא מושלם — Rename משתנים לשיפור הקריאות
- Workflow: String Search → Xref → Rename → קרא Pseudocode → מצא Comparison → פצח
- Function Graph: ויזואליזציה של Control Flow. זהה if/loops/switch. חץ ירוק=True, אדום=False
- Anti-Analysis: String Obfuscation, UPX Packing, Anti-Debug, Control Flow Obfuscation
- upx -d binary: פריקת UPX. entropy גבוה = packed. strings עם "UPX!" = זיהוי
- GDB: break, run, info registers, x/ לזיכרון, set $eax=0 לעקיפת checks
- checksec: No canary + No PIE = Buffer Overflow קל יותר. NX = Shellcode ב-Stack לא עובד
strings crackme מראה: "Enter key:", "Nope", "Win!". grep -i flag ריק. מה עושים?// check(char *input):
// 1. if (strlen(input) != 6) return 0;
// 2. if (input[0] != 'G') return 0;
// 3. sum = 0;
// for(i=1; i<6; i++) sum += input[i];
// 4. if (sum != 500) return 0;
// 5. return 1;מה input תקין (אחד מיניים)?תרגול נוסף — RE: Ghidra ו-checksec
שאלות העשרה נוספות לחיזוק החומר של השיעור — באותו פורמט בחירה של הקורס.
if (local_10 == 0x539). מה על המשתמש להזין?checksec מציג: NX=enabled, Canary=found, PIE=enabled. מה כל הגנה עושה?אתגר מעשי — פורמט ELF — פרסור כותרת
שיא השיעור: להבין את הפגיעות, לזהות אותה בקוד ובבינארי, ולהקשיח מולה. ההגשה נבדקת אוטומטית מול בינאריים אמיתיים.
שאלות תרגול ותשובות (20)
שאלה 1.1 — Magic Bytes · הרצת xxd binary | head -1 וראית: 4d5a 9000 0300 0000. מה הקובץ?
xxd binary | head -1
- א. קובץ ELF של לינוקס ב-64-bit, לפי הבתים הראשונים של הכותרת
- ב. קובץ ZIP דחוס, שכן הבתים הראשונים מזהים ארכיון דחוס רגיל
- ג. קובץ PE (Windows EXE/DLL): הבתים 0x4D5A הם 'MZ', סימן ה-PE
- ד. קובץ PDF, שכן הבתים הראשונים תואמים לחתימת מסמך מסוג זה
✓ תשובה: ג) PE (Windows) — 0x4D5A = 'MZ' = Mark Zbikowski (מפתח MS-DOS). כל EXE/DLL/SYS מתחיל כך. ELF מתחיל ב-0x7F454C46 (\x7fELF). ZIP מתחיל ב-0x504B (PK). PDF מתחיל ב-25504446 (%PDF).
שאלה 1.2 — Sections · חיפשת strings בקובץ ELF. הם נמצאים ב-Section הנקרא:
- א. .text: כאן יושב קוד הבינארי עצמו שאותו המעבד מריץ בפועל ממש
- ב. .data: כאן יושבים המשתנים הגלובליים הניתנים לכתיבה בזמן הריצה
- ג. .rodata (read-only): כאן יושבים ה-Strings הקבועים וה-Literals
- ד. .bss: כאן יושבים המשתנים הגלובליים שאינם מאותחלים כלל בהתחלה
✓ תשובה: ג) .rodata — "Enter password:", "Correct!", "flag{..." מאוחסנים ב-.rodata (Read-Only Data). .text = קוד Assembly. .data = משתנים גלובליים עם ערכים. .bss = משתנים גלובליים ריקים (zero-initialized). ב-PE: .rdata ≈ .rodata.
שאלה 1.3 — strings command · הרצת strings crackme | grep -i flag ואין פלט. מה המסקנה הנכונה?
strings crackme | grep -i flag
- א. ה-Binary כלל אינו מכיל Flag, ולכן יש לחפש אותו בקובץ אחר לגמרי
- ב. קובץ ה-Binary פשוט פגום ומשובש, ולכן לא ניתן כלל לנתח אותו כאן
- ג. ה-Flag כנראה מוסתר (XOR/Packing) ומחושב בזמן ריצה; צריך Ghidra
- ד. צריך פשוט להריץ strings עם הדגל n 3 כדי לתפוס תווים קצרים יותר
✓ תשובה: ג) — strings מוצא רק טקסט קריא בבינארי. אם ה-Flag מוצפן/XOR-encoded, strings לא יראה אותו. הצעד הבא: פתח ב-Ghidra → חפש פונקציות Decryption → GDB לראות הזיכרון בזמן ריצה. CTF Challenges רבים מסתירים strings כך.
שאלה 2.1 — Ghidra Workflow · פתחת binary ב-Ghidra. רצית למצוא את הפונקציה שבודקת סיסמה. מה הצעד הראשון?
- א. לקרוא מיד את כל קוד ה-Assembly מן ההתחלה ועד הסוף באופן ידני
- ב. Search ← For Strings ← חפש "correct"/"wrong" ← Xref ← קפוץ לפונקציה
- ג. לכתוב Script בפייתון שמחפש את הקריאה ל-strcmp בתוך כל הבינארי
- ד. להריץ את ה-binary באופן מיידי בתוך GDB ולעקוב אחר התנהגותו
✓ תשובה: ב) — Strings Search → Xref הוא Workflow סטנדרטי ב-RE. מחרוזות כמו "Access Granted!" או "Wrong!" מובילות ישירות לפונקציית הבדיקה. הרבה CTF Crackmes נפתרים ב-5 דקות עם שיטה זו בלבד.
שאלה 2.2 — Decompiler Reading · ה-Decompiler מציג:if (strcmp(param_1, "G4MM4_CTF") == 0) { puts("You Win!"); }מה הסיסמה?
if (strcmp(param_1, "G4MM4_CTF") == 0) {
puts("You Win!");
}- א. G4MM4_CTF: הפונקציה strcmp מחזירה 0 כשהמחרוזות זהות, ואז מנצחים
- ב. You Win!: זו המחרוזת שמוצגת על המסך ברגע שהבדיקה מצליחה לעבור
- ג. param_1: זהו פשוט שם הפרמטר של הפונקציה כפי שה-Decompiler הציגו
- ד. לא ניתן לדעת זאת כלל מבלי להריץ את הבינארי ולנסות קלטים שונים
✓ תשובה: א) G4MM4_CTF — strcmp(a, b) מחזיר 0 כשa==b. אם הuser מכניס "G4MM4_CTF", strcmp מחזיר 0 → if(0==0)=true → "You Win!". זה הדפוס הכי נפוץ ב-Crackme challenges — strcmp עם hardcoded string.
שאלה 2.3 — Dry Run: XOR Decryption · // Pseudocode מ-Ghidra: char enc[] = {0x26, 0x22, 0x2d, 0x2d, 0x22}; char key = 0x47; for (int i = 0; i < 5; i++) enc[i] ^= key; // decrypt printf("Password: %s\n", enc);מה הסיסמה שתודפס?
// Pseudocode מ-Ghidra:
char enc[] = {0x26, 0x22, 0x2d, 0x2d, 0x22};
char key = 0x47;
for (int i = 0; i < 5; i++)
enc[i] ^= key; // decrypt
printf("Password: %s\n", enc);- א. "hello": פענוח נפוץ, אך אינו תואם את תוצאת ה-XOR עם המפתח כאן
- ב. "GAMMA": מחרוזת שנראית סבירה אך אינה תוצאת ה-XOR עם 0x47 כלל
- ג. "aello": פענוח חלקי שגוי שבו רק שני התווים הראשונים נכונים כאן
- ד. "aejje": כל בית ב-XOR עם 0x47 נותן 'a','e','j','j','e' בהתאמה
✓ תשובה: ד) "aejje" — חישוב XOR: 0x26^0x47=0x61='a', 0x22^0x47=0x65='e', 0x2d^0x47=0x6a='j', 0x2d^0x47=0x6a='j', 0x22^0x47=0x65='e'. תשובה ב' הייתה כמעט נכונה אבל לא חישבה עד הסוף. ב-Python: bytes([x^0x47 for x in [0x26,0x22,0x2d,0x2d,0x22]]).
שאלה 3.1 — Function Graph · ב-Function Graph רואים: Block עם cmp eax, 5 / jge .else ושני Blocks יוצאים. מה זה מייצג?
cmp eax, 5 / jge .else
- א. if/else: אם eax קטן מ-5 ממשיכים ל-True, אחרת קופצים ל-else Block
- ב. לולאת while עם תנאי יציאה שנבדק מחדש בכל איטרציה ואיטרציה עד לסיום
- ג. מבנה switch/case שמפצל את זרימת התוכנית לשני מקרים נפרדים ושונים
- ד. פונקציה שמחזירה שני ערכים שונים בהתאם לתוצאת ההשוואה שבוצעה כאן
✓ תשובה: א) if/else — cmp eax, 5 בודק את הערך. jge (Jump if Greater or Equal): אם eax >= 5 → קפוץ ל-.else. אם eax < 5 → Fall-through לBlock הבא (True Branch). בפסאודוקוד: if(eax < 5) { true_block } else { else_block }.
שאלה 3.2 — IDA Shortcuts · מצאת פונקציה sub_401230 ב-IDA. לחצת X. מה קרה?
sub_401230
- א. נפתחה רשימת Cross-References: כל המקומות שקוראים לפונקציה הזו
- ב. הפונקציה עצמה נמחקה כליל מן הבינארי יחד עם כל הקריאות אליה כאן
- ג. הפונקציה הוקצתה והתווספה אל תוך רשימת ה-Export של הקובץ הנתון
- ד. נפתח חלון ה-Pseudocode דמוי-C של הפונקציה כפי שהודקומפל אותה כעת
✓ תשובה: א) Cross-References — X = Xrefs. רואים: "sub_401230 is called from: main+0x3F, sub_4011A0+0x12". חיוני להבנת Context — אם מצאת check_password, Xrefs מראה מי קורא לה ועם אילו ארגומנטים. N = Rename, F5 = Pseudocode.
שאלה 3.3 — Dry Run: Graph Reading · ; Block A: cmp eax, 7 jne .block_c ; Block B (Fall-through): call success_func jmp .end ; Block C: call fail_func ; Block D (.end): retעם eax=7, לאיזה Block נגיע?
; Block A: cmp eax, 7 jne .block_c ; Block B (Fall-through): call success_func jmp .end ; Block C: call fail_func ; Block D (.end): ret
- א. Block C (fail): הקפיצה jne מתבצעת דווקא כאשר הערך של eax הוא 7 כאן
- ב. Block D ישירות: הזרימה מדלגת על שני הבלוקים ומגיעה מיד אל ה-ret
- ג. זה תלוי לחלוטין בערכו הנוכחי של דגל האפס (Zero Flag) ברגע הזה
- ד. Block B (success): jne אינו מתקיים כי 7 שווה ל-7, אז Fall-through
✓ תשובה: ד) Block B — cmp eax, 7 בודק אם eax==7. אם שווה: ZF=1. jne (Jump if Not Equal) = קפוץ אם ZF=0. כאן ZF=1 → jne לא קופץ → Fall-through לBlock B → success_func. אם eax≠7 → ZF=0 → jne קופץ לBlock C → fail_func.
שאלה 4.1 — זיהוי Anti-Analysis · פתחת binary ב-Ghidra. ה-Decompiler מראה פונקציה ראשית עם: if (IsDebuggerPresent() != 0) { exit(1); }. מה הפעולה הנכונה?
if (IsDebuggerPresent() != 0) { exit(1); }- א. להריץ ב-GDB פשוט ולהתעלם מן ה-Check הזה מבלי לגעת בבינארי כלל
- ב. לפתוח binary אחר לגמרי, מכיוון שקובץ זה אינו ניתן כלל לניתוח כאן
- ג. לכתוב Script בפייתון שמבטל את הקריאה ל-IsDebuggerPresent מרחוק
- ד. Patch ב-Ghidra: שנה את התוצאה ל-XOR EAX,EAX כך שהבדיקה תמיד תעבור
✓ תשובה: ד) Patch — הדרך הנקייה: ב-Ghidra בוחרים Instruction לאחר הCALL → Right click → Patch Instruction → XOR EAX,EAX → EAX=0 → IsDebuggerPresent חוזר 0 → if(0 != 0) = false → exit לא נקרא. גם: שינוי JNZ ל-NOP NOP.
שאלה 4.2 — UPX Unpacking · הרצת strings crackme | grep -i upx וקיבלת: UPX!. מה הצעד הבא?
strings crackme | grep -i upx
- א. להריץ את ה-binary הדחוס ישירות בתוך GDB בלי לפרוק אותו קודם לכן
- ב. לנתח ב-Ghidra ישירות, שכן הסימון UPX הוא רק metadata חסר חשיבות
- ג. לחפש מפתחות (keys) של UPX בתוך קוד ה-Assembly של הבינארי הזה כאן
- ד. upx -d crackme -o crackme_unpacked ← פתח את הקובץ המפורק ב-Ghidra
✓ תשובה: ד) — UPX! בstrings = הbinary packed. ניתוח ב-Ghidra ישיר ייתן רק את Unpacking Stub, לא את הקוד האמיתי. upx -d מפרק לגרסה המקורית → ניתוח מלא אפשרי. אם upx -d כושל (Modified UPX): GDB → BP על JMP OEP → dump memory.
שאלה 4.3 — Dry Run: XOR Obfuscation · ; ב-Ghidra רואים: char hidden[] = {0x4b, 0x4f, 0x5a, 0x50}; char key = 0x20; for(int i=0; i<4; i++) hidden[i] ^= key; printf("%s\n", hidden);מה יודפס?
; ב-Ghidra רואים:
char hidden[] = {0x4b, 0x4f, 0x5a, 0x50};
char key = 0x20;
for(int i=0; i<4; i++)
hidden[i] ^= key;
printf("%s\n", hidden);- א. "KOZP": אלו הבתים המקוריים לפני שבוצעה עליהם כלל פעולת ה-XOR
- ב. "kOzP": פענוח שבו רק חלק מן התווים הומרו לאותיות קטנות בלבד
- ג. "\x20\x20\x20\x20": ערך המפתח עצמו שחוזר על עצמו ארבע פעמים ברצף
- ד. "kozp": כל בית ב-XOR עם 0x20 נותן 'k','o','z','p' בהתאמה מלאה
✓ תשובה: ד) "kozp" — XOR עם 0x20: הופך Uppercase לLowercase (הבדל של bit 5). 0x4b='K'^0x20=0x6b='k'. 0x4f='O'^0x20=0x6f='o'. 0x5a='Z'^0x20=0x7a='z'. 0x50='P'^0x20=0x70='p'. ב-Python: ''.join(chr(b^0x20) for b in [0x4b,0x4f,0x5a,0x50]) = 'kozp'.
שאלה 5.1 — GDB Basics · ב-GDB הגעת ל-Breakpoint בפונקציית check_password. איך תראה את ה-Stack?
check_password
- א. info stack: פקודה שמציגה את ה-Call Stack של הפונקציות הפעילות כעת
- ב. print stack: פקודת GDB שמדפיסה את תוכן ה-Stack הנוכחי כמשתנה רגיל
- ג. x/20x $esp (או $rsp ב-64bit): מציג 20 מילים מן ה-Stack בהקסדצימלי
- ד. show stack: פקודה שמציגה את מבנה ה-Stack בצבעים שונים ובהירים כאן
✓ תשובה: ג) x/20x $esp — x = examine memory. 20 = כמות units. x = hex format. $esp/$rsp = Stack Pointer. גם: x/20s $esp לstrings, x/20i $eip לinstructions. info stack מציג Call Stack (מי קרא למי), לא את תוכן ה-Stack.
שאלה 5.2 — checksec · checksec מראה: No canary found, NX enabled, PIE: No PIE. מה אפשרי?
No canary found, NX enabled, PIE: No PIE
- א. הזרקת Shellcode אל ה-Stack והרצתו משם, אף שה-NX המופעל מונע זאת
- ב. ה-Binary מוגן לחלוטין מכל כיוון, ולכן לא ניתן בכלל לנצל אותו כאן
- ג. BOF אפשרי (אין Canary) וכתובות קבועות (אין PIE) → ret2libc/ROP קל
- ד. קיים רק Stack Overflow, אך לא ניתן כלל לנצל אותו בהיעדר Canary כאן
✓ תשובה: ג) — No Canary: Buffer Overflow ל-Return Address אפשרי. No PIE: כתובות קבועות — לא צריך ל-Leak כתובות. NX enabled: הStack לא Executable → Shellcode לא עובד → במקום: ret2libc (קפוץ לlibc), ROP Chains. שלוש ההגנות ביחד = קשה, אחת חסרה = נקודת חולשה.
שאלה 5.3 — Dynamic vs Static · ב-Ghidra (Static Analysis) לא הצלחת להבין פונקציה מורכבת עם Obfuscation. מה Dynamic Analysis יכול לתת שStatic לא?
# ─── שלב 1: Reconnaissance ────────────────────────────────
$ file crackme01
crackme01: ELF 32-bit LSB executable, Intel 80386
$ strings crackme01 | head -20
Enter password:
Nope.
You Win!
# ← לא רואים ישירות את הסיסמה. צריך Ghidra.
# ─── שלב 2: פתיחה ב-Ghidra ───────────────────────────────
# Symbol Tree → Functions → main
# Pseudocode:
int main(void) {
char local_2c [32];
printf("Enter password: ");
fgets(local_2c, 32, stdin);
local_2c[strcspn(local_2c, "\n")] = 0; // trim newline
if (check(local_2c) == 1) {
puts("You Win!");
} else {
puts("Nope.");
}
return 0;
}
# ─── שלב 3: ניתוח check() ─────────────────────────────────
int check(char *s) {
char key[] = {0x67, 0x34, 0x6d, 0x6d, 0x34, 0x5f, 0x70, 0x77, 0x6e};
if (strlen(s) != 9) return 0;
for (int i = 0; i < 9; i++) {
if (s[i] != (key[i] ^ 0x00)) return 0; // XOR 0 = no-op
}
return 1;
}
# ─── שלב 4: פיצוח ──────────────────────────────────────────
# key bytes: 0x67 0x34 0x6d 0x6d 0x34 0x5f 0x70 0x77 0x6e
# XOR 0x00 = הbytes עצמם
# convert: g 4 m m 4 _ p w n
# → הסיסמה: g4mm4_pwn
$ ./crackme01
Enter password: g4mm4_pwn
You Win!- א. את קוד המקור המלא והמקורי של הבינארי כפי שנכתב על-ידי המפתח עצמו
- ב. פלט Pseudocode יפה וקריא הרבה יותר מזה שמפיק הכלי Ghidra כרגע
- ג. קוד Assembly מדויק ונקי הרבה יותר מזה שמפיק הכלי IDA כרגע כאן
- ד. ערכי משתנים בזמן אמת: הזיכרון, הרגיסטרים וה-Stack בכל נקודת ריצה
✓ תשובה: ד) — Static: מנתח bytes בקובץ, לא יודע ערכים אמיתיים. Dynamic: מריץ את הקוד בפועל — ניתן לראות מה EAX=? מה ה-String שנטען לזיכרון? מה ה-Key שXOR נעשה איתו? פונקציית Obfuscation כשרצה חושפת את output שלה → BP לאחר הפונקציה + x/s $rax.
גאמא סגנון א׳ — Workflow · קיבלת binary ב-CTF. strings crackme מראה: "Enter key:", "Nope", "Win!". grep -i flag ריק. מה עושים?
strings crackme
- א. לנסות קלטים שונים ורבים אל תוך התוכנית עד שאחד מהם יתקבל כנכון
- ב. פתח ב-Ghidra ← חפש "Win!" ← Xref ← קרא את ה-check ← חשב את ה-Flag
- ג. ה-CTF פשוט שבור וה-Flag כלל אינו קיים בו, אז אין טעם כלל להמשיך
- ד. לבצע Brute Force על כל הקלטים האפשריים עד שה-Flag יימצא סוף-סוף
✓ תשובה: ב) — Flag לא נמצא ב-strings = מחושב דינמית. Ghidra Workflow: "Win!" → Xref → פונקציית validation → Pseudocode → הבן לוגיקה (XOR key? math check?) → פתרון Python. ב-CTF: 90% מה-Crackmes נפתרים עם String Xref + Pseudocode Analysis.
גאמא סגנון ב׳ — Dry Run מלא · // check(char *input): // 1. if (strlen(input) != 6) return 0; // 2. if (input[0] != 'G') return 0; // 3. sum = 0; // for(i=1; i<6; i++) sum += input[i]; // 4. if (sum != 500) return 0; // 5. return 1;מה input תקין (אחד מיניים)?
// check(char *input): // 1. if (strlen(input) != 6) return 0; // 2. if (input[0] != 'G') return 0; // 3. sum = 0; // for(i=1; i<6; i++) sum += input[i]; // 4. if (sum != 500) return 0; // 5. return 1;
- א. "GAAAAA": התו הראשון 'G' והאורך 6, אך סכום 'A' כפול 5 הוא 325 בלבד
- ב. "Gdddd": התו הראשון 'G' וסכום מתאים, אך האורך הוא 5 ולא 6 כנדרש
- ג. "GAMMA!": התו הראשון 'G' והאורך 6, אך סכום התווים שונה מ-500 כאן
- ד. "Gddddd": התו הראשון 'G', האורך 6, וסכום 'd' כפול 5 שווה בדיוק 500
✓ תשובה: ד) "Gddddd" — בדיקות: 1) strlen("Gddddd")=6 ✓. 2) input[0]='G' ✓. 3) sum = input[1..5] = 'd'+'d'+'d'+'d'+'d' = 100*5 = 500 ✓. 4) 500 == 500 ✓. 5) return 1 ✓. 'ד'=ASCII 100=0x64. בPython: chr(100)='d'. פתרון כללי: sum צריך 500 ב-5 תווים. 500/5=100 → 'ddddd'.
גאמא סגנון ג׳ — שאלת ידע · מה ההבדל בין Static Analysis ל-Dynamic Analysis ב-RE?
- א. Static הוא ניתוח של קוד ה-Assembly בלבד ו-Dynamic של ה-Pseudocode
- ב. Static הוא ניתוח שמתבצע בלינוקס ו-Dynamic ניתוח שמתבצע בחלונות
- ג. Static הוא פשוט השם לשיטה הישנה ו-Dynamic השם לשיטה המודרנית כיום
- ד. Static: ניתוח בלי הרצה (Ghidra), מהיר; Dynamic: הרצה תחת Debugger
✓ תשובה: ד) — Static: Ghidra/IDA/strings — לא מריצים את הbinary. מהיר, בטוח (לא נדבקים מMalware!), עובד על כל binary. Dynamic: GDB/x64dbg — מריצים בפועל. יתרון: רואים ערכים אמיתיים, מוצאים Decrypted strings. גישה אופטימלית: Static תחילה → Dynamic לאימות ולעקיפת Obfuscation.
שאלה 6.1 [בינוני] · ב-Ghidra רואים if (local_10 == 0x539). מה על המשתמש להזין?
if (local_10 == 0x539)
- א. 1337 — הערך ההקסדצימלי 0x539 שווה ל-1337 בעשרוני (5×256 ועוד 3×16 ועוד 9), ולכן זהו המספר שהתוכנית משווה אליו ושצריך להזין כדי לעבור
- ב. 0x539 כמחרוזת: הנחה שגויה, אף שההשוואה היא לערך מספרי ולא לטקסט, ולכן יש להזין את הערך העשרוני 1337 ולא את המחרוזת 0x539 כפי שהיא בכל מקרה
- ג. 539: הנחה שגויה, אף ש-0x539 הוא ערך הקסדצימלי ולא עשרוני, ולכן המרתו לעשרוני נותנת 1337 ולא 539 שהוא הספרות ללא תחשיב הבסיס כאן בפועל
- ד. 1593: הנחה שגויה, אף ש-0x539 מתפרק ל-5×256 ועוד 3×16 ועוד 9 שהם 1337, ולכן התוצאה היא 1337 ולא 1593 שנובע מחישוב שגוי של הבסיס כן
✓ תשובה נכונה: א — 0x539 = 5×256 + 3×16 + 9 = 1280 + 48 + 9 = 1337. השוואה לקבוע = 'סיסמה' מספרית גלויה ב-RE. הזן 1337.
שאלה 6.2 [קשה] · checksec מציג: NX=enabled, Canary=found, PIE=enabled. מה כל הגנה עושה?
checksec
- א. כל השלוש זהות: הנחה שגויה, אף שכל הגנה שונה — NX חוסם הרצה מה-Stack, Canary מזהה דריסה, ו-PIE מקשה על ניחוש כתובות, ולכן אינן זהות כן
- ב. כולן מצפינות זיכרון: הנחה שגויה, אף שאף אחת מההגנות אינה מצפינה, שכן NX חוסם הרצה, Canary מזהה דריסה, ו-PIE מרנדם כתובות טעינה כן
- ג. רק NX פעיל בפועל: הנחה שגויה, אף שכל השלוש דלוקות לפי checksec, ולכן גם Canary וגם PIE פעילים ומקשים על הניצול יחד עם NX כפי שמוצג
- ד. NX מונע הרצת קוד מה-Stack, Canary הוא ערך שמירה לפני כתובת החזרה שנבדק לפני ret, ו-PIE ממקם את הבינארי בכתובות אקראיות בכל הרצה
✓ תשובה נכונה: ד — NX: Stack לא ניתן להרצה (חוסם Shellcode קלאסי → ROP). Canary: ערך לפני RetAddr, נבדק ב-ret (מזהה Overflow). PIE: ASLR לבינארי עצמו.