Tải bản đầy đủ (.doc) (59 trang)

KỸ THUẬT KHAI THÁC lỗi TRÀN TRONG bộ đệm

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (185.21 KB, 59 trang )

KỸ THUẬT KHAI THÁC LỖI TRÀN BỘ ĐỆM
trang này đã được đọc lần
Tóm tắt :
Loạt bài viết này trình bày về tràn bộ đệm (buffer overflow)
xảy ra trên stack và kỹ thuật khai thác lỗi bảo mật phổ biến
nhất này. Kỹ thuật khai thác lỗi tràn bộ đệm (buffer overflow
exploit) được xem là một trong những kỹ thuật hacking kinh
điển nhất. Bài viết được chia làm 2 phần:
Phần 1: Tổ chức bộ nhớ, stack, gọi hàm, shellcode. Giới
thiệu tổ chức bộ nhớ của một tiến trình (process), các thao
tác trên bộ nhớ stack khi gọi hàm và kỹ thuật cơ bản để tạo
shellcode - đoạn mã thực thi một giao tiếp dòng lệnh (shell).
Phần 2: Kỹ thuật khai thác lỗi tràn bộ đệm. Giới thiệu kỹ
thuật tràn bộ đệm cơ bản, tổ chức shellcode, xác định địa
chỉ trả về, địa chỉ shellcode, cách truyền shellcode cho
chương trình bị lỗi.
Các chi tiết kỹ thuật minh hoạ ở đây được thực hiện trên
môi trường Linux x86 (kernel 2.2.20, glibc-2.1.3), tuy nhiên
về mặt lý thuyết có thể áp dụng cho bất kỳ môi trường nào
khác. Người đọc cần có kiến thức cơ bản về lập trình C, hợp
ngữ (assembly), trình biên dịch gcc và công cụ gỡ rối gdb
(GNU Debugger).
Nếu bạn đã biết kỹ thuật khai thác lỗi tràn bộ đệm qua các
tài liệu khác, bài viết này cũng có thể giúp bạn củng cố lại
kiến thức một cách chắc chắn hơn.
Phần 1: Tổ chức bộ nhớ, stack, gọi hàm, shellcode
Mục lục :
• Giới thiệu
• 1. Tổ chức bộ nhớ
o 1.1 Tổ chức bộ nhớ của một tiến trình (process)
o 1.2 Stack


• 2. Gọi hàm
o 2.1 Giới thiệu


2.2 Khởi đầu
o 2.3 Gọi hàm
o 2.3 Kết thúc
3. Shellcode
o 3.1 Viết shellcode trong ngôn ngữ C
o 3.2 Giải mã hợp ngữ các hàm
o 3.3 Định vị shellcode trên bộ nhớ
o 3.4 Vấn đề byte giá trị null
o 3.5 Tạo shellcode
o



Giới thiệu
Để tìm hiểu chi tiết về lỗi tràn bộ đệm, cơ chế hoạt động và
cách khai thác lỗi ta hãy bắt đầu bằng một ví dụ về chương
trình bị tràn bộ đệm.
/* vuln.c */
int main(int argc, char **argv)
{
char buf[16];
if (argc>1) {
strcpy(buf, argv[1]);
printf("%s\n", buf);
}
}

[SkZ0@gamma bof]$ gcc -o vuln -g vuln.c
[SkZ0@gamma bof]$ ./vuln AAAAAAAA // 8 ký tự A (1)
AAAAAAAA
[SkZ0@gamma bof]$ ./vuln
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA // 24 ký tự A (2)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
Chạy chương trình vuln với tham số là chuỗi dài 8 ký tự A
(1), chương trình hoạt động bình thường. Với tham số là


