手把手簡易實現shellcode及詳解
轉載理由: 處於安全行業靠邊產業,經常聽和看到shellcode,但是隻有模糊的認識,於是學習一把,畢竟C和彙編瞭解,開闊下視野;
轉載於:http://blog.nsfocus.net/simple-realization-hand-handle-shellcode-detailed-explanation/?utm_source=tuicool&utm_medium=referral
讀者注意:此文只做瞭解,不用於用危害網路安全;
學而不知道,與不學同;知而不能行,與不知同。 ——黃睎
文章目錄
簡介
漏洞利用中必不可缺的部分就是shellcode,shellcode能在極小的空間內完成一些基本而重要的工作。
Shellcode編寫方式基本有3種:
- 直接編寫十六進位制操作碼(不現實);
- 採用像C這樣的高階語言編寫程式,編譯後,進行反彙編以獲取彙編指令和十六進位制操作碼。
- 編譯彙編程式,將該程式彙編,然後從二進位制中提取十六進位制操作碼。
C語言編寫獲取shell的shellcode程式
shellcode注意事項
寫shellcode的需要注意的兩個重要問題:
- 系統呼叫的問題。
- 壞字元問題
一般來說,shellcode都是由十幾或是幾十個位元組組成,這樣的小程式如果要像linux服務程式一樣,引入標頭檔案,匯入符號表,呼叫系統函式,這樣的步驟的話;那麼短短的幾十個位元組根本就不能滿足需求,這就需要利用系統最核心的呼叫機制,即通過軟中斷的方式獲取需要的資源,以此來繞開系統呼叫。
Shellcode如果儲存在堆或是棧的記憶體中,這樣在shellcode執行時就不能出現\x00這樣的階段字元,這就需要我們在構造shellcode時防止此類壞字元的出現。
C語言返回shell例項
本例以簡單的返回本地shell為例。來說明shellcode的構建過程,程式如下圖所示
execve(執行檔案)在父程序中fork一個子程序,在子程序中呼叫exec函式啟動新的程式。execve()用來執行第一引數字串所代表的檔案路徑,第二個引數是利用指標陣列來傳遞給執行檔案,並且需要以空指標(NULL)結束,最後一個引數則為傳遞給執行檔案的新環境變數陣列。從圖中可以,如果通過C語言呼叫execve來返回shell的話,首先需要引入相應的標頭檔案,然後在主函式中呼叫系統呼叫函式execve;同時傳入三個引數。
編譯之後執行結果,如下圖。
為了能夠之後能夠看到反彙編的結果,這次採用的靜態編譯。正常返回shell。
那麼要想提取其中的shellcode就需要通過反彙編來獲取相應的彙編程式碼或是二進位制程式碼。
如果要提取該程式中的獲取shell的shellcode,就是要獲取函式execve呼叫時引數及相應的系統呼叫。
接下來看看程式retsh的反彙編結果
首先檢視一下execve函式反彙編後的結果
從反彙編結果來看,execve函式執行的前一部分首先將向暫存器ebx,ecx,edx中賦值。之後呼叫了(*0x80ef5a4)處的程式碼,該處就是_dl_sysinfo_int80,反彙編後發現其實是通過中斷指令int 0x80進入ring0。
也就是說exceve函式是通過呼叫軟中斷int 0x80進入ring0。
提取shellcode
Shellcode的提取就是要獲取exceve函式呼叫時的引數及軟中斷呼叫。通過軟終端載入相應的系統呼叫號及引數來執行相應的任務。
Int 0x80軟中斷呼叫
第一步,就是需要將系統呼叫號加入到eax中。
第二步,ebx用於儲存函式呼叫的第一個引數(ecx存放第二個引數,edx存放第三個引數,esi存放第四個引數,edi存放第五個引數)
如果引數個數超過5個,那麼就必須將引數陣列儲存在記憶體中,而且必須將該陣列的地址儲存在ebx中。
一旦載入暫存器之後,就會呼叫int 0x80 彙編指令來發出軟中斷,強迫核心暫停手頭上的工作並處理該中斷。
驗證int 0x80 呼叫。
由上面的反彙編我們可以在地址0x8053c32出下斷點,執行到該地址處,此時暫存器eax,ebx,ecx,edx中都已經通過esp的偏移指標得到了賦值,接下來就是要呼叫int 0x80軟中斷指令。
檢視四個暫存器。
前面我們已經提過了execve系統呼叫的引數部分。看看上圖的暫存器賦值,第1個引數ebx,剛好是“/bin/sh”;第2個引數ecx是一個指標陣列,第一個元素是第一個引數地址,第二個元素為空;第3個引數是edx為空。最後execve的系統呼叫號就放在了暫存器eax中=0xb。
關於查詢系統函式呼叫號,可以通過如下方式搜尋:
剛好是eax中的0xb.
由此可以看出如果想要得到shellcode就需要將部分指令程式碼拼接。組成execve的系統呼叫如下圖所示。
由上圖不難看出,儘管這樣可以實現shell返回的shellcode,但是裡面包含裡很多\x00空字元,只要在單獨拷貝shellcode就很有可能導致shellcode階段而不能正常執行shellcode.
彙編形式編寫shellcode
編寫彙編原始碼
一般來說shellcode的總長度都非常短,所以可以直接採用彙編形式編寫,這樣不但可以直接通過軟中斷形式執行系統呼叫,而且可以控制壞字元的出現。如下圖所示,為一個返回彙編行的shellcode程式碼
程式碼非常簡單,沒有資料段,只有一個程式碼段。
先編譯、連結、執行,看看結果
OK,沒有什麼問題。執行之後返回成功返回shell。
彙編程式碼分析
根據之前int 0x80中斷指令呼叫形式,要求eax存放系統呼叫號;ebx、ecx、edx分別存放參數部分。
彙編原始碼中,首先是第4行eax清零;之後第5行壓棧;然後第6行,第7行字串壓棧,這樣在棧中就構造了以”\x00”結尾的字串”/bin//sh”。注意這裡的“/bin//sh”與“/bin/sh”同樣效果。
此時的ESP指標指向了這個字串首地址,第8行將該首地址賦給ebx,這樣就有了int 0x80中斷指令的第一個引數ebx;第9行中eax入棧,此時eax值還是0;第10行ebx入棧也就是把字串”/bin//sh”地址入棧,兩次壓棧,此時棧中就有了字串地址和一個0,剛好構成了一個指標陣列;第11行將該指標陣列的地址也就是esp賦給ecx,系統呼叫的第2個引數ecx中就保持了指標陣列的地址;第12行edx清零,剛好是系統呼叫的第3個引數為零。第13行將系統呼叫號0xB賦給al,這樣可以避免出現壞字元。最後呼叫軟中斷指令執行。
整個棧結果如下圖所示
Shellcode提取及驗證
接下來就要提取shellcode的指令程式碼
紅框中的部分就是該shellcode對應的指令程式碼,中間沒有\x00壞字元,這樣在拷貝過程中也就不會有截斷的問題。
通過一小段C語言程式碼來驗證該shellcode有效性。程式碼如下:
很精緻的一段程式碼。將shellcode程式碼放到一塊記憶體區域,通過定義的一個函式指標指向該記憶體區並執行;編譯、執行的結果。
好吧,Segmentation fault了。
什麼情況導致的?gdb除錯看一下Main函式對fp函式的呼叫,如下圖所示:
單步執行到地址0x080483f5處
從圖中可以看出,在主函式呼叫eax所執行地址時,此時eax=0x804a010,在該地址執行異或指令時,此時eax還是0x804a010,但是在單步執行該異或指令後就報錯了。也就是說在地址0x804a010執行寫操作時(異或操作)發生了錯誤。
由此我們可以想到要麼該地址不讓執行,要麼該地址不讓寫。
接下來就要檢視記憶體地址0x804a010的許可權,如下圖所示。
在前面原始碼中可以shellcode是一個區域性變數,所以該部分資料放到了堆疊區。檢視該程式的堆疊許可權:
圖中顯示堆疊空間只有讀寫許可權,沒有可執行許可權,所以在該地址執行程式碼導致錯誤。
編譯時對該程式啟動棧空間可執行許可權:
成功返回shell。到此,shellcode的編寫及驗證過程已經完成。
通過Metaspolit的shellcode自動生成工具可以自動生成各種功能shellcode,為快速利用漏洞攻擊系統提供更便捷方式。但是如果想自己想學習shellcode的編寫過程還是需要親身試驗,親自操作,才能發現問題,解決問題。