printf()影响缓冲区溢出情况

时间:2018-05-23 16:08:05

标签: c string memory buffer overflow

我有一个简单的程序,用于初始化c样式字符串,然后初始化一个字符。然后我使用函数strcpy导致缓冲区溢出情况,这似乎会覆盖字符变量x的内存内容(假设它存储在相邻的内存中)。

char str[] = "Testt";
char x = 'X';

// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);

// print value of x
printf("%c\n", x);

// cause buffer overflow
strcpy(str, "Hello world");

// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);

// print address and value of x
// printf("%p: ", &x);
printf("%c\n", x);
return 0;

运行时,此代码生成类似于

的输出
0061FF29: Testt
X
0061FF29: Hello world
w

这种情况表明发生了缓冲区溢出,并导致x变量的值从'X'更改为'w'

但是,如果我删除第三行到最后一行的注释// printf("%p: ", &x);,则缓冲区溢出不会导致x变量被覆盖。

为清楚起见,这里是代码(注意第三行到最后一行的变化)

char str[] = "Testt";
char x = 'X';

// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);

// print value of x
printf("%c\n", x);

// cause buffer overflow
strcpy(str, "Hello world");

// print address and value of str
printf("%p: ", &str);
printf("%s\n", str);

// print address and value of x
printf("%p: ", &x);
printf("%c\n", x);
return 0;

这会导致输出为:

0061FF2A: Testt
X
0061FF2A: Hello world
0061FF29: X

因此在这种情况下,缓冲区溢出不会覆盖x变量。

为什么简单地打印x变量的内存地址会对缓冲区溢出情况产生影响?

编辑:在两个情况的程序集中添加 为第一种情况生成的程序集(无printf):

    .file   "hello.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "%p: \0"
LC1:
    .ascii "%c\12\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB17:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    call    ___main
    movl    $1953719636, 25(%esp)
    movw    $116, 29(%esp)
    movb    $88, 31(%esp)
    leal    25(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $LC0, (%esp)
    call    _printf
    leal    25(%esp), %eax
    movl    %eax, (%esp)
    call    _puts
    movsbl  31(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    leal    25(%esp), %eax
    movl    $1819043144, (%eax)
    movl    $1870078063, 4(%eax)
    movl    $6581362, 8(%eax)
    leal    25(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $LC0, (%esp)
    call    _printf
    leal    25(%esp), %eax
    movl    %eax, (%esp)
    call    _puts
    movsbl  31(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE17:
    .ident  "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
    .def    _printf;    .scl    2;  .type   32; .endef
    .def    _puts;  .scl    2;  .type   32; .endef

和第二种情况

    .file   "hello.c"
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "%p: \0"
LC1:
    .ascii "%c\12\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB17:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $32, %esp
    call    ___main
    movl    $1953719636, 26(%esp)
    movw    $116, 30(%esp)
    movb    $88, 25(%esp)
    leal    26(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $LC0, (%esp)
    call    _printf
    leal    26(%esp), %eax
    movl    %eax, (%esp)
    call    _puts
    movzbl  25(%esp), %eax
    movsbl  %al, %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    leal    26(%esp), %eax
    movl    $1819043144, (%eax)
    movl    $1870078063, 4(%eax)
    movl    $6581362, 8(%eax)
    leal    26(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $LC0, (%esp)
    call    _printf
    leal    26(%esp), %eax
    movl    %eax, (%esp)
    call    _puts
    leal    25(%esp), %eax
    movl    %eax, 4(%esp)
    movl    $LC0, (%esp)
    call    _printf
    movzbl  25(%esp), %eax
    movsbl  %al, %eax
    movl    %eax, 4(%esp)
    movl    $LC1, (%esp)
    call    _printf
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE17:
    .ident  "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
    .def    _printf;    .scl    2;  .type   32; .endef
    .def    _puts;  .scl    2;  .type   32; .endef

1 个答案:

答案 0 :(得分:1)

首先让我们看看第二个例子中缓冲区溢出没有发生的原因。

看看你的输出:

0061FF2A: Testt
X
0061FF2A: Hello world
0061FF29: X

我们可以看到堆栈上str x

字符串"Hello world"占用内存地址0061FF2A0061FF36

堆栈看起来像

0061FF29   0061FF2A        0061FF36
       |   |                      |
       ----------------------------
       | X | H e l l o  w o r l d |
       ----------------------------

在这种情况下,由于str位于堆栈上的x之前,我们写str的末尾并不重要。

接下来让我们看看第一个例子中缓冲区溢出发生的原因。

我们无法直接在输出中看到每个变量的地址但是我们可以在程序集的堆栈中看到它们的位置。

movl    $1953719636, 25(%esp)
movw    $116, 29(%esp)
movb    $88, 31(%esp)

x变量肯定在31(%esp),因为我们看到'X'的小数ASCII值被放置在那里。

由于"Testt"25(%esp)之间的距离足够,假设5 25(%esp)字符串存储在31(%esp),这并不是太大的飞跃存储5个字符和一个空终止符。

因此,我们知道str位于25(%esp)x位于31(%esp)。堆栈应该类似于:

esp  +25         +31
  |    |           | 
  ---------------------- 
  |    | T e s t t | X |
  ----------------------

现在我们可以很容易地看到str出现在x之前,很清楚地看到为什么写str的结尾会导致x被覆盖。< / p>

现在主要问题是,为什么这在第一种情况下起作用而在第二种情况下不起作用?

由于某种原因,编译器决定在第一个示例中x之后放置str,在第二个示例中放置x str

正如在注释中指出的那样,堆栈上的局部变量的确切位置不是由C定义的。编译器可以决定它想要存储的顺序,并且可以在程序之间改变该顺序,以便非显而易见的原因。

本质上,堆栈上局部变量的确切位置和排序是未定义的,因此未定义的行为是缓冲区溢出在一种情况下工作但不在另一种情况下工作的原因。