GammaCyber

מעבדת Low-Level מסכמת

Low-Level: C, Assembly ו-RE · 5–6 שעות

// סקירה

מעבדת Low-Level מסכמת — ניתוח Assembly ללא קוד מקור

השיעור האחרון ביחידה 4. כאן תרכוש מתודולוגיה לניתוח קטעי Assembly ארוכים תחת לחץ זמן — בדיוק כמו בסדנת גאמא האמיתית. Function Call Chains, Recursive Functions, אלגוריתמי מיון, ופעולות מחרוזת — הכל Assembly גולמי.

  • מתודולוגיה 5 שלבים — ניתוח 50+ שורות ב-10 דקות
  • Function Call Chains — ניתוח Bottom-Up של 3 פונקציות קשורות
  • Recursive Function ב-Assembly — זיהוי ו-Dry Run
  • Bubble Sort ו-Selection Sort — זיהוי Pattern מהקוד
  • strlen, strcmp, strcpy — זיהוי פונקציות מחרוזת מה-Assembly
  • 4 תרגילי RE מלאים — Fibonacci, String Reverse, Collatz, count positive/negative
  • טכניקות לחץ זמן — 30 שניות לתמונה כללית
// פרק 1

מתודולוגיה — ניתוח קטע ארוך תחת לחץ

הסדנה האמיתית של גאמא נותנת קטע Assembly ושואלת שאלות עליו. אין אינטרנט, לפעמים אין מחשב — רק עט ונייר. פרק זה נותן את השיטה לפצח 50+ שורות ב-5-10 דקות.

1.1 — 5 שלבי ניתוח מהיר

שלב 1: סריקה ראשונית — 30 שניות → כמה פונקציות? (חפשו: push ebp / mov ebp,esp) → יש CALLים לפונקציות? → יש JMP אחורה? (= לולאה) → כמה args? (ספרו PUSHים לפני CALL) שלב 2: זיהוי גבולות פונקציות → כל push ebp + mov ebp,esp = תחילת פונקציה → כל ret = סיום פונקציה → תנו שם זמני: func_A, func_B, func_C שלב 3: ניתוח Bottom-Up — מהפנימי לחיצוני → נתחו קודם פונקציות שלא קוראות לאחרות (עלים) → ואז עלו לפונקציות שקוראות להן שלב 4: Dry Run מדויק על פונקציה אחת → טבלת רגיסטרים: EAX, EBX, ECX, EDX, ESP, EBP, FLAGS → טבלת Stack: מה בכל כתובת שלב 5: תרגום ל-C ומענה לשאלות → כתבו C מקביל בצד — עוזר לאמת הבנה

1.2 — Calling Signature: ניחוש מה הפונקציה עושה לפני שקוראים אותה

Signatureניחוש ראשוניPattern לאמת
f(ptr, int)פעולה על מערך/מחרוזת בגודל Nmov + loop over [ptr+i]
f(ptr, ptr)השוואה או העתקה בין שנייםcmp byte [p1], [p2] + loop
f(int) → intחישוב מתמטי על מספרimul/add/sub + ret
f(int) → int + CALL עצמהרקורסיה!cmp + jle base + call self
f(ptr, ptr, int)memcpy / strncpy סגנוןmov byte + loop + counter
✏תרגול — מתודולוגיה3 שאלות
שאלה 1 — זיהוי Pattern מהיר
קטע ASM מכיל: MOVZX byte [ptr+i] + TEST + JZ + inc i. מהו האלגוריתם הסביר?
✓ תשובה: ד) strlen — MOVZX byte = קריאת תו בודד, TEST + JZ = בדיקת null, inc i = קידום אינדקס. Pattern קלאסי של strlen.
שאלה 2 — Recursive זיהוי
מה הסימן הבלתי-מתפשר לרקורסיה ב-Assembly?
✓ תשובה: א) — CALL לכתובת הפונקציה עצמה = רקורסיה. שתי CALLים לעצמה = רקורסיה כפולה (חשד ל-Fibonacci/Tree). תמיד חפשו Base Case (CMP + Jcc) לפניה.
שאלה 3 — Pattern
לולאה כפולה מקוננת + השוואת arr[j] עם arr[j+1] + SWAP בפנים. מהו?
✓ תשובה: ג) Bubble Sort — השוואה של שכן ישיר (arr[j] vs arr[j+1]) + SWAP בפנים = Bubble. Selection Sort: לולאה פנימית מחפשת min_idx, SWAP רק בסוף הלולאה החיצונית.
// פרק 2

