Lỗi tràn bộ đệm (2)

Ngô Quang Hưng | 15 tháng 08, 2005 | Bản để in Bản để in

Trong loạt bài này, tôi sẽ viết thêm (và học thêm) về các chi tiết kỹ thuật của sự tràn bộ đệm và các cách phòng chống. Một phần của loạt bài này tôi viết đã lâu, nhưng chưa bao giờ tìm ra thời gian viết tiếp. Nay nhân vụ Cisco chọi Michael Lynn, khi lỗi trong IOS của Cisco là lỗi tràn bộ đệm kinh điển, tôi nghĩ vẫn có nhu cầu cần phổ cập hóa kiến thức về cái bug này. (Xem thêm bài trình bày của Michael Lynn do một bạn đọc gửi.)

2. Căn bản về buffer overflow với ngôn ngữ C

Lỗi buffer overflow (hay overrun) khá phổ biến trong các chương trình viết bằng C, vì C không cung cấp cấu trúc dữ liệu mảng (array) kèm kích thước và sự kiểm tra kích thước mảng trước khi dùng. Đây là một trong những cái giá phải trả cho sự hiệu quả (efficiency) của C, ngôn ngữ thông dụng nhất cho lập trình hệ thống.

Với mục tiêu minh họa, C là ngôn ngữ lý tưởng cho các thao tác liên quan đến bộ nhớ, stack, heap, …

Hãy xét một chương trình C nho nhỏ và “vô tội” sau:

/* ---------------------------------------------------------------------
 * Vi' du. 1:
 * ---------------------------------------------------------------------
 */
#include 

void foo() {
  unsigned char buffer[20]; int i=0; int c;
  while ((c = getc(stdin)) != EOF) {
    buffer[i++] = (unsigned char) c;
  }
  buffer[i] = '\0';
  printf("You entered %s\n", buffer);
}

int main() {
  foo();
  return 0;
}

Ta dĩ nhiên có thể dùng stdin làm input cho chương trình. Tuy vậy, để làm ví dụ ta tạo một file dữ liệu làm input cho chương trình này. Tạo một file tên là data với nội dung chỉ có một dòng:

01234567890123456789012345678901234567

Sau khi dịch, bạn thử chạy chương trình:

hqn@hanoi (~/BO/Examples) % e1 < data
You entered 01234567890123456789012345678901234567

Không có vấn đề gì hết. Chương trình làm cái nó được thiết kết để làm. Thử cái khác nhé. Làm cho data dài hơn một byte:

hqn@hanoi (~/BO/Examples) % more data
01234567890123456789012345678901234567*
hqn@hanoi (~/BO/Examples) % e1 < data
You entered 01234567890123456789012345678901234567*
Segmentation fault
hqn@hanoi (~/BO/Examples) %

Tôi chỉ thêm vào data một byte (dấu *) và chương trình đã bị lỗi phân đoạn (segmentation fault). Dễ thấy rằng segmentation fault là do ta đọc vào nhiều bytes hơn là ta đã dành cho buffer (20 bytes). Đây chính là nội dung cơ bản của sự tràn bộ đệm.

Segmentation fault thường xuất hiện khi ta truy cập phần bộ nhớ không thuộc về process hiện hành. Thế nhưng tại sao data file đầu tiên có đến 38 bytes thì vẫn chưa bị tràn mà 39 bytes thì lại bị, trong khi ta chỉ có 20 bytes cho buffer? Ta sẽ trả lời câu hỏi này sau.

Cũng cần chú ý rằng tôi đang dùng gcc phiên bản 3.2. Với gcc phiên bản khác và cấu hình máy khác thì con số 38, 39 có thể khác do stack alignment khác nhau (xem thêm về data alignment trên MSDN). Tuy vậy nguyên tắc overflow này không đổi. Nếu 38 không phải là con số của bạn thì bạn có thể thí nghiệm tăng từ từ kể tử 21 lên đến con số cần thiết cho overflow.

