GammaCyber

Reverse Engineering מתקדם

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

// סקירה

Reverse Engineering מתקדם — IDA Pro, Ghidra ו-Anti-Debug

Reverse Engineering (RE) הוא אמנות הניתוח של קוד בינארי ללא קוד מקור. שיעור זה מלמד שימוש ב-IDA Pro ו-Ghidra לניתוח קוד, זיהוי פגיעויות ב-Assembly, ועקיפת טכניקות Anti-Debug שמתוכנות מגינות בהן.

✓נושאי השיעור
  • IDA Pro — ניווט, Graph View, פונקציות, Xrefs, Pseudocode
  • Ghidra — חלופה חינמית ל-IDA, Decompiler, Scripting
  • ניתוח Assembly — זיהוי לולאות, תנאים, קריאות פונקציה
  • Anti-Debug Techniques — IsDebuggerPresent, Timing Checks, Exception-based
  • עקיפת Anti-Debug — Patching, Plugin-ים, ScyllaHide
  • Obfuscation ו-Packing — UPX, VMProtect, זיהוי ועקיפה
  • Keygen ו-License Checks — ניתוח ועקיפה
  • RE Workflow — מקצה לקצה עם דוגמה מעשית
🎯 קשר למיוני גאמא
שיעור זה מכין לשלבים: RE Challenge — ניתוח Binary ב-IDA/Ghidra ומציאת Flag · Anti-Debug Bypass — עקיפת הגנות כדי לנתח תהליך · שאלון Exploitation — זיהוי טכניקות Anti-Debug בקוד Assembly · CTF Reverse — crack crackme, bypass license checks
// פרק 1

IDA Pro — הכלי הסטנדרטי לRE

IDA Pro הוא הDisassembler/Debugger המקצועי ביותר בתעשייה. הוא הופך Binary לAssembly קריא, מזהה פונקציות אוטומטית, ומייצר Pseudocode (Hex-Rays Decompiler).

ניווט בסיסי ב-IDA

; ─── Windows חשובים ב-IDA ──────────────────────────────── ; IDA View (Disassembly) : קוד Assembly — Text Mode ; Graph View : תרשים זרימה — Graph Mode (Space) ; Functions Window : רשימת כל הפונקציות (Shift+F3) ; Strings Window : כל המחרוזות בBinary (Shift+F12) ; Imports : פונקציות מיובאות מDLL/libc ; Exports : פונקציות מיוצאות ; ─── קיצורי מקלדת חיוניים ───────────────────────────────── ; F5 → Decompile לPseudocode (Hex-Rays) ; G → קפוץ לכתובת ; N → שנה שם (Rename) לפונקציה/משתנה ; ; → הוסף תגובה (Comment) ; X → Xrefs — מי קורא לפונקציה זו? ; D / A → הגדר Data / ASCII string ; Space → עבור בין Text Mode ל-Graph Mode ; Ctrl+F → חיפוש טקסט ; ─── זיהוי פונקציות חשובות ──────────────────────────────── ; IDA מזהה אוטומטית: main, WinMain, printf, strcpy... ; פונקציות לא מזוהות: sub_401234 (לפי כתובת) ; שנה שם: N → הקלד שם חדש → ב-Xrefs יופיע השם בכל מקום

Graph View — קריאת זרימת קוד

; ─── קריאת Graph View ─────────────────────────────────────── ; כל בלוק = Basic Block (קוד רציף ללא קפיצות) ; חץ ירוק = JMP/JE שמתקיים (True branch) ; חץ אדום = JMP שלא מתקיים (False branch) ; חץ כחול = JMP ללא תנאי (Unconditional) ; ─── דוגמה: if/else בAssembly ──────────────────────────── ; C code: if (x > 5) { do_a(); } else { do_b(); } ; Assembly (32bit): cmp eax, 5 ; השווה eax ל-5 jle .else_branch ; אם eax <= 5 → קפוץ לelse call do_a ; ← True branch jmp .end .else_branch: call do_b ; ← False branch .end: ... ; בGraph View: ; [cmp + jle] → ירוק → [do_b block] ; ↓ אדום (Fall-through) ; [do_a block] ; ↓ ; [end block]
הרחבה Pseudocode עם Hex-Rays Decompiler ›

לחיצה על F5 מגנרת Pseudocode — קוד דמוי-C שIDA מייצר מה-Assembly. זה מקצר את זמן הניתוח דרמטית.

; ─── Assembly ───────────────────────────────────────────── ; push ebp ; mov ebp, esp ; sub esp, 8 ; mov eax, [ebp+8] ; arg1 ; cmp eax, 0 ; je short loc_4011A0 ; push offset aSuccess ; "Success!" ; call printf ; add esp, 4 ; jmp short loc_4011B0 ; loc_4011A0: ; push offset aFailed ; "Failed!" ; call printf ; add esp, 4 ; loc_4011B0: ; leave ; ret ; ─── Pseudocode (F5) ─────────────────────────────────────── int __cdecl check(int arg1) { if ( arg1 ) printf("Success!"); else printf("Failed!"); return 0; }

שנה שמות משתנים ב-Pseudocode (N) — זה עוזר לזכור מה כל דבר עושה בניתוח ארוך.

✏תרגול עצמי3 שאלות
שאלה 1.1 — IDA Basics
מה עושה לחיצה על F5 ב-IDA Pro?
✓ תשובה: ג) — F5 = Decompile. Hex-Rays Decompiler הופך Assembly לPseudocode דמוי-C. זה מקצר ניתוח מדרמטית. שים לב: הPseudocode לא זהה ל-C המקורי — IDA מנסה לשחזר אותו.
שאלה 1.2 — Graph View
cmp eax, 10 jg loc_PASS call fail_func jmp loc_END loc_PASS: call pass_func loc_END:בGraph View, איזה חץ מוביל ל-pass_func?
✓ תשובה: ב) חץ ירוק — jg (Jump if Greater) מתקיים כשeax > 10. ב-IDA: חץ ירוק = תנאי מתקיים = jump taken. jg loc_PASS → ירוק → loc_PASS → pass_func. אם eax ≤ 10: Fall-through (חץ אדום) → fail_func.
שאלה 1.3 — Xrefs
מצאנו פונקציה בשם sub_4012A0. לחצנו X. מה נראה?
✓ תשובה: ד) Xrefs — X = Cross-References. מציג: "Function sub_4012A0 is called from: main+0x3F, sub_401180+0x12...". חיוני להבנת Context — אם מצאת check_password, Xrefs מראה מי קורא לה ומתי.
// פרק 2

Ghidra — RE חינמי ועוצמתי

Ghidra הוא כלי RE בקוד פתוח של ה-NSA. חינמי, תומך בארכיטקטורות רבות, ומכיל Decompiler מובנה. בCTF ובגאמא — Ghidra הוא כלי סטנדרטי.

הבדלים בין Ghidra ל-IDA

תכונהIDA ProGhidra
מחיריקר מאוד ($$$)חינמי ו-Open Source
DecompilerHex-Rays (מצוין)מובנה (טוב מאוד)
ארכיטקטורותרוב הנפוצותרבות מאוד כולל נדירות
ScriptingIDAPythonJava + Python
Collaborationמוגבלמובנה (Server mode)
שימוש בגאמא/CTFכןכן — נפוץ מאוד

Workflow ב-Ghidra

