GammaCyber

Reverse Engineering — Ghidra, IDA ו-Binary Analysis

Binary Exploitation ו-Pwn · 5–6 שעות

// סקירה

Reverse Engineering — Ghidra, IDA ו-Binary Analysis

Reverse Engineering (RE) היא האמנות לפרק תוכנה ולהבין מה היא עושה — בלי קוד מקור. שיעור זה מלמד ניתוח Binary בכלים Ghidra ו-IDA Free, קריאת Assembly, זיהוי Patterns, עקיפת Anti-Analysis, ו-Dynamic Analysis עם GDB.

🔗 משלים את שיעור 43

בשיעור 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 — פתרון מלא מקצה לקצה
🎯 קשר למיוני גאמא
שיעור זה מכין לשלבים: Black Box CTF בסדנה — ניתוח binary לא מוכר ומציאת הלוגיקה הסודית · CTF Reverse Engineering — Crackme challenges: מצא את הסיסמה הנסתרת · שאלון ידע — שאלות על ELF/PE Format, Anti-Analysis · ראיון מקצועי — שימוש ב-Ghidra/GDB כהוכחת יכולת טכנית
// פרק 1

פורמט Binary: ELF ו-PE

לפני שפותחים Ghidra, צריך להבין איך binary בנוי. קיימים שני פורמטים עיקריים: ELF (Linux) ו-PE (Windows).

היבטELF (Linux)PE (Windows)
קיצורExecutable and Linkable FormatPortable Executable
סיומות.elf, ללא סיומת, .so (lib)EXE, DLL, SYS, OCX
Magic Bytes0x7F 'ELF' (4 bytes ראשונים)'MZ' = 0x4D5A (2 bytes ראשונים)
Sections חשובות.text (קוד), .data, .bss, .rodata (strings).text, .data, .rdata (strings), .rsrc
כלי קריאהreadelf, objdump, filePEview, 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 ראשון!
הרחבה כותרות ELF ו-PE בעומק ›

ELF Header מכיל: e_type (EXEC/DYN/REL), e_machine (x86/ARM/MIPS), e_entry (נקודת כניסה), e_phoff/e_shoff (offset לHeaders). Program Headers מתארים Segments שנטענים לזיכרון. Section Headers מתארים Sections לכלי פיתוח.

PE Header: DOS Stub → PE Signature ('PE\0\0') → COFF Header → Optional Header (ImageBase, AddressOfEntryPoint) → Section Table. Optional Header מכיל את כל הכתובות החשובות — חיוני ל-RE על Malware.

# ─── ELF Header עיקרי ──────────────────────────────────── $ readelf -h binary Magic: 7f 45 4c 46 02 01 01 00 # \x7fELF + 64bit + LE Entry point address: 0x401060 # OEP — שם מתחיל הריצה Start of program headers: 64 # אחרי Header # ─── PE: ImageBase + Entry Point ───────────────────────── # ב-CFF Explorer: Optional Header → AddressOfEntryPoint # ImageBase = 0x400000 (default) → VA = ImageBase + RVA
✏תרגול עצמי3 שאלות
שאלה 1.1 — Magic Bytes
הרצת xxd binary | head -1 וראית: 4d5a 9000 0300 0000. מה הקובץ?
✓ תשובה: ג) 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 הנקרא:
✓ תשובה: ג) .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 מוצא רק טקסט קריא בבינארי. אם ה-Flag מוצפן/XOR-encoded, strings לא יראה אותו. הצעד הבא: פתח ב-Ghidra → חפש פונקציות Decryption → GDB לראות הזיכרון בזמן ריצה. CTF Challenges רבים מסתירים strings כך.
// פרק 2

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 שנוצר אוטומטית
ReferencesReferencesמי קורא לפונקציה הנוכחית (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.

כלל עיב: אם Pseudocode נראה מוזר — בדוק את Assembly המקביל. Rename משתנים לשמות תיאוריים כדי להבין טוב יותר.
✏תרגול עצמי3 שאלות
שאלה 2.1 — Ghidra Workflow
פתחת binary ב-Ghidra. רצית למצוא את הפונקציה שבודקת סיסמה. מה הצעד הראשון?
✓ תשובה: ב) — 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!"); }מה הסיסמה?
✓ תשובה: א) 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);מה הסיסמה שתודפס?
✓ תשובה: ד) "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

IDA Free ו-Function Graph

