วันอังคารที่ 31 พฤษภาคม พ.ศ. 2554

What's Buffer Overflow? (เบื้องต้น)

เพื่อผู้อ่านจะได้มีความเข้าใจในเรื่อง Buffer-

Overflow เบื้องต้น และพัฒนา skill ในการ

เขียนโปรแกรมมากขึ้น

What's Buffer Overflow?

คือ ข้อมูลที่เขียนเกิน ล้นเข้าไปทับข้อมูลอื่น ที่อยู่ในระบบ (เกิดขี้นจากการเขียน หรืออ้างอิงข้อมูลเกิน
ขอบเขตที่กำหนด)

ผลจากการ Overflow อาจจะเป็นตั้งแต่ทำให้ โปรแกรมประมวลผลข้อมูลผิดพลาด (Segmentation 

False) หรืออาจจะร้ายแรงถึงขั้น เปลี่ยนการทำงานของระบบให้ประมวลผล (Code ใดๆ ก็ได้ ตามที่ 

Attackers ต้องการ) และ ปัญหาง่ายๆ อย่างนี้ อาจนำไปสู่ความเสียหายอื่นๆ อันเป็นพื้นฐานของ

Computer Viruses หรือ Computer Worms อีกด้วย

อนึ่ง นอกจาก Buffer-Overflow แล้ว ยังมีปัญหาอื่นๆ คล้ายๆกัน เช่น Integer Overflow หรือ การใช้ printf ในภาษา C แบบไม่ถูกวิธี


Buffer-Overflow Attacks สร้างความเสียหายได้หลายอย่าง เช่น


1. Buffer-Overflow Attacks (แบบเบื้องต้น)

ตัวอย่างที่ 1

โปรแกรมภาษา C แบบง่ายๆ  เมื่อถูก Overflow จะทำให้การทำงานบางอย่างผิดไป ในที่นี้ คือ

ตัวแปร age  ซึ่งเป็นตัวแปรเก็บตัวเลขจะถูก Overflow จนค่าที่ได้เปลี่ยนไป  ในตัวอย่างด้านล่างจะพบ

ว่าอายุเปลี่ยนจาก 15 เป็น 49 (ทั้งที่ไม่ได้ทำการกำหนดค่าตัวแปร age เป็น 49)








#include                                                                                             $./a.out
int main(char argc,char *argv[]) {                                                      Enter your age:15
        int age;                                                                                      Enter your name: Krerk.P01
        char name[8];                                                                            -----------
        char tmp[20];                                                                             Krerk.P01 is 49 years old
        printf("Enter your age:");                                                                                                                               
        gets(tmp);
        age=atoi(tmp);
        printf("Enter your name:");
        gets(name);  /* 1 */
        printf("----------- ");
        printf("%s is %d years old " ,name,age);
}
   $./a.out

**** ค่าที่ได้อาจจะแตกต่างกันไป ขึ้นอยู่กับ Processor, ระบบปฏิบัติการ และ ตัว compiler *****



สิ่งที่เกิดขึ้นคือ คำสั่ง gets(name) (1) ซึ่งรับข้อมูล input ที่มีขนาด 10 ตัว (9 ตัวอักษร "Krerk.P01" 

และ 1 terminator) มาเก็บไว้ในตัวแปร name แต่ตัวแปร name นั้นมีขนาดเพียง 8 ตัวอักษร ทำให้

ข้อมูลที่ได้มาเกิด Overflow ไปทับ age ผลที่ตามมาคือตัวแปร age ซึ่งเดิมเก็บตัวเลข 15 ถูกเปลี่ยน

เป็นเก็บตัวอักษร '1' แทน เมื่อ age ถูกอ้างอิงอีกครั้ง ตัวอักษร '1' จึงถูกอ้างอิงเป็นตัวเลขซึ่งมีค่าเป็น 

49  (ในที่นี้ถือตามมาตรฐานรหัส ASCII)
 



ตัวอย่างที่ 2 