# ─── פתיחת Binary ב-Ghidra ──────────────────────────────── # 1. File → New Project → Import File → בחר binary # 2. Double-click → Analysis Options → Analyze # 3. CodeBrowser נפתח # ─── חלונות עיקריים ─────────────────────────────────────── # Symbol Tree → רשימת פונקציות, imports, exports # Listing → Assembly (כמו IDA View) # Decompile → Pseudocode (אוטומטי בצד) # Functions → כל הפונקציות עם שמות # ─── Scripting ב-Ghidra (Java/Python) ───────────────────── # Script Manager → New Script → Python from ghidra.program.model.listing import * fm = currentProgram.getFunctionManager() for func in fm.getFunctions(True): if "check" in func.getName().lower(): print(f"Found: {func.getName()} @ {func.getEntryPoint()}") # ─── חיפוש מחרוזות ──────────────────────────────────────── # Search → For Strings → מוצא "password", "flag{", "Wrong!" # לחץ על מחרוזת → Show References → מי משתמש בה?
! Ghidra vs IDA — מתי להשתמש בכל אחד ›

בחר IDA כשיש: ניתוח עמוק של binary מסחרי, צורך ב-Debugger מובנה מלא, plugin-ים ספציפיים (למשל Keypatch, BinDiff).

בחר Ghidra כשיש: CTF (חינמי וזמין), ארכיטקטורות נדירות (ARM, MIPS, PowerPC), צורך בScripting מתקדם, שיתוף פעולה בצוות.

בגאמא: שניהם מקובלים. Ghidra נפוץ בCTF challenges, IDA ב-Professional RE. ידע בשניהם = יתרון.
✏תרגול עצמי3 שאלות
שאלה 2.1 — Ghidra Basics
ב-CTF קיבלת binary ללא קוד מקור. מה הצעד הראשון ב-Ghidra?
✓ תשובה: א) — Workflow סטנדרטי: Import → Analyze → חפש מחרוזות מעניינות → Xrefs → נתח פונקציות. מחרוזות כמו "Correct!", "Wrong!", "flag{" מובילות ישירות לפונקציות ה-Check הרלוונטיות.
שאלה 2.2 — Decompiler
מצאת פונקציה check_serial. הDecompiler מראה:
int check_serial(char *input) { if (strlen(input) != 16) return 0; for (int i=0; i<16; i++) { if (input[i] != secret[i] ^ 0x42) return 0; } return 1; }מה צריך לעשות כדי למצוא ה-Serial?
✓ תשובה: ד) — הקוד בודק: input[i] == secret[i] ^ 0x42. כלומר: input[i] = secret[i] ^ 0x42. מצא secret[] ב-Ghidra (Data section / .rodata), XOR כל byte עם 0x42, וקבל ה-Serial! הב-XOR הוא Symmetric: (x ^ k) ^ k = x.
שאלה 2.3 — Dry Run: Assembly לC
; Function sub_401050 (32bit) push ebp mov ebp, esp mov eax, [ebp+8] ; arg1 → eax imul eax, eax, 3 ; eax = eax * 3 add eax, 7 ; eax = eax + 7 pop ebp retמה עושה פונקציה זו (ב-Pseudocode)?
✓ תשובה: ב) x * 3 + 7 — mov eax, [ebp+8] = arg1. imul eax, eax, 3 → eax = arg1 * 3. add eax, 7 → eax = arg1*3 + 7. ret → מחזיר eax. בPseudocode: return x * 3 + 7.
// פרק 3

Anti-Debug Techniques: הגנות מפני ניתוח

תוכנות מגינות על עצמן בטכניקות Anti-Debug שמזהות debugger ומשנות התנהגות. כRE, עליך לזהות ולעקוף אותן.

IsDebuggerPresent — הפשוטה ביותר

; ─── IsDebuggerPresent ──────────────────────────────────── ; Windows API: קורא ל-IsDebuggerPresent() מkernel32.dll ; מחזיר 1 אם debugger מחובר, 0 אחרת ; Assembly בIDA: call IsDebuggerPresent test eax, eax ; בדוק אם eax != 0 jz short .no_debugger ; 0 = אין debugger → המשך call exit_or_corrupt ; ← אם debugger → יציאה! .no_debugger: ; קוד רגיל ; ─── עקיפה ──────────────────────────────────────────────── ; שיטה 1: Patch — שנה 'jz' ל-'jmp' (תמיד קפוץ לno_debugger) ; שיטה 2: ScyllaHide plugin (IDA/x64dbg) — מסתיר debugger ; שיטה 3: ב-GDB: set *(BYTE*)(IsDebuggerPresent+N) = 0x31 ; שיטה 4: Python script שמ-patch את הcheck בזמן ריצה ; ─── NtGlobalFlag — בדיקה עמוקה יותר ─────────────────── ; PEB.NtGlobalFlag = 0x70 כשdebugger מחובר (Windows) ; בדיקה ב-Assembly: mov eax, fs:[0x30] ; PEB mov eax, [eax + 0x68] ; NtGlobalFlag and eax, 0x70 cmp eax, 0x70 je .debugger_detected

Timing Checks ו-Exception-based Anti-Debug

; ─── Timing Check ────────────────────────────────────────── ; Debugger → Single-step → איטי משמעותית מריצה רגילה ; תוכנה מודדת זמן → debugger = זמן גבוה → זיהוי ; C code: DWORD t1 = GetTickCount(); // code... DWORD t2 = GetTickCount(); if (t2 - t1 > THRESHOLD) { /* debugger detected */ } ; ─── RDTSC — Timestamp Counter ──────────────────────────── ; הוראת Assembly שמחזירה מונה זמן מדויק מאוד: rdtsc ; EDX:EAX = timestamp mov esi, eax ; שמור t1 ; ... קוד ... rdtsc sub eax, esi ; t2 - t1 cmp eax, 500 ; אם > 500 cycles → debugger ja .detected ; עקיפה: Patch את הcheck (jbe/jae) → תמיד "אין debugger" ; או ScyllaHide שמרמה את RDTSC ; ─── Exception-based ───────────────────────────────────── ; INT 3 (0xCC) = Breakpoint exception ; Debugger: תופס את ה-exception ומחזיר → לא קורסת ; ללא debugger: Exception handler של הprocess מטפל ; בודק: האם exception הגיע ל-handler שלי או נתפס? __try { __asm { int 3 } // אם debugger → לא יגיע לexcept // ← הגענו כאן = אין debugger } __except(EXCEPTION_EXECUTE_HANDLER) { // ← הגענו כאן = אין debugger! (exception לא נתפס ע"י debugger) normal_execution(); }
הרחבה עוד טכניקות Anti-Debug מתקדמות ›
; ─── CheckRemoteDebuggerPresent ────────────────────────── BOOL bDebug; CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebug); if (bDebug) { exit(1); } ; ─── Heap Flag (Windows) ────────────────────────────────── ; כשdebugger מחובר, ה-Heap flags שונים ; PEB → ProcessHeap → Flags & ForceFlags ; ─── Parent Process Check ───────────────────────────────── ; בדרך כלל Parent = explorer.exe / cmd.exe ; אם Parent = ollydbg.exe / x64dbg.exe → debugger! ; ─── TLS Callback Anti-Debug ────────────────────────────── ; TLS Callbacks רצים לפני main → debugger לא מוצב עדיין ; בדיקות Anti-Debug שם קשות יותר לעקיפה ; ─── כלים לעקיפה ───────────────────────────────────────── ; ScyllaHide : plugin לOllyDbg/x64dbg/IDA — מסתיר debugger ; TitanHide : kernel driver שמסתיר debugger ברמת OS ; x64dbg : מובנה hiding חלקי ; Manual Patch: שנה bytes ב-IDA/Hex editor → JMP תמיד לקוד הנכון
✏תרגול עצמי3 שאלות
שאלה 3.1 — IsDebuggerPresent
call IsDebuggerPresent test eax, eax jnz loc_EXIT ; ← כתובת 0x401200 ; קוד רגיל... loc_EXIT: call ExitProcessמה צריך לעשות ב-IDA כדי לעקוף בדיקה זו (Patching)?
✓ תשובה: א) — jnz = Jump if Not Zero = קפוץ אם debugger. NOP את הjnz (2 bytes → 0x90 0x90) → לא יקפוץ לEXIT → ימשיך לקוד הרגיל תמיד. גם: xor eax,eax לפני test → eax=0 → jnz לא מתקיים.
שאלה 3.2 — Timing Check
מדוע Timing Check מזהה debugger?
✓ תשובה: ג) — בריצה רגילה: loop של 100 פעולות = ~100-300 cycles. בDebugger עם Breakpoint: עצירה, עיבוד Debug event, המשך → אלפי cycles. RDTSC מדויק מאוד → מזהה את ההפרש.
שאלה 3.3 — INT 3
מה ייחודי בטכניקת Anti-Debug המשתמשת ב-INT 3?
✓ תשובה: א) — SEH (Structured Exception Handling): כשINT 3 מתרחש, debugger תופס אותו ראשון ומחזיר — ה-handler לא רץ. בלי debugger: Exception מגיע ל-handler → קוד רגיל. הbranch שלא רץ = debugger זוהה.
// פרק 4

