An ninh mạng máy tính - Bài 10: An toàn vùng nhớ tiến trình
• Lỗ hổng tràn bộ đệm (Buffer Overflow)
• Lỗ hổng tràn số nguyên
• Lỗ hổng xâu định dạng
• Cơ bản về lập trình an toàn
Trang 1
Trang 2
Trang 3
Trang 4
Trang 5
Trang 6
Trang 7
Trang 8
Trang 9
Trang 10
Tải về để xem bản đầy đủ
Bạn đang xem 10 trang mẫu của tài liệu "An ninh mạng máy tính - Bài 10: An toàn vùng nhớ tiến trình", để tải tài liệu gốc về máy hãy click vào nút Download ở trên
Tóm tắt nội dung tài liệu: An ninh mạng máy tính - Bài 10: An toàn vùng nhớ tiến trình
1BÀI 10. AN TOÀN VÙNG NHỚ TIẾN TRÌNH Bùi Trọng Tùng, Viện Công nghệ thông tin và Truyền thông, Đại học Bách khoa Hà Nội 1 Nội dung • Lỗ hổng tràn bộ đệm (Buffer Overflow) • Lỗ hổng tràn số nguyên • Lỗ hổng xâu định dạng • Cơ bản về lập trình an toàn 2 1 2 22020 CWE Top 25 • Danh sách 25 lỗ hổng phần mềm nguy hiểm nhất: 4 trong số Top 10 là dạng lỗ hổng truy cập bộ nhớ +1 lỗ hổng liên quan: CWE-20 3 1. TỔNG QUAN VỀ TIẾN TRÌNH (NHẮC LẠI) Bùi Trọng Tùng, Viện Công nghệ thông tin và Truyền thông, Đại học Bách khoa Hà Nội 4 3 4 3Tiến trình là gì? • Là chương trình đang được thực hiện • Các tài nguyên tối thiểu của tiến trình: Vùng nhớ được cấp phát Con trỏ lệnh(Program Counter) Các thanh ghi của CPU • Khối điều khiển tiến trình(Process Control Block-PCB): Cấu trúc chứa thông tin của tiến trình 5 Bộ nhớ của tiến trình(Linux 32-bit) 6 0xffffffff 0x00000000 Thực tế đây là bộ nhớ ảo với địa chỉ ảo, sẽ được HĐH/CPU ánh xạ sang địa chỉ vật lý Tiến trình coi bộ nhớ thuộc toàn bộ sở hữu của nó 5 6 4Bộ nhớ của tiến trình(Linux 32-bit) 7 0xffffffff 0x00000000 Unused 0x08048000 Kernel 0xc0000000 Text Data BSS Heap Stack Xác định ở thời điểm biên dịch Thay đổi khi thực thi Thiết lập khi tiến trình bắt đầu Không gian địa chỉ của thiết bị vào-ra cmdline & env Vùng nhớ stack và heap 8 0xffffffff0x00000000 Heap Stack Trình biên dịch cung cấp các hàm làm thay đổi kích thước vùng nhớ stack khi thực thi chương trình push 1 push 2 push 3 return 123 Con trỏ stackĐược quản lý trong tiến trình bởi các hàm cấp phát bộ nhớ động (malloc, calloc) 7 8 5Stack - Thực hiện lời gọi hàm 9 caller’s dataarg2arg1 void func(char *arg1, int arg2) { char loc1[4]; int loc2; } loc1loc2 Các tham số đưa vào stack theo thứ tự ngược Các cục bộ được đưa vào stack theo thứ tự 0xffffffff 8 byte giữa các tham số và các biến ??? ??? Stack frame 10 caller’s dataarg2arg1 void func(char *arg1, int arg2) { char loc1[4]; int loc2; } loc1loc2 0xffffffff Stack frame: Một phần của vùng nhớ stack tương ứng với lời gọi của một hàm ??? ??? 9 10 6Stack frame 11 main()countUp(3) void main(){ countUp(3);} void countUp(int n) { if(n > 1) countUp(n-1); printf(“%d\n”, n); } 0xffffffff countUp(2)countUp(1) Con trỏ stack Stack frame 12 caller’s dataarg2arg1 void func(char *arg1, int arg2) { char loc1[4]; int loc2; loc2++; } loc1loc2 0xffffffff ??? Không thể đoán được ở thời điểm dịch ??? %ebp Q: loc2 nằm ở đâu? A: -8(%ebp) • %ebp: con trỏ frame. • (%ebp): nội dung vùng nhớ trỏ bởi %ebp 11 12 7Stack – Trả về từ hàm 13 int main() { ... func(“Hey”, 10); ... } caller’s dataarg2arg1loc1loc2 0xffffffff ??? ??? %ebp %ebp Q: Làm cách nào để khôi phục %ebp của hàm gọi ? Stack – Trả về từ hàm 14 int main() { ... func(“Hey”, 10); ... } Q: Làm cách nào để khôi phục %ebp của hàm gọi caller’s dataarg2arg1??? %ebp %esp 13 14 8Stack – Trả về từ hàm 15 int main() { ... func(“Hey”, 10); ... } caller’s dataarg2arg1 0xffffffff %ebp ??? %ebp Q: Làm cách nào để khôi phục %ebp của hàm gọi 1. Đưa %ebp vào stack trước biến cục bộ (pushl %ebp) %esp Stack – Trả về từ hàm 16 int main() { ... func(“Hey”, 10); ... } caller’s dataarg2arg1 0xffffffff %ebp ??? %ebp Q: Làm cách nào để khôi phục %ebp của hàm gọi 1. Đưa %ebp vào stack trước biến cục bộ (pushl %ebp) 2. Thiết lập %ebp bằng với %esp (movl %esp %ebp) loc1loc2 15 16 9Stack – Trả về từ hàm 17 int main() { ... func(“Hey”, 10); ... } caller’s dataarg2arg1 0xffffffff %ebp ??? Q: Làm cách nào để thực thi tiếp lệnh sau khi hàm trả về 1. Đưa %ebp vào stack trước biến cục bộ (pushl %ebp) 2. Thiết lập %ebp bằng với %esp (movl %esp %ebp) 3. Khi hàm trả về, thiết lập %ebp bằng (%ebp) (movl (%ebp) %ebp) loc1loc2 %ebp Con trỏ lệnh - %eip 18 Text ... 0x4a7 mov $0x0,%eax 0x4a2 call 0x49b movl $0x804..,(%esp) 0x493 movl $0xa,0x4(%esp) ... ... 0x5bf mov %esp,%ebp 0x5be push %ebp ... %eip 17 18 10 Stack – Trả về từ hàm 19 int main() { ... func(“Hey”, 10); ... } caller’s dataarg2arg1 0xffffffff %ebp %eip %ebp Q: Làm cách nào để khôi phục %ebp của hàm gọi Đưa %eip của lệnh tiếp theo vào stack trước khi gọi hàm loc1loc2 Stack – Trả về từ hàm 20 int main() { ... func(“Hey”, 10); ... } caller’s dataarg2arg1 0xffffffff %ebp %eip %ebp Q: Làm cách nào để khôi phục %ebp của hàm gọi Đưa %eip của lệnh tiếp theo vào stack trước khi gọi hàm loc1loc2 Thiết lập %eip bằng 4(%ebp) khi trả về 19 20 11 Stack – Trả về từ hàm 21 return; arg2arg1%ebp %eip %ebp Trong C Con trỏ frame cũ loc1text leave: mov %ebp %esp pop %ebp ret: pop %eip Mã assembly sau khi dịch Caller’s stack frame %esp Caller’s code loc2 %eip Callee’s stack frame Stack – Trả về từ hàm 22 return; arg2arg1%ebp %eip %ebp Trong C Con trỏ frame cũ loc1text leave: mov %ebp %esp pop %ebp ret: pop %eip Mã assembly sau khi dịch Caller’s stack frame %esp Caller’s code loc2 %eip Callee’s stack frame 21 22 12 Stack – Trả về từ hàm 23 return; arg2arg1%ebp %eip %ebp Trong C loc1text leave: mov %ebp %esp pop %ebp ret: pop %eip Mã assembly sau khi dịch Caller’s stack frame %esp Caller’s code loc2 %eip Callee’s stack frame Stack – Trả về từ hàm 24 return; arg2arg1%ebp %eip %ebp Trong C loc1text leave: mov %ebp %esp pop %ebp ret: pop %eip Mã assembly sau khi dịch Caller’s stack frame %esp Caller’s code loc2 %eip Callee’s stack frame 23 24 13 Stack – Trả về từ hàm 25 return; arg2arg1%ebp %eip %ebp Trong C loc1text leave: mov %ebp %esp pop %ebp ret: pop %eip Mã assembly sau khi dịch Caller’s stack frame %esp Caller’s code loc2 %eip Callee’s stack frame Các lệnh tiếp theo xóa tham số khỏi stack Tổng kết Hàm gọi(trước khi gọi): 1. Đẩy các tham số vào stack theo thứ tự ngược 2. Đẩy địa chỉ trả về vào stack, ví dụ %eip + 2 3. Nhảy tới địa chỉ của hàm được gọi Hàm được gọi: 4. Đẩy %ebp cũ vào stack 5. Thiết lập %ebp tới đỉnh của stack 6. Đẩy các biến cục bộ vào stack truy cập theo độ lệch từ %ebp Hàm được gọi trả về: 7. Thiết lập lại %ebp cũ 8. Nhảy tới địa chỉ trả về Hàm gọi: 9. Xóa các tham số khỏi stack 26 25 26 14 2. TẤN CÔNG TRÀN BỘ ĐỆM Bùi Trọng Tùng, Viện Công nghệ thông tin và Truyền thông, Đại học Bách khoa Hà Nội 27 Khái niệm • Bộ đệm (Buffer): tập hợp liên tiếp các phần tử có kiểu dữ liệu xác định Ví dụ: Trong ngôn ngữ C/C++, xâu là bộ đệm của các ký tự Có thể hiểu theo nghĩa rộng: bộ đệm = vùng nhớ chứa dữ liệu • Tràn bộ đệm (Buffer Overflow): Đưa dữ liệu vào bộ đệm nhiều hơn khả năng chứa của nó • Lỗ hổng tràn bộ đệm: Không kiểm soát kích thước dữ liệu đầu vào. • Tấn công tràn bộ đệm: Phần dữ liệu tràn ra khỏi bộ đệm làm thay đổi luồng thực thi của tiến trình. Dẫn tới một kết quả ngoài mong đợi • Ngôn ngữ bị ảnh hưởng: C/C++ 28 27 28 15 C/C++ vẫn rất phổ biến(2020) 29 Sự phổ biến của lỗ hổng BoF 910 880 704 841 287 6.21 5.33 4.07 4.58 5.25 0 1 2 3 4 5 6 7 0 100 200 300 400 500 600 700 800 900 1000 2017 2018 2019 2020 2021 S ố lỗ h ổ n g Sự phổ biến của lỗ hổng Buffer Overflow Số lỗ hổng Tỉ lệ (%) 30 29 30 16 Ví dụ về tràn bộ đệm 31 void func(char *arg1) { char buffer[4]; strcpy(buffer, arg1); return; } int main() { char *mystr = “AuthMe!”; func(mystr); ... } &arg100 00 00 00 buffer %ebp %eip Ví dụ về tràn bộ đệm 32 void func(char *arg1) { char buffer[4]; strcpy(buffer, arg1); return; } int main() { char *mystr = “AuthMe!”; func(mystr); ... } &arg1A u t h buffer 4d 65 21 00 %eip M e ! \0 31 32 17 Ví dụ về tràn bộ đệm 33 void func(char *arg1) { char buffer[4]; strcpy(buffer, arg1); return; } int main() { char *mystr = “AuthMe!”; func(mystr); ... } &arg1A u t h buffer 4d 65 21 00 %eip M e ! \0 pop %ebp %ebp = 0x0021654d SEGMENTATION FAULT Tràn bộ đệm – Ví dụ khác 34 void func(char *arg1) { int authenticated = 0 char buffer[4]; strcpy(buffer, arg1); if(authenticated){//privileged execution} } int main() { char *mystr = “AuthMe!”; func(mystr); ... } Hàm được thực thi như thế nào? &arg1A u t h buffer 4d 65 21 00 %eip M e ! \0 authenticated %ebp 33 34 18 Tràn bộ đệm – Ví dụ khác 35 void func(char *arg1) { int authenticated = 0 char buffer[4]; strcpy(buffer, arg1); if(authenticated){//privileged execution} } int main() { char *mystr = “AuthMe!”; func(mystr); ... } Người dùng có thể ghi đè dữ liệu tùy ý tới các vùng nhớ khác Khai thác lỗ hổng tràn bộ đệm • Lỗ hổng tràn bộ đệm cho phép kẻ tấn công truy cập (read/write/execute) tùy ý vào vùng nhớ khác • Phương thức khai thác phổ biến nhất: chèn mã nguồn thực thi (code injection) • Ý tưởng 36 Malcode00 00 00 00 buffer %ebp %eip &arg1 text %eip %eipX 35 36 19 Code Injection • Vấn đề 1: Nạp mã độc(malcode) vào stack Phải là mã máy Không chứa byte có giá trị 0 Không sử dụng bộ nạp (loader) Không sử dụng vùng nhớ stack • Vấn đề 2: Nạp đúng các địa chỉ lệnh thực thi sau khi kết thúc lời gọi hàm Xác định đúng %eip Mức độ khó khi xác định giá trị %eip phụ thuộc vị trí của malcode • Vấn đề 3: Nạp đúng địa chỉ trả về Xác định đúng %ebp 37 Buffer Overflow – Phòng chống • Secure Coding: sử dụng các hàm an toàn có kiểm soát kích thước dữ liệu đầu vào. fgets(), strlcpy(), strlcat() • Stack Shield: Lưu trữ địa chỉ trả về vào vùng nhớ bảo vệ không thể bị ghi đè Sao chép địa chỉ trả về từ vùng nhớ bảo vệ • Stack Guard: sử dụng các giá trị canh giữ (canary) để phát hiện mã nguồn bị chèn • Non-executable stack: Không cho phép thực thi mã nguồn trong stack Linux: sysctl -w kernel.exec-shield=0 Vẫn bị khai thác bởi kỹ thuật return-to-libc 38 37 38 20 Sử dụng giá trị canh giữ - Ví dụ 39 callee() { int canary = random; char buffer[]; ... if(canary!=random) //detect attack else return; } static int random; caller() { random = rand(); callee(); } &arg100 00 00 00 buffer 4d 65 21 00 %eip canary &arg1 buffer 4d 65 21 00 %eip canary Buffer Overflow attack Buffer Overflow – Phòng chống • Address Space Layout Randomization 40 0xffffffff 0x00000000 0x08048000 0xc0000000 Xác định ở thời điểm biên dịch Thay đổi khi thực thi Thiết lập khi tiến trình bắt đầu Không gian địa chỉ của thiết bị vào-ra Unused Kernel Text Data BSS Heap Stack cmdline & env Nạp vào với địa chỉ bắt đầu của mỗi vùng là ngẫu nhiên 39 40 21 3. MỘT SỐ LỖ HỔNG TRUY CẬP BỘ NHỚ KHÁC Bùi Trọng Tùng, Viện Công nghệ thông tin và Truyền thông, Đại học Bách khoa Hà Nội 41 Lỗ hổng xâu định dạng • Format String: Xâu định dạng vào ra dữ liệu • Lỗ hổng Format String: xâu định dạng không phù hợp với danh sách tham số • Ví dụ 42 void func() { char buf[32]; if(fgets(buf, sizeof(buf),stdin) == NULL) return; printf(buf); } &fmt%ebp %eip printf’s stack frame Caller’s stack frame 41 42 22 Lỗ hổng xâu định dạng • printf(“%d”); Hiển thị 4 byte phía trước địa chỉ đầu tiên của stack frame của hàm printf • printf(“%s”); Hiển thị các byte cho tới khi gặp ký tự kết thúc xâu • printf(“%d%d%d”) Hiển thị chuỗi byte dưới dạng số nguyên • printf(“%x%x%x”) Hiển thị chuỗi byte dưới dạng hexa • printf(“%n”): Ghi số byte đã hiển thị vào vùng nhớ 43 Lỗ hổng tràn số nguyên • Trong máy tính, số nguyên được biểu diễn bằng trục số tròn. Dải biểu diễn: Số nguyên có dấu: [–2n – 1, 2n–1 – 1] Số nguyên không dấu: [0, 2n – 1] • Integer Overflow: Biến số nguyên của chương trình nhận một giá trị nằm ngoài dải biểu diễn. Ví dụ Số nguyên có dấu: 0x7ff..f + 1 = 0x80..0, 0xff..f + 1 = 0x0 Số nguyên không dấu: 0xff..f + 1 = 0x0, 0x0 – 1 = 0xff...f • Ngôn ngữ bị ảnh hưởng: Tất cả • Việc không kiểm soát hiện tượng tràn số nguyên có thể dẫn đến các truy cập các vùng nhớ mà không thể kiểm soát. 44 43 44 23 Lỗ hổng tràn số nguyên – Ví dụ 1 • Lỗ hổng nằm ở đâu? 45 #define MAX 1024 void vul_func1() { char buff[1024]; int len = recv_len_from_client(); char *mess = recv_mess_from_client(); if (len > 1024) printf (“Too large”); else memcpy(buf, mess, len); } Lỗ hổng tràn số nguyên – Ví dụ 2 46 int main() { int *arr; int len; printf(“Number of items: ”); scanf(“%d”, &len); arr = malloc(len * sizeof(int)); for(int i = 0; i < len; i++) scanf(“%d”, arr[i]); return 0; } • Lỗ hổng nằm ở đâu? 45 46 24 4. LẬP TRÌNH AN TOÀN Bùi Trọng Tùng, Viện Công nghệ thông tin và Truyền thông, Đại học Bách khoa Hà Nội 47 Lập trình an toàn • Yêu cầu: Viết mã nguồn chương trình để đạt được các mục tiêu an toàn bảo mật • Bao gồm nhiều kỹ thuật khác nhau: Kiểm soát giá trị đầu vào Kiểm soát truy cập bộ nhớ chính Che giấu mã nguồn Chống dịch ngược Kiểm soát kết quả đầu ra Kiểm soát quyền truy cập • Bài này chỉ đề cập đến một số quy tắc và nhấn mạnh vào vấn đề truy cập bộ nhớ một cách an toàn 48 47 48 25 An toàn truy cập bộ nhớ • An toàn không gian(Spatial safety): thao tác chỉ nên truy cập vào đúng vùng nhớ đã xác định • Nếu gọi: b: địa chỉ ô nhớ đầu tiên của vùng nhớ được chỉ ra p: địa chỉ cần truy cập tới e: địa chỉ ô nhớ cuối cùng của vùng nhớ được chỉ ra s: kích thước vùng nhớ cần truy cập • Thao tác truy cập bộ nhớ chỉ an toàn khi và chỉ khi: b ≤ p ≤ e – s • Lưu ý: Các toán tử tác động trên p không làm thay đổi b và e. 49 An toàn không gian – Ví dụ • Lỗi truy cập không an toàn về không gian gây ra các lỗ hổng như đã biết 50 int x = 0; int *y = &x; // b = &x, e = &x + 4, s = 4 int *z = y + 1; // b = &x, e = &x + 4, s = 4 *y = 10; //OK: &x ≤ p = &x ≤ (&x + 4) - 4 *z = 10; //Fail: &x ≤ p = &x + 4 ≤ (&x + 4) - 4 char str[10]; //b = &str, e = &str + 10 str[5] = 'A'; //OK: &str ≤ p = &str + 5 ≤ (&str + 10) - 1 str[10] = 'F'; //Fail: &str ≤ p = &str + 10 ≤ (&str + 10) - 1/ / 49 50 26 An toàn truy cập bộ nhớ • An toàn thời gian(): thao tác chỉ truy cập vào vùng nhớ mà đã được khởi tạo: Đã cấp phát bộ nhớ Đã được khởi tạo giá trị • Ví dụ: Vi phạm an toàn về thời gian 51 int n; printf("%d", n); // Fail int *p; *p = 0; // Fail p = (int *) malloc(sizeof(int)); *P = 0; // OK free(p); *p = 10; // Fail Điều kiện truy cập bộ nhớ • Tiền điều kiện(precondition): điều kiện để câu lệnh/hàm được thực thi đúng đắn • Hậu điều kiện(postcondition): khẳng định trạng thái đúng đắn của các đối tượng khi lệnh/hàm kết thúc • Ví dụ: Xác định các điều kiện truy cập bộ nhớ 52 void displayArr(int a[], size_t n) { for(size_t i = 0; i < n, i++) printf(“%d”, a[i]); } 51 52 27 Các nguyên tắc lập trình an toàn • Không tin cậy những thứ mà không do bạn tạo ra • Người dùng chỉ là những kẻ ngốc nghếch Hàm gọi (Caller) = Người dùng • Hạn chế cho kẻ khác tiếp cận những gì quan trọng. Ví dụ: thành phần bên trong của một cấu trúc/đối tượng Ngôn ngữ OOP: nguyên lý đóng gói Ngôn ngữ non-OOP: sử dụng token • Không bao giờ nói “không bao giờ” • Sau đây sẽ đề cập đến một số quy tắc trong C/C++ • Về chủ đề lập trình an toàn, tham khảo tại đây: https://security.berkeley.edu/secure-coding-practice- guidelines 53 Kiểm tra mọi dữ liệu đầu vào • Các giá trị do người dùng nhập • File được mở • Các gói tin nhận được từ mạng • Các dữ liệu thu nhận từ thiết bị cảm biến (Ví dụ: QR code, âm thanh, hình ảnh,) • Thư viện của bên thứ 3 • Mã nguồn được cập nhật • Khác 54 53 54 28 Sử dụng các hàm xử lý xâu an toàn • Sử dụng các hàm xử lý xâu an toàn thay cho các hàm thông dụng strcat, strncat strlcat strcpy, strncpy strlcpy gets fgets, fprintf • Luôn đảm bảo xâu được kết thúc bằng ‘\0’ • Nếu có thể, hãy sử dụng các thư viện an toàn hơn Ví dụ: std::string trong C++ 55 Sử dụng con trỏ một cách an toàn • Hiểu biết về các toán tử con trỏ: +, -, sizeof • Cần xóa con trỏ về NULL sau khi giải phóng bộ nhớ 56 int x = 5; int *p = (int *)malloc(sizeof(int)); free(p); p = NULL; int **q = (int **)malloc(sizeof(int*)); *q = &x; *p = 5; //Crash OK **q = 3; 55 56 29 Cẩn trọng khi sử dụng lệnh goto 57 • Ví dụ: Sử dụng các thư viện an toàn hơn • Nên sử dụng chuẩn C/C++11 thay cho các chuẩn cũ • Sử dụng std::string trong C++ để xử lý xâu • Truyền dữ liệu: sử dụng Goolge Protocol Buffers hoặc Apache Thrift 58 57 58
File đính kèm:
- an_ninh_mang_may_tinh_bai_10_an_toan_vung_nho_tien_trinh.pdf