chuỗi dài 24 ký tự A (2), chương trình bị lỗi Segmentation
fault. Dễ thấy bộ đệm buf trong chương trình chỉ chứa được
tối đa 16 ký tự đã bị làm tràn bởi 24 ký tự A.
[SkZ0@gamma bof]$ gdb vuln -c core -q
Core was generated by `./vuln
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0 0x41414141 in ?? ()
(gdb) info register eip
eip
0x41414141
1094795585
(gdb)
Thanh ghi eip - con trỏ lệnh hiện hành - có giá trị
0x41414141, tương đương 'AAAA' (ký tự A có giá trị 0x41
hexa). Ta thấy, có thể thay đổi giá trị của thanh ghi con trỏ
lệnh eip bằng cách làm tràn bộ đệm buf. Khi lỗi tràn bộ đệm

đã xảy ra, ta có thể khiến chương trình thực thi mã lệnh tuỳ
ý bằng cách thay đổi con trỏ lệnh eip đến địa chỉ bắt đầu
của đoạn mã lệnh đó.
Để hiểu rõ quá trình tràn bộ đệm xảy ra như thế nào, chúng
ta sẽ xem xét chi tiết tổ chức bộ nhớ, stack và cơ chế gọi
hàm của một chương trình.
1. Tổ chức bộ nhớ
1.1 Tổ chức bộ nhớ của một tiến trình (process)


Mỗi tiến trình thực thi đều được hệ điều hành cấp cho một
không gian bộ nhớ ảo (logic) giống nhau. Không gian nhớ
này gồm 3 vùng: text, data và stack. Ý nghĩa của 3 vùng này
như sau:
Vùng text là vùng cố định, chứa các mã lệnh thực thi
(instruction) và dữ liệu chỉ đọc (read-only). Vùng này được
chia sẻ giữa các tiến trình thực thi cùng một file chương
trình và tương ứng với phân đoạn text của file thực thi. Dữ
liệu ở vùng này là chỉ đọc, mọi thao tác nhằm ghi lên vùng
nhớ này đều gây lỗi segmentation violation.
Vùng data chứa các dữ liệu đã được khởi tạo hoặc chưa khởi
tạo giá trị. Các biến toàn cục và biến tĩnh được chứa trong
vùng này. Vùng data tương ứng với phân đoạn data-bss của
file thực thi.
Vùng stack là vùng nhớ được dành riêng khi thực thi chương
trình dùng để chứa giá trị các biến cục bộ của hàm, tham số
gọi hàm cũng như giá trị trả về. Thao tác trên bộ nhớ stack
được thao tác theo cơ chế "vào sau ra trước" - LIFO (Last
In, First Out) với hai lệnh quan trọng nhất là PUSH và POP.
Trong phạm vi bài viết này, chúng ta chỉ tập trung tìm hiểu

về vùng stack.
1.2 Stack


Stack là một kiểu cấu trúc dữ liệu trừu tượng cấp cao được
dùng cho các thao tác đặc biệt dạng LIFO.
Tổ chức của vùng stack gồm các stack frame được push vào
khi gọi một hàm và pop ra khỏi stack khi trở về. Một stack
frame chứa các thông số cần thiết cho một hàm: biến cục
bộ, tham số hàm, giá trị trả về; và các dữ liệu cần thiết để
khôi phục stack frame trước đó, kể cả giá trị của con trỏ
lệnh (instruction pointer) vào thời điểm gọi hàm.
Địa chỉ đáy của stack được gán một giá trị cố định. Địa chỉ
đỉnh của stack được lưu bởi thanh ghi "con trỏ stack" (ESP –
extended stack pointer). Tuỳ thuộc vào hiện thực, stack có
thể phát triển theo hướng địa chỉ nhớ từ cao xuống thấp
hoặc từ thấp lên cao. Trong các ví dụ về sau, chúng ta sử
dụng stack có địa chỉ nhớ phát triển từ cao xuống thấp, đây
là hiện thực của kiến trúc Intel. Con trỏ stack (SP) cũng phụ
thuộc vào kiến trúc hiện thực. Nó có thể trỏ đến địa chỉ cuối
cùng trên đỉnh stack hoặc địa chỉ vùng nhớ trống kế tiếp
trên stack. Trong các minh hoạ về sau (với kiến trúc Intel
x86), SP trỏ đến địa chỉ cuối cùng trên đỉnh stack.
Về lý thuyết, các biến cục bộ trong một stack frame có thể
được truy xuất dựa vào độ dời (offset) so với SP. Tuy nhiên
khi có các thao tác thêm vào hay lấy ra trên stack, các độ
dời này cần phải được tính toán lại, làm giảm hiệu quả. Để
tăng hiệu quả, các trình biên dịch sử dụng một thanh ghi
thứ hai gọi là "con trỏ nền" (EBP – extended base pointer)
hay còn gọi là "con trỏ frame" (FP – frame pointer). FP trỏ

đến một giá trị cố định trong một stack frame, thường là giá
trị đầu tiên của stack frame, các biến cục bộ và tham số
được truy xuất qua độ dời so với FP và do đó không bị thay
đổi bởi các thao tác thêm/bớt tiếp theo trên stack.
Đơn vị lưu trữ cơ bản trên stack là word, có giá trị bằng 32
bit (4 byte) trên các CPU Intel x86. (Trên các CPU Alpha hay


Sparc giá trị này là 64 bit). Mọi giá trị biến được cấp phát
trên stack đều có kích thước theo bội số của word.
Thao tác trên stack được thực hiện bởi 2 lệnh máy:
• push value: đưa giá trị ‘value’ vào đỉnh của stack. Giảm
giá trị của %esp đi 1 word và đặt giá trị ‘value’ vào
word đó.
• pop dest: lấy giá trị từ đỉnh stack đưa vào ‘dest’. Đặt
giá trị trỏ bởi %esp vào ‘dest’ và tăng giá trị của %esp
lên 1 word.
2. Hàm và gọi hàm
2.1 Giới thiệu
Để giải thích hoạt động của chương trình khi gọi hàm, chúng
ta sẽ sử dụng đoạn chương trình ví dụ sau:
/* fct.c */
void toto(int i, int j)
{
char str[5] = "abcde";
int k = 3;
j = 0;
return;
}
int main(int argc, char **argv)

{
int i = 1;
toto(1, 2);
i = 0;
printf("i=%d\n",i);
}
Quá trình gọi hàm có thể được chia làm 3 bước:
1. Khởi đầu (prolog): trước khi chuyển thực thi cho một
hàm cần chuẩn bị một số công việc như lưu lại trạng


thái hiện tại của stack, cấp phát vùng nhớ cần thiết để
thực thi.
2. Gọi hàm (call): khi hàm được gọi, các tham số được
đặt vào stack và con trỏ lệnh (IP – instruction pointer)
được lưu lại để cho phép chuyển quá trình thực thi đến
đúng điểm sau gọi hàm.
3. Kết thúc (epilog): khôi phục lại trạng thái như trước
khi gọi hàm.
2.2 Khởi đầu
Một hàm luôn được khởi đầu với các lệnh máy sau:
push %ebp
mov %esp,%ebp
sub $0xNN,%esp // (giá trị 0xNN phụ thuộc vào từng hàm
cụ thể)
3 lệnh máy này được gọi là bước khởi đầu (prolog) của hàm.
Hình sau giải thích bước khởi đầu của hàm toto() và giá trị
của các thanh ghi %esp, %ebp.
Hình 1: Bước khởi đầu của hàm
Gi


sử
ba
n
đầ
u
%
eb
p
trỏ
đế
n
địa
chỉ
X


bất
kỳ
trê
n
bộ
nh
ớ,
%
es
p
trỏ
đế
n

mộ
t
địa
chỉ
Y
thấ
p

n

n

ới.
Tr
ướ
c
khi
ch
uy
ển



o
mộ
t

m,
cần
ph

ải
lưu
lại

i
trư
ờn
g
của
sta
ck
fra
me
hiệ
n
tại,
do
mọ
i
giá
trị
tro
ng
mộ
t
sta
ck
fra



me
đề
u

thể
đư
ợc
tha
m
kh
ảo
qu
a
%
eb
p,
ta
chỉ
cần
lưu
%
eb
p

đủ.

%
eb
p
đư

ợc
pu
sh



o
sta
ck,

n
%
es
p
sẽ
giả
m
đi
1
wo
rd.
Gi
á
trị
%
eb
p
đư
ợc
pu

sh

o
sta
ck

y
đư
ợc
gọi



"c
on
trỏ
nề
n
bả
o

u"
(S
FP
sa
ve
d
fra
me
poi

nte
r).
Lệ
nh

y
thứ
hai
sẽ
thi
ết
lập
mộ
t

i


trư
ờn
g
mớ
i
bằ
ng
các
h
đặt
%
eb

p
trỏ
đế
n
đỉn
h
của
sta
ck
(gi
á
trị
đầ
u
tiê
n
của
mộ
t
sta
ck
fra


me
),
lúc

y
%

eb
p

%
es
p
sẽ
trỏ

ng
đế
n
mộ
t vị
trí

địa
chỉ

(Y1w
ord
).


Lệ
nh

y
thứ
ba

cấp
ph
át

ng
nh


nh
ch
o
biế
n
cục
bộ.
Mả
ng

tự

độ
dài
5
byt
e,
tuy
nhi
ên
sta



ck
sử
dụ
ng
đơ
n
vị
lưu
trữ

wo
rd,
do
đó

ng
nh

đư
ợc
cấp
ch
o
mả
ng

tự
sẽ


mộ
t
bội
số
của
wo


rd
sao
ch
o
lớn

n
ho
ặc
bằ
ng
kíc
h
thư
ớc
của
mả
ng.
Dễ
thấ
y
giá

trị
đó

8
byt
e
(2
wo
rd)
.
Biế
nk
kiể


u
ng
uy
ên

kíc
h
thư
ớc
4
byt
e,

vậ
y

kíc
h
thư
ớc

ng
nh


nh
ch
o
biế
n
cục
bộ
sẽ

8+
4=


12
byt
e
(3
wo
rd)
,
đư

ợc
cấp
ph
át
bằ
ng
các
h
giả
m
%e
sp
đi
mộ
t
giá
trị
0x
c
(bằ
ng
12
tro
ng
hệ

số


16)

.
Một điều cần lưu ý ở đây là biến cục bộ luôn có độ dời âm
so với con trỏ nền %ebp. Lệnh máy thực hiện phép gán i=0
trong hàm main() có thể minh hoạ điều này. Mã hợp ngữ
dùng định vị gián tiếp để xác định vị trí của i:
movl $0x0,0xfffffffc(%ebp)
0xfffffffc tương đương giá trị số nguyên bằng –4. Lệnh trên
có nghĩa: đặt giá trị 0 vào biến ở địa chỉ có độ dời “-4” byte
so với thanh ghi %ebp. i là biến đầu tiên trong hàm main()
và có địa chỉ cách 4 byte ngay dưới %ebp.
2.3 Gọi hàm
Cũng giống như bước khởi đầu, bước này cũng chuẩn bị môi
trường cho phép nơi gọi hàm truyền các tham số cho hàm
được gọi và trở về lại nơi gọi hàm khi kết thúc.
Hình 2 : Gọi hàm
Tr
ướ
c
khi
gọi

m
các
tha
m
số
sẽ
đư
ợc
đặt


o


sta
ck,
the
o
thứ
tự
ng
ượ
c
lại,
tha
m
số
cu
ối

ng
sẽ
đư
ợc
đặt

o
trư
ớc.
Tr

on
g

dụ
trê
n,
trư
ớc
tiê


n
các
giá
trị
1

2
sẽ
đư
ợc
đặt

o
sta
ck.
Th
an
h
ghi

%
eip
giữ
giá
trị
địa
chỉ
của
lện
h
kế
tiế
p,
tro
ng
trư


ờn
g
hợ
p

y

chỉ
thị
gọi

m.

Kh
i
thự
c
hiệ
n
lện
h
cal
l,
%
eip
sẽ
lấy
giá
trị
địa
chỉ
của
kế
tiế
p


ng
ay
sau
gọi

m

(trê
n
hìn
h
vẽ,
giá
trị

y

Z+
5
do
lện
h
gọi

m
chi
ếm
5
byt
e
the
o
hiệ
n
thự
c



của
CP
U
Int
el
x8
6).
Lệ
nh
cal
l
sau
đó
sẽ
lưu
lại
giá
trị
của
%
eip
để

thể
tiế
p
tục
thự
c

thi
sau
khi
trở
về.
Qu


×