Obfuscation ו-Packing: הגנות על קוד

Packing ו-Obfuscation מקשים על ניתוח Binary. Packers כמו UPX דוחסים/מצפינים את הקוד; Obfuscators מסבכים את הלוגיקה. עליך לזהות ולפרוק אותם.

זיהוי Packed Binary

# ─── זיהוי Packing ─────────────────────────────────────── # כלי: file, strings, PEiD, Detect-It-Easy (DIE) file ./binary # ELF 64-bit LSB executable (אם רגיל) # UPX compressed data (אם packed!) strings ./binary | head -20 # Binary רגיל: הרבה strings מוכרות (API names, messages) # Packed: מעט strings, "UPX0", "UPX1", entropy גבוה # ─── אנטרופיה ───────────────────────────────────────────── # Binary רגיל: entropy 5-6 bits/byte (לא דחוס) # Packed/Encrypted: entropy 7.5-8 bits/byte (כמו random!) # כלי: binwalk -E binary → מציג entropy per section # ─── UPX — הנפוץ ביותר ─────────────────────────────────── upx -d ./packed_binary # Unpack אוטומטי! # לא תמיד עובד אם הUPX Modified/Custom # ─── Manual Unpacking (OEP Finding) ────────────────────── # 1. בGDB/x64dbg: הרץ binary # 2. Packed code מריץ Stub שמפרק לזיכרון # 3. בסוף Stub: JMP לOEP (Original Entry Point) # 4. שים BP על jmp/push-ret בסוף Stub → dump memory # 5. כלי: OllyDumpEx, x64dbg Plugin dump
הרחבה VMProtect ו-Obfuscation מתקדם ›
; ─── VMProtect / Themida ──────────────────────────────────── ; הקוד מומר ל-Virtual Machine bytecode ; VM מבצע ה-bytecode → Assembly המקורי לא נראה ב-IDA ; ניתוח VMProtect: כלי VTIL, ניתוח ידני של ה-VM interpreter ; ─── Obfuscation טכניקות נפוצות ─────────────────────────── ; Junk Code: הוספת קוד חסר תועלת (nop, xor eax, eax; ...) ; Dead Code: קוד שלא מגיע אליו אף פעם ; Opaque Predicates: תנאים שתמיד true/false אבל נראים מורכבים ; Control Flow Flattening: כל הloogic ב-switch ענק ; String Encryption: מחרוזות מוצפנות, מפוענחות בזמן ריצה ; ─── טיפ לגאמא CTF ────────────────────────────────────── ; 1. זהה packer עם DIE / file / entropy ; 2. נסה upx -d תחילה ; 3. אם לא עובד: dynamic analysis — הרץ ב-sandbox, dump memory ; 4. VMProtect: focus על Input/Output, לא על ה-VM עצמו
✏תרגול עצמי3 שאלות
שאלה 4.1 — זיהוי Packing
פתחת binary ב-IDA ורואה רק פונקציה אחת בתחילה עם הרבה XOR ו-MOV פעולות, ו-strings מציג "UPX0", "UPX1". מה המסקנה?
✓ תשובה: ב) — UPX0/UPX1 הם שמות ה-sections של UPX. הפונקציה היחידה = Unpacking Stub שמפרק את הקוד האמיתי לזיכרון ואז JMP לOEP. פתרון: upx -d filename → binary מפורק לניתוח רגיל.
שאלה 4.2 — String Encryption
ב-IDA לא מצאת strings כמו "correct" או "wrong". אבל בRuntime (בGDB) ראית אותן. מה זה אומר?
✓ תשובה: ב) — String Encryption נפוץ: מחרוזות שמורות כ-XOR/RC4/AES, פונקציה מפענחת אותן לפני שימוש. ב-IDA: חפש פונקציות שמחזירות char*/string, עם לולאה על byte array. בRuntime: שים BP לאחר הDecryption ו-dump הזיכרון.
שאלה 4.3 — Dry Run: Unpacking Stub
; Unpacking Stub (simplified): mov ecx, 0x1000 ; size mov esi, packed_data ; source mov edi, 0x401000 ; destination .loop: lodsb ; AL = *esi++ xor al, 0x55 ; decrypt byte stosb ; *edi++ = AL loop .loop ; ecx-- jmp 0x401000 ; ← OEP!מה אם נרצה ל-Dump את הbinary המפורק לאחר XOR?
✓ תשובה: ד) — Workflow: BP על jmp OEP → הרץ → BP מתפוצץ → הקוד מפורק. בx64dbg: Plugin → OllyDumpEx → Dump. בGDB: dump binary memory output.bin 0x401000 0x402000. כעת ניתח output.bin ב-IDA/Ghidra.
// פרק 5

License Checks ו-Keygens: Cracking 101

בCTF ובגאמא, Cracking Tasks כוללות ניתוח קוד License Check ומציאת Key תקין (Keygen) או Patching לעקיפת הבדיקה. זה מסכם את כל מה שלמדנו בשיעור.

Flow של ניתוח License Check