IDA Pro הוא הכלי המקצועי הקלאסי לRE. IDA Free חינמי ומוגבל. Function Graph מציג פונקציה כגרף של Basic Blocks — הדרך המהירה ביותר להבין Control Flow.

השוואה: Ghidra vs IDA

היבטGhidra (NSA)IDA FreeIDA Pro
מחירחינם ✅חינם (מוגבל) ⚠️$1,000–$4,000+ ❌
Decompiler✅ מצוין⚠️ x86 32-bit בלבד✅ HexRays — מעולה
ScriptingPython/Java APIPython/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 callstrncmp? malloc? fopen?
Block עם XOR loopDecryptionמצא המפתח ופרק את הנתון
✏תרגול עצמי3 שאלות
שאלה 3.1 — Function Graph
ב-Function Graph רואים: Block עם cmp eax, 5 / jge .else ושני Blocks יוצאים. מה זה מייצג?
✓ תשובה: א) 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. מה קרה?
✓ תשובה: א) 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 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

Anti-Analysis Techniques

מפתחים ומחברי Malware משתמשים בטכניקות כדי להקשות על RE. ב-CTF מופיעות גרסאות פשוטות יחסית — חשוב לזהות ולעקוף.

טכניקהמה זהאיך מזהיםאיך עוקפים
String ObfuscationStrings מוצפנות ב-XOR/Base64strings ריק / חסר הגיוןמצא פונקציית decrypt, הרץ דינמית
Packing (UPX)כל הקובץ דחוס, נפרק ב-Runtimeentropy גבוה, UPX header בstringsupx -d לunpack, או dump מזיכרון
Anti-Debugבודק אם רצים תחת DebuggerIsDebuggerPresent, ptrace callsPatch את הcall ל-XOR EAX,EAX
Control Flow Obfuscationקפיצות מיותרות, junk codeגרף מסובך מאוד, NOP sledsעקוב רק אחרי executed paths
Self-Modifying Codeקוד ששינה את עצמו ב-RuntimeSections עם 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!)
הרחבה VMProtect ו-Anti-Analysis מתקדם ›
; ─── Control Flow Obfuscation ───────────────────────────── ; Opaque Predicates: תנאים שתמיד true/false אבל נראים מורכבים mov eax, some_value imul eax, eax ; eax = eax^2 (תמיד >= 0) cmp eax, -1 jl .dead_code ; ← NEVER happens! (eax^2 >= 0) ; קוד אמיתי... .dead_code: ; junk code שלא רץ ; ─── VMProtect (מתקדם) ──────────────────────────────────── ; הקוד מומר ל-Virtual Machine bytecode ; VM interpreter מריץ הbytecode → Assembly המקורי לא נראה ; ניתוח: focus על Input/Output, לא על ה-VM עצמו ; כלים: Triton (symbolic execution), VTIL ; ─── טיפ לCTF ───────────────────────────────────────────── ; 1. זהה packer עם file / strings / binwalk -E ; 2. נסה upx -d תחילה (הכי נפוץ) ; 3. אם לא עובד: GDB → שים BP על entry point → dump memory ; 4. Anti-Debug: Patch ב-Ghidra לפני ניתוח
✏תרגול עצמי3 שאלות
שאלה 4.1 — זיהוי Anti-Analysis
פתחת binary ב-Ghidra. ה-Decompiler מראה פונקציה ראשית עם: if (IsDebuggerPresent() != 0) { exit(1); }. מה הפעולה הנכונה?
✓ תשובה: ד) 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!. מה הצעד הבא?
✓ תשובה: ד) — 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);מה יודפס?
✓ תשובה: ד) "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

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 views
הרחבה עקיפת checks ב-GDB בזמן ריצה ›
# ─── עקיפת Password Check בזמן ריצה ──────────────────────── # Static: מצאנו ב-Ghidra: cmp eax, 1 / jne .fail # Dynamic: נשנה eax בזמן ריצה (gdb) break check_password (gdb) run wrong_input # ← BP מתפוצץ בcheck_password (gdb) finish # סיים ריצת הפונקציה # ← EAX = 0 (כשלון) (gdb) set $eax = 1 # שנה ל-1 (הצלחה!) (gdb) continue # ← "You Win!" — עקפנו את הcheck! # ─── Dump זיכרון עם password מפוענח ───────────────────── (gdb) break decrypt_string # אחרי Decryption (gdb) run # ← BP אחרי הDecryption (gdb) x/s $rax # הstring המפוענח ב-RAX # "s3cr3t_p4ssw0rd" ← הFlag! # ─── ניתוח ב-pwndbg ─────────────────────────────────────── pwndbg> telescope $rsp # ממשק נוח לStack pwndbg> x/s 0x402000 # string מ-.rodata
✏תרגול עצמי3 שאלות
שאלה 5.1 — GDB Basics
ב-GDB הגעת ל-Breakpoint בפונקציית check_password. איך תראה את ה-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: 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 לא?
✓ תשובה: ד) — Static: מנתח bytes בקובץ, לא יודע ערכים אמיתיים. Dynamic: מריץ את הקוד בפועל — ניתן לראות מה EAX=? מה ה-String שנטען לזיכרון? מה ה-Key שXOR נעשה איתו? פונקציית Obfuscation כשרצה חושפת את output שלה → BP לאחר הפונקציה + x/s $rax.
// פרק 6

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!
✓מה למדנו בשיעור 44
  • 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 לא עובד
