Bài giảng An toàn an ninh thông tin - Chương 10: An toàn vùng nhớ tiến trình - Bùi Trọng Tùng
Tiế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
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++
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 đủ
Tóm tắt nội dung tài liệu: Bài giảng An toàn an ninh thông tin - Chương 10: An toàn vùng nhớ tiến trình - Bùi Trọng Tùng
[4]; int loc2; } 0xffffffff loc2 loc1 ??? ??? arg1 arg2 caller’s data 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 10 5 10 Stack frame void main(){ countUp(3);} void countUp(int n) { if(n > 1) countUp(n-1); printf(“%d\n”, n); } 0xffffffff countUp(1) countUp(2) countUp(3) main() Con trỏ stack 11 11 Stack frame void func(char *arg1, int arg2) { char loc1[4]; int loc2; loc2++; Q: loc2 nằm ở đâu? } A: -8(%ebp) • %ebp: con trỏ frame. • (%ebp): nội dung vùng nhớ trỏ bởi %ebp 0xffffffff loc2 loc1 ??? arg1 arg2 caller’s data ??? %ebp Không thể đoán được ở thời điểm dịch 12 6 12 Stack – Trả về từ hàm int main() { ... func(“Hey”, 10); ... Q: Làm cách nào để khôi } phục %ebp của hàm gọi 0xffffffff loc2 loc1 ??? ??? arg1 arg2 caller’s data %ebp %ebp ? 13 13 Stack – Trả về từ hàm int main() { ... func(“Hey”, 10); ... Q: Làm cách nào để khôi } phục %ebp của hàm gọi %esp ??? arg1 arg2 caller’s data %ebp 14 7 14 Stack – Trả về từ hàm int main() { ... func(“Hey”, 10); ... Q: Làm cách nào để khôi } phục %ebp của hàm gọi %esp 0xffffffff %ebp ??? arg1 arg2 caller’s data %ebp 1. Đưa %ebp vào stack trước biến cục bộ (pushl %ebp) 15 15 Stack – Trả về từ hàm int main() { ... func(“Hey”, 10); ... Q: Làm cách nào để khôi } phục %ebp của hàm gọi 0xffffffff loc2 loc1 %ebp ??? arg1 arg2 caller’s data %ebp 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) 16 8 16 Stack – Trả về từ hàm int main() { ... func(“Hey”, 10); ... Q: Làm cách nào để thực thi } tiếp lệnh sau khi hàm trả về 0xffffffff loc2 loc1 %ebp ??? arg1 arg2 caller’s data %ebp 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) 17 17 Con trỏ lệnh - %eip ... 0x5bf mov %esp,%ebp 0x5be push %ebp ... ... 0x4a7 mov $0x0,%eax 0x4a2 call 0x49b movl $0x804..,(%esp) 0x493 movl $0xa,0x4(%esp) %eip ... Text 18 9 18 Stack – Trả về từ hàm int main() { ... func(“Hey”, 10); ... Q: Làm cách nào để khôi } phục %ebp của hàm gọi 0xffffffff loc2 loc1 %ebp %eip arg1 arg2 caller’s data %ebp Đưa %eip của lệnh tiếp theo vào stack trước khi gọi hàm 19 19 Stack – Trả về từ hàm int main() { ... func(“Hey”, 10); ... Q: Làm cách nào để khôi } phục %ebp của hàm gọi 0xffffffff loc2 loc1 %ebp %eip arg1 arg2 caller’s data Thiết lập %eip bằng %ebp Đưa %eip của 4(%ebp) khi trả về lệnh tiếp theo vào stack trước khi gọi hàm 20 10 20 Stack – Trả về từ hàm Trong C Mã assembly sau khi dịch return; leave: mov %ebp %esp pop %ebp ret: pop %eip Caller’s Callee’s Caller’s code stack frame stack frame text loc2 loc1 %ebp %eip arg1 arg2 %eip %esp %ebp Con trỏ frame cũ 21 21 Stack – Trả về từ hàm Trong C Mã assembly sau khi dịch return; leave: mov %ebp %esp pop %ebp ret: pop %eip Caller’s Callee’s Caller’s code stack frame stack frame text loc2 loc1 %ebp %eip arg1 arg2 %eip %ebp Con trỏ frame cũ %esp 22 11 22 Stack – Trả về từ hàm Trong C Mã assembly sau khi dịch return; leave: mov %ebp %esp pop %ebp ret: pop %eip Caller’s Callee’s Caller’s code stack frame stack frame text loc2 loc1 %ebp %eip arg1 arg2 %eip %esp %ebp 23 23 Stack – Trả về từ hàm Trong C Mã assembly sau khi dịch return; leave: mov %ebp %esp pop %ebp ret: pop %eip Caller’s Callee’s Caller’s code stack frame stack frame text loc2 loc1 %ebp %eip arg1 arg2 %eip %esp %ebp 24 12 24 Stack – Trả về từ hàm Trong C Mã assembly sau khi dịch return; leave: mov %ebp %esp pop %ebp ret: pop %eip Caller’s Callee’s Caller’s code stack frame stack frame text loc2 loc1 %ebp %eip arg1 arg2 %eip %ebp %esp Các lệnh tiếp theo xóa tham số khỏi stack 25 25 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 13 26 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 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 14 28 C/C++ vẫn rất phổ biến(2020) 29 29 Sự phổ biến của lỗ hổng BoF Sự phổ biến của lỗ hổng Buffer Overflow 1000 7 910 880 900 6.21 841 6 800 5.33 704 5.25 700 5 4.58 600 4.07 4 500 3 Số lỗ Số lỗ hổng 400 287 300 2 200 1 100 0 0 2017 2018 2019 2020 2021 Số lỗ hổng Tỉ lệ (%) 30 15 30 Ví dụ về tràn bộ đệm void func(char *arg1) { char buffer[4]; strcpy(buffer, arg1); return; } int main() { char *mystr = “AuthMe!”; func(mystr); ... } 00 00 00 00 %ebp %eip &arg1 buffer 31 31 Ví dụ về tràn bộ đệm void func(char *arg1) { char buffer[4]; strcpy(buffer, arg1); return; } int main() { char *mystr = “AuthMe!”; func(mystr); ... } M e ! \0 A u t h 4d 65 21 00 %eip &arg1 buffer 32 16 32 Ví dụ về tràn bộ đệm void func(char *arg1) { char buffer[4]; strcpy(buffer, arg1); return; pop %ebp %ebp = 0x0021654d } SEGMENTATION FAULT int main() { char *mystr = “AuthMe!”; func(mystr); ... } M e ! \0 A u t h 4d 65 21 00 %eip &arg1 buffer 33 33 Tràn bộ đệm – Ví dụ khác void func(char *arg1) { int authenticated = 0 char buffer[4]; strcpy(buffer, arg1); if(authenticated){//privileged execution} } int main() { Hàm được thực char *mystr = “AuthMe!”; func(mystr); thi như thế nào? ... } M e ! \0 A u t h 4d 65 21 00 %ebp %eip &arg1 buffer authenticated 34 17 34 Tràn bộ đệm – Ví dụ khác 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 35 35 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 %eipX %eip text 00 00 00 00 %ebp %eip &arg1 Malcode buffer 36 18 36 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 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 19 38 Sử dụng giá trị canh giữ - Ví dụ callee() static int random; { caller() int canary = random; { char buffer[]; random = rand(); ... callee(); if(canary!=random) } //detect attack else return; } 00 00 00 00 4d 65 21 00 %eip &arg1 buffer canary Buffer4d 65 Overflow 21 00 attack %eip &arg1 buffer canary 39 39 Buffer Overflow – Phòng chống • Address Space Layout Randomization 0xffffffff Kernel 0xc0000000 Thiết lập khi tiến trình cmdline & env bắt đầu Stack Thay đổi khi thực Nạp vào với địa chỉ thi bắt đầu của mỗi Heap vùng là ngẫu nhiên Xác định ở thời BSS điểm biên dịch Data Text Không gian địa chỉ 0x08048000 của thiết bị vào-ra Unused 0x00000000 40 20 40 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 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ụ void func() { char buf[32]; if(fgets(buf, sizeof(buf),stdin) == NULL) return; printf(buf); } %ebp %eip &fmt printf’s stack frame Caller’s stack frame 42 21 42 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 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 22 44 Lỗ hổng tràn số nguyên – Ví dụ 1 • Lỗ hổng nằm ở đâu? #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); } 45 45 Lỗ hổng tràn số nguyên – Ví dụ 2 • Lỗ hổng nằm ở đâu? 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; } 46 23 46 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 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 24 48 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 49 An toàn không gian – Ví dụ 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 • 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 25 50 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 int n; printf("%d", n); // Fail int *p; *p = 0; // Fail p = (int *) malloc(sizeof(int)); *P = 0; // OK free(p); *p = 10; // Fail 51 51 Đ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ớ void displayArr(int a[], size_t n) { for(size_t i = 0; i < n, i++) printf(“%d”, a[i]); } 52 26 52 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 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 27 54 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 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ớ 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; 56 28 56 Cẩn trọng khi sử dụng lệnh goto • Ví dụ: 57 57 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 29 58
File đính kèm:
- bai_giang_an_toan_an_ninh_thong_tin_chuong_10_an_toan_vung_n.pdf