; ─── דוגמה: License Check פשוטה ──────────────────────────── ; C code שב-IDA מראה: ; int check_key(char *key) { ; if (strlen(key) != 12) return 0; ; if (key[0] != 'G') return 0; ; int sum = 0; ; for (int i=1; i<12; i++) sum += key[i]; ; return sum == 0x3E8; // 1000 ; } ; ─── Keygen (Python) ───────────────────────────────────── # מגבלות: אורך=12, key[0]='G', sum(key[1:12])=1000 # פתרון: 11 תווים שסכומם = 1000 # 1000 / 11 = ~90 (0x5A = 'Z') # 11 * 90 = 990 → חסר 10 → key[1] = chr(90+10) = 'd' = 100 # key = 'G' + 'd' + 'Z'*10 = 'GdZZZZZZZZZZ' # בדיקה: ord('d') + 10*ord('Z') = 100 + 10*90 = 1000 ✓ # ─── Patching (עקיפה) ───────────────────────────────────── ; בIDA: מצא את ה-check_key, שנה: ; return sum == 0x3E8; ; ב-Assembly: cmp eax, 0x3E8 → jnz .fail ; Patch: שנה jnz (75 XX) ל-jmp (EB XX) → תמיד עובר ; או: שנה הbyte של jnz ל-NOP NOP (90 90)
✏תרגול עצמי3 שאלות
שאלה 5.1 — Keygen
ניתחת פונקציה שבודקת: input[0] ^ input[1] == 0x41. מה זוג תווים תקין?
✓ תשובה: א) — 0x41 ^ 0x00 = 0x41. או: 0x00 ^ 0x41 = 0x41. גם: input[0]='A'(0x41), input[1]=0x00 → 0x41^0x00=0x41 ✓. XOR הוא Symmetric: אם a^b=c, אז a=b^c. בחר input[0] כלשהו, חשב input[1] = input[0] ^ 0x41.
שאלה 5.2 — Patching
מצאת ב-IDA: cmp eax, 1 / jne loc_FAIL. מה הPatch הפשוט ביותר?
✓ תשובה: ד) — jne = Jump if Not Equal. NOP-ification (שינוי ל-0x90 0x90): הcpu מריץ NOP ומשם ממשיך לקוד הבא — הצלחה תמיד. ב-IDA: Edit → Patch program → Change bytes. ד) גם עובד אבל פחות נקי.
שאלה 5.3 — Dry Run מלא: Crackme
; check_flag(char *input): ; 1. strlen(input) == 8 → אחרת return 0 ; 2. for i in 0..7: ; input[i] -= 3 ; כל תו מוקטן ב-3 ; 3. strcmp(input, "FLAG") ; אחרי הפחתה! ; 4. אם זהה → return 1מה הFlag (הinput) הנכון?
✓ תשובה: ד) — Dry Run: input[i] -= 3, אחר כך strcmp ל-"FLAG". כלומר: input_before - 3 = 'F','L','A','G'. input_before = 'F'+3=0x49='I', 'L'+3=0x4F='O', 'A'+3=0x44='D', 'G'+3=0x4A='J'. אורך חייב 8, אז: "IODJ" + 4 bytes נוספים (הstrcmp בודק 4 בלבד).
// פרק 6

RE Workflow מלא + סיכום

הנה ה-Workflow המלא ל-RE Challenge — מקצה לקצה. שמור אותו בראש לכל CTF ומיון.

# ─── RE Workflow מלא ───────────────────────────────────────── # שלב 1: Static Recon file ./binary strings ./binary | grep -i "flag\|pass\|correct\|wrong\|key" # → מחרוזות חשודות? # שלב 2: זיהוי Packing # כלי: Detect-It-Easy, binwalk -E # אם packed → upx -d / dynamic unpack # שלב 3: Static Analysis (IDA/Ghidra) # א. פתח ב-IDA/Ghidra → Functions list # ב. חפש: main, check, verify, validate, compare # ג. Strings → Xrefs → הפונקציות שמשתמשות ב"Correct!" # ד. F5 לPseudocode → הבן את הלוגיקה # ה. Rename: פונקציות + משתנים חשובים # שלב 4: Anti-Debug Bypass # זיהוי: IsDebuggerPresent, RDTSC, INT3 checks # עקיפה: ScyllaHide / Patch / Manual NOP # שלב 5: Dynamic Analysis (GDB/x64dbg) # שים BP על check function # קרא ארגומנטים, עקוב אחרי הלוגיקה # Memory dump אם צריך # שלב 6: Keygen / Patch # Keygen: הבן את האלגוריתם → כתוב Python שמייצר Input תקין # Patch: NOP את ה-jne/jz → תמיד "Correct" # שלב 7: Verify # הרץ עם הInput/Patch → קבל Flag!
✓מה למדנו בשיעור 43
  • IDA Pro: F5=Pseudocode, X=Xrefs, N=Rename, Graph View קריאת זרימה
  • Ghidra: חינמי, Decompiler מובנה, Scripting ב-Python/Java, חזק ל-CTF
  • Assembly Reading: cmp/jg/jle = תנאים, Graph arrows: ירוק=True, אדום=False
  • Anti-Debug: IsDebuggerPresent, Timing/RDTSC, INT3/SEH, PEB flags
  • עקיפה: ScyllaHide, Patching (NOP), TitanHide, xor eax,eax לפני check
  • Packing: UPX strings, entropy גבוה = packed. upx -d לפריקה
  • OEP Finding: BP על JMP האחרון של Stub → Dump memory
  • License Checks: חפש check function → Keygen (הפוך את האלגוריתם) או Patch
  • String Encryption: Strings חסרות בStatic אך נראות בRuntime → Runtime Decryption
  • RE Workflow: Recon → Unpack → Static → Anti-Debug → Dynamic → Keygen/Patch
🎯 השיעור הבא
שיעור 44 — Heap Exploitation: Heap Layout, Use-After-Free, Double Free ו-tcache Poisoning
✏תרגול סיום — גאמא סגנון3 שאלות
גאמא סגנון א׳ — Workflow
קיבלת binary ב-CTF. strings מראה רק "UPX0" ו-"UPX1". IDA מראה פונקציה אחת עם XOR loop. מה הצעד הנכון?
✓ תשובה: ג) — Packed binary: הניתוח הסטטי חסר תועלת כי הקוד האמיתי מוסתר. תמיד: Unpack קודם! upx -d הוא הכי מהיר. אם Custom UPX (Modified): Dynamic analysis → OEP dump → נתח ב-IDA.
גאמא סגנון ב׳ — Assembly + Anti-Debug
call IsDebuggerPresent ; addr 0x401020 test eax, eax ; addr 0x401025 jnz loc_EXIT ; addr 0x401027 ; ... קוד הצלחה ... loc_EXIT: call ExitProcessבGDB, איך עוקפים את הcheck ללא Patching?
✓ תשובה: א) — Runtime bypass ב-GDB: break *0x401025 → run → (BP) → set $eax = 0 → continue. test eax, eax עם eax=0 → ZF=1 → jnz לא מתקיים → ממשיך לקוד הצלחה! ללא שינוי קובץ.
גאמא סגנון ג׳ — Dry Run Keygen
; check(char *key): ; if len(key) != 5 → return 0 ; if key[0] != 'A' → return 0 ; result = 0 ; for i in 1..4: ; result ^= key[i] ; if result != 0x52 → return 0 ; return 1מה Key תקין?
✓ תשובה: א) — צריך key[0]='A', אורך 5, ו-key[1]^key[2]^key[3]^key[4] = 0x52. ד) "A\x52\x00\x00\x00": 0x52^0^0^0 = 0x52 ✓. שאר האפשרויות נותנות 0: א) 'A'^'A'^'A'^'A'=0. ב) 'R'^'R'^'R'^'\x52' = 0x52^0x52^0x52^0x52 = 0 (ארבעה ערכים זהים מתבטלים). ג) 'B'^'C'^'D'^'E'=0. רק ד) מגיע ל-0x52.
// תרגול נוסף

תרגול נוסף — קריפטוגרפיה: Hash ו-HTTPS

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