Stack Smashing  ซัพซ้อนขึ้นอีกนิด (สามารถผ่านระบบป้องกัน Buffer Overflow บน Software ที่ใช้

กันทั่วไป)                                                                                         
 
ตัวอย่างนี้ จะเป็นทำงานที่ซับซ้อนยิ่งขึ้น ผู้อ่านจะต้องมีความเข้าใจเรื่องของ Stack Frame สักเล็กน้อย


ทุกครั้งที่โปรแกรมมีการเรียก sub routine ค่า parameters และ ตำแหน่งการทำงานปัจจุบัน (Return 

Address) จะถูกเก็บไว้ที่ข้อมูลชั่วคราวคือ Stack (memory เฉพาะ) หาก sub routine นั้นๆ มีตัวแปรที่

เป็น local variable โปรแกรมก็จะจองเนื้อที่บน Stack เพื่อใช้สำหรับเก็บตัวแปรดังกล่าว การทำงาน

ลักษณะนี้ ทำให้โปรแกรมสามารถอ้างอิง scope ของ variable ได้ (local variables อ้างอิงจาก stack 

frame ปัจจุบันเสมอ)

ตามตัวอย่างด้านล่าง  ตัว function func ประกอบไปด้วยตัวแปร i, f, ptr, และ buffer โดยในที่นี้ f เป็น 


function pointer (สำหรับผู้ที่ไม่คุ้นเคย funtion pointer เป็น pointer ที่ใช้สำหรับการ dynamic bind 

ตัวแปร f ให้เป็น funtion ใดๆ ก็ได้ ในตัวอย่างนี้ เรา bind f ให้เป็น printf) และ ptr เป็น pointer ทั่วไป 

และ buffer เป็น array of characters ธรรมดา เมื่อ func ถูกเรียกใช้งาน ระบบจะทำการสร้าง stack 

frame ขึ้นเพื่อจำค่าตำแหน่งปัจจุบัน (ก่อนจะ call subroutine) ที่โปรแกรมจะต้องกลับมาทำงานต่อ 

เมื่อเข้ามาสู่โปรแกรม ตัว subroutine จะบันทึกค่า frame pointer funtion เดิม และจองเนื้อที่สำหรับ 

local variables ดังแสดงได้ในด้านขวาของตามตัวอย่างด้านล่าง
 


int func(char **argv) {                                      return address                                       & buffer
    int x;                                                              frame pointer                                         & buffer
    int (*f) (const char *, ...);                                         x                                                  & buffer
    char *ptr;                                                                 f                                                   & buffer
    char buffer[30];                                                      ptr                                                 & buffer
    ptr=buffer;                                                            
    f=& printf;
    f("ptr %p - before ",ptr);                                       buffer                                         Malicious Code
    strcpy(ptr,argv[1]);
    f("ptr %p - after ",ptr);
    strcpy(ptr,argv[2]);
}




เมื่อวิเคราะห์ โปรแกรมดังกล่าว จะพบว่า หากเกิด overflow ที่ตัวแปร buffer ค่าต่างๆ (อาทิ ptr, f , x, 

frame pointer, return address) สามารถถูกเปลี่ยนเป็นค่าใดๆ ก็ได้ เมื่อมี input ที่เหมาะสม Buffer-

overflow attack แบบแรกๆ ที่พบมักจะแทรก code ที่ต้องการให้โปรแกรม run ลงในตอนต้นของ buffer 

จากนั้นก็เขียน address ของ buffer ดังกล่าวต่อท้ายไป ผลที่ได้คือ address ทั้งหลายจะถูกชี้กลับมาที่ 

buffer ซึ่งมี code ที่ hacker ต้องการอยู่ เมื่อ address ดังกล่าวถูกอ้างอิง code เหล่านี้ก็จะถูกประมวล

ผลโดยปริยาย buffer-overflow attack ในลักษณะนี้ บางครั้งนิยมเรียกว่า stack smashing เนื่องจาก

ข้อมูลขนาดใหญ่ ถูกปะลงบนเนื้อที่ Stack อย่างไรก็ตาม บางคนจึงคิดว่าหากเราทำให้ stack ไม่

