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

BÀ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
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
2020 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
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
2
4
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  
5
5
Bộ nhớ của tiến trình(Linux 32-bit)  
0xffffffff  
Tiến trình coi bộ  
nhớ thuộc toàn bộ  
sở hữu của nó  
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ý  
0x00000000  
6
3
6
Bộ nhớ của tiến trình(Linux 32-bit)  
0xffffffff  
0xc0000000  
Kernel  
Thiết lập khi tiến  
cmdline & env  
trình bắt đầu  
Stack  
Thay đổi khi  
thực thi  
Heap  
BSS  
Xác định ở thời  
Data  
điểm biên dịch  
Text  
0x08048000  
0x00000000  
Không gian địa chỉ  
của thiết bị vào-ra  
Unused  
7
7
Vùng nhớ stack và heap  
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  
0xffffffff  
0x00000000  
3
Heap  
2
1
Stack  
Con trỏ push 1  
Được quản lý trong tiến  
trình bởi các hàm cấp  
phát bộ nhớ động  
(malloc, calloc)  
stack  
push 2  
push 3  
return  
8
4
8
Stack - Thực hiện lời gọi hàm  
void func(char *arg1, int arg2)  
{
char loc1[4];  
int loc2;  
}
8 byte giữa các  
tham số và các biến  
0xffffffff  
loc1  
???  
arg1  
loc2  
???  
arg2 caller’s data  
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ự  
9
9
Stack frame  
void func(char *arg1, int arg2)  
{
char loc1[4];  
int loc2;  
}
0xffffffff  
loc1  
???  
arg1  
loc2  
???  
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  
loc1  
arg1  
loc2  
???  
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  
loc1  
???  
arg1  
loc2  
???  
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  
%ebp  
arg1  
loc2  
???  
arg2  
caller’s data  
loc1  
%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  
%ebp  
arg1  
loc2  
???  
arg2  
caller’s data  
loc1  
%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 <func>  
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  
%ebp  
arg1  
loc2  
%eip  
arg2  
caller’s data  
loc1  
%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  
%ebp  
arg1  
loc2  
%eip  
arg2  
caller’s data  
loc1  
%ebp  
Đưa %eip của  
lệnh tiếp theo  
vào stack trước  
khi gọi hàm  
Thiết lập %eip bằng  
4(%ebp) khi trả về  
20  
10  
20  
Stack – Trả về từ hàm  
Mã assembly sau khi dịch  
Trong C  
return;  
leave:  
ret:  
mov %ebp %esp  
pop %ebp  
pop %eip  
Caller’s  
Caller’s  
stack frame  
Callee’s  
stack frame  
code  
text  
%ebp  
arg1  
%eip  
arg2  
loc2  
loc1  
%esp  
%ebp  
%eip  
Con trỏ frame cũ  
21  
21  
Stack – Trả về từ hàm  
Mã assembly sau khi dịch  
Trong C  
return;  
leave:  
ret:  
mov %ebp %esp  
pop %ebp  
pop %eip  
Caller’s  
Caller’s  
stack frame  
Callee’s  
stack frame  
code  
text  
%ebp  
arg1  
%eip  
arg2  
loc2  
loc1  
%ebp  
%esp  
%eip  
Con trỏ frame cũ  
22  
11  
22  
Stack – Trả về từ hàm  
Mã assembly sau khi dịch  
Trong C  
return;  
leave:  
ret:  
mov %ebp %esp  
pop %ebp  
pop %eip  
Caller’s  
Caller’s  
stack frame  
Callee’s  
stack frame  
code  
text  
%ebp  
arg1  
%eip  
arg2  
loc2  
loc1  
%ebp  
%eip  
%esp  
23  
23  
Stack – Trả về từ hàm  
Mã assembly sau khi dịch  
Trong C  
return;  
leave:  
ret:  
mov %ebp %esp  
pop %ebp  
pop %eip  
Caller’s  
Caller’s  
stack frame  
Callee’s  
stack frame  
code  
text  
%ebp  
arg1  
%eip  
arg2  
loc2  
loc1  
%ebp  
%eip  
%esp  
24  
12  
24  
Stack – Trả về từ hàm  
Mã assembly sau khi dịch  
Trong C  
return;  
leave:  
ret:  
mov %ebp %esp  
pop %ebp  
pop %eip  
Caller’s  
Caller’s  
stack frame  
Callee’s  
stack frame  
code  
text  
%ebp  
arg1  
%eip  
arg2  
loc2  
loc1  
%ebp  
%eip  
%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  
910  
7
6
5
4
3
2
1
0
880  
900  
6.21  
841  
800  
700  
600  
500  
400  
300  
200  
100  
0
5.33  
5.25  
704  
4.58  
4.07  
287  
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);  
...  
}
&arg1  
%eip  
00 00 00 00  
buffer  
%ebp  
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  
&arg1  
%eip  
A
u
t
h
4d 65 21 00  
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  
&arg1  
%eip  
A
u
t
h
4d 65 21 00  
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  
thi như thế nào?  
char *mystr = “AuthMe!”;  
func(mystr);  
...  
}
M
e
! \0  
%ebp  
&arg1  
%eip  
A
u
t
h
4d 65 21 00  
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  
%eip  
%eip  
X
Malcode  
%eip  
text  
00 00 00 00  
buffer  
%ebp  
&arg1  
…
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[];  
...  
if(canary!=random)  
//detect attack  
else return;  
random = rand();  
callee();  
}
}
&arg1  
arg1  
%eip  
00 00 00 00 4d 65 21 00  
buffer  
canary  
Buffer Overflow attack  
canary  
buffer  
39  
39  
Buffer Overflow – Phòng chống  
• Address Space Layout Randomization  
0xffffffff  
0xc0000000  
Kernel  
cmdline & env  
Thiết lập khi tiến trình  
bắt đầu  
Stack  
Thay đổi khi thực  
thi  
Nạp vào với địa chỉ  
bắt đầu của mỗi  
Heap  
vùng là ngẫu nhiên  
BSS  
Data  
Xác định ở thời  
điểm biên dịch  
Text  
Không gian địa chỉ  
của thiết bị vào-ra  
0x08048000  
Unused  
40  
0x00000000  
20  
40  
Tải về để xem bản đầy đủ
pdf 29 trang baolam 13680 Free
Bạn đang xem 20 trang mẫu của 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", để tải tài liệu gốc về máy hãy click vào nút Download ở trên

File đính kèm:

  • pdfbai_giang_an_toan_an_ninh_thong_tin_chuong_10_an_toan_vung_n.pdf