✏תרגול נוסף3 שאלות
שאלה 6.1 [קל]
מה ההבדל בין Hash ל-Encryption?
✓ תשובה נכונה: ב — Hash: חד-כיווני, אורך קבוע, לאימות שלמות/סיסמאות. Encryption: דו-כיווני עם מפתח, לסודיות. אין 'פענוח Hash' — רק Brute Force.
שאלה 6.2 [בינוני]
מדוע MD5 אינו בטוח לסיסמאות?
✓ תשובה נכונה: ד — MD5 מהיר → מיליוני ניסיונות בשנייה ב-Brute Force/Rainbow. גם Collisions ידועות. לסיסמאות: bcrypt/argon2 (איטיים בכוונה) + Salt.
שאלה 6.3 [קשה]
HTTPS משתמש גם ב-Symmetric וגם ב-Asymmetric. למה בשניהם?
✓ תשובה נכונה: ב — Asymmetric (RSA/ECDHE): איטי, משמש ל-Key Exchange בטוח בתחילת TLS. Symmetric (AES): מהיר, מצפין את כל שאר התעבורה. השילוב = מהיר ובטוח.
// אתגר מעשי

אתגר מעשי — Anti-Debugging — זיהוי ב-RE

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

T3 · בינוני250 נק'~12 דק'analysis
זיהוי Anti-Debugging
בהיפוך בינארי אתה מוצא קריאה ל-`ptrace(PTRACE_TRACEME, 0, 0, 0)` בתחילת main, ואם היא נכשלת התוכנית יוצאת. מה מטרת הקוד? (a) הקצאת זיכרון (b) תקשורת רשת (c) Anti-Debugging (זיהוי דיבאגר) (d) Multithreading
דוגמאות
(פורמט) → אות
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס זיהוי Anti-Debugging ›
c — תהליך יכול להיות traced פעם אחת; אם התוכנית עושה PTRACE_TRACEME על עצמה, דיבאגר שינסה להיצמד ייכשל. זו טכניקת anti-debugging נפוצה.
שאלות תרגול ותשובות (21)

שאלה 1.1 — IDA Basics · מה עושה לחיצה על F5 ב-IDA Pro?

  • א. מריצה את ה-Binary עצמו בתוך סביבת הניפוי המובנית של IDA Pro
  • ב. מבצעת חיפוש של כל המחרוזות (strings) הקיימות בתוך הקובץ הנתון
  • ג. מייצרת Pseudocode דמוי-C מה-Assembly (Hex-Rays Decompiler)
  • ד. שומרת את הפרויקט הנוכחי על הדיסק יחד עם כל ההערות שנוספו לו
תשובה: ג

✓ תשובה: ג) — F5 = Decompile. Hex-Rays Decompiler הופך Assembly לPseudocode דמוי-C. זה מקצר ניתוח מדרמטית. שים לב: הPseudocode לא זהה ל-C המקורי — IDA מנסה לשחזר אותו.

שאלה 1.2 — Graph View · cmp eax, 10 jg loc_PASS call fail_func jmp loc_END loc_PASS: call pass_func loc_END:בGraph View, איזה חץ מוביל ל-pass_func?

cmp  eax, 10
jg   loc_PASS
call fail_func
jmp  loc_END
loc_PASS:
call pass_func
loc_END:
  • א. החץ האדום (False): הענף שנלקח כאשר תנאי הקפיצה אינו מתקיים כלל
  • ב. החץ הירוק (True): jg מתקיים כאשר eax גדול מ-10 ומגיע ל-pass_func
  • ג. החץ הכחול (Unconditional): קפיצה שאינה תלויה כלל בשום תנאי שהוא
  • ד. אין כלל חץ מתאים, מכיוון שמדובר כאן ב-Fall-through רציף בתוך הקוד
תשובה: ב

✓ תשובה: ב) חץ ירוק — jg (Jump if Greater) מתקיים כשeax > 10. ב-IDA: חץ ירוק = תנאי מתקיים = jump taken. jg loc_PASS → ירוק → loc_PASS → pass_func. אם eax ≤ 10: Fall-through (חץ אדום) → fail_func.

שאלה 1.3 — Xrefs · מצאנו פונקציה בשם sub_4012A0. לחצנו X. מה נראה?

# ─── פתיחת Binary ב-Ghidra ────────────────────────────────
# 1. File → New Project → Import File → בחר binary
# 2. Double-click → Analysis Options → Analyze
# 3. CodeBrowser נפתח

# ─── חלונות עיקריים ───────────────────────────────────────
# Symbol Tree    → רשימת פונקציות, imports, exports
# Listing        → Assembly (כמו IDA View)
# Decompile      → Pseudocode (אוטומטי בצד)
# Functions      → כל הפונקציות עם שמות

# ─── Scripting ב-Ghidra (Java/Python) ─────────────────────
# Script Manager → New Script → Python
from ghidra.program.model.listing import *
fm = currentProgram.getFunctionManager()
for func in fm.getFunctions(True):
    if "check" in func.getName().lower():
        print(f"Found: {func.getName()} @ {func.getEntryPoint()}")

# ─── חיפוש מחרוזות ────────────────────────────────────────
# Search → For Strings → מוצא "password", "flag{", "Wrong!"
# לחץ על מחרוזת → Show References → מי משתמש בה?
  • א. קוד ה-Assembly המלא של הפונקציה, החל מכתובת ההתחלה שלה בזיכרון
  • ב. ה-Pseudocode דמוי-C של הפונקציה כפי שמייצר אותו ה-Decompiler
  • ג. רשימה של כל ה-Pseudocode הקיים בכל הפונקציות שבתוך ה-Binary כולו
  • ד. Cross-References: רשימת כל המקומות בקוד שקוראים אל הפונקציה הזו
תשובה: ד

✓ תשובה: ד) Xrefs — X = Cross-References. מציג: "Function sub_4012A0 is called from: main+0x3F, sub_401180+0x12...". חיוני להבנת Context — אם מצאת check_password, Xrefs מראה מי קורא לה ומתי.

שאלה 2.1 — Ghidra Basics · ב-CTF קיבלת binary ללא קוד מקור. מה הצעד הראשון ב-Ghidra?

  • א. Import ← Analyze ← חפש מחרוזות כמו "flag"/"password" ← Xrefs ← נתח
  • ב. לחפש מיד פעולות XOR בתוך קוד ה-Assembly של הקובץ הנתון לניתוח
  • ג. להריץ את ה-binary באופן מיידי כדי לראות את התנהגותו בפועל תחילה
  • ד. לכתוב Script אוטומטי כבר בתחילת הניתוח, עוד לפני שבודקים דבר
תשובה: א

✓ תשובה: א) — Workflow סטנדרטי: Import → Analyze → חפש מחרוזות מעניינות → Xrefs → נתח פונקציות. מחרוזות כמו "Correct!", "Wrong!", "flag{" מובילות ישירות לפונקציות ה-Check הרלוונטיות.

שאלה 2.2 — Decompiler · מצאת פונקציה check_serial. הDecompiler מראה: int check_serial(char *input) { if (strlen(input) != 16) return 0; for (int i=0; i<16; i++) { if (input[i] != secret[i] ^ 0x42) return 0; } return 1; }מה צריך לעשות כדי למצוא ה-Serial?

