ตามรอยช่องโหว่: ปลดล็อกความลับของโปรแกรมด้วยเทคนิค Pwn

ตามรอยช่องโหว่: ปลดล็อกความลับของโปรแกรมด้วยเทคนิค Pwn

เคยสงสัยไหมว่าแฮกเกอร์เจาะระบบได้อย่างไร? หนึ่งในเทคนิคสุดคลาสสิกที่มักเห็นในการแข่งขัน CTF (Capture The Flag) คือการโจมตีประเภท “Pwn” ซึ่งย่อมาจาก “pwned” หมายถึงการเข้าควบคุมโปรแกรมหรือระบบได้เบ็ดเสร็จ บทความนี้จะพาไปสำรวจกลไกเบื้องหลังการโจมตี Pwn แบบหนึ่งที่เรียกว่า Use-After-Free (UAF) ซึ่งอาศัยความผิดพลาดในการจัดการหน่วยความจำ

โปรแกรมจัดการโน้ตกับความเสี่ยงที่ซ่อนอยู่

ลองจินตนาการถึงโปรแกรมง่ายๆ ที่ให้เราสร้าง ลบ และดู “โน้ต” หรือบันทึกได้ แต่ละโน้ตจะประกอบด้วย ID, ขนาดของข้อมูล และตัวข้อมูลจริงๆ โปรแกรมจะเก็บข้อมูลเหล่านี้ไว้ในส่วนของหน่วยความจำที่เรียกว่า “ฮีป” (Heap) ซึ่งเป็นพื้นที่ที่โปรแกรมใช้จัดสรรหน่วยความจำแบบไดนามิกขณะทำงาน

เมื่อเราสร้างโน้ต โปรแกรมจะจัดสรรพื้นที่ในฮีปสำหรับโครงสร้างของโน้ต และอีกพื้นที่สำหรับข้อมูลที่เราบันทึกตามขนาดที่ระบุ

แต่ปัญหาจะเกิดเมื่อเราลบโน้ต โปรแกรมจะคืนพื้นที่หน่วยความจำที่เคยเก็บข้อมูลโน้ตและโครงสร้างโน้ตกลับคืนสู่ระบบ เพื่อให้พร้อมนำไปใช้ใหม่ แต่บางครั้งโปรแกรมก็ลืมทำความสะอาดให้หมดจด

เปิดโปงช่องโหว่ Use-After-Free (UAF)

ช่องโหว่ Use-After-Free เกิดขึ้นเมื่อโปรแกรมคืนพื้นที่หน่วยความจำไปแล้ว แต่ยังคงมี “ตัวชี้ค้าง” (Dangling Pointer) ที่ยังคงชี้ไปยังพื้นที่นั้นอยู่ แม้ว่าพื้นที่นั้นจะไม่ได้ถูกจัดสรรให้เราใช้อีกต่อไป

ในกรณีของโปรแกรมโน้ต หากเราสร้างโน้ต A แล้วลบทิ้ง หน่วยความจำสำหรับโน้ต A ถูกคืนแล้ว แต่บางส่วนของข้อมูลเก่า เช่น ค่า “ขนาด” (size) ของข้อมูลเดิม อาจยังคงอยู่ในพื้นที่นั้น

เมื่อเราสร้างโน้ตใหม่ (โน้ต B) และมันบังเอิญได้พื้นที่หน่วยความจำเดียวกับโน้ต A ที่ถูกลบทิ้งไป ตัวโน้ต B ก็จะ “สืบทอด” ค่าขนาดข้อมูลเก่าของโน้ต A มาโดยไม่ตั้งใจ แม้ว่าเราจะขอพื้นที่สำหรับโน้ต B ขนาดเล็กกว่าก็ตาม นี่คือหัวใจสำคัญของการโจมตี

ขั้นตอนการโจมตี: เปลี่ยนข้อมูลสู่การควบคุม

ด้วยสถานการณ์ที่โน้ต B ได้รับค่าขนาดข้อมูลที่ผิดจากโน้ต A มันก็เหมือนกับว่ามีพื้นที่ให้เขียนข้อมูลได้มากกว่าที่ควรจะเป็น

หากเราเขียนข้อมูลลงไปในโน้ต B เกินขนาดที่ตั้งใจไว้ ข้อมูลส่วนเกินนั้นจะ “ล้น” (Overflow) เข้าไปเขียนทับพื้นที่หน่วยความจำที่อยู่ถัดไปในฮีป พื้นที่ที่ถูกเขียนทับนี้มักเป็นข้อมูลสำคัญที่ระบบใช้จัดการฮีป เช่น เมตาดาต้า (Metadata) ที่บอกว่าบล็อกหน่วยความจำถัดไปอยู่ที่ไหน

การเขียนทับเมตาดาต้าของฮีปสามารถทำให้เรา “หลอก” ระบบได้ หากเราเปลี่ยนเมตาดาต้าให้ชี้ไปยังที่อยู่ที่เราควบคุม เราก็สามารถกำหนดทิศทางการทำงานของโปรแกรมได้

เป้าหมายยอดนิยมหนึ่งคือการเขียนทับ __free_hook ซึ่งเป็นตัวชี้พิเศษในไลบรารีของระบบ (glibc) ที่ปกติจะชี้ไปยังฟังก์ชัน free() ที่ใช้คืนหน่วยความจำ

หากเราเปลี่ยน __free_hook ให้ชี้ไปยังฟังก์ชัน system() (ซึ่งรันคำสั่ง Shell) แทน เมื่อใดก็ตามที่โปรแกรมเรียกใช้ free() มันก็จะกลายเป็นการเรียก system() ทันที

สู่การควบคุมระบบอย่างเต็มรูปแบบ

เมื่อเราเปลี่ยน __free_hook ให้ชี้ไปที่ system() ได้แล้ว ขั้นตอนสุดท้ายคือการ “กระตุ้น” ให้โปรแกรมเรียก free() บนข้อมูลที่เราควบคุม

ตัวอย่างเช่น หากเราสร้างโน้ตที่มีข้อมูลเป็น /bin/sh แล้วเรียก free() บนโน้ตนั้น ระบบก็จะเรียก system("/bin/sh") ซึ่งจะเปิด Shell ให้เราควบคุมเครื่องได้เต็มที่

การโจมตีประเภทนี้แสดงให้เห็นถึงความสำคัญของการจัดการหน่วยความจำที่ถูกต้องในโลกของโปรแกรมมิ่ง การละเลยรายละเอียดเล็กๆ น้อยๆ เช่น การไม่ตั้งค่าตัวชี้ให้เป็น NULL หลังคืนหน่วยความจำ สามารถนำไปสู่ช่องโหว่ร้ายแรงที่เปิดประตูให้ผู้โจมตีเข้าควบคุมระบบได้อย่างง่ายดาย ดังนั้นการใส่ใจเรื่องความปลอดภัยของหน่วยความจำจึงเป็นสิ่งสำคัญอย่างยิ่งในการพัฒนาซอฟต์แวร์