Function Call Chains — מעקב אחרי שרשרת פונקציות

בקוד אמיתי, פונקציות קוראות לפונקציות. כדי להבין את התוכנית, חייבים לעקוב אחרי השרשרת Bottom-Up — קודם פנימיות, אחר כך חיצוניות.

2.1 — קוד לניתוח: 3 פונקציות קשורות

; ── func_C (הפנימית ביותר): func_C: push ebp / mov ebp, esp mov eax, [ebp+8] ; a mov ecx, [ebp+12] ; b cmp eax, ecx jle .c_swap sub eax, ecx ; a > b: return a-b jmp .c_done .c_swap: mov eax, ecx sub eax, [ebp+8] ; a <= b: return b-a .c_done: pop ebp / ret ; ── func_B (אמצעית): func_B: push ebp / mov ebp, esp / sub esp, 4 mov eax, [ebp+8] ; x imul eax, eax ; x*x mov [ebp-4], eax ; local = x*x push [ebp+12] ; push y push [ebp-4] ; push x*x call func_C ; func_C(x*x, y) add esp, 8 leave / ret ; ── func_A (החיצונית): func_A: push ebp / mov ebp, esp / sub esp, 8 push [ebp+12] ; push b push [ebp+8] ; push a call func_B ; func_B(a, b) add esp, 8 mov [ebp-4], eax ; local1 = func_B(a,b) push [ebp+16] ; push c push [ebp-4] ; push local1 call func_B ; func_B(local1, c) add esp, 8 mov [ebp-8], eax ; local2 mov eax, [ebp-8] leave / ret

2.2 — ניתוח Bottom-Up

שלב 1: func_C → a>b: return a-b → a<=b: return b-a → זוהי: |a - b| (ערך מוחלט של הפרש) שלב 2: func_B(x, y) → local = x*x → קוראת func_C(x*x, y) = |x²-y| → זוהי: f(x,y) = |x²-y| שלב 3: func_A(a, b, c) → local1 = func_B(a,b) = |a²-b| → local2 = func_B(local1,c) = ||a²-b|²-c| → זוהי: g(a,b,c) = ||a²-b|²-c| בדיקה: g(2,3,5) = ||4-3|²-5| = |1-5| = 4

2.3 — Dry Run: func_A(2, 3, 5)

שלבEAX[ebp-4][ebp-8]הסבר
func_B(2,3) → x²4——2²=4
func_C(4,3)1——|4-3|=1
local1=111?שמור תוצאה
func_B(1,5) → x²1——1²=1
func_C(1,5)4——|1-5|=4
local2=4, return414תשובה: 4
✏תרגול — Call Chains2 שאלות
שאלה 1 — ניתוח func_C
func_C מקבלת (a=7, b=3). מה היא מחזירה?
✓ תשובה: ד) 4 — a=7 > b=3 → return a-b = 7-3 = 4. func_C מחשבת |a-b| ומחזירה תמיד ערך חיובי.
שאלה 2 — Bottom-Up
מה מחזירה func_A(3, 5, 2)? עבדו Bottom-Up.
✓ פתרון:
func_B(3,5): 3²=9, |9-5|=4 → local1=4
func_B(4,2): 4²=16, |16-2|=14 → local2=14
func_A(3,5,2) = 14
g(a,b,c) = ||a²-b|²-c| = ||9-5|²-2| = |16-2| = 14
// פרק 3

Recursive Function ב-Assembly — זיהוי וניתוח

רקורסיה ב-Assembly מזוהה על ידי סימן אחד בלבד: CALL לעצמה. ברגע שמזהים CALL לשם הפונקציה הנוכחית — מיד יודעים שמדובר ברקורסיה, ומחפשים את ה-Base Case.

3.1 — קוד לניתוח: פיבונאצ'י רקורסיבי