int check_serial(char *input) {
    if (strlen(input) != 16) return 0;
    for (int i=0; i<16; i++) {
        if (input[i] != secret[i] ^ 0x42) return 0;
    }
    return 1;
}
  • א. הרץ Brute Force על כל 16 התווים עד שאחד הצירופים יתקבל כתקין
  • ב. הכנס 16 תווים כלשהם, שכן הבדיקה מוודאת רק את אורך הקלט שנשלח
  • ג. השתמש בטכניקת SQL Injection כדי לעקוף את בדיקת ה-Serial הזו כאן
  • ד. מצא את המערך secret[] ב-Binary ובצע XOR לכל בית עם הערך 0x42
תשובה: ד

✓ תשובה: ד) — הקוד בודק: input[i] == secret[i] ^ 0x42. כלומר: input[i] = secret[i] ^ 0x42. מצא secret[] ב-Ghidra (Data section / .rodata), XOR כל byte עם 0x42, וקבל ה-Serial! הב-XOR הוא Symmetric: (x ^ k) ^ k = x.

שאלה 2.3 — Dry Run: Assembly לC · ; Function sub_401050 (32bit) push ebp mov ebp, esp mov eax, [ebp+8] ; arg1 → eax imul eax, eax, 3 ; eax = eax * 3 add eax, 7 ; eax = eax + 7 pop ebp retמה עושה פונקציה זו (ב-Pseudocode)?

; Function sub_401050 (32bit)
push  ebp
mov   ebp, esp
mov   eax, [ebp+8]     ; arg1 → eax
imul  eax, eax, 3      ; eax = eax * 3
add   eax, 7           ; eax = eax + 7
pop   ebp
ret
  • א. int f(int x) { return x + 3 * 7; }
  • ב. int f(int x) { return x * 3 + 7; }
  • ג. int f(int x) { return (x + 7) * 3; }
  • ד. int f(int x) { return x * 7 + 3; }
תשובה: ב

✓ תשובה: ב) x * 3 + 7 — mov eax, [ebp+8] = arg1. imul eax, eax, 3 → eax = arg1 * 3. add eax, 7 → eax = arg1*3 + 7. ret → מחזיר eax. בPseudocode: return x * 3 + 7.

שאלה 3.1 — IsDebuggerPresent · call IsDebuggerPresent test eax, eax jnz loc_EXIT ; ← כתובת 0x401200 ; קוד רגיל... loc_EXIT: call ExitProcessמה צריך לעשות ב-IDA כדי לעקוף בדיקה זו (Patching)?

call  IsDebuggerPresent
test  eax, eax
jnz   loc_EXIT        ; ← כתובת 0x401200
; קוד רגיל...
loc_EXIT:
call  ExitProcess
  • א. שנה את ה-jnz (0x75) ל-NOP NOP (0x90 0x90) כדי שהקפיצה ל-EXIT תבוטל
  • ב. מחק לחלוטין את השורה call IsDebuggerPresent מתוך קוד הפונקציה כאן
  • ג. שנה את ה-jnz ל-jz, כלומר הפוך את תנאי הקפיצה למצב ההפוך לגמרי ממנו
  • ד. הוסף את הפקודה XOR eax, eax עוד לפני ה-test כדי לאפס את הרגיסטר
תשובה: א

✓ תשובה: א) — jnz = Jump if Not Zero = קפוץ אם debugger. NOP את הjnz (2 bytes → 0x90 0x90) → לא יקפוץ לEXIT → ימשיך לקוד הרגיל תמיד. גם: xor eax,eax לפני test → eax=0 → jnz לא מתקיים.

שאלה 3.2 — Timing Check · מדוע Timing Check מזהה debugger?

  • א. מכיוון שכל תוכנת Debugger צורכת הרבה יותר זיכרון RAM מן הרגיל
  • ב. מכיוון שה-Debugger משנה את תדר השעון (Clock) של המעבד עצמו כאן
  • ג. Single-Step ו-Breakpoints גורמים לעיכובים, וההפרש הגדול בזמן מסגיר
  • ד. מכיוון שה-Debugger שולח מנות (packets) אל הרשת במהלך הניפוי
תשובה: ג

✓ תשובה: ג) — בריצה רגילה: loop של 100 פעולות = ~100-300 cycles. בDebugger עם Breakpoint: עצירה, עיבוד Debug event, המשך → אלפי cycles. RDTSC מדויק מאוד → מזהה את ההפרש.

שאלה 3.3 — INT 3 · מה ייחודי בטכניקת Anti-Debug המשתמשת ב-INT 3?

# ─── זיהוי Packing ───────────────────────────────────────
# כלי: file, strings, PEiD, Detect-It-Easy (DIE)

file ./binary
# ELF 64-bit LSB executable (אם רגיל)
# UPX compressed data (אם packed!)

strings ./binary | head -20
# Binary רגיל: הרבה strings מוכרות (API names, messages)
# Packed: מעט strings, "UPX0", "UPX1", entropy גבוה

# ─── אנטרופיה ─────────────────────────────────────────────
# Binary רגיל: entropy 5-6 bits/byte (לא דחוס)
# Packed/Encrypted: entropy 7.5-8 bits/byte (כמו random!)
# כלי: binwalk -E binary → מציג entropy per section

# ─── UPX — הנפוץ ביותר ───────────────────────────────────
upx -d ./packed_binary    # Unpack אוטומטי!
# לא תמיד עובד אם הUPX Modified/Custom

# ─── Manual Unpacking (OEP Finding) ──────────────────────
# 1. בGDB/x64dbg: הרץ binary
# 2. Packed code מריץ Stub שמפרק לזיכרון
# 3. בסוף Stub: JMP לOEP (Original Entry Point)
# 4. שים BP על jmp/push-ret בסוף Stub → dump memory
# 5. כלי: OllyDumpEx, x64dbg Plugin dump
  • א. INT 3 היא הוראת Breakpoint; אם ה-Debugger תפס את החריגה - זוהה
  • ב. היא תמיד גורמת לקריסה מיידית של התוכנית, ללא כל דרך למנוע זאת
  • ג. היא בודקת כתובות זיכרון מסוימות כדי לאתר בהן נוכחות של Debugger
  • ד. ההוראה INT 3 משנה את דגלי ה-CPU (flags) בדרך שמסגירה את ה-Debugger
תשובה: א

✓ תשובה: א) — SEH (Structured Exception Handling): כשINT 3 מתרחש, debugger תופס אותו ראשון ומחזיר — ה-handler לא רץ. בלי debugger: Exception מגיע ל-handler → קוד רגיל. הbranch שלא רץ = debugger זוהה.

שאלה 4.1 — זיהוי Packing · פתחת binary ב-IDA ורואה רק פונקציה אחת בתחילה עם הרבה XOR ו-MOV פעולות, ו-strings מציג "UPX0", "UPX1". מה המסקנה?

  • א. ה-Binary כתוב כולו בשפת Assembly טהורה, ומשום כך הוא נראה כך כעת
  • ב. ה-Binary דחוס עם UPX; הפונקציה הנראית היא Stub לפריקה בזמן ריצה
  • ג. ה-Binary מוצפן בהצפנת AES, ולכן לא ניתן כלל לקרוא את תוכנו הפנימי
  • ד. קובץ ה-Binary פשוט פגום או משובש, ולכן אין כל טעם להמשיך ולנתחו
תשובה: ב

✓ תשובה: ב) — UPX0/UPX1 הם שמות ה-sections של UPX. הפונקציה היחידה = Unpacking Stub שמפרק את הקוד האמיתי לזיכרון ואז JMP לOEP. פתרון: upx -d filename → binary מפורק לניתוח רגיל.

