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
58File đính kèm:
bai_giang_an_toan_an_ninh_thong_tin_chuong_10_an_toan_vung_n.pdf