🎯 השיעור הבא
שיעור 45 — חולשות זיכרון: Buffer Overflow וניצול
✏תרגול סיום — גאמא סגנון3 שאלות
גאמא סגנון א׳ — Workflow
קיבלת binary ב-CTF. strings crackme מראה: "Enter key:", "Nope", "Win!". grep -i 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 תקין (אחד מיניים)?
✓ תשובה: ד) "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: Ghidra/IDA/strings — לא מריצים את הbinary. מהיר, בטוח (לא נדבקים מMalware!), עובד על כל binary. Dynamic: GDB/x64dbg — מריצים בפועל. יתרון: רואים ערכים אמיתיים, מוצאים Decrypted strings. גישה אופטימלית: Static תחילה → Dynamic לאימות ולעקיפת Obfuscation.
// תרגול נוסף

תרגול נוסף — RE: Ghidra ו-checksec

שאלות העשרה נוספות לחיזוק החומר של השיעור — באותו פורמט בחירה של הקורס.

✏תרגול נוסף2 שאלות
שאלה 6.1 [בינוני]
ב-Ghidra רואים if (local_10 == 0x539). מה על המשתמש להזין?
✓ תשובה נכונה: א — 0x539 = 5×256 + 3×16 + 9 = 1280 + 48 + 9 = 1337. השוואה לקבוע = 'סיסמה' מספרית גלויה ב-RE. הזן 1337.
שאלה 6.2 [קשה]
checksec מציג: NX=enabled, Canary=found, PIE=enabled. מה כל הגנה עושה?
✓ תשובה נכונה: ד — NX: Stack לא ניתן להרצה (חוסם Shellcode קלאסי → ROP). Canary: ערך לפני RetAddr, נבדק ב-ret (מזהה Overflow). PIE: ASLR לבינארי עצמו.
// אתגר מעשי

אתגר מעשי — פורמט ELF — פרסור כותרת

שיא השיעור: להבין את הפגיעות, לזהות אותה בקוד ובבינארי, ולהקשיח מולה. ההגשה נבדקת אוטומטית מול בינאריים אמיתיים.

T3 · בינוני400 נק'~25 דק'algorithmic
פרסור כותרת ELF
ממש parse_elf(data) שמפרק את כותרת ה-ELF מרצף בתים. בדוק magic (0x7F 'E' 'L' 'F') → אם שגוי החזר {'magic_ok': False}. אחרת חלץ: class (בית 4: 1→32, 2→64), endian (בית 5: 1→'little', 2→'big'), ו-type (בתים 16–17 כ-uint16 לפי ה-endianness: 1→'REL', 2→'EXEC', 3→'DYN', 4→'CORE'). החזר מילון.
דוגמאות
parse_elf(bytes של בינארי PIE 64-bit) → {'magic_ok':True,'class':64,'endian':'little','type':'DYN'}
parse_elf(b'NOPE...') → {'magic_ok': False}
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס פרסור כותרת ELF ›
def parse_elf(data) -> dict: d = bytes(data) if len(d) < 18 or d[:4] != b'\x7fELF': return {'magic_ok': False} cls = {1:32, 2:64}.get(d[4]) endian = {1:'little', 2:'big'}.get(d[5]) t = int.from_bytes(d[16:18], 'little' if endian=='little' else 'big') typ = {1:'REL', 2:'EXEC', 3:'DYN', 4:'CORE'}.get(t, 'UNKNOWN') return {'magic_ok': True, 'class': cls, 'endian': endian, 'type': typ}
שאלות תרגול ותשובות (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 לבינארי עצמו.