C中printf()中单引号和双引号之间的区别

difference between single and double quotes in printf() in C

我对在C的printf()中使用双引号和单引号有疑问。我有以下代码:

1
2
3
4
5
6
7
#include<stdio.h>
int main(int argc, char** argv) {
    char buffer[100];
    strncpy(buffer, argv[1], 100);
    printf(buffer);
    return 0;
}

所以当我编译并运行它时,会发生以下结果:

1
2
3
4
5
6
7
./a.out 'AAAA%8$p'

AAAA0x70243825**41414141**

./o"AAAA%8$p"

AAAA

注意,在第一种情况下,我输入了单引号,在第二种情况下输入了双引号。

这里发生了什么。

Q2。我在这里关注此链接。所以我的问题是,运行时输入argv [1]中的AAAA如何影响printf()的输出。首先,我认为这仅仅是一个巧合,但是我输入并得到以下内容:

1
2
3
./o 'BBBB%8$p'

BBBB0x70243825**42424242**

Edit:所以我认为看到答案有些误解。我知道直接\\ printf(buffer)\\的滥用。因此,我将引用前面提到的链接:

由于printf具有可变数量的参数,因此它必须使用格式字符串来确定参数数量。在上述情况下,攻击者可以传递字符串"%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p%p "并将printf欺骗到认为它有15个论点。它会天真地在堆栈上打印接下来的15个地址,并认为它们是它的参数:

1
2
$ ./a.out"%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p"
0xffffdddd 0x64 0xf7ec1289 0xffffdbdf 0xffffdbde (nil) 0xffffdcc4 0xffffdc64 (nil) 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520

在堆栈上大约10个参数处,我们可以看到重复模式0x252070-这些是我们的%ps堆栈!我们以AAAA开头的字符串可以更清楚地看到这一点:

1
2
$ ./a.out"AAAA%p %p %p %p %p %p %p %p %p %p"
AAAA0xffffdde8 0x64 0xf7ec1289 0xffffdbef 0xffffdbee (nil) 0xffffdcd4 0xffffdc74 (nil) 0x41414141

0x41414141是AAAA的十六进制表示形式。现在,我们有一种方法可以传递一个任意值(在本例中,我们将传递0x41414141)作为printf的参数。此时,我们将利用另一种格式字符串功能:在格式说明符中,我们还可以选择一个特定的参数。例如,printf("%2 $ x ",1,2,3)将打印2。通常,我们可以执行printf("%$ x ")选择printf的任意参数。在我们的例子中,我们看到0x41414141是printf的第十个参数,因此我们可以简化我们的string1:

1
2
$ ./a.out 'AAAA%10$p'
AAAA0x41414141

,所以我真的不认为这是垃圾值。而是实际上是该位置的地址。

我的平台:Ubuntu 14.04,64位计算机。

所以我想知道输入中的'AAAA'或'BBBB'对输出'AAAA0x7024382541414141'或BBBB0x7024382542424242。


这与C中的引号无关。shell会从命令行参数中删除单引号和双引号。但是shell可能对美元符号也有特殊含义(如果使用双引号而不是单引号,则$p可能会在传递给应用程序之前被shell扩展)。

所以这是./a.out 'AAAA%8$p'作为argv[1]传入AAAA%8$p,这:./a.out"AAAA%8$p"可能传入AAAA%8(如果未定义p环境变量)。

现在有关您奇怪的输出正在查看。

printf是cdecl函数,无法知道传递给它的参数数量。因此,它希望您传递与格式字符串中占位符一样多的参数。

然后堆栈看起来像:

1

,但是您不这样做提供足够的论据。因此,您的堆栈看起来更像:

2

然后发生的事情是-printf读取参数应该位于的内存,并命中main()的局部变量,即buffer

然后从那里读取8个字节并以十六进制打印(%p打印出十六进制格式的指针,在您的情况下指针大小为8字节)。

<十六进制中的pa> A41,这就是为什么您看到4乘以41的原因,对于B则是十六进制42

这是未定义的行为。不应依赖它(除非您正在编写漏洞利用程序)。它会在平台,编译器甚至优化模式之间发生变化。

通常,您希望停留在已定义行为的领域。

p.s。除了您可能已经意识到的错误使用printf的危险之外,您还可能希望检查一些边缘情况,例如未提供任何参数或输入时间过长。


printf(缓冲区)非常非常非常危险。

printf的第一个参数是格式字符串,其中包含文本,还包括格式说明符,例如%d用于打印整数,%s用于打印字符串等等。

如果缓冲区包含格式说明符,则printf将尝试根据格式说明符处理格式字符串之后的下一个参数。但是您没有传递任何其他参数,而且您也不知道要传递什么参数。这样您就获得了不确定的行为。

在实践中,

1
2
3
char buffer [100];
strcpy (buffer,"%s");
printf (buffer);

非常有可能崩溃。