printf中的缺陷
来个简单的程序,体验体验漏洞
#include <stdio.h> int main(){ int a=44, b=77; printf("a=%d, b=%d\n", a, b); printf("a=%d, b=%d\n"); return 0; }
release版本编译运行
第一个的结果是正确的,第二个有点奇怪,因为我们没给他传递参数,所以它会自己从栈上找数据输出
载入od看看
代码也是简单,毕竟是release版本
也可以看到调用了第二个printf之后才进行栈平衡操作(printf等库函数是要调用者清理堆栈的)
第一次调用,栈内容如下(参数从右到左入栈)
确实这样的
当到第二个的时候就变成
那么最终就是
printf("a=%d, b=%d\n",&"a=%d, b=%d\n", 0x2c );
那么为什么这样输出我们就明白了
这里还有一个问题就是应该是编译器优化的问题,他不会调用一次printf函数就平衡一次栈,最后才平衡,这样可以提高一丁丁的效率
那么这样不就可能泄露栈上的数据了吗
用printf读取内存数据
其实上面的程序只能算是一个bug,如果我们能控制字符串格式控制符,那么就是格式化串漏洞了
比如下面的
#include <stdio.h> int main(int argc, char** argv){ printf(argv[1]); return 0; }
看看我们能用那些控制符
用p的话就可以输出栈上的数据了
我们测试一下吧
用printf向内存写数据
有个很少人知道的控制符%n,他是将当前输出的所有的数据的长度写回一个变量中去
#include <stdio.h> int main(int argc, char** argv){ int len_printf = 0; printf("before write: length=%d\n", len_printf); printf("giantbranch:%d%n\n", len_printf, &len_printf); printf("after write: length=%d\n", len_printf); return 0; }
可以看到%n前面输出了13个字符(giantbranch:0),所以最终len_printf的长度被改了
加入我们要精确控制len_printf的值,那么在%加个数字就好了
检测与防范
直接静态代码扫描,看看第一个参数是否用户可控就行了,
据说vs2005在编译级别对参数做了更好的检查,而且默认情况关闭了对%n控制符的使用