DANH MỤC TÀI LIỆU
Tràn bộ nhớ đệm và những điều cần biết
Tìm hiểu đầy đủ về tràn bộ đệm
ĐT - Vicki's real fan
Lời mở đầu
Tràn bộ đệm là một trong những lỗ hỏng bảo mật lớn nhất hiện nay. Vậy
tràn bộ đệm là gì? Làm thế nào để thi hành các mã lệnh nguy hiểm qua
tràn bộ đệm...?
***Lưu ý*** một ít kiến thức về Assembly, C, GDB và Linux là điều cần
thiết đối với bạn!
Sơ đồ tổ chức bộ nhớ của một chương trình
/------------------\ địa ch vùng nh cao
| |
| Stack |
| |
|------------------|
| (Initialized) |
| Data |
| (Uninitialized) |
|------------------|
| |
| Text |
| |
\------------------/ địa ch vùng nh thp
Stack và Heap?
Heap là vùng nhớ dùng để cấp phát cho các biến tỉnh hoặc các vùng nhớ
được cấp phát bằng hàm malloc()
Stack là vùng nhớ dùng để lưu các tham số và các biến cục bộ của hàm.
Các biến trên heap được cấp phát từ vùng nhớ thấp đến vùng nhớ cao.
Trên stack thì hoàn toàn ngược lại, các biến được cấp phát từ vùng nhớ
cao đến vùng nhớ thấp.
Stack hoạt động theo nguyên tắc "vào sau ra trước"(Last In First Out -
LIFO). Các giá trị được đẩy vào stack sau cùng sẽ được lấy ra khỏi stack
trước tiên.
PUSH và POP
Stack đổ từ trên xuống duới(từ vùng nhớ cao đến vùng nhớ thấp). Thanh
ghi ESP luôn trỏ đến đỉnh của stack(vùng nhớ có địa chỉ thấp).
đỉnh ca b nh /------------\ đáy của stack
| |
| |
| |
| |
| |
| | <-- ESP
đáy của b nh \------------/ đnh ca stack
* PUSH một value vào stack
đỉnh ca b nh /------------\ đáy của stack
| |
| |
| |
| |
| | <- ESP cũ
|------------|
(2) -> value | <- ESP mới = ESP cũ -
sizeof(value) (1)
đáy của b nh \------------/ đnh ca stack
1/ ESP=ESP-sizeof(value)
2/ value được đẩy vào stack
* POP một value ra khỏi stack
đỉnh ca b nh /------------\ đáy của stack
| |
| |
| |
| |
| | <- ESP mới = ESP cũ +
sizeof(value)(2)
|------------|
(1) <- value | <- ESP cũ
đáy của b nh \------------/ đnh ca stack
1/ value được lấy ra khỏi stack
2/ ESP=ESP+sizeof(value)
Khác nhau giữa các lệnh hợp ngữ AT&T với Intel
Khác với MSDOS và WINDOWS, *NIX dùng các lệnh hợp ngữ AT&T.
Nó hoàn toàn ngược lại với chuẩn của Intel/Microsoft.
Ví dụ:
Intel
AT&T
mov eax, esp
movl %esp, %eax
push 7
push $7
mov [esp+5], eax
movl %eax, 0x5(%esp)
inc ah
incb %ah
push 7
push $7
...
* Ghi chú:
e - Extended 32 bits
% - register
mov %src, %des
movl - move 1 long
movb - move 1 byte
movw - move 1 word
$ - hằng
# - chú thích
...
Cách làm việc của hàm
Thanh ghi EIP luôn trỏ đến địa chỉ của câu lệnh tiếp theo cần thi hành.
Khi gọi hàm, đầu tiên các tham số được push vào stack theo thứ tự ngược
lại. Tiếp theo địa chỉ của câu lệnh được push vào stack. Sau đó, thanh ghi
EBP được push vào stack(dùng để lưu giá trị cũ của EBP).
Khi kết thúc hàm, thanh ghi EBP được pop ra khỏi stack(phục hồi lại giá
trị cũ của EBP). Sau đó địa chỉ trở về(ret address) được pop ra khỏi stack
và lệnh tiếp theo sau lời gọi hàm sẽ được thi hành.
Thanh ghi EBP được dùng để xác định các tham số và các biến cục bộ của
hàm.
Ví dụ:
test.c
-----------------------------------------------------------
-------------------
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
-----------------------------------------------------------
-------------------
Để hiểu được chương trình gọi hàm function() như thế nào, bạn hãy
compile vidu1.c, dùng tham số -S để phát mã assembly:
[đt@localhost ~/vicki]$cc -S -o test.s test.c
Xem file test.s, chúng ta sẽ thấy call function() được chuyển thành:
pushl $3
pushl $2
pushl $1
call function
3 tham số truyền cho function() lần lượt được push vào stack theo thứ tự
ngược lại. Câu lệnh 'call' sẽ push con trỏ lệnh(tức là thanh ghi EIP) vào
stack để lưu địa chỉ trở về.
Các lệnh đầu tiêu trong hàm function() sẽ có dạng như sau:
pushl %ebp
movl %esp,%ebp
subl $20,%esp
Đầu tiên ESP(frame pointer) được push vào stack. Sau đó chương trình
copy ESP vào EBP để tạo một FP pointer mới. Bạn dễ nhận thấy lúc này
ESP và EBP đều đang trỏ đến ô nhớ chứa EBP cũ. Hãy ghi nhớ điều này.
Tiếp theo ESP được trừ đi 20 để dành không gian cho các biến cục bộ của
hàm function()
Vì chương trình 32 bits nên 5 bytes buffer1 sẽ là 8 bytes(2 words) trong bộ
nhớ(do làm tròn đến 4 bytes hay là 32 bits), 10 bytes buffer2 sẽ là 12 bytes
trong bộ nhớ(3 words). Tổng cộng sẽ tốn 8+12=20 bytes cho các biến cục
bộ của function() nên ESP phải bị trừ đi 20! Stack sẽ có dạng như sau:
đáy của
đỉnh ca
b nh
b nh
buffer2 buffer1 sfp ret a b
c
<------ [ ][ ][ ][ ][ ][ ][
]
đỉnh ca 12 bytes 8 bytes 4b 4b
đáy của
stack
stack
Trong hàm function(), nội dung thanh ghi EBP không bị thay đổi.
0xz%ebp dùng để xác định ô nhớ chứa tham số của hàm
0xfffffz%ebp dùng để xác định ô nhớ chứa biến cục bộ của hàm
Khi kết thúc hàm function():
movl %ebp,%esp
popl %ebp
ret
movl %ebp, %esp sẽ copy EBP vào ESP. Vì EBP khi bắt đầu hàm trỏ
đến ô nhớ chứa EBP cũ và EBP không bị thay đổi trong hàm function()
nên sau khi thực hiện lệnh movl, ESP sẽ trỏ đến ô nhớ chứa EBP cũ. popl
%ebp sẽ phục hồi lại giá trị cũ cho EBP đồng thời ESP sẽ bị giảm
4(ESP=ESP-sizeof(EBP cũ)) sau lệnh popl. Như vậy ESP sẽ trỏ đến ô nhớ
chứa địa chỉ trở về(nằm ngay trên ô nhớ chứa EBP cũ). ret sẽ pop địa chỉ
trở về ra khỏi stack, ESP sẽ bị giảm 4 và chương trình tiếp tục thi hành câu
lệnh sau lệnh call function().
Chương trình bị tràn bộ đệm
Ví dụ:
gets.c:
---------------------------------------
int main()
{
char buf[20];
gets(buf);
}
---------------------------------------
[đt@localhost ~/vicki]$ cc gets.c -o gets
/tmp/cc4C6vaT.o: In function `main':
/tmp/cc4C6vaT.o(.text+0xe): the `gets' function is
dangerous and should not be used.
[đt@localhost ~/vicki]$
gets(buf) sẽ nhận input data vào buf. Kích thước của buf chỉ là 20 bytes.
Nếu ta đẩy data có kích thước lớn hơn 20 bytes vào buf, 20 bytes data đầu
tiên sẽ vào mảng buf[20], các bytes data sau sẽ ghi đè lên EBP cũ và tiếp
theo là ret addr. Như vậy chúng ta có thể thay đổi được địa chỉ trở về, điều
này đồng nghĩa với việc chương trình bị tràn bộ đệm.
đỉnh ca b nh +-------------+ đáy của stack
| return addr |
+-------------+
| EBP cũ |
+-------------+
| |
| |
| buf[20] |
| |
| |
đáy ca b nh +-------------+ đỉnh ca stack
Bạn hãy thử:
[đt@localhost ~/vicki]$ perl -e 'print "A" x 24' | ./gets
[đt@localhost ~/vicki]$ gdb gets core
GNU gdb 5.0mdk-11mdk Linux-Mandrake 8.0
Copyright 2001 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public
License, and you are
welcome to change it and/or distribute copies of it under
certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show
warranty" for details.
This GDB was configured as "i386-mandrake-linux"...
Core was generated by `./gets'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0 0x41414141 in ?? ()
(gdb) info all
eax 0xbffffbc4 -1073742908
ecx 0xbffffbc4 -1073742908
edx 0x40105dbc 1074814396
ebx 0x4010748c 1074820236
esp 0xbffffbe0 0xbffffbe0
ebp 0x41414141 0x41414141 // hãy nhìn xem,
chúng ta vừa ghi đè lên ebp
esi 0x4000a610 1073784336
edi 0xbffffc24 -1073742812
eip 0x40031100 0x40031100
eflags 0x10282 66178
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x2b 43
gs 0x2b 43
(gdb) quit
[đt@localhost ~/vicki]$
0x41 chính là "A" ở dạng hex
Bây giờ bạn hãy thử tiếp:
[đt@localhost ~/vicki]$ perl -e 'print "A" x 28' | ./gets
Segmentation fault
[đt@localhost ~/vicki]$ gdb gets core
thông tin tài liệu
Tổng quan về tràn bộ nhớ đệm và một số cách khắc phục
Mở rộng để xem thêm
xem nhiều trong tuần
yêu cầu tài liệu
Giúp bạn tìm tài liệu chưa có

LÝ THUYẾT TOÁN


×