Monday, December 13, 2010

Process Memory Layout บน Linux x86

ในแต่ละ process จะมีการจอง memory ไว้ใช้งาน โดยทุก process จะเห็น memory เป็นโครงสร้างเดียวกันทั้งหมด หรือที่เรียกว่า virtual memory (หน่วยความจำเสมือน) และ memory จะมีการแบ่งส่วนไว้เก็บข้อมูลต่างๆ โดยผมจะพูดถึงส่วนที่สำคัญ ตามรูปข้างล่าง (ถ้าใครไม่รู้ว่า virtual memory ก็คิดซะว่าเป็น memory ไปก่อน แต่ถ้าสนใจ แนะนำให้ไปอ่านหนังสือพวก Operating System)

  • .text ส่วนนี้ใช้สำหรับเก็บ code ของโปรแกรมที่แปลงเป็น machine code แล้ว โดย default ส่วนนี้จะเป็น read-only (อ่านได้อย่างเดียว) ไม่สามารถเขียนทับได้
  • .bss ส่วนนี้ตัวแปร global ที่มีการ initialize (ถูกกำหนดค่าไว้ตั้งแต่โปรแกรมเริ่ม) เช่น int a = 0;
  • .data ส่วนนี้ตัวแปร global ที่ไม่มีการ initialize (ไม่ถูกกำหนดค่าไว้ตั้งแต่โปรแกรมเริ่ม) เช่น int a;
  • heap เป็นส่วนที่สามารถขยายได้ โดย address จะขยายจาก low address ไปยัง high address เพื่อใช้สำหรับจองและเก็บข้อมูลที่มีขนาดไม่แน่นอน ในขณะที่โปรแกรมทำงานอยู่ โดยคำสั่งที่ใช้ใน C เพื่อควบคุมส่วนนี้คือ malloc() และ free()
  • stack ใช้สำหรับเก็บข้อมูลของการเรียกฟังก์ชัน (function call) เพื่อให้โปรแกรมสามารถกลับมาทำงานต่อได้ หลังจากโปรแกรมจบฟังก์ชันที่เรียก โดย address ของส่วนนี้จะขยายจาก high address ไปยัง low address
    จะเห็นว่าในรูป ผมได้มี argv กับ env ใน stack ด้วย ในขณะนี้โปรแกรมจะเริ่มทำงาน โปรแกรมได้มีการเก็บข้อมูล environment (สามารถดูได้จากคำสั่ง env) และ arguments ของโปรแกรมนั้นลงบน stack ด้วย

Endian

Endian นั้นจะมี big endian กับ little endian โดยจะต่างกันในเรื่องของ byte order เช่นถ้าเราจะเก็บข้อมูล integer ที่มีขนาด 4 bytes (32 bits) ค่า 0x41424344 ใน memory ก็จะเก็บได้ตามรูป

จะเห็นว่า big endian นั้นเก็บข้อมูลเรียงตามปกติที่เราเขียน แต่ little endian นั้นจะเก็บข้อมูลจาก byte ที่มีค่าน้อยที่สุดก่อน
สำหรับ x86 ที่ผมจะพูดถึงในเรื่องการเขียน exploit จะใช้การเก็บข้อมูลเป็นแบบ little endian ถ้าใครไม่เคยอ่านแบบนี้ แรกๆอาจจะงงกันได้ แต่ถ้าได้ทำบ่อยๆ ก็จะคุ้นเคยกับมันไปเอง

Stack

ปกติเราคงคุ้นเคยกับ queue (คิว) คือใครต่อแถวก่อนได้ก่อน หรือคนเรียนคอมพิวเตอร์จะเรียกว่า First In First Out(เข้าก่อนออกก่อน) ย่อๆว่า FIFO ส่วน stack (กองซ้อน) คือวิธีการเก็บข้อมูลแบบนี้ ที่ตรงกับข้ามกับ queue โดยจะเรียกว่า Last In First Out (เข้าทีหลังออกก่อน) ย่อๆ ว่า LIFO โดย stack ที่น่าเคยเห็นกันทุกคนคือ กล่องใส่ซีดีที่มีแกนอยู่ตรงกลาง แผ่นซีดีที่เราใส่เป็นแผ่นสุดท้าย เราต้องเอาออกก่อน ส่วนแผ่นซีดีที่เราใส่เป็นแผ่นแรก จะเป็นแผ่นสุดท้ายที่เราเอาออกมาได้

ในคอมพิวเตอร์จะมี 2 operation (การกระทำ) หลักๆ คือ push กับ pop โดย push คือการใส่ข้อมูลที่บนสุดของ stack ส่วน pop คือการเอาข้อมูลที่อยู่บนสุดของ stack ออกมา ตามรูปข้างล่าง


ในหัวข้อนี้ ผมได้พูดถึง memory layout ของ process บน Linux x86 โดยส่วนที่จะได้พบและใช้บ่อยๆในการเขียน exploit คือส่วนของ stack ที่ผมยังไม่ได้ลงรายละเอียดมากนัก ซึ่งจะมีเพิ่มเติมในหัวข้อถัดๆ ไป

No comments:

Post a Comment