本文是coursera軟件安全課程學習總結,算是梳理知識,細節太多,只寫了要點。shell
使用malloc函數分配的內存在heap區域,stack從高地址向低地址生長,heap相反。segmentfault
每當使用call指令進行函數調用時,都會將原來的eip寄存器中的值壓棧,而後,將新的函數指針寫入eip寄存器,這是由機器自動執行的,保存原eip的同時,將新的執行地址寫入eip.詳細過程能夠關注個人博客中一篇詳細描述堆棧變化的博文click here。安全
這裏咱們知道,一旦函數調用完畢,返回地址若是被修改(好比被修改爲爲惡意程序的入口地址),那麼後果不堪設想。使用緩衝區溢出能夠實現攻擊 ,咱們會在例子中給出解釋。bash
接下來咱們使用一個例子來形象的表示出函數調用時堆棧的變化服務器
void f(char* str,int i,int j){ int local1; int local2; ... } int main(){ ... f("tom",8,9); ... }
如何進行代碼注入?首先,咱們須要把代碼放入內存。其次,須要讓eip指向咱們的代碼起始位置,才能執行它。dom
(1)代碼必須是已編譯的可執行機器碼
(2)代碼不能包括零,不然,零以後I/O函數將中止拷貝
(3)不能使用loder函數
咱們的目標是執行一個咱們能夠操縱的shell,加載shell的代碼被稱爲shellcode。學習
#include <stdio.h> int main( ) { char *name[2]; name[0] = 「/bin/sh」; name[1] = NULL; execve(name[0], name, NULL); }
因爲在函數調用的末尾,須要將原eip值取出加載到eip寄存器,那麼,若是咱們修改了原eip的值,使其變爲咱們shellcode代碼執行地址,那麼函數返回後就執行shellcode。優化
但是,怎麼知道咱們的shellcode指令開始地址呢?由於若是地址不正確,CPU就故障了。spa
若是咱們沒有權限獲取代碼,咱們當讓不知道緩衝區距離ebp有多遠,那麼,咱們怎麼辦呢?
(1)嘗試!不停嘗試(這個看運氣,並且概率不高)
(2)若是沒有地址隨機優化,那麼每次堆棧都從一個固定的地址開始執行,並且堆棧通常不會很深,那麼,能夠知道esp大致在某個區間。可使用 nop sleds 提升咱們的命中概率。
nop sleds:
以上咱們討論的就是所謂的stack smashing。
把緩衝區溢出的原理用在堆上,就是所謂的堆溢出。
讀取了不應讀取的內存
the Heartbleed bug 經過發送特定的消息,擁有bug的ssl服務器沒有檢查長度就將攻擊者指定的返回字符串返回攻擊者。所以,攻擊者能夠經過增大字符串長度,非法讀取其餘數據。
讀取了調用者的數據!
舉例:
printf(「100% dave」); //Prints stack entry 4 byes above saved %eip printf(「%s」); //Prints bytes pointed to by that stack entry printf(「%d %d %d %d …」); //Prints a series of stack entries as integers printf(「%08x %08x %08x %08x …」); // Same, but nicely formatted hex printf(「100% no way!」)" //WRITES the number 3 to address pointed to by stack entry
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <time.h> char greeting[] = "Hello there\n1. Receive wisdom\n2. Add wisdom\nSelection >"; char prompt[] = "Enter some wisdom\n"; char pat[] = "Achievement unlocked!\n"; char secret[] = "secret key"; int infd = 0; /* stdin */ int outfd = 1; /* stdout */ #define DATA_SIZE 128 typedef struct _WisdomList { struct _WisdomList *next; char data[DATA_SIZE]; } WisdomList; struct _WisdomList *head = NULL; typedef void (*fptr)(void); void write_secret(void) { write(outfd, secret, sizeof(secret)); return; } void pat_on_back(void) { write(outfd, pat, sizeof(pat)); return; } void get_wisdom(void) { char buf[] = "no wisdom\n"; if(head == NULL) { write(outfd, buf, sizeof(buf)-sizeof(char)); } else { WisdomList *l = head; while(l != NULL) { write(outfd, l->data, strlen(l->data)); write(outfd, "\n", 1); l = l->next; } } return; } void put_wisdom(void) { char wis[DATA_SIZE] = {0}; int r; r = write(outfd, prompt, sizeof(prompt)-sizeof(char)); if(r < 0) { return; } r = (int)gets(wis); if (r == 0) return; WisdomList *l = malloc(sizeof(WisdomList)); if(l != NULL) { memset(l, 0, sizeof(WisdomList)); strcpy(l->data, wis); if(head == NULL) { head = l; } else { WisdomList *v = head; while(v->next != NULL) { v = v->next; } v->next = l; } } return; } fptr ptrs[3] = { NULL, get_wisdom, put_wisdom }; int main(int argc, char *argv[]) { while(1) { char buf[1024] = {0}; int r; fptr p = pat_on_back; r = write(outfd, greeting, sizeof(greeting)-sizeof(char)); if(r < 0) { break; } r = read(infd, buf, sizeof(buf)-sizeof(char)); if(r > 0) { buf[r] = '\0'; int s = atoi(buf); fptr tmp = ptrs[s]; tmp(); } else { break; } } return 0; }
本實驗全部材料來自coursera軟件安全課程。
這個例子包含兩個緩衝區溢出攻擊。主函數中包含一個全局緩衝區攻擊,函數put_wisdom中的wis緩衝區是一個棧上的緩衝區溢出。
執行過程:
(1)編譯程序,gcc -fno-stack-protector -ggdb -m32 wisdom-alt.c -o wisdom-alt
(2)使用bash打開一個終端,運行./runbin.sh
(3)打開另外一個終端,使用命令 gdb -p `pgrep wisdom-alt`調試
回想以前的緩衝區溢出,若是咱們輸入的索引值剛好能到達fptr p = pat_on_back;
中p的存儲區域,那麼就能讀取到pat_on_back,進而執行該函數!
首先,肯定p的地址:在啓動運行gdb中print &p
和print buf
:
經過計算,知道p在buf以前771675416個內存位置處,咱們輸入該數字:
發現咱們獲取到了到了pat_on_back函數指針!
一樣的原理,咱們經過找到函數void put_wisdom(void) 被調用時緩衝區wis的地址和返回地址在內存中的差,用一樣的方法,將咱們函數指針write_secret的地址寫入保存返回地址的內存區域,那麼函數put_wisdom調用結束後,就會執行write_secret函數。