so true

心怀未来,开创未来!
随笔 - 160, 文章 - 0, 评论 - 40, 引用 - 0
数据加载中……

printf函数里面大有文章!

先给出printf函数格式控制的基本语法:

还有一篇网文中,更为详细的讲述了C语言格式输入输出的内容:

如有兴趣,请查看:C语言格式输入输出总结   http://hi.bccn.net/space-241412-do-blog-id-10499.html

printf的格式控制的完整格式:
%  -  0  m.n  l或h  格式字符
下面对组成格式说明的各项加以说明:
①%:表示格式说明的起始符号,不可缺少。
②-:有-表示左对齐输出,如省略表示右对齐输出。
③0:有0表示指定空位填0,如省略表示指定空位不填。
④m.n:m指域宽,即对应的输出项在输出设备上所占的字符数。N指精度。用于说明输出的实型数的小数位数。为指定n时,隐含的精度为n=6位。
⑤l或h:l对整型指long型,对实型指double型。h用于将整型的格式字符修正为short型。

---------------------------------------
格式字符
格式字符用以指定输出项的数据类型和输出格式。
 ①d格式:用来输出十进制整数。有以下几种用法:
%d:按整型数据的实际长度输出。
%md:m为指定的输出字段的宽度。如果数据的位数小于m,则左端补以空格,若大于m,则按实际位数输出。
%ld:输出长整型数据。
②o格式:以无符号八进制形式输出整数。对长整型可以用"%lo"格式输出。同样也可以指定字段宽度用“%mo”格式输出。
例:
   main()
   { int a = -1;
     printf("%d, %o", a, a);
   }
  运行结果:-1,177777
  程序解析:-1在内存单元中(以补码形式存放)为(1111111111111111)2,转换为八进制数为(177777)8。
③x格式:以无符号十六进制形式输出整数。对长整型可以用"%lx"格式输出。同样也可以指定字段宽度用"%mx"格式输出。
④u格式:以无符号十进制形式输出整数。对长整型可以用"%lu"格式输出。同样也可以指定字段宽度用“%mu”格式输出。
⑤c格式:输出一个字符。
⑥s格式:用来输出一个串。有几中用法
%s:例如:printf("%s", "CHINA")输出"CHINA"字符串(不包括双引号)。
%ms:输出的字符串占m列,如字符串本身长度大于m,则突破获m的限制,将字符串全部输出。若串长小于m,则左补空格。
%-ms:如果串长小于m,则在m列范围内,字符串向左靠,右补空格。
%m.ns:输出占m列,但只取字符串中左端n个字符。这n个字符输出在m列的右侧,左补空格。
%-m.ns:其中m、n含义同上,n个字符输出在m列范围的左侧,右补空格。如果n>m,则自动取n值,即保证n个字符正常输出。
⑦f格式:用来输出实数(包括单、双精度),以小数形式输出。有以下几种用法:
%f:不指定宽度,整数部分全部输出并输出6位小数。
%m.nf:输出共占m列,其中有n位小数,如数值宽度小于m左端补空格。 
%-m.nf:输出共占n列,其中有n位小数,如数值宽度小于m右端补空格。
⑧e格式:以指数形式输出实数。可用以下形式:
%e:数字部分(又称尾数)输出6位小数,指数部分占5位或4位。
%m.ne和%-m.ne:m、n和”-”字符含义与前相同。此处n指数据的数字部分的小数位数,m表示整个输出数据所占的宽度。
⑨g格式:自动选f格式或e格式中较短的一种输出,且不输出无意义的零。

---------------------------------------
关于printf函数的进一步说明:
如果想输出字符"%",则应该在“格式控制”字符串中用连续两个%表示,如:
printf("%f%%", 1.0/3);
输出0.333333%。

---------------------------------------
对于单精度数,使用%f格式符输出时,仅前7位是有效数字,小数6位.
对于双精度数,使用%lf格式符输出时,前16位是有效数字,小数6位.

再给出一些特殊用法:

由高手指点
对于m.n的格式还可以用如下方法表示(例)
int m=10,n=5;
 char ch[]="abcdefghijklmnopqrst";
 printf("%*.*s\n",m,n,ch);//输出为     abcde
前边的*定义的是总的宽度,后边的定义的是输出的个数。分别对应外面的参数m和n 。我想这种方法的好处是可以在语句之外对参数m和n赋值,从而控制输出格式。

---------------------------------------------------------------

利用%p可以输出指针,默认是16进制输出的,因此%#p等同于%p,也可以用10进制输出,格式为%ip


 

今天(06.6.9)又看到一种输出格式 %n 可以将所输出字符串的长度值赋绐一个变量, 见下例:

int slen;

printf("hello world%n", &slen);

执行后变量被赋值为11。

但是这一特性会被黑客用于恶意攻击:

//有关这部分的详细内容,请查看完整的网页:格式化字符串攻击笔记  http://www.3800hk.com/news/w44/14907.html 