mystery_recursive: push ebp / mov ebp, esp / sub esp, 4 ; ── Base Case 1: n==0 ───────────────────── cmp DWORD PTR [ebp+8], 0 ; (A) jne .not_base mov eax, 0 ; (C) return 0 jmp .done .not_base: ; ── Base Case 2: n==1 ───────────────────── cmp DWORD PTR [ebp+8], 1 ; (D) jne .recurse mov eax, 1 ; (F) return 1 jmp .done .recurse: ; ── f(n-1) ─────────────────────────────── mov eax, [ebp+8] sub eax, 1 push eax call mystery_recursive ; ← CALL לעצמה! add esp, 4 mov [ebp-4], eax ; local = f(n-1) ; ── f(n-2) ─────────────────────────────── mov eax, [ebp+8] sub eax, 2 push eax call mystery_recursive ; ← שוב CALL לעצמה! add esp, 4 add eax, [ebp-4] ; EAX = f(n-2) + f(n-1) .done: leave / ret

3.2 — ניתוח

שתי קריאות רקורסיביות + חיבור תוצאות + Base Cases ב-0 ו-1 = סדרת פיבונאצ'י!

// C מקביל: int fib(int n) { if (n == 0) return 0; if (n == 1) return 1; return fib(n-1) + fib(n-2); }

3.3 — Dry Run: fib(4)

קריאהnf(n-1)f(n-2)תוצאה
fib(4)4fib(3)fib(2)?
fib(3)3fib(2)fib(1)?
fib(2)2fib(1)fib(0)?
fib(1)1——1 (Base)
fib(0)0——0 (Base)
fib(2) חוזר2101
fib(1)1——1 (Base)
fib(3) חוזר3112
fib(2) חוזר2——1
fib(4) חוזר4213
✏תרגול — Recursive2 שאלות
שאלה 1
מה מחזיר mystery_recursive(6)?
✓ תשובה: ג) 8 — סדרת פיבונאצ'י: 0,1,1,2,3,5,8,13... fib(6)=8.
שאלה 2 — זיהוי סוג רקורסיה
שתי CALLים לעצמה + חיבור תוצאות = איזה Pattern?
✓ תשובה: א) Fibonacci / Tree — CALL אחד = רקורסיה ליניארית (Factorial, Sum). שני CALLים לעצמה = Tree recursion = חשד ל-Fibonacci או מבנה עץ.
// פרק 4

Sorting ו-String Operations ב-Assembly

אלגוריתמי מיון ופעולות מחרוזת הם מהמבנים הכי שכיחים בשאלות RE. לכל אחד יש Pattern מוכר שאפשר לזהות תוך שניות.

4.1 — Bubble Sort — Pattern זיהוי

✓ סימני Bubble Sort
✓ שתי לולאות מקוננות (outer + inner)
✓ השוואה arr[j] עם arr[j+1] (שכן ישיר!)
✓ SWAP: 3 MOVים (tmp=a; a=b; b=tmp) בתוך הלולאה הפנימית
✓ inner loop עד n-i-1 (קצרה יותר בכל סיבוב חיצוני)

4.2 — Selection Sort — הבדל מ-Bubble

✓ סימני Selection Sort
✓ שתי לולאות מקוננות
✓ לולאה פנימית מחפשת min_idx — לא מחליפה!
✓ SWAP קורה פעם אחת בלבד בסוף הלולאה החיצונית
תכונהBubble SortSelection Sort
מה מושווהarr[j] עם arr[j+1]arr[j] עם arr[min_idx]
מתי SWAPבכל השוואה שמוצאת גדולפעם אחת לסיבוב חיצוני
סימן מזההjle .no_swap בפניםmin_idx משתנה, swap בסוף

4.3 — String Operations — Pattern לכל פונקציה

פונקציהסימן Assemblyמחזירסיכון
strlenMOVZX byte + TEST + JZ עד nullאורך (int)אין
strcmp2x MOVZX + CMP + JNE0 / pos / negאין
strcpyMOVZX read + MOV BYTE writedst pointerBuffer Overflow!
; strlen — Pattern: .loop: movzx ecx, BYTE PTR [edx+eax] ; ECX = s[i] test ecx, ecx ; s[i] == 0? jz .done ; כן → סיום add DWORD PTR [ebp-4], 1 ; i++ jmp .loop ; מחזיר: EAX = מונה ; strcpy — Pattern (שים לב לכתיבה!): .loop: movzx ecx, BYTE PTR [edx+eax] ; READ מ-src mov BYTE PTR [edx+eax], cl ; WRITE ל-dst ← כתיבה! test ecx, ecx jz .done add DWORD PTR [ebp-4], 1 jmp .loop
✏תרגול — מיון ומחרוזות3 שאלות
שאלה 1 — זיהוי strlen
מהו הסימן המובהק לפונקציית strlen ב-Assembly?
✓ תשובה: א) — strlen: לולאה שקוראת byte אחד, בודקת אם null (TEST/JZ), ומקדמת מונה. מחזירה את המונה = אורך המחרוזת.
שאלה 2 — strcpy ואבטחה
מדוע strcpy מסוכנת מבחינת אבטחה?
✓ תשובה: א) — strcpy מעתיקה עד null byte ללא בדיקת גודל dst. אם src גדול מ-dst → Buffer Overflow! תמיד השתמשו ב-strncpy או strlcpy.
שאלה 3 — Trace strlen
strlen רצה על "Gama\0". מה ערך ECX (המונה) בסיום?
✓ תשובה: א) 4 — G,a,m,a = 4 תווים. בסיבוב 5 נקרא '\0' → JZ לסיום. ECX=4 = אורך המחרוזת "Gama".
// פרק 5

תרגילי RE מלאים — סימולציית סדנה

4 תרגילים מלאים, מדורגים, כפי שמופיעים בסדנת גאמא. לכל אחד: קטע Assembly, שאלות, ופתרון מלא. השתמשו בשיטת 5 השלבים.

תרגיל א׳ — Fibonacci Iterative (★★★☆☆)

func_ex1: push ebp / mov ebp, esp / sub esp, 8 mov DWORD PTR [ebp-4], 1 ; (1) a = 1 mov DWORD PTR [ebp-8], 1 ; (2) b = 1 cmp DWORD PTR [ebp+8], 1 jle .done ; n<=1: return a mov ecx, [ebp+8] sub ecx, 1 ; (5) counter = n-1 .loop: cmp ecx, 0 jle .done mov eax, [ebp-4] add eax, [ebp-8] ; tmp = a + b ; b = a (שני שלבים): mov [ebp-8], [ebp-4] mov [ebp-4], eax ; a = tmp dec ecx jmp .loop .done: mov eax, [ebp-4] leave / ret
✏תרגיל א׳שאלה
תרגיל א׳ — n=6
מה מחזיר func_ex1(6)?
✓ תשובה: ג) 13 — Fibonacci iterative עם counter=5 סיבובים:
a=1,b=1 → a=2,b=1 → a=3,b=2 → a=5,b=3 → a=8,b=5 → a=13,b=8. תשובה: EAX=13=fib(7).

תרגיל ב׳ — String Reverse (★★★★☆)

func_ex2: ; (s, len) push ebp / mov ebp, esp / sub esp, 8 mov DWORD PTR [ebp-4], 0 ; i = 0 mov eax, [ebp+12] sub eax, 1 mov DWORD PTR [ebp-8], eax ; j = len-1 .loop: mov eax, [ebp-4] ; i mov ecx, [ebp-8] ; j cmp eax, ecx jge .done ; i >= j? סיים ; החלפת s[i] ↔ s[j]: mov edx, [ebp+8] movzx eax, BYTE PTR [edx + [ebp-4]] ; AL = s[i] movzx ecx, BYTE PTR [edx + [ebp-8]] ; CL = s[j] mov BYTE PTR [edx + [ebp-4]], cl ; s[i] = s[j] mov BYTE PTR [edx + [ebp-8]], al ; s[j] = s[i] add DWORD PTR [ebp-4], 1 ; i++ sub DWORD PTR [ebp-8], 1 ; j-- jmp .loop .done: leave / ret
✏תרגיל ב׳שאלה
תרגיל ב׳ — "hello"
מה הפונקציה עושה? מה תוצאה ל-"hello"?
✓ תשובה: ד) "olleh" — i=0,j=len-1. בכל סיבוב: swap(s[i],s[j]), i++, j--. עוצר כש-i≥j. זוהי string_reverse קלאסית.

תרגיל ג׳ — Collatz Conjecture (★★★★★)