שאלה 4.2 — String Encryption · ב-IDA לא מצאת strings כמו "correct" או "wrong". אבל בRuntime (בGDB) ראית אותן. מה זה אומר?

  • א. ה-IDA אכן מצא את המחרוזות, אך פשוט בחר שלא להציג אותן למשתמש
  • ב. המחרוזות מוצפנות ב-Binary ומפוענחות בזמן ריצה; מצא את פונקציית הפענוח
  • ג. תוכנת ה-GDB מדמיינת מחרוזות שאינן קיימות כלל בתוך הקובץ הזה
  • ד. ה-Binary כותב את המחרוזות אל הדיסק במהלך הריצה, ולכן הן נראות
תשובה: ב

✓ תשובה: ב) — String Encryption נפוץ: מחרוזות שמורות כ-XOR/RC4/AES, פונקציה מפענחת אותן לפני שימוש. ב-IDA: חפש פונקציות שמחזירות char*/string, עם לולאה על byte array. בRuntime: שים BP לאחר הDecryption ו-dump הזיכרון.

שאלה 4.3 — Dry Run: Unpacking Stub · ; Unpacking Stub (simplified): mov ecx, 0x1000 ; size mov esi, packed_data ; source mov edi, 0x401000 ; destination .loop: lodsb ; AL = *esi++ xor al, 0x55 ; decrypt byte stosb ; *edi++ = AL loop .loop ; ecx-- jmp 0x401000 ; ← OEP!מה אם נרצה ל-Dump את הbinary המפורק לאחר XOR?

; Unpacking Stub (simplified):
mov  ecx, 0x1000      ; size
mov  esi, packed_data ; source
mov  edi, 0x401000    ; destination
.loop:
    lodsb              ; AL = *esi++
    xor  al, 0x55     ; decrypt byte
    stosb              ; *edi++ = AL
    loop .loop         ; ecx--
jmp  0x401000         ; ← OEP!
  • א. הרץ את הפקודה upx -d כדי לפרוק את הקובץ הדחוס באופן אוטומטי לגמרי
  • ב. הרץ את התוכנית ללא Debugger כלל, וכך הפריקה תתבצע מעצמה אל הדיסק
  • ג. בצע Patch לפעולת ה-XOR שבתוך הלולאה כדי לבטל את הפענוח כליל
  • ד. שים Breakpoint על ה-JMP האחרון (ה-OEP), ואז בצע Dump מ-0x401000
תשובה: ד

✓ תשובה: ד) — Workflow: BP על jmp OEP → הרץ → BP מתפוצץ → הקוד מפורק. בx64dbg: Plugin → OllyDumpEx → Dump. בGDB: dump binary memory output.bin 0x401000 0x402000. כעת ניתח output.bin ב-IDA/Ghidra.

שאלה 5.1 — Keygen · ניתחת פונקציה שבודקת: input[0] ^ input[1] == 0x41. מה זוג תווים תקין?

input[0] ^ input[1] == 0x41
  • א. 'A' ו-'\x00': שכן 0x41 ^ 0x00 שווה ל-0x41, וכך גם כל זוג מתאים אחר
  • ב. 'A' ו-'A': שכן 0x41 ^ 0x41 שווה ל-0, ולא לערך 0x41 שנדרש כאן
  • ג. 'A' ו-'B': שכן 0x41 ^ 0x42 שווה ל-0x03, ולא לערך 0x41 שנדרש כאן
  • ד. אי אפשר לפתור זאת כלל, מכיוון שאין שום זוג תווים שמקיים את התנאי
תשובה: א

✓ תשובה: א) — 0x41 ^ 0x00 = 0x41. או: 0x00 ^ 0x41 = 0x41. גם: input[0]='A'(0x41), input[1]=0x00 → 0x41^0x00=0x41 ✓. XOR הוא Symmetric: אם a^b=c, אז a=b^c. בחר input[0] כלשהו, חשב input[1] = input[0] ^ 0x41.

שאלה 5.2 — Patching · מצאת ב-IDA: cmp eax, 1 / jne loc_FAIL. מה הPatch הפשוט ביותר?

cmp eax, 1 / jne loc_FAIL
  • א. שנה את פקודת ה-cmp לפקודת add כדי לשבש בכך את תוצאת ההשוואה כאן
  • ב. מחק את הפונקציה כולה מן ה-Binary כדי לדלג על בדיקת התקינות הזו
  • ג. שנה את cmp eax, 1 ל-cmp eax, 0 כדי להשוות את הרגיסטר מול ערך אחר
  • ד. שנה את ה-jne (0x75) ל-NOP NOP (0x90 0x90) כדי שהקפיצה ל-FAIL תבוטל
תשובה: ד

✓ תשובה: ד) — jne = Jump if Not Equal. NOP-ification (שינוי ל-0x90 0x90): הcpu מריץ NOP ומשם ממשיך לקוד הבא — הצלחה תמיד. ב-IDA: Edit → Patch program → Change bytes. ד) גם עובד אבל פחות נקי.

שאלה 5.3 — Dry Run מלא: Crackme · ; check_flag(char *input): ; 1. strlen(input) == 8 → אחרת return 0 ; 2. for i in 0..7: ; input[i] -= 3 ; כל תו מוקטן ב-3 ; 3. strcmp(input, "FLAG") ; אחרי הפחתה! ; 4. אם זהה → return 1מה הFlag (הinput) הנכון?

; check_flag(char *input):
;   1. strlen(input) == 8  → אחרת return 0
;   2. for i in 0..7:
;        input[i] -= 3      ; כל תו מוקטן ב-3
;   3. strcmp(input, "FLAG") ; אחרי הפחתה!
;   4. אם זהה → return 1
  • א. "FLAG" עם ריפוד אפסים לאורך 8: המחרוזת FLAG עצמה בתוספת בתי אפס
  • ב. "FLAG" באורך 4: המחרוזת המקורית עצמה, אף שהאורך הנדרש הוא 8 תווים
  • ג. "CCCC": ארבעה תווים זהים שמפוענחים כולם לאותו התו לאחר החיסור ב-3
  • ד. "IODJ" ועוד 4 בתים: כי 'I','O','D','J' פחות 3 נותנים 'F','L','A','G'
תשובה: ד

✓ תשובה: ד) — Dry Run: input[i] -= 3, אחר כך strcmp ל-"FLAG". כלומר: input_before - 3 = 'F','L','A','G'. input_before = 'F'+3=0x49='I', 'L'+3=0x4F='O', 'A'+3=0x44='D', 'G'+3=0x4A='J'. אורך חייב 8, אז: "IODJ" + 4 bytes נוספים (הstrcmp בודק 4 בלבד).

גאמא סגנון א׳ — Workflow · קיבלת binary ב-CTF. strings מראה רק "UPX0" ו-"UPX1". IDA מראה פונקציה אחת עם XOR loop. מה הצעד הנכון?

  • א. לנתח לעומק את לולאת ה-XOR בתוך IDA כדי להבין את אלגוריתם הפענוח
  • ב. לשלוח הרבה קלטים שונים אל ה-Binary ולבדוק כיצד הוא מגיב לכל אחד
  • ג. upx -d ל-Binary; אם נכשל: x64dbg, שים BP על ה-JMP, ו-Dump מן ה-OEP
  • ד. לחפש פגיעויות (vulnerabilities) בתוך לולאת ה-XOR של קוד הפריקה הזה
תשובה: ג