สามารถ execute ได้ (เช่น AMD non-executable area --- NX หรือ patch ที่ Linux kernel บางอัน) 

น่าจะสามารถหยุด buffer-overflow attack ได้

ในความเป็นจริง การ injected code มิใช่สิ่งสำคัญ เพราะหากเราทราบว่ามี code ที่เราต้องการให้


โปรแกรมประมวลผล อยู่ที่ตำแหน่งอันใดอันหนึ่งใน memory (เช่น code จาก share library หรือตัว 

code ของโปรแกรมเอง) เราก็เพียงเปลี่ยนตำแหน่งของ pointers และ addresses ต่างๆ ให้ชี้ไปยัง 

code ส่วนนั้น การ attack ในลักษณะนี้ บางคนเรียกว่า arc injection
 



ตัวอย่างที่ 3 

Multi-stage attacks ในหลายๆ กรณี buffer-overflow attacks จะเกิดขี้นจากการ overflow หลายครั้ง

ต่อกัน โดยทั่วไปการ overflow ครั้งแรกจะทำให้เกิด pointer ชี้ไปยังที่ใดก็ได้ในตัวโปรแกรมนั้นๆ และ

การ overflow อีกครั้งนึงจะเปลี่ยนค่าที่ pointer ชี้อยู่ เป็นค่าที่ต้องการ

ตัวอย่าง การ attack ที่พบเช่น Apache mod_SSL SLAPPER Worm ซึ่งการ overflow ครั้งแรก ช่วยให้


เกิด pointer ชี้ไปยัง global offset table (jump table ที่ใช้อ้างอิงตัวโปรแกรมหลักกับ share library) 

ของ funtion free จากนั้น การ overflow ครั้งที่ 2 จะเปลี่ยน entry ดังกล่าวให้มา run remote shell

ลอง พิจารณาโปรแกรมในตัวอย่างที่ 2 อีกครั้งหนึ่ง เราจะพบว่ามี strcpy 2 ครั้ง ในกรณีของ 


Multi-stage attacks นี้ strcpy ครั้งแรก จะoverflow ตัว pointer ptr ให้ชี้ไปยังที่ใดก็ได้ของโปรแกรม 

สุมมุติว่าชี้ไปที่ jump table ของ printf เมื่อมี strcpy ครั้งที่ 2 เราก็สามารถจะเขียนค่าใดๆ ก็ได้ที่ entry 

นั้น ผลที่เกิดขึ้นก็คือ เมื่อเรียก printf ครั้งต่อไป ตัวโปรมก็จะไปเรียกทำงานค่าที่ระบุแทนโปรแกรม 

printf ที่อ้างอิงกับ library

นักวิจัยหลายคนเสนอแนวทางการป้องกัน global offset table โดยให้ทำส่วนดังกล่าวเป็น read only 


หลังจากที่ได้มีตัวค่าโดย loader เรียบร้อยแล้ว อย่างไรก็การป้องกันดังกล่าว ก็มิได้ป้องกัน function 

pointer หรือส่วนอื่นๆ แต่อย่างใด

ยิ่งไปกว่านั้น หลักการดังกล่าวยังใช้ attacks โปรแกรมที่มีการป้องกัน Buffer Overflow ด้วยวิธีการที่นำ


เสนอกันหลายๆ แบบ เช่น การแทรกค่า cannary เพื่อตรวจสอบว่า address มีการเปลี่ยนแปลงหรือไม่ได้

อีกด้วย เช่น การเปลี่ยนตัว error handling routine ให้เป็นของผู้บุกรุกเอง เป็นต้น (ดูรายละเอียดเพิ่ม

เติมได้ที่ Secure Bit 2, และ Buffer Overflow: the Fundamentals และ Secure Bit)
 


หลักการสำคัญของ Buffer-Overflow

ถึงตรงนี้บางคนอาจจะตั้งข้อสังเกตุว่า Buffer Overflow ดูเหมือนจะเป็นปัญหาที่เกิดขึ้นจาก ภาษา C ซึ่ง