func_helper: ; (n) → n/2 or 3n+1 push ebp / mov ebp, esp mov eax, [ebp+8] test eax, 1 ; n זוגי? jnz .odd sar eax, 1 ; זוגי: n/2 (SAR = /2 signed) jmp .done .odd: imul eax, 3 add eax, 1 ; אי-זוגי: 3n+1 .done: pop ebp / ret func_main: ; (n) → מספר צעדים עד 1 push ebp / mov ebp, esp / sub esp, 8 mov eax, [ebp+8] mov [ebp-4], eax ; current = n mov DWORD PTR [ebp-8], 0 ; steps = 0 .loop: cmp DWORD PTR [ebp-4], 1 je .done ; current == 1? סיים push [ebp-4] call func_helper add esp, 4 mov [ebp-4], eax ; current = helper(current) add DWORD PTR [ebp-8], 1 ; steps++ jmp .loop .done: mov eax, [ebp-8] ; return steps leave / ret
✏תרגיל ג׳שאלה
תרגיל ג׳ — func_main(10)
מה מחזירה func_main(10)? (רמז: 10→5→16→8→4→2→1)
✓ תשובה: ד) 6 — 10→5 (÷2) →16 (×3+1) →8 (÷2) →4 (÷2) →2 (÷2) →1. סה"כ 6 צעדים. זוהי השערת קולץ (Collatz Conjecture)!

תרגיל ד׳ — ספירת חיוביים/שליליים (★★★★★)

mystery_full: ; (arr, len) → count_pos - count_neg push ebp / mov ebp, esp / sub esp, 12 mov ecx, [ebp+12] ; len mov DWORD PTR [ebp-4], 0 ; count_pos mov DWORD PTR [ebp-8], 0 ; count_neg mov DWORD PTR [ebp-12], 0 ; i .loop: cmp [ebp-12], ecx jge .done mov eax, [ebp-12] mov edx, [ebp+8] mov eax, [edx + eax*4] ; arr[i] test eax, eax jz .next ; == 0: skip js .negative ; SF=1: שלילי add DWORD PTR [ebp-4], 1 ; count_pos++ jmp .next .negative: add DWORD PTR [ebp-8], 1 ; count_neg++ .next: add DWORD PTR [ebp-12], 1 ; i++ jmp .loop .done: mov eax, [ebp-4] sub eax, [ebp-8] ; return count_pos - count_neg leave / ret
✏תרגיל ד׳שאלה
תרגיל ד׳ — arr=[3,-1,0,-2,4,-5]
מה מחזירה mystery_full([3,-1,0,-2,4,-5], 6)?
✓ תשובה: ג) -1 — חיוביים: 3,4 → count_pos=2. שליליים: -1,-2,-5 → count_neg=3. 0 מדולג. תוצאה: 2-3=-1.
// אתגר מעשי

אתגר מעשי — RE — Crackme רקורסיבי ורקורסיה

שיא השיעור: היפוך, ניתוח וזיהוי — כלי ה-RE וה-Low-Level בפעולה. ההגשה נבדקת אוטומטית.

T4 · מתקדם550 נק'~45 דק'reverse
Crackme — XOR משורשר (רקורסיבי)
מסופק בינארי crackme2. פונקציית ה-check שלו רקורסיבית ומשתמשת ב-XOR *משורשר* (כל תו תלוי בקודמו). הפוך את הבינארי, הבן את השרשור, ומצא את ה-serial (6 תווים) שמדפיס 'Correct!' ואת ה-flag. המקור אינו מסופק.
דוגמאות
serial שגוי → Wrong.
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס Crackme — XOR משורשר (רקורסיבי) ›
# XOR משורשר: t[0]=s[0]^K ; t[i]=s[i]^s[i-1]^K (K=0x5a) # היפוך: s[0]=t[0]^K ; s[i]=t[i]^s[i-1]^K target=[0x8,0x3b,0xa,0x4c,0x5d,0x5b]; K=0x5a; s=[] for i in range(6): c = target[i]^K if i==0 else target[i]^ord(s[i-1])^K s.append(chr(c)) print(''.join(s)) # R3curs -> flag{gamma_re_R3curs}
T3 · בינוני300 נק'~15 דק'analysis
מעקב אחר רקורסיה
נתונה פונקציה רקורסיבית (שוחזרה מ-disassembly): f(n) = n אם n < 3 f(n) = f(n-1) + f(n-3) אחרת חשבו ידנית והגישו את הערך של f(8).
דוגמאות
f(3) = f(2)+f(0) → = 2
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס מעקב אחר רקורסיה ›
f: 0,1,2 → 2,3,5,7,10,15 עבור n=0..8. f(8)=f(7)+f(5)=10+5=15.
// סיכום