✓ תשובה: ג) — Packed binary: הניתוח הסטטי חסר תועלת כי הקוד האמיתי מוסתר. תמיד: Unpack קודם! upx -d הוא הכי מהיר. אם Custom UPX (Modified): Dynamic analysis → OEP dump → נתח ב-IDA.

גאמא סגנון ב׳ — Assembly + Anti-Debug · call IsDebuggerPresent ; addr 0x401020 test eax, eax ; addr 0x401025 jnz loc_EXIT ; addr 0x401027 ; ... קוד הצלחה ... loc_EXIT: call ExitProcessבGDB, איך עוקפים את הcheck ללא Patching?

call  IsDebuggerPresent   ; addr 0x401020
test  eax, eax             ; addr 0x401025
jnz   loc_EXIT             ; addr 0x401027
; ... קוד הצלחה ...
loc_EXIT:
call  ExitProcess
  • א. שים BP אחרי ה-test, וכשהוא נעצר בצע set $eax = 0 כדי שלא יקפוץ ל-EXIT
  • ב. הרץ את התוכנית לגמרי ללא GDB, וכך הבדיקה פשוט לא תזהה את הניפוי
  • ג. שנה את שם התהליך (process) כדי שה-IsDebuggerPresent לא יזהה אותו
  • ד. בטל את מנגנון ה-ASLR במערכת כדי שכל הכתובות יהיו קבועות וידועות
תשובה: א

✓ תשובה: א) — Runtime bypass ב-GDB: break *0x401025 → run → (BP) → set $eax = 0 → continue. test eax, eax עם eax=0 → ZF=1 → jnz לא מתקיים → ממשיך לקוד הצלחה! ללא שינוי קובץ.

גאמא סגנון ג׳ — Dry Run Keygen · ; check(char *key): ; if len(key) != 5 → return 0 ; if key[0] != 'A' → return 0 ; result = 0 ; for i in 1..4: ; result ^= key[i] ; if result != 0x52 → return 0 ; return 1מה Key תקין?

; check(char *key):
;   if len(key) != 5 → return 0
;   if key[0] != 'A' → return 0
;   result = 0
;   for i in 1..4:
;     result ^= key[i]
;   if result != 0x52 → return 0
;   return 1
  • א. "A\x52\x00\x00\x00": key[0] הוא 'A' ו-XOR של השאר נותן 0x52 כנדרש
  • ב. "AAAAA": חמישה תווים זהים שה-XOR של ארבעת האחרונים אינו נותן 0x52
  • ג. "ARRR\x52": ארבעה תווים אחרי ה-A שה-XOR שלהם כלל אינו שווה ל-0x52
  • ד. "ABCDE": חמישה תווים עוקבים שה-XOR של ארבעת האחרונים אינו 0x52 כלל
תשובה: א

✓ תשובה: א) — צריך key[0]='A', אורך 5, ו-key[1]^key[2]^key[3]^key[4] = 0x52. ד) "A\x52\x00\x00\x00": 0x52^0^0^0 = 0x52 ✓. שאר האפשרויות נותנות 0: א) 'A'^'A'^'A'^'A'=0. ב) 'R'^'R'^'R'^'\x52' = 0x52^0x52^0x52^0x52 = 0 (ארבעה ערכים זהים מתבטלים). ג) 'B'^'C'^'D'^'E'=0. רק ד) מגיע ל-0x52.

שאלה 6.1 [קל] · מה ההבדל בין Hash ל-Encryption?

Hash
  • א. Hash הפיך תמיד: הנחה שגויה, אף שדווקא Encryption הוא ההפיך עם מפתח, בעוד Hash הוא חד-כיווני ואי אפשר לשחזר ממנו את הקלט המקורי
  • ב. Hash הוא חד-כיווני ואי אפשר לשחזר ממנו את המקור, ואילו Encryption הוא דו-כיווני וניתן לפענח את הטקסט המקורי בעזרת המפתח המתאים
  • ג. שניהם דו-כיווניים: הנחה שגויה, אף ש-Hash הוא חד-כיווני ואינו ניתן לשחזור, ולכן רק Encryption הוא הדו-כיווני שניתן לפענח עם מפתח
  • ד. Encryption חד-כיווני: הנחה שגויה, אף שדווקא Hash הוא החד-כיווני, ו-Encryption הפיך עם המפתח, ולכן התיאור בתשובה זו מהופך לחלוטין כן
תשובה: ב

✓ תשובה נכונה: ב — Hash: חד-כיווני, אורך קבוע, לאימות שלמות/סיסמאות. Encryption: דו-כיווני עם מפתח, לסודיות. אין 'פענוח Hash' — רק Brute Force.

שאלה 6.2 [בינוני] · מדוע MD5 אינו בטוח לסיסמאות?

MD5
  • א. MD5 איטי מדי: הנחה שגויה, אף שדווקא המהירות הגבוהה של MD5 היא הבעיה כי היא מזרזת Brute Force, ולכן הוא אינו איטי אלא מהיר מדי לשימוש זה כן
  • ב. MD5 הפיך בקלות: הנחה שגויה, אף ש-MD5 הוא חד-כיווני ואינו הפיך, הבעיה היא מהירותו הגבוהה ומתקפות ה-Collision ולא היפוך ישיר של ה-Hash כן
  • ג. MD5 ארוך מדי: הנחה שגויה, אף שאורך ה-Hash אינו הבעיה, החולשה נובעת ממהירות החישוב שמאיצה Brute Force ומקיומן של התנגשויות ידועות
  • ד. MD5 מהיר מאוד ולכן מאפשר לתוקף לנסות מיליוני Hashes בשנייה במתקפת כוח, ובנוסף ידועות בו מתקפות Collision, ולכן הוא אינו בטוח לסיסמאות
תשובה: ד

✓ תשובה נכונה: ד — MD5 מהיר → מיליוני ניסיונות בשנייה ב-Brute Force/Rainbow. גם Collisions ידועות. לסיסמאות: bcrypt/argon2 (איטיים בכוונה) + Salt.

שאלה 6.3 [קשה] · HTTPS משתמש גם ב-Symmetric וגם ב-Asymmetric. למה בשניהם?

c — תהליך יכול להיות traced פעם אחת; אם התוכנית עושה PTRACE_TRACEME על עצמה, דיבאגר שינסה להיצמד ייכשל. זו טכניקת anti-debugging נפוצה.
  • א. רק Asymmetric בשימוש: הנחה שגויה, אף ש-Asymmetric משמש לחילוף מפתח בלבד בגלל איטיותו, ואילו הנתונים עצמם מוצפנים ב-Symmetric המהיר יותר
  • ב. Asymmetric משמש לחילופי המפתח (Key Exchange) בלבד כי הוא איטי, ואילו Symmetric כמו AES מצפין את הנתונים עצמם כי הוא מהיר בהרבה ממנו
  • ג. Symmetric לחילוף מפתח: הנחה שגויה, אף שדווקא Asymmetric משמש לחילוף המפתח בתחילת החיבור, ו-Symmetric מצפין את הנתונים בהמשך הפעולה
  • ד. שניהם מצפינים נתונים: הנחה שגויה, אף שרק Symmetric מצפין את גוף הנתונים, בעוד Asymmetric משמש רק לחילוף המפתח הראשוני בזכות אבטחתו כן
תשובה: ב

✓ תשובה נכונה: ב — Asymmetric (RSA/ECDHE): איטי, משמש ל-Key Exchange בטוח בתחילת TLS. Symmetric (AES): מהיר, מצפין את כל שאר התעבורה. השילוב = מהיר ובטוח.