ไม่มีระบบ Bound Checking แต่ในความเป็นจริงคือ ทุกภาษาในปัจจุบัน มักจะถูกแปลงลงมาเป็นภาษา

เครื่อง หรือติดต่อกับระบบปฏิบัติการ ซึ่ง component ต่างๆ มักเขียนในภาษา C (และ assembly) 

ตัวอย่างที่เห็นได้ชัดเช่น Buffer-overflow attacks ที่พบใน Java, Perl, หรือแม้แต่ .Net

 

เมื่อวิเคราะห์ถึงประเด็นสำคัญที่ทำให้ เกิด Buffer-Overflow Attacks แล้ว เราจะพบว่าปัญหาโดยตรง

เกิดจากการที่ Input ซึ่งเป็นข้อมูลที่นอกเหนือความควบคุมของผู้เขียนโปรแกรม เข้ามาทำให้เกิดความ

เสียหายในระบบ จนทำให้การทำงานของโปรแกรมผิดไป [Howard and LeBlanc] จาก Microsoft กล่าว

ในหนังสือ Threat Model ว่า “All input is evil until proven otherwise” และเสนอว่า ข้อมูลทุกอัน

ต้องมีการตรวจสอบเมื่อมีการส่งผ่านขอบเขตของโปรแกรม (“Data must be validated as it crosses 

the boundary between untrusted and trusted environments.”) ซึ่งข้อคิดนี้ มิใช่แนวคิดใหม่ หาก

แต่ผู้เขียนโปรแกรมทั่วไปมักมิได้คำนึงถึง

 

Secure Bit 2 ซึ่งเป็นงานวิจัยของผู้เขียนเอง เป็นเทคโนโลยีที่จะฝังหลักการดังกล่าวลงในระบบโดยผู้

พัฒนาโปรแกรม ไม่จำเป็นต้องคอยระมัดระวังตรวจสอบ Input ของตัวเองตลอดเวลา (เพราะแม้นัก

พัฒนาซอฟต์แวร์มืออาชีพหลายคน ยังกล่าวว่ามีบ่อยครั้งที่ไม่สามารถตรวจสอบตัวโปรแกรมได้ครบทุก

กรณีในขณะ พัฒนาตัวโปรแกรม) รายละเอียดเพิ่มเติมศึกษาได้ที่ Secure Bit 2
 


บทสรุป

ในโอกาสต่อไป ผู้เขียนจะนำเสนอแนวทางการป้องกัน buffer overflow แบบต่างๆ ที่นักวิจัยทัวโลก


พยายามพัฒนากันออกมา พร้อมกับชี้ให้เห็นข้อดีข้อเสีย โดยสรุปแล้ว buffer overflow เป็นปัญหาพื้น

ฐานของระบบคอมพิวเตอร์ตั้งแต่เรามีคอมพิวเตอร์จนถึงปัจจุบัน ยิ่งมีคอมพิวเตอร์ต่อไว้กับ internet 

มากขึ้นเพียงใด ก็มีเป้านิ่งให้ hackers ทั้งหลายได้โจมตีมาขึ้น ประกอบกับแต่เดิมผู้พัฒนาซอฟต์แวร์ราย

ใหญ่ๆ (อาที MS) เน้นขาย funtioncs ใช้งาน และ มิได้ใส่ใจกับปัญหาดังกล่าวมากนัก แม้ปัจจุบันจะมี


แนวทางที่เสนอออกมามากมาย แต่ก็ยังไม่มีแนวทางใดที่สมบูรณ์แบบ ทางแก้ปัญหาที่ดีที่สุด อาจจะเป็น

พยายามฝึกให้ นักพัฒนาซอฟต์แวร์ทั้งหลายใส่ใจกับโปรแกรมที่ตัวเองเขียนมากขึ้น


Cradit  Krerk Piromsopa


Debit All Admin in Thailand

ไม่มีความคิดเห็น:

แสดงความคิดเห็น