✓ מה למדנו בשיעור 30

✓נקודות מפתח — שיעור 30
  • מתודולוגיה 5 שלבים: סריקה → גבולות → Bottom-Up → Dry Run → תרגום C
  • Function Chains: נתחו Bottom-Up — קודם פנימיות, אחר כך חיצוניות
  • Recursive ASM: סימן = CALL לעצמה. 2 CALLים = Fibonacci/Tree
  • Bubble Sort: שתי לולאות + SWAP בכל השוואה + j עד n-i-1
  • Selection Sort: שתי לולאות + min_idx + SWAP פעם אחת בסוף
  • strlen: MOVZX byte + TEST + JZ עד null — מחזירה מונה
  • strcmp: שני MOVZX + CMP + JNE — מחזירה 0/pos/neg
  • strcpy: MOVZX read + MOV BYTE write — כותבת! סיכון Buffer Overflow
  • Quick Patterns: 30 שניות — ספרו push ebp, JMP אחורה, CALL לעצמה
  • כשתקועים: ענו חלקית, Dry Run לn קטן, זהו מה זה אינו
🎯 יחידה 4 הסתיימה — מה הלאה?
יחידה 5: רשתות ולינוקס עמוק — שיעורים 31-40
שיעור 31: מבוא ללינוקס CLI — ניווט, קבצים והרשאות
שאלות תרגול ותשובות (10)

שאלה 1 · איך מזהים במהירות פונקציה רקורסיבית בפלט objdump?

  • א. היא מכילה CALL לכתובת של עצמה
  • ב. היא משתמשת רק ברגיסטרים ולא ב-Stack
  • ג. אין בה פקודת RET
  • ד. היא מתחילה בלולאת jmp אחורה
תשובה: א

רקורסיה = פונקציה שקוראת לעצמה, ולכן ב-Disassembly רואים CALL שהיעד שלו הוא כתובת ההתחלה של אותה פונקציה. לולאה רגילה משתמשת ב-jmp, לא ב-CALL.

שאלה 2 · מה מקבלים מניתוח Bottom-Up של שרשרת קריאות?

  • א. מבינים קודם מה עושות פונקציות העלה הפשוטות, ואז מרכיבים את התמונה הכוללת
  • ב. חוסכים את הצורך לקרוא את הפונקציה הראשית
  • ג. מגלים את שמות הפונקציות גם בקובץ Stripped
  • ד. מזהים מיד פגיעויות אבטחה
תשובה: א

הפונקציות התחתונות בשרשרת קצרות וברורות. אחרי שמפענחים אותן, כל CALL אליהן הופך למשמעות ידועה, והפונקציה הקוראת נקראת הרבה יותר בקלות.

שאלה 3 · מה מחזירה הפונקציה עבור הקריאה fib(4)?

fib(n):
  if (n < 2) return n;
  return fib(n-1) + fib(n-2);
  • א. 3
  • ב. 4
  • ג. 5
  • ד. 2
תשובה: א

fib(4) = fib(3)+fib(2) = (fib(2)+fib(1)) + (fib(1)+fib(0)) = (1+1) + (1+0) = 3. בסדרה 0,1,1,2,3 — האיבר הרביעי הוא 3.

שאלה 4 · איזה Pattern מאפיין Bubble Sort באסמבלי?

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

Bubble Sort מבצע השוואה בין ‎arr[j]‎ ל-‎arr[j+1]‎ ומחליף מיד — ולכן רואים בלולאה הפנימית cmp על שתי כתובות סמוכות ורצף של mov לביצוע ההחלפה.

שאלה 5 · מהו ההבדל המזהה בין Selection Sort ל-Bubble Sort באסמבלי?

  • א. ב-Selection ההחלפה מתבצעת פעם אחת בסוף כל מעבר, אחרי שנמצא המינימום
  • ב. ב-Selection אין לולאות מקוננות כלל
  • ג. ב-Bubble אין פקודת cmp
  • ד. ב-Selection המערך נסרק מהסוף להתחלה
