我目前正试图了解C语言中的字符串格式化漏洞,但要做到这一点,我必须了解内存堆栈的一些奇怪(至少对我来说)行为。
我有个计划
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
char buffer[200];
char key[] = "secret";
printf("Location of key: %p\n", key);
printf("Location of buffer: %p\n", &buffer);
strcpy(buffer, argv[1]);
printf(buffer);
printf("\n");
return 0;
}
我用它打电话
./form AAAA.BBBE.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
我想得到的是
….414141.42424245。...
但我知道
….414141.4242422e.30252e45。…(在B和E之间有一些字符)。
这里发生了什么事?
我禁用了ASLR和堆栈保护,并用-m32标志编译它。
最佳答案
我认为你的产出还不错。x86是little-endian-数字的最低有效字节在内存中的地址较小,因此1000(0x3E8
)存储为E8 03
,而不是03 E8
(这将是大端)。
假设编译器通过堆栈将所有参数传递给printf
,而可变参数应该从堆栈的顶部到末尾(在x86上,这意味着“从低地址到高地址”)放置在堆栈上。
因此,在调用printf
之前,我们的堆栈希望:
<return address><something>AAAA.BBBE.%08x.%<something>
^ - head of the stack
或者,如果我们用十六进制拼写每个字节:
<return address><something>414141412e424242452e253038782e25<something>
^ - head of the stack A A A A . B B B E . % 0 8 x . %
然后让
printf
从堆栈中提取大量无符号的int
s(可能是32位的),并用十六进制打印出来,中间用点隔开。它跳过堆栈帧的<return address>
和其他一些细节,并在buffer
之前从堆栈中的某个随机点开始(因为buffer
在父堆栈帧中)。假设在某个时刻,它将以下块作为4字节int
:<return address><something>414141412e424242452e253038782e25<something>
^ - head of the stack A A A A . B B B E . % 0 8 x . %
^^^^^^^^
也就是说,我们的int在内存中用四个字节表示。它们的值从地址最小的字节开始:
41 41 41 2e
。由于x86有点尾数,2e
是最有意义的字节,这意味着这个序列被解释为0x2e414141
并以这种方式打印。现在,如果我们看看你的输出:
41414141.4242422e.30252e45
我们看到有三个
int
s:0x41414141
(在内存中存储为41 41 41 41
)0x4242422e
(在内存中存储为2e 42 42 42
,因为最低有效字节具有最小地址)和0x30252e45
(在内存中存储为45 2e 25 30
)。也就是说,在这种情况下,读取以下字节:number one |number two |number three|
41 41 41 41|2e 42 42 42|45 2e 25 30 |
A A A A |. B B B |E . % 0 |
在我看来完全正确-它是
printf
的开始。关于c - 打印出堆栈会产生奇怪的位置,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44457581/