Có thể thấy từ một ví dụ đơn giản này rằng một chương trình nào đó của hệ thống (httpd chẳng hạn), nếu lập trình cẩu thả là đã có thể bị phá hoại dễ dàng. Trong trường hợp này, ít nhất ta có thể làm nó ngưng chạy bằng cách cung cấp nhiều dữ liệu hơn dự tính.

Bạn có thể nghĩ rằng đọc từng byte từng byte một có vẻ không thực tế lắm. Không hẳn thế! Nhiều network protocols dùng các ký tự đặc biệt như '\n' hay '\r' để đánh dấu kết thúc một field nào đó trong packet header. Các TCP sockets cũng chỉ là file descriptors, và ta thường phải kiểm tra từng byte cho đến khi thấy ký tự giới hạn.

Chủ đề: Bảo mật và mật mã học | Bình luận (8) »

8 lời bình

  • Anonymous says:

    Rất mong được đọc tiếp bài viết của anh.
    Xin sửa lại một chút thông tin về input data cho chương trình:
    Độ dài của file dữ liệu đầu tiên là 38 bytes (38 kí tự). Độ dài của file dữ liệu tiếp theo là 39 bytes.
     

    Viết bởi HungQ

  • Anonymous says:

    Cảm ơn bạn, tôi đã sửa lại thành 38, 39. 

    Viết bởi Ngô Quang Hưng

  • Anonymous says:

    Chào anh,
    Tình cờ link đến blog của anh và đọc được chủ đề này tôi thấy vui quá. Máy tính của tôi đang chạy Window 2000 và thỉnh thoảng lại có thông báo lỗi là:

    Microsoft Visual C++ Runtime Library: Buffer overrun detected.
    Program: C:\Program Files\Internet Explorer\iexplore/exe
    A buffer overrun has been detected which has corrupted the program’’s internal state. The program cannot safely continue execution and must now be terminated.

    Sau thông báo này tất cả các cửa sổ Internet Explorer đều bị đóng lại. Đây có phải là một trong những lỗi mà anh đã nêu ra ở đây không? Tôi rất băn khoăn không biết máy tính của mình bị nhiễm virus hay là do chương trình Window của máy tính bị lỗi. Nếu có thể anh giải thích giúp tôi được không? Cảm ơn anh nhiều. ^^ 

    Viết bởi Quyên

  • Anonymous says:

    Chào bạn,

    Lỗi này dĩ nhiên là lỗi … buffer overrun.

    Tôi tìm sơ vài chỗ  trên google thì thấy có lẽ cái MSN Toolbar bị buffer overflow. Bạn thử uninstall MSN Toolbar hoặc tìm chuyên gia edit/remove các registry tương ứng. Có thể dùng Hijackthis để remove.

    Chú ý: tôi không biết nhiều về Windows – chỉ tìm bừa vậy thôi. 

    Viết bởi Ngô Quang Hưng

  • twoask says:

    Anh Hưng ơi em đang làm 1 cái research nho nhỏ về vấn đề stack overflow này. Không biết trong ví dụ này anh Hưng dùng SPARC hay INTEL CPU nhỉ? Bởi vì cấu trúc của SPARC hơi khác 1 chút, nó dùng register để store cái return address, nên muốn overflow cái return address này thì phải làm hơi phức tạp hơn thằng INTEL một chút.

  • Hải đọc tiếp các bài sau, có ví dụ về SPARC. Dùng “tuyển tập blog KHMT” mà browse

  • vinhie47 says:

    Anh cho em hỏi là mấy cái sample code phần shellcode viết bằng C anh sử dụng bộ biên dịch gì vậy? Em dùng VC++6 thì không ra kết quả như vậy :(
    Cám ơn anh

  • Tôi dùng gcc chạy trên Debian. Như đã viết, con số 39 bytes tùy vào trình dịch và cấu hình máy, bạn thay nó bằng buffer dài hơn sẽ thấy bị overflow.

RSS cho bình luận bài này

Ghi bình luận của bạn