תשובה: א

שני האלגוריתמים בנויים משתי לולאות, אבל ב-Selection הלולאה הפנימית רק מעדכנת אינדקס מינימום, וההחלפה (רצף ה-mov) מופיעה מחוץ ללולאה הפנימית — פעם אחת בכל מעבר.

שאלה 6 · איזה Pattern מזהה את הפונקציה strlen באסמבלי?

  • א. לולאה שמתקדמת בבייט אחד בכל סיבוב ועוצרת כשהערך הנקרא הוא 0
  • ב. לולאה שמעתיקה בייטים ממקום למקום
  • ג. השוואה בין שני מצביעים עד שהם שווים
  • ד. חישוב הפרש בין שתי כתובות בלי לולאה
תשובה: א

strlen פשוט סופר עד ה-Null Terminator: קריאת בייט, בדיקה מול 0, קידום מונה. הפסקת הלולאה על ערך 0 היא החתימה הכי ברורה שלה.

שאלה 7 · ראית קריאה ל-strcpy בפונקציה שמעתיקה קלט משתמש לתוך מערך מקומי. למה זה מסוכן?

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

strcpy מעתיקה עד שהיא פוגשת ‎\0‎ במקור, בלי שום מושג מה גודל היעד — קלט ארוך מהחוצץ ידרוס את Saved EBP ואת ה-Return Address. 🎯 דגש למבחן: כשמחפשים Buffer Overflow, סרקו קודם את הפונקציות חסרות בדיקת האורך — strcpy, gets, sprintf, strcat. הגרסאות הבטוחות (strncpy, snprintf) מקבלות פרמטר גודל, ולכן באסמבלי יופיע push נוסף של קבוע הגודל לפני ה-CALL. היעדר הפרמטר הזה הוא הנורה האדומה.

שאלה 8 · בקובץ בינארי אין אף מחרוזת קריאה, אך התוכנית מדפיסה הודעות ברורות. מה ההסבר הסביר?

  • א. המחרוזות מוצפנות ומפוענחות בזמן ריצה על ידי לולאת XOR קצרה
  • ב. המחרוזות נמחקו מהקובץ בזמן הקימפול
  • ג. ההודעות נוצרות באופן אקראי בכל הרצה
  • ד. המחרוזות נטענות מהאינטרנט
תשובה: א

זו טכניקת הסתרה נפוצה: המחרוזת שמורה כבייטים מעורבלים, ולפני השימוש בה רצה לולאה שמבצעת XOR מול מפתח ומחזירה את הטקסט המקורי. 🎯 דגש למבחן: את ה-Decryption Stub מזהים לפי לולאה קצרה מעל אזור נתונים שמכילה xor עם קבוע או עם בייט מפתח מתחלף. איתרו את הלולאה, קראו את המפתח, והריצו XOR ידנית על הבייטים — כך משחזרים את המחרוזת בלי להריץ את התוכנית.

שאלה 9 · לפי איזה סימן מזהים באסמבלי שפונקציה מקבלת מצביע ולא ערך?

  • א. הפרמטר נטען לרגיסטר ואז ניגשים לזיכרון דרכו, למשל mov eax, [ebp+8] ואחריו mov edx, [eax]
  • ב. הפרמטר נדחף ל-Stack בעזרת push
  • ג. הפרמטר נשמר במשתנה מקומי
  • ד. הפונקציה מחזירה ערך ב-EAX
תשובה: א

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

שאלה 10 · איזה קוד C משחזר את הקטע הבא?

mov ecx, DWORD PTR [ebp+8]
xor eax, eax
.L_LOOP:
cmp BYTE PTR [ecx], 0
je .L_END
add eax, 1
add ecx, 1
jmp .L_LOOP
.L_END:
  • א. ספירת אורך מחרוזת — מעבר בייט-בייט עד ל-0 והחזרת המונה
  • ב. העתקת מחרוזת מכתובת אחת לאחרת
  • ג. השוואת שתי מחרוזות והחזרת ההפרש
  • ד. היפוך סדר התווים במחרוזת
תשובה: א

‎[ebp+8]‎ הוא מצביע למחרוזת, EAX מאופס כמונה, והלולאה מתקדמת בבייט בכל סיבוב עד לתו ‎\0‎. זהו מימוש קלאסי של strlen.