%n格式符允许写入指定数据.
上面提到的例子基本上只能做到偷窥堆栈中数据,了解堆栈结构的目的.而和利用格式化字符串漏洞攻击
还是有一定距离的,因为只能做"传说"中的read anywhere是无法改变程序流程的,不改变程序流程,就无法
让程序按照我们的意愿执行下去.:)
但是很幸运*printf的%n格式化说明符它允许向后面一个存储单元写入前面输出数据的总长度,那么只
要前面输出数据的长度(这个长度的控制可以利用格式化说明符的特性,比如%.200d,这样我们就可以控制输
数据长度为200了,想象一下如果我们用%f.呢?嗬嗬,堆栈地址固然很大,但是我们应该可以构造足够的
%f....用来到达我们需要改写的存储单元)等于我们需要程序跳转到的那个地址(通常是shellcode+nop的区
域),而%n恰到好处的将这一地址写入适当位置,那么我们就可以按照我们的意愿改变程序流程了.:)
不过这里有一点需要注意,如果格式化字符串攻击时覆盖函数的返回地址,那么实际上我们是去覆盖存储
这个函数返回地址的那块存储空间.也就是说我们是间接的覆盖.这一点很重要,不能混淆.回想一下C语言的指
针.:)
现在,在VS2005中,提到:就目前来说,printf中的%n格式化指示符一般用于指定输出的字符个数。这已经确认为一个安全隐患,并且已禁用,但可以使用set_printf_count_output来启用它;通过传递给set_printf_count_output一个零值(0)可禁用它,而传递任意一个其他值可再次启用。
示例代码为:
   int e;
   int i;
   e = _set_printf_count_output( 1 );
   printf( "%%n support was %sabled.\n",
        e ? "en" : "dis" );
   printf( "%%n support is now %sabled.\n",
        _get_printf_count_output() ? "en" : "dis" );
   printf( "%.200d%n6789\n", e,&i ); // %n format should set i to 5
   printf( "i = %d\n", i );

输出:

%n support was disabled.
%n support is now enabled.
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000006789
i = 200

讨论完了%n,下面再说说对于使用64位整数

LONGLONG llValue;//也可以用__int64或INT64

int iValue;

printf(“%d, %d”, llValue, iValue);

iValue的值永远不会被输出,第一个%d输出的是llValue的低32位,第二个%d输出的是llValue的高32位。所以程序应该修改为:

printf(“%I64d, %d”, llValue, iValue);

对64位整数的输入输出,在POJ上的C++环境下(即VC),64位整数是:
__int64      (注意int前面是两个下划线)
输入输出格式为”%I64d”.
在G++环境下(即Dev C++) 64位整数是
long long
输入输出格式为”%lld”.

最后,贴出从VS2005F11进去的printf源代码:

/***
*printf.c - print formatted
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       defines printf() - print formatted data
*
*******************************************************************************/

#include <cruntime.h>
#include <stdio.h>
#include <dbgint.h>
#include <stdarg.h>
#include <file2.h>
#include <internal.h>
#include <mtdll.h>
#include <stddef.h>
#include <process.h>

/***
*int printf(format, ...) - print formatted data
*
*Purpose:
*       Prints formatted data on stdout using the format string to
*       format data and getting as many arguments as called for
*       Uses temporary buffering to improve efficiency.
*       _output does the real work here
*
*Entry:
*       char *format - format string to control data format/number of arguments
*       followed by list of arguments, number and type controlled by
*       format string
*
*Exit:
*       returns number of characters printed
*
*Exceptions:
*
*******************************************************************************/

int __cdecl printf (
        const char *format,
        ...
        )
/*
 * stdout 'PRINT', 'F'ormatted
 */
{
        va_list arglist;
        int buffing;
        int retval;

        _VALIDATE_RETURN( (format != NULL), EINVAL, -1);

        va_start(arglist, format);

        _lock_str2(1, stdout);
        __try {

        buffing = _stbuf(stdout);

        retval = _output_l(stdout,format,NULL,arglist);

        _ftbuf(buffing, stdout);

        }
        __finally {
            _unlock_str2(1, stdout);
        }

        return(retval);
}

int __cdecl _printf_l (
        const char *format,
        _locale_t plocinfo,
        ...
        )
{
    va_list arglist;

    va_start(arglist, plocinfo);

    return _vprintf_l(format, plocinfo, arglist);
}


int __cdecl _printf_s_l (
        const char *format,
        _locale_t plocinfo,
        ...
        )
{
    va_list arglist;

    va_start(arglist, plocinfo);

    return _vprintf_s_l(format, plocinfo, arglist);
}

int __cdecl printf_s (
        const char *format,
        ...
        )
{
    va_list arglist;

    va_start(arglist, format);

    return _vprintf_s_l(format, NULL, arglist);
}

int __cdecl _printf_p_l (
        const char *format,
        _locale_t plocinfo,
        ...
        )
{
    va_list arglist;

    va_start(arglist, plocinfo);

    return _vprintf_p_l(format, plocinfo, arglist);
}

int __cdecl _printf_p (
        const char *format,
        ...
        )
{
    va_list arglist;

    va_start(arglist, format);

    return _vprintf_p_l(format, NULL, arglist);
}

static UINT_PTR __enable_percent_n = 0;

/***
 *int _set_printf_count_output(int)
 *
 *Purpose:
 *   Enables or disables %n format specifier for printf family functions
 *
 *Internals:
 *   __enable_percent_n is set to (__security_cookie|1) for security reasons;
 *   if set to a static value, an attacker could first modify __enable_percent_n
 *   and then provide a malicious %n specifier.  The cookie is ORed with 1
 *   because a zero cookie is a possibility.
 ******************************************************************************/
int __cdecl _set_printf_count_output(int value)
{
    int old = (__enable_percent_n == (__security_cookie | 1));
    __enable_percent_n = (value ? (__security_cookie | 1) : 0);
    return old;
}

/***
 *int _get_printf_count_output()
 *
 *Purpose:
 *   Checks whether %n format specifier for printf family functions is enabled
 ******************************************************************************/
int __cdecl _get_printf_count_output()
{
    return ( __enable_percent_n == (__security_cookie | 1));
}

posted on 2008-11-19 00:23 so true 阅读(2904) 评论(1)  编辑  收藏 所属分类: C&C++

评论

# re: printf函数里面大有文章!  回复  更多评论   

谢谢楼主总结!
2008-11-21 20:28 | yball

只有注册用户登录后才能发表评论。


网站导航: