我目前正试图了解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从堆栈中提取大量无符号的ints(可能是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

我们看到有三个ints:0x41414141(在内存中存储为41 41 41 410x4242422e(在内存中存储为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/

10-11 23:00
查看更多