测试环境:VS2012 Update4
测试语言:C++
测试代码:
#include<stdio.h>
int main(int argc,char* argv[])
{
char str[20];
scanf_s("%s",str,18);
return 0;
}
在第五行下断点,断下之后我们观察一下 str
地址:0x003DF9B8
内容如下,处于未初始化状态
接着F11进去,进入 scanf_s函数:
/***
*int scanf_s(format, ...) - read formatted data from stdin
*
* Same as scanf above except that it calls _input_s_l to do the real work.
* _input_s_l has a size check for array parameters.
*
*******************************************************************************/
int __cdecl scanf_s (
const char *format,
...
)
{
va_list arglist;
va_start(arglist, format);
return vscanf(_input_s_l, format, NULL, arglist);
}
从注释中我们可以看到 scanf_s的功能是从stdin中读取格式化的数据
TA 和 scanf 的区别是用了_input_s_l函数,而这个函数有一个长度检查的功能(scanf用的是 _input_l)。
format :参数格式
arglist:参数列表,地址 => 0x003DF8E0
如图,arglist里存放的是 str 的地址和限制长度 0x12(18d)
接下来进入vscanf函数:
/***
*int vscanf(format, ...) - read formatted data from stdin
*
*Purpose:
* This is a helper function to be called from fscanf & fscanf_s
*
*Entry:
* INPUTFN inputfn - scanf & scanf_s pass either _input_l or _input_s_l
* which is then used to do the real work.
* char *format - format string
* va_list arglist - arglist of output pointers
*
*Exit:
* returns number of fields read and assigned
*
*Exceptions:
*
*******************************************************************************/
int __cdecl vscanf (
INPUTFN inputfn,
const char *format,
_locale_t plocinfo,
va_list arglist
)
/*
* stdin 'SCAN', 'F'ormatted
*/
{
int retval;
_VALIDATE_RETURN( (format != NULL), EINVAL, EOF);
_lock_str2(0, stdin); //锁定输入
__try {
retval = (inputfn(stdin, format, plocinfo, arglist));
}
__finally {
_unlock_str2(0, stdin); //解锁输入
}
return(retval);
}
注释里说了,TA 的功能也从 stdin 中读取格式化的数据
这是一个帮衬类型的函数,由 fscanf和
fscanf_s调用。
INPUTFN inputfn :由 scanf 或 scanf_s 传入的参数决定是用_input_l或者 _input_s_l .这个函数才是真正干活的 ! 自己都说了大实话。
constchar*format : 格式。
_locale_t plocinfo :注释里没说。。。
va_list arglist : 参数列表。
constchar*format : 格式。
_locale_t plocinfo :注释里没说。。。
va_list arglist : 参数列表。
其中我们最多只能跟到__iob_func函数(起码我只能进来这里)
这里面只有一句代码,将 _iob 返回.
/* These functions are for enabling STATIC_CPPLIB functionality */
_CRTIMP FILE * __cdecl __iob_func(void)
{
return _iob;
}
那么我们就来看看 这个 _iob 指针是什么,在inputfn 还没有执行完毕,也就是我们还没有进行任何输入的时候是下面这样的~
貌似是一个地址哇~
进去 0x0F5FDE00看一下咯~嘛也没。。
现在我们输入一串字符 "Hello Kiya",按照 scanf_s 的特性我们知道最终 str 中存放的肯定只有 "Hello" 和一个 '\0'。
那么现在我们来看看这个 _iob 变成了什么样子
哎哟地址变了一点点
进来 0x0F5FDE05 看一下咯
我往上拖了一点,因为 0x0F5FDE00 是我们之前 _iob 的地址,现在 TA 指向的是 Hello 和 Kiya 中间的空格
然而输入的字符确实是从 0x0F5FDE00 开始存的,
我们可以猜测肯定是在格式化的过程中指针遇到了空格,函数就在 0x0F5FDE05 的地方结束掉了,后面的不再处理
最终 str 中的内容:
那么现在我们可不可以利用 scanf 或者 scanf_s 获得一串带有空格的字符串呢?
现在已经知道所有输入的字符确实是在缓冲区中的,只是 scanf_s 的格式化功能将 TA 断掉了。
#include<stdio.h>
int main(int argc,char* argv[])
{
char str[20];
scanf_s("%s",str,18);
char* p = *(char**)((int)__iob_func()+8);
printf_s(p);
return 0;
}
这样我们就是利用了一下 __iob_func
函数,让他来为我们返回 _iob 的地址,
然而此时 _iob 已经指向了第一个空格的地方
然后将其强转成char类型的指针,就可以输出啦
可是为什么要加8呢?
(int)__iob_func()是将__iob_func()执行过后
TA 的返回值_iob 转成整形
其实有个小秘密,虽然我们输入过后 _iob 中的地址已经变成指向第一个空格处,但其实,起始地址就在 TA 的不远处~
(这个图是后来截的,地址可能和上面的不同)
如图,选中部分,就是第一个字符的地址,而上面的 0FA1DE05 则是空格的位置
所以我们将 (int)__iob_func() 加上8个字节就得到了起始地址。
其实根据下图这个类型属性,也可以知道TA是个缓冲区,哈哈
结果: