打印出堆栈给出了奇怪的定位
Printing out stack gives weird positioning of
我目前正在尝试了解 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
我希望得到类似
的东西
....41414141.42424245。 ...
但我明白了
....41414141.4242422e.30252e45。 ...(B 和 E 之间有一些字符)。
这里发生了什么?
我禁用了 ASLR 和堆栈保护并使用 -m32 标志编译它。
您正在将 AAAA.BBBE.%08x...
传递给格式说明符 printf。所以 printf 期望每个 %08x
有一个额外的 unsigned integer
参数。但是你不提供任何,行为将是未定义的。
您可以阅读 C Draft Standard (n1256):
If there are insufficient arguments for the format, the behavior is undefined.
您正在从堆栈中的任何地方获取十六进制输出。
我觉得你的输出还不错。 x86 是 little-endian - 数字的最低有效字节在内存中的地址较小,因此 1000 (0x3E8
) 存储为 E8 03
,而不是 03 E8
(那会很大-字节序)。
让我们假设编译器通过堆栈将所有参数传递给 printf
,并且预计可变参数从顶部到末端放置在堆栈上(在 x86 上这意味着 "from lower addresses to higher addresses")。
所以,在调用 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
我们看到有3个int
:0x41414141
(在内存中存储为41 41 41 41
),0x4242422e
(在内存中存储为2e 42 42 42
因为最低有效字节具有最小地址)和 0x30252e45
(在内存中存储为 45 2e 25 30
)。也就是说,在这种情况下 printf
读取以下字节:
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 |
这对我来说完全正确 - 正如预期的那样,它是 buffer
的开始。
这基本上就是您使用 %08x
格式输出的内容,并且您在一台小端机器上:
41 41 41 41 2e 42 42 42 45 2e 25 30 38 78 2e 25 30 38 78 2e 25 30 38 78 2e
- 第一个是所有
41
,然后它们被翻转为所有 41
。
- 接下来的四个字节是
2e424242
,变成4242422e
。
- 那么,
452e2530
就变成了30252e45
。
如果您在调试器中查看内存 window 中的 buffer
,就更容易弄清楚这一点。
顺便说一句,你可以像这样打印缓冲区的地址(不带 &):
printf("Location of buffer: %p\n", buffer);
我目前正在尝试了解 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
我希望得到类似
的东西....41414141.42424245。 ...
但我明白了
....41414141.4242422e.30252e45。 ...(B 和 E 之间有一些字符)。
这里发生了什么?
我禁用了 ASLR 和堆栈保护并使用 -m32 标志编译它。
您正在将 AAAA.BBBE.%08x...
传递给格式说明符 printf。所以 printf 期望每个 %08x
有一个额外的 unsigned integer
参数。但是你不提供任何,行为将是未定义的。
您可以阅读 C Draft Standard (n1256):
If there are insufficient arguments for the format, the behavior is undefined.
您正在从堆栈中的任何地方获取十六进制输出。
我觉得你的输出还不错。 x86 是 little-endian - 数字的最低有效字节在内存中的地址较小,因此 1000 (0x3E8
) 存储为 E8 03
,而不是 03 E8
(那会很大-字节序)。
让我们假设编译器通过堆栈将所有参数传递给 printf
,并且预计可变参数从顶部到末端放置在堆栈上(在 x86 上这意味着 "from lower addresses to higher addresses")。
所以,在调用 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
我们看到有3个int
:0x41414141
(在内存中存储为41 41 41 41
),0x4242422e
(在内存中存储为2e 42 42 42
因为最低有效字节具有最小地址)和 0x30252e45
(在内存中存储为 45 2e 25 30
)。也就是说,在这种情况下 printf
读取以下字节:
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 |
这对我来说完全正确 - 正如预期的那样,它是 buffer
的开始。
这基本上就是您使用 %08x
格式输出的内容,并且您在一台小端机器上:
41 41 41 41 2e 42 42 42 45 2e 25 30 38 78 2e 25 30 38 78 2e 25 30 38 78 2e
- 第一个是所有
41
,然后它们被翻转为所有41
。 - 接下来的四个字节是
2e424242
,变成4242422e
。 - 那么,
452e2530
就变成了30252e45
。
如果您在调试器中查看内存 window 中的 buffer
,就更容易弄清楚这一点。
顺便说一句,你可以像这样打印缓冲区的地址(不带 &):
printf("Location of buffer: %p\n", buffer);